Agent 初版设计
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
"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";
|
||||
|
||||
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 normalizeIds = (params: Record<string, unknown>): string[] => {
|
||||
const rawIds = params.ids;
|
||||
if (Array.isArray(rawIds)) {
|
||||
return rawIds.map((id) => String(id).trim()).filter(Boolean);
|
||||
}
|
||||
if (typeof rawIds === "string") {
|
||||
return rawIds
|
||||
.split(",")
|
||||
.map((id) => id.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const resolveScadaFeatureInfos = (params: Record<string, unknown>): [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<string, unknown>) => ({
|
||||
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<string, unknown>,
|
||||
): { artifact: Omit<AgentArtifact, "id" | "params" | "tool">; 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<string, unknown>,
|
||||
): { 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)),
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
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 (action) {
|
||||
dispatchToolAction(action);
|
||||
}
|
||||
},
|
||||
[dispatchToolAction],
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user