抽象统一定位方法,支持多种地理要素
This commit is contained in:
@@ -34,8 +34,26 @@ type ToolMeta = {
|
|||||||
color: string;
|
color: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LOCATE_TOOL_TO_LAYER: Record<string, string> = {
|
||||||
|
locate_features: "",
|
||||||
|
locate_junctions: "geo_junctions_mat",
|
||||||
|
locate_pipes: "geo_pipes_mat",
|
||||||
|
locate_valves: "geo_valves",
|
||||||
|
locate_reservoirs: "geo_reservoirs",
|
||||||
|
locate_pumps: "geo_pumps",
|
||||||
|
locate_tanks: "geo_tanks",
|
||||||
|
};
|
||||||
|
|
||||||
|
const LOCATE_LINE_TOOLS = new Set<string>(["locate_pipes"]);
|
||||||
|
|
||||||
const TOOL_META: Record<string, ToolMeta> = {
|
const TOOL_META: Record<string, ToolMeta> = {
|
||||||
locate_nodes: {
|
locate_features: {
|
||||||
|
label: "定位要素",
|
||||||
|
icon: <LocationOnRounded sx={{ fontSize: 18 }} />,
|
||||||
|
actionLabel: "定位到地图",
|
||||||
|
color: "#5470c6",
|
||||||
|
},
|
||||||
|
locate_junctions: {
|
||||||
label: "定位节点",
|
label: "定位节点",
|
||||||
icon: <LocationOnRounded sx={{ fontSize: 18 }} />,
|
icon: <LocationOnRounded sx={{ fontSize: 18 }} />,
|
||||||
actionLabel: "定位到地图",
|
actionLabel: "定位到地图",
|
||||||
@@ -47,6 +65,30 @@ const TOOL_META: Record<string, ToolMeta> = {
|
|||||||
actionLabel: "定位到地图",
|
actionLabel: "定位到地图",
|
||||||
color: "#91cc75",
|
color: "#91cc75",
|
||||||
},
|
},
|
||||||
|
locate_valves: {
|
||||||
|
label: "定位阀门",
|
||||||
|
icon: <LocationOnRounded sx={{ fontSize: 18 }} />,
|
||||||
|
actionLabel: "定位到地图",
|
||||||
|
color: "#9a60b4",
|
||||||
|
},
|
||||||
|
locate_reservoirs: {
|
||||||
|
label: "定位水源",
|
||||||
|
icon: <LocationOnRounded sx={{ fontSize: 18 }} />,
|
||||||
|
actionLabel: "定位到地图",
|
||||||
|
color: "#ea7ccc",
|
||||||
|
},
|
||||||
|
locate_pumps: {
|
||||||
|
label: "定位泵站",
|
||||||
|
icon: <LocationOnRounded sx={{ fontSize: 18 }} />,
|
||||||
|
actionLabel: "定位到地图",
|
||||||
|
color: "#fc8452",
|
||||||
|
},
|
||||||
|
locate_tanks: {
|
||||||
|
label: "定位水池",
|
||||||
|
icon: <LocationOnRounded sx={{ fontSize: 18 }} />,
|
||||||
|
actionLabel: "定位到地图",
|
||||||
|
color: "#3ba272",
|
||||||
|
},
|
||||||
view_history: {
|
view_history: {
|
||||||
label: "查看计算结果",
|
label: "查看计算结果",
|
||||||
icon: <TimelineRounded sx={{ fontSize: 18 }} />,
|
icon: <TimelineRounded sx={{ fontSize: 18 }} />,
|
||||||
@@ -71,6 +113,19 @@ const TOOL_META: Record<string, ToolMeta> = {
|
|||||||
|
|
||||||
function getToolDescription(toolCall: ToolCall): string {
|
function getToolDescription(toolCall: ToolCall): string {
|
||||||
const { params } = toolCall;
|
const { params } = toolCall;
|
||||||
|
const normalizeIds = (): string[] => {
|
||||||
|
const rawIds = params.ids;
|
||||||
|
if (Array.isArray(rawIds)) {
|
||||||
|
return rawIds.map((id) => String(id)).filter((id) => id.trim().length > 0);
|
||||||
|
}
|
||||||
|
if (typeof rawIds === "string") {
|
||||||
|
return rawIds
|
||||||
|
.split(",")
|
||||||
|
.map((id) => id.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
const resolveScadaFeatureInfos = (): [string, string][] => {
|
const resolveScadaFeatureInfos = (): [string, string][] => {
|
||||||
const rawFeatureInfos = params.feature_infos;
|
const rawFeatureInfos = params.feature_infos;
|
||||||
if (Array.isArray(rawFeatureInfos)) {
|
if (Array.isArray(rawFeatureInfos)) {
|
||||||
@@ -119,13 +174,36 @@ function getToolDescription(toolCall: ToolCall): string {
|
|||||||
(params.to as string | undefined) ??
|
(params.to as string | undefined) ??
|
||||||
(params.end as string | undefined),
|
(params.end as string | undefined),
|
||||||
});
|
});
|
||||||
|
const resolveLocateFeatureType = (): string => {
|
||||||
|
const rawType = params.feature_type;
|
||||||
|
if (typeof rawType === "string" && rawType.trim()) {
|
||||||
|
return rawType.trim().toLowerCase();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
switch (toolCall.tool) {
|
switch (toolCall.tool) {
|
||||||
case "locate_nodes":
|
case "locate_features":
|
||||||
case "locate_pipes": {
|
case "locate_junctions":
|
||||||
const ids = (params.ids as string[] | undefined) ?? [];
|
case "locate_pipes":
|
||||||
return ids.length > 3
|
case "locate_valves":
|
||||||
|
case "locate_reservoirs":
|
||||||
|
case "locate_pumps":
|
||||||
|
case "locate_tanks": {
|
||||||
|
const ids = normalizeIds();
|
||||||
|
const idsText =
|
||||||
|
ids.length > 3
|
||||||
? `${ids.slice(0, 3).join(", ")} 等 ${ids.length} 个`
|
? `${ids.slice(0, 3).join(", ")} 等 ${ids.length} 个`
|
||||||
: ids.join(", ");
|
: ids.join(", ");
|
||||||
|
if (toolCall.tool !== "locate_features") {
|
||||||
|
return idsText;
|
||||||
|
}
|
||||||
|
const featureType = resolveLocateFeatureType();
|
||||||
|
if (!featureType) {
|
||||||
|
return idsText;
|
||||||
|
}
|
||||||
|
return idsText
|
||||||
|
? `${featureType} · ${idsText}`
|
||||||
|
: featureType;
|
||||||
}
|
}
|
||||||
case "view_history":
|
case "view_history":
|
||||||
case "view_scada": {
|
case "view_scada": {
|
||||||
@@ -155,6 +233,19 @@ function getToolDescription(toolCall: ToolCall): string {
|
|||||||
|
|
||||||
function buildAction(toolCall: ToolCall): ChatToolAction | null {
|
function buildAction(toolCall: ToolCall): ChatToolAction | null {
|
||||||
const { params } = toolCall;
|
const { params } = toolCall;
|
||||||
|
const normalizeIds = (): string[] => {
|
||||||
|
const rawIds = params.ids;
|
||||||
|
if (Array.isArray(rawIds)) {
|
||||||
|
return rawIds.map((id) => String(id)).filter((id) => id.trim().length > 0);
|
||||||
|
}
|
||||||
|
if (typeof rawIds === "string") {
|
||||||
|
return rawIds
|
||||||
|
.split(",")
|
||||||
|
.map((id) => id.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
const resolveScadaFeatureInfos = (): [string, string][] => {
|
const resolveScadaFeatureInfos = (): [string, string][] => {
|
||||||
const rawFeatureInfos = params.feature_infos;
|
const rawFeatureInfos = params.feature_infos;
|
||||||
if (Array.isArray(rawFeatureInfos)) {
|
if (Array.isArray(rawFeatureInfos)) {
|
||||||
@@ -204,16 +295,36 @@ function buildAction(toolCall: ToolCall): ChatToolAction | null {
|
|||||||
(params.end as string | undefined),
|
(params.end as string | undefined),
|
||||||
});
|
});
|
||||||
switch (toolCall.tool) {
|
switch (toolCall.tool) {
|
||||||
case "locate_nodes":
|
case "locate_features": {
|
||||||
|
const featureTypeRaw = params.feature_type;
|
||||||
|
const featureType =
|
||||||
|
typeof featureTypeRaw === "string"
|
||||||
|
? featureTypeRaw.trim().toLowerCase()
|
||||||
|
: "";
|
||||||
|
const config = locateFeatureTypeToConfig(featureType);
|
||||||
|
if (!config) return null;
|
||||||
return {
|
return {
|
||||||
type: "locate_nodes",
|
type: "locate_features",
|
||||||
ids: (params.ids as string[] | undefined) ?? [],
|
ids: normalizeIds(),
|
||||||
|
layer: config.layer,
|
||||||
|
geometryKind: config.geometryKind,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
case "locate_junctions":
|
||||||
case "locate_pipes":
|
case "locate_pipes":
|
||||||
|
case "locate_valves":
|
||||||
|
case "locate_reservoirs":
|
||||||
|
case "locate_pumps":
|
||||||
|
case "locate_tanks": {
|
||||||
|
const layer = LOCATE_TOOL_TO_LAYER[toolCall.tool];
|
||||||
|
if (!layer) return null;
|
||||||
return {
|
return {
|
||||||
type: "locate_pipes",
|
type: "locate_features",
|
||||||
ids: (params.ids as string[] | undefined) ?? [],
|
ids: normalizeIds(),
|
||||||
|
layer,
|
||||||
|
geometryKind: LOCATE_LINE_TOOLS.has(toolCall.tool) ? "line" : "point",
|
||||||
};
|
};
|
||||||
|
}
|
||||||
case "view_history": {
|
case "view_history": {
|
||||||
const historyRange = resolveTimeRange();
|
const historyRange = resolveTimeRange();
|
||||||
return {
|
return {
|
||||||
@@ -383,3 +494,29 @@ export const ChatToolCallBlock: React.FC<ChatToolCallBlockProps> = ({
|
|||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
const locateFeatureTypeToConfig = (
|
||||||
|
featureType: string,
|
||||||
|
): { layer: string; geometryKind: "point" | "line" } | null => {
|
||||||
|
switch (featureType) {
|
||||||
|
case "junction":
|
||||||
|
case "junctions":
|
||||||
|
return { layer: "geo_junctions_mat", geometryKind: "point" };
|
||||||
|
case "pipe":
|
||||||
|
case "pipes":
|
||||||
|
return { layer: "geo_pipes_mat", geometryKind: "line" };
|
||||||
|
case "valve":
|
||||||
|
case "valves":
|
||||||
|
return { layer: "geo_valves", geometryKind: "point" };
|
||||||
|
case "reservoir":
|
||||||
|
case "reservoirs":
|
||||||
|
return { layer: "geo_reservoirs", geometryKind: "point" };
|
||||||
|
case "pump":
|
||||||
|
case "pumps":
|
||||||
|
return { layer: "geo_pumps", geometryKind: "point" };
|
||||||
|
case "tank":
|
||||||
|
case "tanks":
|
||||||
|
return { layer: "geo_tanks", geometryKind: "point" };
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -789,15 +789,50 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Other frontend tools → dispatch to chatToolStore immediately
|
// Other frontend tools → dispatch to chatToolStore immediately
|
||||||
|
const buildLocateFeaturesAction = (
|
||||||
|
layer: string,
|
||||||
|
geometryKind: "point" | "line",
|
||||||
|
): ChatToolAction => ({
|
||||||
|
type: "locate_features" as const,
|
||||||
|
ids: (params.ids as string[]) ?? [],
|
||||||
|
layer,
|
||||||
|
geometryKind,
|
||||||
|
});
|
||||||
|
const buildLocateByFeatureType = (): ChatToolAction | null => {
|
||||||
|
const rawType = params.feature_type;
|
||||||
|
const featureType =
|
||||||
|
typeof rawType === "string" ? rawType.trim().toLowerCase() : "";
|
||||||
|
const featureTypeMap: Record<
|
||||||
|
string,
|
||||||
|
{ layer: string; geometryKind: "point" | "line" }
|
||||||
|
> = {
|
||||||
|
junction: { layer: "geo_junctions_mat", geometryKind: "point" },
|
||||||
|
junctions: { layer: "geo_junctions_mat", geometryKind: "point" },
|
||||||
|
pipe: { layer: "geo_pipes_mat", geometryKind: "line" },
|
||||||
|
pipes: { layer: "geo_pipes_mat", geometryKind: "line" },
|
||||||
|
valve: { layer: "geo_valves", geometryKind: "point" },
|
||||||
|
valves: { layer: "geo_valves", geometryKind: "point" },
|
||||||
|
reservoir: { layer: "geo_reservoirs", geometryKind: "point" },
|
||||||
|
reservoirs: { layer: "geo_reservoirs", geometryKind: "point" },
|
||||||
|
pump: { layer: "geo_pumps", geometryKind: "point" },
|
||||||
|
pumps: { layer: "geo_pumps", geometryKind: "point" },
|
||||||
|
tank: { layer: "geo_tanks", geometryKind: "point" },
|
||||||
|
tanks: { layer: "geo_tanks", geometryKind: "point" },
|
||||||
|
};
|
||||||
|
const config = featureTypeMap[featureType];
|
||||||
|
if (!config) return null;
|
||||||
|
return buildLocateFeaturesAction(config.layer, config.geometryKind);
|
||||||
|
};
|
||||||
const actionMap: Record<string, () => ChatToolAction | null> = {
|
const actionMap: Record<string, () => ChatToolAction | null> = {
|
||||||
locate_nodes: () => ({
|
locate_features: buildLocateByFeatureType,
|
||||||
type: "locate_nodes" as const,
|
locate_pipes: () => buildLocateFeaturesAction("geo_pipes_mat", "line"),
|
||||||
ids: (params.ids as string[]) ?? [],
|
locate_junctions: () =>
|
||||||
}),
|
buildLocateFeaturesAction("geo_junctions_mat", "point"),
|
||||||
locate_pipes: () => ({
|
locate_valves: () => buildLocateFeaturesAction("geo_valves", "point"),
|
||||||
type: "locate_pipes" as const,
|
locate_reservoirs: () =>
|
||||||
ids: (params.ids as string[]) ?? [],
|
buildLocateFeaturesAction("geo_reservoirs", "point"),
|
||||||
}),
|
locate_pumps: () => buildLocateFeaturesAction("geo_pumps", "point"),
|
||||||
|
locate_tanks: () => buildLocateFeaturesAction("geo_tanks", "point"),
|
||||||
view_history: () => ({
|
view_history: () => ({
|
||||||
type: "view_history" as const,
|
type: "view_history" as const,
|
||||||
featureInfos: (params.feature_infos as [string, string][]) ?? [],
|
featureInfos: (params.feature_infos as [string, string][]) ?? [],
|
||||||
@@ -837,6 +872,17 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
);
|
);
|
||||||
} else if (event.type === "done") {
|
} else if (event.type === "done") {
|
||||||
if (!conversationId && event.conversationId) setConversationId(event.conversationId);
|
if (!conversationId && event.conversationId) setConversationId(event.conversationId);
|
||||||
|
setMessages((prev) =>
|
||||||
|
prev.map((m) =>
|
||||||
|
m.id === assistantId && m.content.trim().length === 0
|
||||||
|
? {
|
||||||
|
...m,
|
||||||
|
content: "⚠️ **错误:** Copilot 未返回内容,请稍后重试。",
|
||||||
|
isError: true,
|
||||||
|
}
|
||||||
|
: m
|
||||||
|
)
|
||||||
|
);
|
||||||
setIsStreaming(false);
|
setIsStreaming(false);
|
||||||
} else if (event.type === "error") {
|
} else if (event.type === "error") {
|
||||||
setMessages((prev) =>
|
setMessages((prev) =>
|
||||||
|
|||||||
@@ -56,11 +56,11 @@ describe("parseContentWithToolCalls", () => {
|
|||||||
|
|
||||||
it("parses a complete tool_call block", () => {
|
it("parses a complete tool_call block", () => {
|
||||||
const content =
|
const content =
|
||||||
'分析完成。\n<tool_call>{"tool":"locate_nodes","params":{"ids":["J1","J2"]}}</tool_call>\n以上是结果。';
|
'分析完成。\n<tool_call>{"tool":"locate_junctions","params":{"ids":["J1","J2"]}}</tool_call>\n以上是结果。';
|
||||||
const result = parseContentWithToolCalls(content);
|
const result = parseContentWithToolCalls(content);
|
||||||
|
|
||||||
expect(result.toolCalls).toHaveLength(1);
|
expect(result.toolCalls).toHaveLength(1);
|
||||||
expect(result.toolCalls[0].tool).toBe("locate_nodes");
|
expect(result.toolCalls[0].tool).toBe("locate_junctions");
|
||||||
expect(result.toolCalls[0].params).toEqual({ ids: ["J1", "J2"] });
|
expect(result.toolCalls[0].params).toEqual({ ids: ["J1", "J2"] });
|
||||||
|
|
||||||
expect(result.segments).toHaveLength(3);
|
expect(result.segments).toHaveLength(3);
|
||||||
@@ -70,7 +70,7 @@ describe("parseContentWithToolCalls", () => {
|
|||||||
});
|
});
|
||||||
expect(result.segments[1]).toMatchObject({
|
expect(result.segments[1]).toMatchObject({
|
||||||
type: "tool_call",
|
type: "tool_call",
|
||||||
toolCall: { tool: "locate_nodes" },
|
toolCall: { tool: "locate_junctions" },
|
||||||
});
|
});
|
||||||
expect(result.segments[2]).toEqual({
|
expect(result.segments[2]).toEqual({
|
||||||
type: "text",
|
type: "text",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import VectorLayer from "ol/layer/Vector";
|
|||||||
import { Style, Stroke, Fill, Circle } from "ol/style";
|
import { Style, Stroke, Fill, Circle } from "ol/style";
|
||||||
import Feature from "ol/Feature";
|
import Feature from "ol/Feature";
|
||||||
import { GeoJSON } from "ol/format";
|
import { GeoJSON } from "ol/format";
|
||||||
|
import Point from "ol/geom/Point";
|
||||||
import { bbox, featureCollection } from "@turf/turf";
|
import { bbox, featureCollection } from "@turf/turf";
|
||||||
import StyleEditorPanel from "./StyleEditorPanel";
|
import StyleEditorPanel from "./StyleEditorPanel";
|
||||||
import { LayerStyleState } from "./StyleEditorPanel";
|
import { LayerStyleState } from "./StyleEditorPanel";
|
||||||
@@ -72,38 +73,52 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|||||||
useCallback(
|
useCallback(
|
||||||
(action) => {
|
(action) => {
|
||||||
const geojsonFormat = new GeoJSON();
|
const geojsonFormat = new GeoJSON();
|
||||||
const zoomToFeatures = (features: Feature[]) => {
|
const zoomToFeatures = (
|
||||||
|
features: Feature[],
|
||||||
|
geometryKind: "point" | "line",
|
||||||
|
) => {
|
||||||
if (features.length === 0) return;
|
if (features.length === 0) return;
|
||||||
|
|
||||||
|
if (geometryKind === "point" && features.length === 1) {
|
||||||
|
const geometry = features[0].getGeometry();
|
||||||
|
if (geometry instanceof Point) {
|
||||||
|
map?.getView().animate({
|
||||||
|
center: geometry.getCoordinates(),
|
||||||
|
zoom: 18,
|
||||||
|
duration: 1000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const geojsonFeatures = features.map((f) =>
|
const geojsonFeatures = features.map((f) =>
|
||||||
geojsonFormat.writeFeatureObject(f),
|
geojsonFormat.writeFeatureObject(f),
|
||||||
);
|
);
|
||||||
const extent = bbox(featureCollection(geojsonFeatures as any));
|
const extent = bbox(featureCollection(geojsonFeatures as any));
|
||||||
if (extent) {
|
if (extent) {
|
||||||
map?.getView().fit(extent, { maxZoom: 18, duration: 1000 });
|
map?.getView().fit(extent, {
|
||||||
|
maxZoom: 18,
|
||||||
|
duration: 1000,
|
||||||
|
padding: geometryKind === "line" ? [60, 60, 60, 60] : [40, 40, 40, 40],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const locateFeatures = (
|
||||||
|
ids: string[],
|
||||||
|
layer: string,
|
||||||
|
geometryKind: "point" | "line",
|
||||||
|
) => {
|
||||||
|
queryFeaturesByIds(ids, layer).then((features) => {
|
||||||
|
if (features.length > 0) {
|
||||||
|
setHighlightFeatures(features);
|
||||||
|
zoomToFeatures(features, geometryKind);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "locate_nodes": {
|
case "locate_features": {
|
||||||
queryFeaturesByIds(action.ids, "geo_junctions_mat").then(
|
locateFeatures(action.ids, action.layer, action.geometryKind);
|
||||||
(features) => {
|
|
||||||
if (features.length > 0) {
|
|
||||||
setHighlightFeatures(features);
|
|
||||||
zoomToFeatures(features);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "locate_pipes": {
|
|
||||||
queryFeaturesByIds(action.ids, "geo_pipes_mat").then(
|
|
||||||
(features) => {
|
|
||||||
if (features.length > 0) {
|
|
||||||
setHighlightFeatures(features);
|
|
||||||
zoomToFeatures(features);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "view_history": {
|
case "view_history": {
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import {
|
|||||||
* ```ts
|
* ```ts
|
||||||
* useChatToolActionHandler((action) => {
|
* useChatToolActionHandler((action) => {
|
||||||
* switch (action.type) {
|
* switch (action.type) {
|
||||||
* case "locate_nodes": handleLocateNodes(action.ids); break;
|
* case "locate_features": handleLocateFeatures(action.ids, action.layer, action.geometryKind); break;
|
||||||
* case "locate_pipes": handleLocatePipes(action.ids); break;
|
|
||||||
* case "view_history": openHistoryPanel(action.featureInfos, action.dataType); break;
|
* case "view_history": openHistoryPanel(action.featureInfos, action.dataType); break;
|
||||||
* case "view_scada": openScadaPanel(action.featureInfos); break;
|
* case "view_scada": openScadaPanel(action.featureInfos); break;
|
||||||
* }
|
* }
|
||||||
@@ -23,7 +22,10 @@ export function useChatToolActionHandler(
|
|||||||
handler: (action: ChatToolAction) => void,
|
handler: (action: ChatToolAction) => void,
|
||||||
) {
|
) {
|
||||||
const handlerRef = useRef(handler);
|
const handlerRef = useRef(handler);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
handlerRef.current = handler;
|
handlerRef.current = handler;
|
||||||
|
}, [handler]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = useChatToolStore.subscribe(
|
const unsubscribe = useChatToolStore.subscribe(
|
||||||
|
|||||||
@@ -7,8 +7,12 @@ import { create } from "zustand";
|
|||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
export type ChatToolAction =
|
export type ChatToolAction =
|
||||||
| { type: "locate_nodes"; ids: string[] }
|
| {
|
||||||
| { type: "locate_pipes"; ids: string[] }
|
type: "locate_features";
|
||||||
|
ids: string[];
|
||||||
|
layer: string;
|
||||||
|
geometryKind: "point" | "line";
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: "view_history";
|
type: "view_history";
|
||||||
featureInfos: [string, string][];
|
featureInfos: [string, string][];
|
||||||
|
|||||||
Reference in New Issue
Block a user