添加工具调用解析和聊天工具操作处理

This commit is contained in:
2026-04-03 11:49:05 +08:00
parent a1c8041b11
commit d610a09c14
11 changed files with 1269 additions and 18 deletions
+40 -2
View File
@@ -68,6 +68,10 @@ export interface SCADADataPanelProps {
showCleaning?: boolean;
/** 清洗数据的回调 */
onCleanData?: () => void;
/** 外部传入开始时间(ISO8601 字符串),用于初始化并触发查询 */
start_time?: string;
/** 外部传入结束时间(ISO8601 字符串),用于初始化并触发查询 */
end_time?: string;
}
type PanelTab = "chart" | "table";
@@ -314,6 +318,8 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
fractionDigits = 2,
showCleaning = false,
onCleanData,
start_time,
end_time,
}) => {
const { open } = useNotification();
const { data: user } = useGetIdentity<IUser>();
@@ -396,8 +402,24 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
};
}, [showCleaning]);
const [from, setFrom] = useState<Dayjs>(() => dayjs().subtract(1, "day"));
const [to, setTo] = useState<Dayjs>(() => dayjs());
const [from, setFrom] = useState<Dayjs>(() => {
if (start_time) {
const parsedStart = dayjs(start_time);
if (parsedStart.isValid()) {
return parsedStart;
}
}
return dayjs().subtract(1, "day");
});
const [to, setTo] = useState<Dayjs>(() => {
if (end_time) {
const parsedEnd = dayjs(end_time);
if (parsedEnd.isValid()) {
return parsedEnd;
}
}
return dayjs();
});
const [activeTab, setActiveTab] = useState<PanelTab>(defaultTab);
const [timeSeries, setTimeSeries] = useState<TimeSeriesPoint[]>([]);
const [loadingState, setLoadingState] = useState<LoadingState>("idle");
@@ -412,6 +434,22 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
setActiveTab(defaultTab);
}, [defaultTab]);
useEffect(() => {
if (!start_time && !end_time) return;
if (start_time) {
const parsedStart = dayjs(start_time);
if (parsedStart.isValid()) {
setFrom((prev) => (parsedStart.isSame(prev) ? prev : parsedStart));
}
}
if (end_time) {
const parsedEnd = dayjs(end_time);
if (parsedEnd.isValid()) {
setTo((prev) => (parsedEnd.isSame(prev) ? prev : parsedEnd));
}
}
}, [start_time, end_time]);
const normalizedRange = useMemo(() => ensureValidRange(from, to), [from, to]);
const hasDevices = deviceIds.length > 0;
@@ -59,6 +59,10 @@ export interface SCADADataPanelProps {
defaultTab?: "chart" | "table";
/** Y 轴数值的小数位数 */
fractionDigits?: number;
/** 外部传入开始时间(ISO8601 字符串),用于初始化并触发查询 */
start_time?: string;
/** 外部传入结束时间(ISO8601 字符串),用于初始化并触发查询 */
end_time?: string;
}
type PanelTab = "chart" | "table";
@@ -396,6 +400,8 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
scheme_name,
defaultTab = "chart",
fractionDigits = 2,
start_time,
end_time,
}) => {
// 从 featureInfos 中提取设备 ID 列表
const deviceIds = useMemo(
@@ -403,8 +409,24 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
[featureInfos]
);
const [from, setFrom] = useState<Dayjs>(() => dayjs().subtract(1, "day"));
const [to, setTo] = useState<Dayjs>(() => dayjs());
const [from, setFrom] = useState<Dayjs>(() => {
if (start_time) {
const parsedStart = dayjs(start_time);
if (parsedStart.isValid()) {
return parsedStart;
}
}
return dayjs().subtract(1, "day");
});
const [to, setTo] = useState<Dayjs>(() => {
if (end_time) {
const parsedEnd = dayjs(end_time);
if (parsedEnd.isValid()) {
return parsedEnd;
}
}
return dayjs();
});
const [activeTab, setActiveTab] = useState<PanelTab>(defaultTab);
const [timeSeries, setTimeSeries] = useState<TimeSeriesPoint[]>([]);
const [loadingState, setLoadingState] = useState<LoadingState>("idle");
@@ -418,6 +440,22 @@ const SCADADataPanel: React.FC<SCADADataPanelProps> = ({
setActiveTab(defaultTab);
}, [defaultTab]);
useEffect(() => {
if (!start_time && !end_time) return;
if (start_time) {
const parsedStart = dayjs(start_time);
if (parsedStart.isValid()) {
setFrom((prev) => (parsedStart.isSame(prev) ? prev : parsedStart));
}
}
if (end_time) {
const parsedEnd = dayjs(end_time);
if (parsedEnd.isValid()) {
setTo((prev) => (parsedEnd.isSame(prev) ? prev : parsedEnd));
}
}
}, [start_time, end_time]);
const normalizedRange = useMemo(() => ensureValidRange(from, to), [from, to]);
const hasDevices = deviceIds.length > 0;
+108 -6
View File
@@ -8,16 +8,20 @@ import QueryStatsOutlinedIcon from "@mui/icons-material/QueryStatsOutlined";
import PropertyPanel from "./PropertyPanel"; // 引入属性面板组件
import DrawPanel from "./DrawPanel"; // 引入绘图面板组件
import HistoryDataPanel from "./HistoryDataPanel"; // 引入绘图面板组件
import SCADADataPanel from "@components/olmap/SCADA/SCADADataPanel";
import VectorSource from "ol/source/Vector";
import VectorLayer from "ol/layer/Vector";
import { Style, Stroke, Fill, Circle } from "ol/style";
import Feature from "ol/Feature";
import { GeoJSON } from "ol/format";
import { bbox, featureCollection } from "@turf/turf";
import StyleEditorPanel from "./StyleEditorPanel";
import { LayerStyleState } from "./StyleEditorPanel";
import StyleLegend from "./StyleLegend"; // 引入图例组件
import { handleMapClickSelectFeatures as mapClickSelectFeatures } from "@/utils/mapQueryService";
import { handleMapClickSelectFeatures as mapClickSelectFeatures, queryFeaturesByIds } from "@/utils/mapQueryService";
import { useNotification } from "@refinedev/core";
import { useChatToolActionHandler } from "@/hooks/useChatToolActionHandler";
import { config } from "@/config/config";
import { apiFetch } from "@/lib/apiFetch";
@@ -51,6 +55,89 @@ const Toolbar: React.FC<ToolbarProps> = ({
const selectedDate = data?.selectedDate;
const schemeName = data?.schemeName;
// Chat tool action → direct featureInfos override (bypasses OL Feature lookup)
const [chatPanelFeatureInfos, setChatPanelFeatureInfos] = useState<
[string, string][] | null
>(null);
const [chatPanelType, setChatPanelType] = useState<
"realtime" | "scheme" | "none"
>("none");
const [chatPanelTimeRange, setChatPanelTimeRange] = useState<{
startTime?: string;
endTime?: string;
} | null>(null);
// Wire up chat tool actions (locate, view_history, view_scada)
useChatToolActionHandler(
useCallback(
(action) => {
const geojsonFormat = new GeoJSON();
const zoomToFeatures = (features: Feature[]) => {
if (features.length === 0) return;
const geojsonFeatures = features.map((f) =>
geojsonFormat.writeFeatureObject(f),
);
const extent = bbox(featureCollection(geojsonFeatures as any));
if (extent) {
map?.getView().fit(extent, { maxZoom: 18, duration: 1000 });
}
};
switch (action.type) {
case "locate_nodes": {
queryFeaturesByIds(action.ids, "geo_junctions_mat").then(
(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;
}
case "view_history": {
setChatPanelFeatureInfos(action.featureInfos);
setChatPanelType(action.dataType);
setChatPanelTimeRange({
startTime: action.startTime,
endTime: action.endTime,
});
setShowHistoryPanel(true);
break;
}
case "view_scada": {
setChatPanelFeatureInfos(action.featureInfos);
setChatPanelType("none");
setChatPanelTimeRange({
startTime: action.startTime,
endTime: action.endTime,
});
setShowHistoryPanel(true);
setActiveTools((prev) => {
if (prev.includes("history")) {
return prev;
}
return [...prev, "history"];
});
break;
}
}
},
[map],
),
);
// 样式状态管理 - 在 Toolbar 中管理,带有默认样式
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>([
{
@@ -328,6 +415,8 @@ const Toolbar: React.FC<ToolbarProps> = ({
case "history":
setShowHistoryPanel(false);
setHighlightFeatures([]);
setChatPanelFeatureInfos(null);
setChatPanelTimeRange(null);
break;
}
};
@@ -354,6 +443,8 @@ const Toolbar: React.FC<ToolbarProps> = ({
setHighlightFeatures([]);
setShowDrawPanel(false);
setShowHistoryPanel(false);
setChatPanelFeatureInfos(null);
setChatPanelTimeRange(null);
// 样式编辑器保持其当前状态,不自动关闭
};
const [computedProperties, setComputedProperties] = useState<
@@ -770,9 +861,16 @@ const Toolbar: React.FC<ToolbarProps> = ({
/>
</div>
{showHistoryPanel &&
(HistoryPanel ? (
(chatPanelType === "none" && chatPanelFeatureInfos ? (
<SCADADataPanel
deviceIds={chatPanelFeatureInfos.map(([id]) => id)}
visible={showHistoryPanel}
start_time={chatPanelTimeRange?.startTime}
end_time={chatPanelTimeRange?.endTime}
/>
) : HistoryPanel ? (
<HistoryPanel
featureInfos={(() => {
featureInfos={chatPanelFeatureInfos ?? (() => {
if (highlightFeatures.length === 0 || !showHistoryPanel)
return [];
@@ -810,11 +908,13 @@ const Toolbar: React.FC<ToolbarProps> = ({
})()}
scheme_type="burst_Analysis"
scheme_name={schemeName}
type={queryType as "realtime" | "scheme" | "none"}
type={chatPanelFeatureInfos ? chatPanelType : (queryType as "realtime" | "scheme" | "none")}
start_time={chatPanelTimeRange?.startTime}
end_time={chatPanelTimeRange?.endTime}
/>
) : (
<HistoryDataPanel
featureInfos={(() => {
featureInfos={chatPanelFeatureInfos ?? (() => {
if (highlightFeatures.length === 0 || !showHistoryPanel)
return [];
@@ -852,7 +952,9 @@ const Toolbar: React.FC<ToolbarProps> = ({
})()}
scheme_type="burst_Analysis"
scheme_name={schemeName}
type={queryType as "realtime" | "scheme" | "none"}
type={chatPanelFeatureInfos ? chatPanelType : (queryType as "realtime" | "scheme" | "none")}
start_time={chatPanelTimeRange?.startTime}
end_time={chatPanelTimeRange?.endTime}
/>
))}