优化渲染节点功能,使用 ref 文件渲染大量节点
This commit is contained in:
@@ -268,12 +268,7 @@ function getToolDescription(toolCall: ToolCall): string {
|
|||||||
return (params.title as string | undefined) ?? "数据图表";
|
return (params.title as string | undefined) ?? "数据图表";
|
||||||
}
|
}
|
||||||
case "render_junctions": {
|
case "render_junctions": {
|
||||||
const nodeAreaMap =
|
return (params.render_ref as string | undefined) ?? "渲染引用";
|
||||||
params.node_area_map && typeof params.node_area_map === "object"
|
|
||||||
? (params.node_area_map as Record<string, unknown>)
|
|
||||||
: {};
|
|
||||||
const areaIds = Array.isArray(params.area_ids) ? params.area_ids : [];
|
|
||||||
return `${Object.keys(nodeAreaMap).length} 个节点 · ${areaIds.length || new Set(Object.values(nodeAreaMap).map(String)).size} 个分区`;
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
@@ -398,28 +393,14 @@ function buildAction(toolCall: ToolCall): ChatToolAction | null {
|
|||||||
yAxisName: params.y_axis_name as string | undefined,
|
yAxisName: params.y_axis_name as string | undefined,
|
||||||
};
|
};
|
||||||
case "render_junctions": {
|
case "render_junctions": {
|
||||||
const nodeAreaMap =
|
const renderRef =
|
||||||
params.node_area_map && typeof params.node_area_map === "object"
|
typeof params.render_ref === "string" ? params.render_ref.trim() : "";
|
||||||
? Object.fromEntries(
|
if (!renderRef) {
|
||||||
Object.entries(params.node_area_map as Record<string, unknown>)
|
return null;
|
||||||
.map(([key, value]) => [String(key), String(value ?? "")])
|
}
|
||||||
.filter(([, value]) => value.trim().length > 0),
|
|
||||||
)
|
|
||||||
: {};
|
|
||||||
return {
|
return {
|
||||||
type: "render_junctions",
|
type: "render_junctions",
|
||||||
nodeAreaMap,
|
renderRef,
|
||||||
areaIds: Array.isArray(params.area_ids)
|
|
||||||
? params.area_ids.map((item) => String(item).trim()).filter(Boolean)
|
|
||||||
: [],
|
|
||||||
areaColors:
|
|
||||||
params.area_colors && typeof params.area_colors === "object"
|
|
||||||
? Object.fromEntries(
|
|
||||||
Object.entries(params.area_colors as Record<string, unknown>)
|
|
||||||
.map(([key, value]) => [String(key), String(value ?? "")])
|
|
||||||
.filter(([, value]) => value.trim().length > 0),
|
|
||||||
)
|
|
||||||
: {},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
import React, {
|
||||||
import { Box, Drawer, alpha, useTheme } from "@mui/material";
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { Box, Drawer, alpha, useMediaQuery, useTheme } from "@mui/material";
|
||||||
|
|
||||||
import type { AgentModel } from "@/lib/chatStream";
|
import type { AgentModel } from "@/lib/chatStream";
|
||||||
import { AgentComposer } from "./AgentComposer";
|
import { AgentComposer } from "./AgentComposer";
|
||||||
@@ -27,6 +33,7 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
const bottomRef = useRef<HTMLDivElement>(null);
|
const bottomRef = useRef<HTMLDivElement>(null);
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const isDesktop = useMediaQuery(theme.breakpoints.up("sm"));
|
||||||
|
|
||||||
const {
|
const {
|
||||||
speechState,
|
speechState,
|
||||||
@@ -158,6 +165,32 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
};
|
};
|
||||||
}, [isResizing]);
|
}, [isResizing]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const body = document.body;
|
||||||
|
const html = document.documentElement;
|
||||||
|
const previousBodyPaddingRight = body.style.paddingRight;
|
||||||
|
const previousBodyTransition = body.style.transition;
|
||||||
|
const previousBodyBoxSizing = body.style.boxSizing;
|
||||||
|
const previousHtmlBoxSizing = html.style.boxSizing;
|
||||||
|
const reservedWidth = open && isDesktop ? `${width}px` : "0px";
|
||||||
|
|
||||||
|
body.style.boxSizing = "border-box";
|
||||||
|
html.style.boxSizing = "border-box";
|
||||||
|
body.style.paddingRight = reservedWidth;
|
||||||
|
body.style.transition = isResizing
|
||||||
|
? previousBodyTransition
|
||||||
|
: [previousBodyTransition, "padding-right 240ms cubic-bezier(0.2, 0.8, 0.2, 1)"]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
body.style.paddingRight = previousBodyPaddingRight;
|
||||||
|
body.style.transition = previousBodyTransition;
|
||||||
|
body.style.boxSizing = previousBodyBoxSizing;
|
||||||
|
html.style.boxSizing = previousHtmlBoxSizing;
|
||||||
|
};
|
||||||
|
}, [isDesktop, isResizing, open, width]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
anchor="right"
|
anchor="right"
|
||||||
|
|||||||
@@ -136,26 +136,6 @@ const resolveTimeRange = (params: Record<string, unknown>) => ({
|
|||||||
(params.end as string | undefined),
|
(params.end as string | undefined),
|
||||||
});
|
});
|
||||||
|
|
||||||
const resolveStringRecord = (value: unknown): Record<string, string> => {
|
|
||||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(value as Record<string, unknown>)
|
|
||||||
.map(([key, recordValue]) => [String(key), String(recordValue ?? "")])
|
|
||||||
.filter(([, recordValue]) => recordValue.trim().length > 0),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolveStringArray = (value: unknown): string[] => {
|
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.map((item) => String(item).trim()).filter(Boolean);
|
|
||||||
};
|
|
||||||
|
|
||||||
const compactNames = (names: string[]) => {
|
const compactNames = (names: string[]) => {
|
||||||
if (!names.length) return "";
|
if (!names.length) return "";
|
||||||
return names.length > 3
|
return names.length > 3
|
||||||
@@ -251,20 +231,20 @@ const buildToolAction = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tool === "render_junctions") {
|
if (tool === "render_junctions") {
|
||||||
const nodeAreaMap = resolveStringRecord(params.node_area_map);
|
const renderRef =
|
||||||
const areaIds = resolveStringArray(params.area_ids);
|
typeof params.render_ref === "string" ? params.render_ref.trim() : "";
|
||||||
const areaColors = resolveStringRecord(params.area_colors);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
action: {
|
action: renderRef
|
||||||
|
? {
|
||||||
type: "render_junctions",
|
type: "render_junctions",
|
||||||
nodeAreaMap,
|
renderRef,
|
||||||
areaIds,
|
sessionId: undefined,
|
||||||
areaColors,
|
}
|
||||||
},
|
: null,
|
||||||
kind: "map",
|
kind: "map",
|
||||||
title: "渲染节点分区",
|
title: "渲染节点分区",
|
||||||
description: `${Object.keys(nodeAreaMap).length} 个节点`,
|
description: renderRef || "渲染引用",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +266,11 @@ export const useAgentToolActions = () => {
|
|||||||
event.params,
|
event.params,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const normalizedAction =
|
||||||
|
action?.type === "render_junctions"
|
||||||
|
? { ...action, sessionId: event.sessionId }
|
||||||
|
: action;
|
||||||
|
|
||||||
options.appendArtifact(options.assistantMessageId, {
|
options.appendArtifact(options.assistantMessageId, {
|
||||||
id: `${event.tool}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
id: `${event.tool}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||||
tool: event.tool,
|
tool: event.tool,
|
||||||
@@ -295,8 +280,8 @@ export const useAgentToolActions = () => {
|
|||||||
params: event.params,
|
params: event.params,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (action) {
|
if (normalizedAction) {
|
||||||
dispatchToolAction(action);
|
dispatchToolAction(normalizedAction);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatchToolAction],
|
[dispatchToolAction],
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ import Point from "ol/geom/Point";
|
|||||||
import { bbox, featureCollection } from "@turf/turf";
|
import { bbox, featureCollection } from "@turf/turf";
|
||||||
|
|
||||||
import { useChatToolActionHandler } from "@/hooks/useChatToolActionHandler";
|
import { useChatToolActionHandler } from "@/hooks/useChatToolActionHandler";
|
||||||
import { applyJunctionAreaRender } from "@components/olmap/DMALeakDetection/applyJunctionAreaRender";
|
import {
|
||||||
|
applyJunctionAreaRender,
|
||||||
|
type JunctionAreaRenderPayload,
|
||||||
|
} from "@components/olmap/DMALeakDetection/applyJunctionAreaRender";
|
||||||
|
import { apiFetch } from "@/lib/apiFetch";
|
||||||
import { queryFeaturesByIds } from "@/utils/mapQueryService";
|
import { queryFeaturesByIds } from "@/utils/mapQueryService";
|
||||||
|
import { config } from "@/config/config";
|
||||||
import { useMap } from "../MapComponent";
|
import { useMap } from "../MapComponent";
|
||||||
|
|
||||||
type UseToolbarChatActionsParams = {
|
type UseToolbarChatActionsParams = {
|
||||||
@@ -30,6 +35,7 @@ export const useToolbarChatActions = ({
|
|||||||
}: UseToolbarChatActionsParams) => {
|
}: UseToolbarChatActionsParams) => {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
const chatJunctionRenderCleanupRef = useRef<(() => void) | null>(null);
|
const chatJunctionRenderCleanupRef = useRef<(() => void) | null>(null);
|
||||||
|
const renderRequestSeqRef = useRef(0);
|
||||||
|
|
||||||
const disposeChatJunctionRender = useCallback(() => {
|
const disposeChatJunctionRender = useCallback(() => {
|
||||||
chatJunctionRenderCleanupRef.current?.();
|
chatJunctionRenderCleanupRef.current?.();
|
||||||
@@ -122,22 +128,82 @@ export const useToolbarChatActions = ({
|
|||||||
}
|
}
|
||||||
case "render_junctions": {
|
case "render_junctions": {
|
||||||
disposeChatJunctionRender();
|
disposeChatJunctionRender();
|
||||||
|
renderRequestSeqRef.current += 1;
|
||||||
|
const requestSeq = renderRequestSeqRef.current;
|
||||||
|
|
||||||
if (Object.keys(action.nodeAreaMap).length === 0) {
|
if (!action.renderRef || !map) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map) {
|
void (async () => {
|
||||||
|
try {
|
||||||
|
const query = action.sessionId
|
||||||
|
? `?session_id=${encodeURIComponent(action.sessionId)}`
|
||||||
|
: "";
|
||||||
|
const response = await apiFetch(
|
||||||
|
`${config.AGENT_URL}/api/v1/agent/chat/render-ref/${encodeURIComponent(action.renderRef)}${query}`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
projectHeaderMode: "include",
|
||||||
|
userHeaderMode: "include",
|
||||||
|
skipAuthRedirect: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`render ref request failed: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = (await response.json()) as {
|
||||||
|
data?: {
|
||||||
|
node_area_map?: Record<string, unknown>;
|
||||||
|
area_ids?: unknown[];
|
||||||
|
area_colors?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = payload.data;
|
||||||
|
if (!data?.node_area_map) {
|
||||||
|
throw new Error("render ref payload missing node_area_map");
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderPayload: JunctionAreaRenderPayload = {
|
||||||
|
nodeAreaMap: Object.fromEntries(
|
||||||
|
Object.entries(data.node_area_map).map(([key, value]) => [
|
||||||
|
String(key),
|
||||||
|
String(value ?? ""),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
areaIds: Array.isArray(data.area_ids)
|
||||||
|
? data.area_ids.map((item) => String(item).trim()).filter(Boolean)
|
||||||
|
: [],
|
||||||
|
areaColors:
|
||||||
|
data.area_colors && typeof data.area_colors === "object"
|
||||||
|
? Object.fromEntries(
|
||||||
|
Object.entries(data.area_colors).map(([key, value]) => [
|
||||||
|
String(key),
|
||||||
|
String(value ?? ""),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
requestSeq !== renderRequestSeqRef.current ||
|
||||||
|
Object.keys(renderPayload.nodeAreaMap).length === 0
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
chatJunctionRenderCleanupRef.current = applyJunctionAreaRender(
|
chatJunctionRenderCleanupRef.current = applyJunctionAreaRender(
|
||||||
map,
|
map,
|
||||||
{
|
renderPayload,
|
||||||
nodeAreaMap: action.nodeAreaMap,
|
|
||||||
areaIds: action.areaIds,
|
|
||||||
areaColors: action.areaColors,
|
|
||||||
},
|
|
||||||
{ propertyKey: "chat_junction_render_index" },
|
{ propertyKey: "chat_junction_render_index" },
|
||||||
);
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to resolve render_ref for junction render:", error);
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,9 +37,8 @@ export type ChatToolAction =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "render_junctions";
|
type: "render_junctions";
|
||||||
nodeAreaMap: Record<string, string>;
|
renderRef: string;
|
||||||
areaIds?: string[];
|
sessionId?: string;
|
||||||
areaColors?: Record<string, string>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ChatToolState {
|
interface ChatToolState {
|
||||||
|
|||||||
Reference in New Issue
Block a user