优化渲染节点功能,使用 ref 文件渲染大量节点
Build Push and Deploy / docker-image (push) Successful in 7s
Build Push and Deploy / deploy-fallback-log (push) Has been skipped

This commit is contained in:
2026-05-18 16:15:38 +08:00
parent 39ee9a02e5
commit e4424b87d1
5 changed files with 140 additions and 76 deletions
+7 -26
View File
@@ -268,12 +268,7 @@ function getToolDescription(toolCall: ToolCall): string {
return (params.title as string | undefined) ?? "数据图表";
}
case "render_junctions": {
const nodeAreaMap =
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} 个分区`;
return (params.render_ref as string | undefined) ?? "渲染引用";
}
default:
return "";
@@ -398,28 +393,14 @@ function buildAction(toolCall: ToolCall): ChatToolAction | null {
yAxisName: params.y_axis_name as string | undefined,
};
case "render_junctions": {
const nodeAreaMap =
params.node_area_map && typeof params.node_area_map === "object"
? Object.fromEntries(
Object.entries(params.node_area_map as Record<string, unknown>)
.map(([key, value]) => [String(key), String(value ?? "")])
.filter(([, value]) => value.trim().length > 0),
)
: {};
const renderRef =
typeof params.render_ref === "string" ? params.render_ref.trim() : "";
if (!renderRef) {
return null;
}
return {
type: "render_junctions",
nodeAreaMap,
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),
)
: {},
renderRef,
};
}
default:
+35 -2
View File
@@ -1,7 +1,13 @@
"use client";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { Box, Drawer, alpha, useTheme } from "@mui/material";
import React, {
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from "react";
import { Box, Drawer, alpha, useMediaQuery, useTheme } from "@mui/material";
import type { AgentModel } from "@/lib/chatStream";
import { AgentComposer } from "./AgentComposer";
@@ -27,6 +33,7 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
const bottomRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement | null>(null);
const theme = useTheme();
const isDesktop = useMediaQuery(theme.breakpoints.up("sm"));
const {
speechState,
@@ -158,6 +165,32 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
};
}, [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 (
<Drawer
anchor="right"
@@ -136,26 +136,6 @@ const resolveTimeRange = (params: Record<string, unknown>) => ({
(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[]) => {
if (!names.length) return "";
return names.length > 3
@@ -251,20 +231,20 @@ const buildToolAction = (
}
if (tool === "render_junctions") {
const nodeAreaMap = resolveStringRecord(params.node_area_map);
const areaIds = resolveStringArray(params.area_ids);
const areaColors = resolveStringRecord(params.area_colors);
const renderRef =
typeof params.render_ref === "string" ? params.render_ref.trim() : "";
return {
action: {
type: "render_junctions",
nodeAreaMap,
areaIds,
areaColors,
},
action: renderRef
? {
type: "render_junctions",
renderRef,
sessionId: undefined,
}
: null,
kind: "map",
title: "渲染节点分区",
description: `${Object.keys(nodeAreaMap).length} 个节点`,
description: renderRef || "渲染引用",
};
}
@@ -286,6 +266,11 @@ export const useAgentToolActions = () => {
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,
@@ -295,8 +280,8 @@ export const useAgentToolActions = () => {
params: event.params,
});
if (action) {
dispatchToolAction(action);
if (normalizedAction) {
dispatchToolAction(normalizedAction);
}
},
[dispatchToolAction],
@@ -5,8 +5,13 @@ import Point from "ol/geom/Point";
import { bbox, featureCollection } from "@turf/turf";
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 { config } from "@/config/config";
import { useMap } from "../MapComponent";
type UseToolbarChatActionsParams = {
@@ -30,6 +35,7 @@ export const useToolbarChatActions = ({
}: UseToolbarChatActionsParams) => {
const map = useMap();
const chatJunctionRenderCleanupRef = useRef<(() => void) | null>(null);
const renderRequestSeqRef = useRef(0);
const disposeChatJunctionRender = useCallback(() => {
chatJunctionRenderCleanupRef.current?.();
@@ -122,22 +128,82 @@ export const useToolbarChatActions = ({
}
case "render_junctions": {
disposeChatJunctionRender();
renderRequestSeqRef.current += 1;
const requestSeq = renderRequestSeqRef.current;
if (Object.keys(action.nodeAreaMap).length === 0) {
if (!action.renderRef || !map) {
break;
}
if (map) {
chatJunctionRenderCleanupRef.current = applyJunctionAreaRender(
map,
{
nodeAreaMap: action.nodeAreaMap,
areaIds: action.areaIds,
areaColors: action.areaColors,
},
{ propertyKey: "chat_junction_render_index" },
);
}
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(
map,
renderPayload,
{ propertyKey: "chat_junction_render_index" },
);
} catch (error) {
console.error("Failed to resolve render_ref for junction render:", error);
}
})();
break;
}
}
+2 -3
View File
@@ -37,9 +37,8 @@ export type ChatToolAction =
}
| {
type: "render_junctions";
nodeAreaMap: Record<string, string>;
areaIds?: string[];
areaColors?: Record<string, string>;
renderRef: string;
sessionId?: string;
};
interface ChatToolState {