新增应用样式 agent 工具
Build Push and Deploy / docker-image (push) Successful in 1m30s
Build Push and Deploy / deploy-fallback-log (push) Has been skipped

This commit is contained in:
2026-05-29 10:27:27 +08:00
parent 0e82c080df
commit 9761ade8d8
9 changed files with 549 additions and 69 deletions
+27
View File
@@ -25,6 +25,11 @@ import {
type ChatToolAction,
} from "@/store/chatToolStore";
import type { ToolCall } from "./chatMessageSections";
import {
APPLY_LAYER_STYLE_TOOL,
describeApplyLayerStyle,
parseApplyLayerStylePayload,
} from "./toolCallStyleHelpers";
/* ------------------------------------------------------------------ */
/* Interactive card rendered inside a chat bubble for tool actions */
@@ -137,6 +142,12 @@ const TOOL_META: Record<string, ToolMeta> = {
actionLabel: "应用渲染",
color: "#3b82f6",
},
[APPLY_LAYER_STYLE_TOOL]: {
label: "图层样式",
icon: <LocationOnRounded sx={{ fontSize: 18 }} />,
actionLabel: "应用样式",
color: "#14b8a6",
},
};
/* ---------- helpers ---------- */
@@ -270,6 +281,10 @@ function getToolDescription(toolCall: ToolCall): string {
case "render_junctions": {
return (params.render_ref as string | undefined) ?? "渲染引用";
}
case APPLY_LAYER_STYLE_TOOL: {
const payload = parseApplyLayerStylePayload(params);
return payload ? describeApplyLayerStyle(payload) : "图层样式";
}
default:
return "";
}
@@ -403,6 +418,18 @@ function buildAction(toolCall: ToolCall): ChatToolAction | null {
renderRef,
};
}
case APPLY_LAYER_STYLE_TOOL: {
const payload = parseApplyLayerStylePayload(params);
if (!payload) {
return null;
}
return {
type: "apply_layer_style",
layerId: payload.layerId,
resetToDefault: payload.resetToDefault,
styleConfig: payload.styleConfig,
};
}
default:
return null;
}
@@ -5,6 +5,11 @@ import { useCallback } from "react";
import { useChatToolStore, type ChatToolAction } from "@/store/chatToolStore";
import type { StreamEvent } from "@/lib/chatStream";
import type { AgentArtifact, AgentArtifactKind } from "../GlobalChatbox.types";
import {
APPLY_LAYER_STYLE_TOOL,
describeApplyLayerStyle,
parseApplyLayerStylePayload,
} from "../toolCallStyleHelpers";
type ToolCallEvent = StreamEvent & { type: "tool_call" };
@@ -248,6 +253,23 @@ const buildToolAction = (
};
}
if (tool === APPLY_LAYER_STYLE_TOOL) {
const payload = parseApplyLayerStylePayload(params);
return {
action: payload
? {
type: "apply_layer_style",
layerId: payload.layerId,
resetToDefault: payload.resetToDefault,
styleConfig: payload.styleConfig,
}
: null,
kind: "map",
title: payload?.resetToDefault ? "重置图层样式" : "应用图层样式",
description: payload ? describeApplyLayerStyle(payload) : "图层样式",
};
}
return {
action: null,
kind: "tool",
+150
View File
@@ -0,0 +1,150 @@
import type { StyleConfig, DefaultLayerStyleId } from "@components/olmap/core/Controls/styleEditorTypes";
export type ApplyLayerStyleActionPayload = {
layerId: DefaultLayerStyleId;
resetToDefault: boolean;
styleConfig?: Partial<StyleConfig>;
};
export const APPLY_LAYER_STYLE_TOOL = "apply_layer_style";
const LAYER_LABELS: Record<DefaultLayerStyleId, string> = {
junctions: "节点",
pipes: "管道",
};
const asString = (value: unknown): string | undefined =>
typeof value === "string" && value.trim() ? value.trim() : undefined;
const asNumber = (value: unknown): number | undefined =>
typeof value === "number" && Number.isFinite(value)
? value
: typeof value === "string" && value.trim() && Number.isFinite(Number(value))
? Number(value)
: undefined;
const asBoolean = (value: unknown): boolean | undefined =>
typeof value === "boolean"
? value
: typeof value === "string"
? value === "true"
? true
: value === "false"
? false
: undefined
: undefined;
const asNumberArray = (value: unknown): number[] | undefined =>
Array.isArray(value)
? value
.map((item) => asNumber(item))
.filter((item): item is number => item !== undefined)
: undefined;
const asStringArray = (value: unknown): string[] | undefined =>
Array.isArray(value)
? value
.map((item) => asString(item))
.filter((item): item is string => item !== undefined)
: undefined;
export const normalizeStyleLayerId = (value: unknown): DefaultLayerStyleId | null => {
const normalized = asString(value)?.toLowerCase();
if (normalized === "junctions" || normalized === "pipes") {
return normalized;
}
return null;
};
export const getStyleLayerLabel = (layerId: DefaultLayerStyleId): string =>
LAYER_LABELS[layerId];
export const parseApplyLayerStylePayload = (
params: Record<string, unknown>,
): ApplyLayerStyleActionPayload | null => {
const layerId = normalizeStyleLayerId(params.layer_id ?? params.layerId);
if (!layerId) {
return null;
}
const resetToDefault = Boolean(
asBoolean(params.reset_to_default ?? params.resetToDefault),
);
const rawStyleConfig =
params.style_config && typeof params.style_config === "object"
? (params.style_config as Record<string, unknown>)
: params.styleConfig && typeof params.styleConfig === "object"
? (params.styleConfig as Record<string, unknown>)
: null;
const styleConfig: Partial<StyleConfig> | undefined = rawStyleConfig
? {
property: asString(rawStyleConfig.property),
classificationMethod: asString(
rawStyleConfig.classification_method ?? rawStyleConfig.classificationMethod,
),
segments: asNumber(rawStyleConfig.segments),
minSize: asNumber(rawStyleConfig.min_size ?? rawStyleConfig.minSize),
maxSize: asNumber(rawStyleConfig.max_size ?? rawStyleConfig.maxSize),
minStrokeWidth: asNumber(
rawStyleConfig.min_stroke_width ?? rawStyleConfig.minStrokeWidth,
),
maxStrokeWidth: asNumber(
rawStyleConfig.max_stroke_width ?? rawStyleConfig.maxStrokeWidth,
),
fixedStrokeWidth: asNumber(
rawStyleConfig.fixed_stroke_width ?? rawStyleConfig.fixedStrokeWidth,
),
colorType: asString(rawStyleConfig.color_type ?? rawStyleConfig.colorType),
singlePaletteIndex: asNumber(
rawStyleConfig.single_palette_index ?? rawStyleConfig.singlePaletteIndex,
),
gradientPaletteIndex: asNumber(
rawStyleConfig.gradient_palette_index ?? rawStyleConfig.gradientPaletteIndex,
),
rainbowPaletteIndex: asNumber(
rawStyleConfig.rainbow_palette_index ?? rawStyleConfig.rainbowPaletteIndex,
),
showLabels: asBoolean(rawStyleConfig.show_labels ?? rawStyleConfig.showLabels),
showId: asBoolean(rawStyleConfig.show_id ?? rawStyleConfig.showId),
opacity: asNumber(rawStyleConfig.opacity),
adjustWidthByProperty: asBoolean(
rawStyleConfig.adjust_width_by_property ??
rawStyleConfig.adjustWidthByProperty,
),
customBreaks: asNumberArray(
rawStyleConfig.custom_breaks ?? rawStyleConfig.customBreaks,
),
customColors: asStringArray(
rawStyleConfig.custom_colors ?? rawStyleConfig.customColors,
),
}
: undefined;
const hasStyleOverrides =
styleConfig &&
Object.values(styleConfig).some((value) =>
Array.isArray(value) ? value.length > 0 : value !== undefined,
);
if (!resetToDefault && !hasStyleOverrides) {
return null;
}
return {
layerId,
resetToDefault,
styleConfig: hasStyleOverrides ? styleConfig : undefined,
};
};
export const describeApplyLayerStyle = (
payload: ApplyLayerStyleActionPayload,
): string => {
const layerLabel = getStyleLayerLabel(payload.layerId);
if (payload.resetToDefault) {
return `${layerLabel} · 重置默认样式`;
}
const property = payload.styleConfig?.property;
return property ? `${layerLabel} · ${property}` : `${layerLabel} · 应用样式`;
};