新增应用样式 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} · 应用样式`;
};
@@ -2,34 +2,25 @@ import React from "react";
import StyleEditorForm from "./StyleEditorForm";
import { createDefaultLayerStyleState, createDefaultLayerStyleStates } from "./styleEditorPresets";
import { useStyleEditor } from "./useStyleEditor";
import { LayerStyleState, StyleConfig, StyleEditorPanelProps } from "./styleEditorTypes";
const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
layerStyleStates,
setLayerStyleStates,
isReady,
renderLayers,
selectedRenderLayer,
styleConfig,
setStyleConfig,
availableProperties,
onLayerChange,
onPropertyChange,
onClassificationMethodChange,
onSegmentsChange,
onCustomBreakChange,
onCustomBreakBlur,
onColorTypeChange,
onApply,
onReset,
}) => {
const {
isReady,
renderLayers,
selectedRenderLayer,
styleConfig,
setStyleConfig,
availableProperties,
handleLayerChange,
handlePropertyChange,
handleClassificationMethodChange,
handleSegmentsChange,
handleCustomBreakChange,
handleCustomBreakBlur,
handleColorTypeChange,
handleApply,
handleReset,
} = useStyleEditor({
layerStyleStates,
setLayerStyleStates,
});
if (!isReady) {
return <div>Loading...</div>;
}
@@ -41,15 +32,15 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
styleConfig={styleConfig}
setStyleConfig={setStyleConfig}
availableProperties={availableProperties}
onLayerChange={handleLayerChange}
onPropertyChange={handlePropertyChange}
onClassificationMethodChange={handleClassificationMethodChange}
onSegmentsChange={handleSegmentsChange}
onCustomBreakChange={handleCustomBreakChange}
onCustomBreakBlur={handleCustomBreakBlur}
onColorTypeChange={handleColorTypeChange}
onApply={handleApply}
onReset={handleReset}
onLayerChange={onLayerChange}
onPropertyChange={onPropertyChange}
onClassificationMethodChange={onClassificationMethodChange}
onSegmentsChange={onSegmentsChange}
onCustomBreakChange={onCustomBreakChange}
onCustomBreakBlur={onCustomBreakBlur}
onColorTypeChange={onColorTypeChange}
onApply={onApply}
onReset={onReset}
/>
);
};
+28 -7
View File
@@ -24,6 +24,7 @@ import {
buildFeatureProperties,
} from "./toolbarFeatureHelpers";
import { useToolbarChatActions } from "./useToolbarChatActions";
import { useStyleEditor } from "./useStyleEditor";
import { config } from "@/config/config";
import { apiFetch } from "@/lib/apiFetch";
@@ -81,20 +82,27 @@ const Toolbar: React.FC<ToolbarProps> = ({
endTime?: string;
} | null>(null);
// 样式状态管理 - 在 Toolbar 中管理,带有默认样式
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>(
() => createDefaultLayerStyleStates()
);
const styleEditor = useStyleEditor({
layerStyleStates,
setLayerStyleStates,
});
useToolbarChatActions({
setHighlightFeatures,
setChatPanelFeatureInfos,
setChatPanelType,
setChatPanelTimeRange,
setShowHistoryPanel,
setShowStyleEditor,
setActiveTools,
applyExternalStyle: styleEditor.applyExternalStyle,
resetExternalStyle: styleEditor.resetExternalStyle,
});
// 样式状态管理 - 在 Toolbar 中管理,带有默认样式
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>(
() => createDefaultLayerStyleStates()
);
// 计算激活的图例配置
const activeLegendConfigs = layerStyleStates
.filter((state) => state.isActive && state.legendConfig.property)
@@ -444,8 +452,21 @@ const Toolbar: React.FC<ToolbarProps> = ({
{showDrawPanel && map && <DrawPanel />}
<div style={{ display: showStyleEditor ? "block" : "none" }}>
<StyleEditorPanel
layerStyleStates={layerStyleStates}
setLayerStyleStates={setLayerStyleStates}
isReady={styleEditor.isReady}
renderLayers={styleEditor.renderLayers}
selectedRenderLayer={styleEditor.selectedRenderLayer}
styleConfig={styleEditor.styleConfig}
setStyleConfig={styleEditor.setStyleConfig}
availableProperties={styleEditor.availableProperties}
onLayerChange={styleEditor.handleLayerChange}
onPropertyChange={styleEditor.handlePropertyChange}
onClassificationMethodChange={styleEditor.handleClassificationMethodChange}
onSegmentsChange={styleEditor.handleSegmentsChange}
onCustomBreakChange={styleEditor.handleCustomBreakChange}
onCustomBreakBlur={styleEditor.handleCustomBreakBlur}
onColorTypeChange={styleEditor.handleColorTypeChange}
onApply={styleEditor.handleApply}
onReset={styleEditor.handleReset}
/>
</div>
<ToolbarHistoryPanel
@@ -34,7 +34,7 @@ export interface LayerStyleState {
export type DefaultLayerStyleId = "junctions" | "pipes";
export interface StyleEditorPanelProps {
export interface StyleEditorStateProps {
layerStyleStates: LayerStyleState[];
setLayerStyleStates: React.Dispatch<React.SetStateAction<LayerStyleState[]>>;
}
@@ -60,3 +60,7 @@ export interface StyleEditorFormProps {
onApply: () => void;
onReset: () => void;
}
export interface StyleEditorPanelProps extends StyleEditorFormProps {
isReady: boolean;
}
@@ -25,8 +25,10 @@ import {
} from "./styleEditorUtils";
import {
AvailableProperty,
DefaultLayerStyleId,
LayerStyleState,
StyleEditorPanelProps,
StyleConfig,
StyleEditorStateProps,
} from "./styleEditorTypes";
import { LegendStyleConfig } from "./StyleLegend";
import { calculateClassification } from "@utils/breaks_classification";
@@ -36,7 +38,7 @@ const UNIT_HEADLOSS_RANGE: [number, number] = [0, 5];
export const useStyleEditor = ({
layerStyleStates,
setLayerStyleStates,
}: StyleEditorPanelProps) => {
}: StyleEditorStateProps) => {
const map = useMap();
const data = useData();
const { open } = useNotification();
@@ -85,6 +87,51 @@ export const useStyleEditor = ({
latestLayerStyleStatesRef.current = layerStyleStates;
}, [layerStyleStates]);
const upsertLayerStyleState = useCallback(
(newStyleState: LayerStyleState) => {
const existingState = latestLayerStyleStatesRef.current.find(
(state) => state.layerId === newStyleState.layerId
);
if (
existingState &&
JSON.stringify(existingState.styleConfig) ===
JSON.stringify(newStyleState.styleConfig) &&
JSON.stringify(existingState.legendConfig) ===
JSON.stringify(newStyleState.legendConfig) &&
existingState.layerName === newStyleState.layerName &&
existingState.isActive === newStyleState.isActive
) {
return;
}
setLayerStyleStates((prev) => {
const existingIndex = prev.findIndex(
(state) => state.layerId === newStyleState.layerId
);
const nextStates =
existingIndex === -1
? [...prev, newStyleState]
: prev.map((state, index) =>
index === existingIndex ? newStyleState : state
);
latestLayerStyleStatesRef.current = nextStates;
return nextStates;
});
},
[setLayerStyleStates]
);
const removeLayerStyleState = useCallback(
(layerId: string) => {
setLayerStyleStates((prev) => {
const nextStates = prev.filter((state) => state.layerId !== layerId);
latestLayerStyleStatesRef.current = nextStates;
return nextStates;
});
},
[setLayerStyleStates]
);
const getRenderLayersById = useCallback(
(layerId: string) =>
activeMaps.flatMap((targetMap) =>
@@ -191,28 +238,9 @@ export const useStyleEditor = ({
isActive: true,
};
setLayerStyleStates((prev) => {
const existingIndex = prev.findIndex((state) => state.layerId === layerId);
if (existingIndex !== -1) {
const existingState = prev[existingIndex];
if (
JSON.stringify(existingState.styleConfig) ===
JSON.stringify(newStyleState.styleConfig) &&
JSON.stringify(existingState.legendConfig) ===
JSON.stringify(newStyleState.legendConfig) &&
existingState.layerName === newStyleState.layerName &&
existingState.isActive === newStyleState.isActive
) {
return prev;
}
const updated = [...prev];
updated[existingIndex] = newStyleState;
return updated;
}
return [...prev, newStyleState];
});
upsertLayerStyleState(newStyleState);
},
[availableProperties, selectedRenderLayer, setLayerStyleStates, styleConfig]
[availableProperties, selectedRenderLayer, styleConfig, upsertLayerStyleState]
);
const applyContourLayerStyle = useCallback(
@@ -520,9 +548,7 @@ export const useStyleEditor = ({
setShowJunctionTextLayer?.(styleConfig.showLabels);
setShowJunctionId?.(styleConfig.showId);
setApplyJunctionStyle(true);
if (property === "pressure") {
setContourLayerAvailable?.(true);
}
setContourLayerAvailable?.(property === "pressure");
saveLayerStyle(layerId);
open?.({
type: "success",
@@ -571,7 +597,7 @@ export const useStyleEditor = ({
targetLayer.setStyle(defaultFlatStyle);
});
setLayerStyleStates((prev) => prev.filter((state) => state.layerId !== layerId));
removeLayerStyleState(layerId);
if (layerId === "junctions") {
setApplyJunctionStyle(false);
@@ -593,7 +619,7 @@ export const useStyleEditor = ({
setContourLayerAvailable,
setContours,
setJunctionText,
setLayerStyleStates,
removeLayerStyleState,
setPipeText,
setShowJunctionId,
setShowJunctionTextLayer,
@@ -602,6 +628,211 @@ export const useStyleEditor = ({
setWaterflowLayerAvailable,
]);
const normalizeExternalStyleConfig = useCallback(
(layerId: DefaultLayerStyleId, overrides?: Partial<StyleConfig>): StyleConfig => {
const currentStyleState = latestLayerStyleStatesRef.current.find(
(state) => state.layerId === layerId
);
const baseStyleConfig =
currentStyleState?.styleConfig || createDefaultLayerStyleState(layerId).styleConfig;
const nextStyleConfig: StyleConfig = {
...baseStyleConfig,
...overrides,
customBreaks: overrides?.customBreaks
? [...overrides.customBreaks]
: [...(baseStyleConfig.customBreaks || [])],
customColors: overrides?.customColors
? [...overrides.customColors]
: [...(baseStyleConfig.customColors || [])],
};
nextStyleConfig.segments = Math.max(1, Math.round(nextStyleConfig.segments || 1));
nextStyleConfig.opacity = Math.min(1, Math.max(0, nextStyleConfig.opacity));
nextStyleConfig.singlePaletteIndex = Math.max(
0,
Math.round(nextStyleConfig.singlePaletteIndex || 0)
);
nextStyleConfig.gradientPaletteIndex = Math.max(
0,
Math.round(nextStyleConfig.gradientPaletteIndex || 0)
);
nextStyleConfig.rainbowPaletteIndex = Math.max(
0,
Math.round(nextStyleConfig.rainbowPaletteIndex || 0)
);
nextStyleConfig.minSize = Math.max(1, nextStyleConfig.minSize);
nextStyleConfig.maxSize = Math.max(nextStyleConfig.minSize, nextStyleConfig.maxSize);
nextStyleConfig.minStrokeWidth = Math.max(1, nextStyleConfig.minStrokeWidth);
nextStyleConfig.maxStrokeWidth = Math.max(
nextStyleConfig.minStrokeWidth,
nextStyleConfig.maxStrokeWidth
);
nextStyleConfig.fixedStrokeWidth = Math.max(1, nextStyleConfig.fixedStrokeWidth);
nextStyleConfig.customColors =
nextStyleConfig.colorType === "custom"
? getDefaultCustomColors(
nextStyleConfig.segments,
nextStyleConfig.customColors || []
)
: nextStyleConfig.customColors;
nextStyleConfig.customBreaks =
nextStyleConfig.classificationMethod === "custom_breaks"
? normalizeCustomBreaks(
nextStyleConfig.customBreaks ||
getBreakDefaults(
nextStyleConfig.segments,
nextStyleConfig.property,
getRenderLayersById(layerId)[0]
),
nextStyleConfig.segments
)
: nextStyleConfig.customBreaks;
return nextStyleConfig;
},
[getBreakDefaults, getRenderLayersById]
);
const applyExternalStyle = useCallback(
(layerId: DefaultLayerStyleId, overrides?: Partial<StyleConfig>) => {
const targetLayer = getRenderLayersById(layerId)[0];
if (!targetLayer) {
open?.({
type: "error",
message: `未找到${layerId === "junctions" ? "节点" : "管道"}图层,无法应用样式。`,
});
return;
}
const nextStyleConfig = normalizeExternalStyleConfig(layerId, overrides);
if (!nextStyleConfig.property) {
open?.({
type: "error",
message: "样式工具缺少有效的渲染属性,无法应用样式。",
});
return;
}
const layerName = targetLayer.get("name") || (layerId === "junctions" ? "节点" : "管道");
const targetProperties = (targetLayer.get("properties") || []) as AvailableProperty[];
const propertyLabel =
targetProperties.find((item) => item.value === nextStyleConfig.property)?.name ||
nextStyleConfig.property;
setSelectedRenderLayer(targetLayer);
setStyleConfig(nextStyleConfig);
upsertLayerStyleState({
layerId,
layerName,
styleConfig: nextStyleConfig,
legendConfig: {
layerId,
layerName,
property: propertyLabel,
colors: [],
type: targetLayer.get("type") || (layerId === "junctions" ? "point" : "linestring"),
dimensions: [],
breaks: [],
},
isActive: true,
});
if (layerId === "junctions") {
setJunctionText?.(nextStyleConfig.property);
setShowJunctionTextLayer?.(nextStyleConfig.showLabels);
setShowJunctionId?.(nextStyleConfig.showId);
setContourLayerAvailable?.(nextStyleConfig.property === "pressure");
setApplyJunctionStyle(true);
} else {
setPipeText?.(nextStyleConfig.property);
setShowPipeTextLayer?.(nextStyleConfig.showLabels);
setShowPipeId?.(nextStyleConfig.showId);
setWaterflowLayerAvailable?.(true);
setApplyPipeStyle(true);
}
applyClassificationStyle(layerId, nextStyleConfig);
open?.({
type: "success",
message: `${layerId === "junctions" ? "节点" : "管道"}图层样式已应用。`,
});
},
[
applyClassificationStyle,
getRenderLayersById,
normalizeExternalStyleConfig,
open,
setContourLayerAvailable,
setJunctionText,
setPipeText,
setShowJunctionId,
setShowJunctionTextLayer,
setShowPipeId,
setShowPipeTextLayer,
setWaterflowLayerAvailable,
upsertLayerStyleState,
]
);
const resetExternalStyle = useCallback(
(layerId: DefaultLayerStyleId) => {
const targetLayer = getRenderLayersById(layerId)[0];
if (!targetLayer) {
open?.({
type: "error",
message: `未找到${layerId === "junctions" ? "节点" : "管道"}图层,无法重置样式。`,
});
return;
}
const defaultStyleConfig = createDefaultLayerStyleState(layerId).styleConfig;
const defaultFlatStyle: FlatStyleLike = config.MAP_DEFAULT_STYLE;
setSelectedRenderLayer(targetLayer);
setStyleConfig(defaultStyleConfig);
getRenderLayersById(layerId).forEach((renderLayer) => {
renderLayer.setStyle(defaultFlatStyle);
});
removeLayerStyleState(layerId);
if (layerId === "junctions") {
setApplyJunctionStyle(false);
setShowJunctionTextLayer?.(false);
setShowJunctionId?.(false);
setJunctionText?.("");
setContours?.([]);
setContourLayerAvailable?.(false);
} else {
setApplyPipeStyle(false);
setShowPipeTextLayer?.(false);
setShowPipeId?.(false);
setPipeText?.("");
setWaterflowLayerAvailable?.(false);
}
open?.({
type: "success",
message: `${layerId === "junctions" ? "节点" : "管道"}图层样式已重置。`,
});
},
[
getRenderLayersById,
open,
removeLayerStyleState,
setContourLayerAvailable,
setContours,
setJunctionText,
setPipeText,
setShowJunctionId,
setShowJunctionTextLayer,
setShowPipeId,
setShowPipeTextLayer,
setWaterflowLayerAvailable,
]
);
const handleLayerChange = useCallback(
(index: number) => {
const newLayer = index >= 0 ? renderLayers[index] : undefined;
@@ -747,6 +978,7 @@ export const useStyleEditor = ({
nextStates[index] = defaultState;
}
});
latestLayerStyleStatesRef.current = nextStates;
return nextStates;
});
@@ -940,5 +1172,7 @@ export const useStyleEditor = ({
handleColorTypeChange,
handleApply,
handleReset,
applyExternalStyle,
resetExternalStyle,
};
};
@@ -13,6 +13,7 @@ import { apiFetch } from "@/lib/apiFetch";
import { queryFeaturesByIds } from "@/utils/mapQueryService";
import { config } from "@/config/config";
import { useMap } from "../MapComponent";
import type { DefaultLayerStyleId, StyleConfig } from "./styleEditorTypes";
type UseToolbarChatActionsParams = {
setHighlightFeatures: Dispatch<SetStateAction<Feature[]>>;
@@ -22,7 +23,13 @@ type UseToolbarChatActionsParams = {
SetStateAction<{ startTime?: string; endTime?: string } | null>
>;
setShowHistoryPanel: Dispatch<SetStateAction<boolean>>;
setShowStyleEditor: Dispatch<SetStateAction<boolean>>;
setActiveTools: Dispatch<SetStateAction<string[]>>;
applyExternalStyle: (
layerId: DefaultLayerStyleId,
styleConfig?: Partial<StyleConfig>
) => void;
resetExternalStyle: (layerId: DefaultLayerStyleId) => void;
};
export const useToolbarChatActions = ({
@@ -31,7 +38,10 @@ export const useToolbarChatActions = ({
setChatPanelType,
setChatPanelTimeRange,
setShowHistoryPanel,
setShowStyleEditor,
setActiveTools,
applyExternalStyle,
resetExternalStyle,
}: UseToolbarChatActionsParams) => {
const map = useMap();
const chatJunctionRenderCleanupRef = useRef<(() => void) | null>(null);
@@ -206,17 +216,30 @@ export const useToolbarChatActions = ({
})();
break;
}
case "apply_layer_style": {
setShowStyleEditor(true);
setActiveTools((prev) => (prev.includes("style") ? prev : [...prev, "style"]));
if (action.resetToDefault) {
resetExternalStyle(action.layerId);
} else {
applyExternalStyle(action.layerId, action.styleConfig);
}
break;
}
}
},
[
applyExternalStyle,
disposeChatJunctionRender,
map,
resetExternalStyle,
setActiveTools,
setChatPanelFeatureInfos,
setChatPanelTimeRange,
setChatPanelType,
setHighlightFeatures,
setShowHistoryPanel,
setShowStyleEditor,
],
),
);
+8
View File
@@ -1,5 +1,7 @@
import { create } from "zustand";
import type { DefaultLayerStyleId, StyleConfig } from "@components/olmap/core/Controls/styleEditorTypes";
/* ------------------------------------------------------------------ */
/* Chat Tool Action Store */
/* Decouples chat tool calls from map/panel execution. */
@@ -39,6 +41,12 @@ export type ChatToolAction =
type: "render_junctions";
renderRef: string;
sessionId?: string;
}
| {
type: "apply_layer_style";
layerId: DefaultLayerStyleId;
resetToDefault: boolean;
styleConfig?: Partial<StyleConfig>;
};
interface ChatToolState {