"use client"; import { useCallback } from "react"; import { useChatToolStore, type ChatToolAction } from "@/store/chatToolStore"; import type { StreamEvent } from "@/lib/chatStream"; import type { AgentArtifact, AgentArtifactKind } from "../GlobalChatbox.types"; import { APPLY_LAYER_STYLE_TOOL, describeApplyLayerStyle, parseApplyLayerStylePayload, } from "../toolCallStyleHelpers"; type ToolCallEvent = StreamEvent & { type: "tool_call" }; type HandleToolCallOptions = { assistantMessageId: string; appendArtifact: (messageId: string, artifact: AgentArtifact) => void; }; const FEATURE_TYPE_MAP: Record< string, { layer: string; geometryKind: "point" | "line"; label: string } > = { junction: { layer: "geo_junctions_mat", geometryKind: "point", label: "节点" }, junctions: { layer: "geo_junctions_mat", geometryKind: "point", label: "节点" }, pipe: { layer: "geo_pipes_mat", geometryKind: "line", label: "管道" }, pipes: { layer: "geo_pipes_mat", geometryKind: "line", label: "管道" }, valve: { layer: "geo_valves", geometryKind: "point", label: "阀门" }, valves: { layer: "geo_valves", geometryKind: "point", label: "阀门" }, reservoir: { layer: "geo_reservoirs", geometryKind: "point", label: "水源" }, reservoirs: { layer: "geo_reservoirs", geometryKind: "point", label: "水源" }, pump: { layer: "geo_pumps", geometryKind: "point", label: "泵站" }, pumps: { layer: "geo_pumps", geometryKind: "point", label: "泵站" }, tank: { layer: "geo_tanks", geometryKind: "point", label: "水池" }, tanks: { layer: "geo_tanks", geometryKind: "point", label: "水池" }, }; const LOCATE_TOOL_CONFIG: Record< string, { layer: string; geometryKind: "point" | "line"; label: string } > = { locate_pipes: { layer: "geo_pipes_mat", geometryKind: "line", label: "管道" }, locate_junctions: { layer: "geo_junctions_mat", geometryKind: "point", label: "节点" }, locate_valves: { layer: "geo_valves", geometryKind: "point", label: "阀门" }, locate_reservoirs: { layer: "geo_reservoirs", geometryKind: "point", label: "水源" }, locate_pumps: { layer: "geo_pumps", geometryKind: "point", label: "泵站" }, locate_tanks: { layer: "geo_tanks", geometryKind: "point", label: "水池" }, }; const LOCATE_ID_PARAM_KEYS = [ "ids", "id", "feature_ids", "feature_id", "node_ids", "node_id", "junction_ids", "junction_id", "pipe_ids", "pipe_id", "valve_ids", "valve_id", "reservoir_ids", "reservoir_id", "pump_ids", "pump_id", "tank_ids", "tank_id", ] as const; const normalizeIds = (params: Record): string[] => { for (const key of LOCATE_ID_PARAM_KEYS) { const rawValue = params[key]; if (Array.isArray(rawValue)) { const normalized = rawValue.map((id) => String(id).trim()).filter(Boolean); if (normalized.length > 0) { return normalized; } } if (typeof rawValue === "string" || typeof rawValue === "number") { const normalized = String(rawValue) .split(",") .map((id) => id.trim()) .filter(Boolean); if (normalized.length > 0) { return normalized; } } } return []; }; const resolveScadaFeatureInfos = (params: Record): [string, string][] => { const rawFeatureInfos = params.feature_infos; if (Array.isArray(rawFeatureInfos)) { const normalizedFeatureInfos = rawFeatureInfos .map((item) => (Array.isArray(item) ? item : null)) .filter((item): item is [unknown, unknown] => Boolean(item)) .map( (item) => [String(item[0] ?? ""), String(item[1] ?? "scada")] as [ string, string, ], ) .filter(([id]) => id.trim().length > 0); if (normalizedFeatureInfos.length > 0) { return normalizedFeatureInfos; } } const rawDeviceIds = params.device_ids ?? params.deviceId ?? params.device_id ?? params.id ?? params.ids; const deviceIds = Array.isArray(rawDeviceIds) ? rawDeviceIds.map((id) => String(id)) : typeof rawDeviceIds === "string" ? rawDeviceIds .split(",") .map((id) => id.trim()) .filter(Boolean) : []; return deviceIds.map((id) => [id, "scada"]); }; const resolveTimeRange = (params: Record) => ({ startTime: (params.start_time as string | undefined) ?? (params.startTime as string | undefined) ?? (params.from as string | undefined) ?? (params.start as string | undefined), endTime: (params.end_time as string | undefined) ?? (params.endTime as string | undefined) ?? (params.to as string | undefined) ?? (params.end as string | undefined), }); const compactNames = (names: string[]) => { if (!names.length) return ""; return names.length > 3 ? `${names.slice(0, 3).join(", ")} 等 ${names.length} 个` : names.join(", "); }; const buildLocateArtifact = ( tool: string, params: Record, ): { artifact: Omit; action: ChatToolAction | null } => { const ids = normalizeIds(params); const rawType = params.feature_type; const featureType = typeof rawType === "string" ? rawType.trim().toLowerCase() : ""; const config = tool === "locate_features" ? FEATURE_TYPE_MAP[featureType] : LOCATE_TOOL_CONFIG[tool]; return { artifact: { kind: "map", title: config ? `地图定位${config.label}` : "地图定位", description: compactNames(ids), }, action: config ? { type: "locate_features", ids, layer: config.layer, geometryKind: config.geometryKind, } : null, }; }; const buildToolAction = ( tool: string, params: Record, ): { action: ChatToolAction | null; kind: AgentArtifactKind; title: string; description?: string } => { if (tool === "show_chart") { return { action: null, kind: "chart", title: (params.title as string | undefined) ?? "生成图表", description: "已生成可视化图表", }; } if (tool === "locate_features" || LOCATE_TOOL_CONFIG[tool]) { const locate = buildLocateArtifact(tool, params); return { action: locate.action, kind: locate.artifact.kind, title: locate.artifact.title, description: locate.artifact.description, }; } if (tool === "view_history") { const featureInfos = (params.feature_infos as [string, string][] | undefined) ?? []; const { startTime, endTime } = resolveTimeRange(params); return { action: { type: "view_history", featureInfos, dataType: (params.data_type as "realtime" | "scheme" | "none" | undefined) ?? "realtime", startTime, endTime, }, kind: "panel", title: "打开计算结果曲线", description: compactNames(featureInfos.map(([id]) => id)), }; } if (tool === "view_scada") { const featureInfos = resolveScadaFeatureInfos(params); const { startTime, endTime } = resolveTimeRange(params); return { action: { type: "view_scada", featureInfos, startTime, endTime, }, kind: "panel", title: "打开 SCADA 数据面板", description: compactNames(featureInfos.map(([id]) => id)), }; } if (tool === "render_junctions") { const renderRef = typeof params.render_ref === "string" ? params.render_ref.trim() : ""; return { action: renderRef ? { type: "render_junctions", renderRef, sessionId: undefined, } : null, kind: "map", title: "渲染节点分区", description: renderRef || "渲染引用", }; } if (tool === APPLY_LAYER_STYLE_TOOL) { const payload = parseApplyLayerStylePayload(params); return { action: payload ? { type: "apply_layer_style", layerId: payload.layerId, resetToDefault: payload.resetToDefault, styleConfig: payload.styleConfig, } : null, kind: "map", title: payload?.resetToDefault ? "重置图层样式" : "应用图层样式", description: payload ? describeApplyLayerStyle(payload) : "图层样式", }; } return { action: null, kind: "tool", title: tool || "工具调用", description: "Agent 已执行工具动作", }; }; export const useAgentToolActions = () => { const dispatchToolAction = useChatToolStore((s) => s.dispatch); return useCallback( (event: ToolCallEvent, options: HandleToolCallOptions) => { const { action, kind, title, description } = buildToolAction( event.tool, event.params, ); const normalizedAction = action?.type === "render_junctions" ? { ...action, sessionId: event.sessionId } : action; options.appendArtifact(options.assistantMessageId, { id: `${event.tool}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, tool: event.tool, kind, title, description, params: event.params, }); if (normalizedAction) { dispatchToolAction(normalizedAction); } }, [dispatchToolAction], ); };