fix: align drainage frontend configuration

This commit is contained in:
2026-07-22 15:01:35 +08:00
parent 72850761ce
commit 0995172828
45 changed files with 853 additions and 339 deletions
@@ -17,7 +17,7 @@ export function AgentCollapsedRail({
statusLabel = "Agent 已就绪",
onExpand
}: AgentCollapsedRailProps) {
const expandLabel = `展开排水助手面板,当前状态:${statusLabel}`;
const expandLabel = `展开 Agent 助手面板,当前状态:${statusLabel}`;
return (
<aside
@@ -29,7 +29,7 @@ export function AgentCollapsedRail({
>
<button
type="button"
aria-label="展开排水助手面板"
aria-label={expandLabel}
title={expandLabel}
onClick={onExpand}
className="agent-rail-item surface-control group relative flex h-14 w-full items-center justify-center rounded-xl border transition-[background-color,transform] hover:bg-white active:scale-95"
+1 -2
View File
@@ -1,7 +1,6 @@
"use client";
import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
import { env } from "@/shared/config/env";
import {
splitSpeechTextIntoChunks,
type AgentSpeakOptions,
@@ -123,7 +122,7 @@ export function useAgentSpeechSynthesis() {
const controller = new AbortController();
fetchControllersRef.current.add(controller);
const promise = fetch(env.DRAINAGE_TTS_API_URL, {
const promise = fetch("/api/tts/edge", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text }),
@@ -20,6 +20,13 @@ describe("toTrustedMapAction", () => {
})
).toBeNull();
expect(toTrustedMapAction("apply_layer_style", {})).toBeNull();
expect(
toTrustedMapAction("apply_layer_style", {
layer_group_id: "conduits",
layer_id: "scada",
visible: true
})
).toBeNull();
});
it("rejects out-of-range coordinates and zoom", () => {
@@ -63,11 +70,11 @@ describe("toTrustedMapAction", () => {
expect(action?.type === "zoom_to_map" ? action.center[1] : undefined).toBeCloseTo(39.1, 3);
});
it("normalizes legacy locate tool actions", () => {
expect(toTrustedMapAction("locate_pipes", { pipe_ids: "P1,P2" })).toEqual({
it("normalizes drainage locate tool actions", () => {
expect(toTrustedMapAction("locate_conduits", { conduit_ids: "C1,C2" })).toEqual({
type: "locate_features",
featureIds: ["P1", "P2"],
layer: "geo_pipes_mat",
featureIds: ["C1", "C2"],
layer: "geo_conduits_mat",
fallbackText: undefined
});
});
@@ -79,4 +86,34 @@ describe("toTrustedMapAction", () => {
layer: "geo_junctions_mat"
});
});
it.each([
["locate_orifices", "orifice_id", "geo_orifices_mat"],
["locate_outfalls", "outfall_id", "geo_outfalls_mat"],
["locate_pumps", "pump_id", "geo_pumps_mat"],
["locate_scada", "scada_id", "geo_scadas_mat"]
])("accepts %s for a drainage source", (action, key, layer) => {
expect(toTrustedMapAction(action, { [key]: "asset-1" })).toMatchObject({
type: "locate_features",
featureIds: ["asset-1"],
layer
});
});
it("rejects stale supply layers and accepts drainage visibility controls", () => {
expect(toTrustedMapAction("locate_pipes", { pipe_ids: ["P1"] })).toBeNull();
expect(toTrustedMapAction("locate_features", { ids: ["P1"], layer: "geo_pipes_mat" })).toBeNull();
expect(toTrustedMapAction("apply_layer_style", { layer_id: "conduits", visible: true })).toMatchObject({
type: "apply_layer_style",
layerGroupId: "conduits",
layerId: "conduits",
visible: true
});
expect(toTrustedMapAction("apply_layer_style", { layer_group_id: "simulation", visible: false })).toMatchObject({
type: "apply_layer_style",
layerGroupId: "simulation",
visible: false
});
expect(toTrustedMapAction("apply_layer_style", { layer_id: "pipes", visible: true })).toBeNull();
});
});
+40 -20
View File
@@ -14,7 +14,7 @@ export type TrustedMapAction =
}
| {
type: "apply_layer_style";
layerGroupId?: string;
layerGroupId: string;
layerId?: string;
visible?: boolean;
fallbackText?: string;
@@ -33,12 +33,14 @@ export function toTrustedMapAction(action: string, params: unknown, fallbackText
if (action === "locate_features" || action in LEGACY_LOCATE_ACTION_LAYERS) {
const featureIds = readLocateIds(params);
if (featureIds.length === 0 || featureIds.length > 100 || featureIds.some((id) => id.length > 128)) return null;
const layer =
stringValue(params.layer ?? params.target_layer ?? params.targetLayer) ??
LEGACY_LOCATE_ACTION_LAYERS[action];
if (layer && !ALLOWED_LOCATE_LAYERS.has(layer)) return null;
return {
type: "locate_features",
featureIds,
layer:
stringValue(params.layer ?? params.target_layer ?? params.targetLayer) ??
LEGACY_LOCATE_ACTION_LAYERS[action],
layer,
fallbackText
};
}
@@ -66,11 +68,18 @@ export function toTrustedMapAction(action: string, params: unknown, fallbackText
if (action === "apply_layer_style") {
const layerGroupId = stringValue(params.layer_group_id ?? params.layerGroupId);
const layerId = stringValue(params.layer_id ?? params.layerId);
const resolvedLayerGroupId = layerGroupId ?? layerId;
const visible = booleanValue(params.visible);
if ((!layerGroupId && !layerId) || visible === undefined || (layerId && !ALLOWED_LAYER_IDS.has(layerId))) return null;
if (
!resolvedLayerGroupId ||
visible === undefined ||
(layerGroupId && !ALLOWED_LAYER_GROUP_IDS.has(layerGroupId)) ||
(layerId && !ALLOWED_LAYER_IDS.has(layerId)) ||
(layerGroupId && layerId && layerGroupId !== layerId)
) return null;
return {
type: "apply_layer_style",
layerGroupId,
layerGroupId: resolvedLayerGroupId,
layerId,
visible,
fallbackText
@@ -155,16 +164,27 @@ function isRecord(value: unknown): value is Record<string, unknown> {
}
const LEGACY_LOCATE_ACTION_LAYERS: Record<string, string> = {
locate_conduits: "geo_conduits_mat",
locate_junctions: "geo_junctions_mat",
locate_pipes: "geo_pipes_mat",
locate_valves: "geo_valves",
locate_reservoirs: "geo_reservoirs",
locate_pumps: "geo_pumps",
locate_tanks: "geo_tanks"
locate_orifices: "geo_orifices_mat",
locate_outfalls: "geo_outfalls_mat",
locate_pumps: "geo_pumps_mat",
locate_scada: "geo_scadas_mat"
};
const RESULT_REF_PATTERN = /^res-[A-Za-z0-9_-]{8,128}$/;
const ALLOWED_LAYER_IDS = new Set(["junctions", "pipes"]);
const DRAINAGE_SOURCE_IDS = ["conduits", "junctions", "orifices", "outfalls", "pumps", "scada"];
const ALLOWED_LAYER_IDS = new Set(DRAINAGE_SOURCE_IDS);
const ALLOWED_LAYER_GROUP_IDS = new Set([...DRAINAGE_SOURCE_IDS, "simulation"]);
const ALLOWED_LOCATE_LAYERS = new Set([
...DRAINAGE_SOURCE_IDS,
"geo_conduits_mat",
"geo_junctions_mat",
"geo_orifices_mat",
"geo_outfalls_mat",
"geo_pumps_mat",
"geo_scadas_mat"
]);
const WEB_MERCATOR_RADIUS = 6378137;
const LOCATE_ID_PARAM_KEYS = [
@@ -178,16 +198,16 @@ const LOCATE_ID_PARAM_KEYS = [
"node_id",
"junction_ids",
"junction_id",
"pipe_ids",
"pipe_id",
"valve_ids",
"valve_id",
"reservoir_ids",
"reservoir_id",
"conduit_ids",
"conduit_id",
"orifice_ids",
"orifice_id",
"outfall_ids",
"outfall_id",
"pump_ids",
"pump_id",
"tank_ids",
"tank_id"
"scada_ids",
"scada_id"
] as const;
function readLocateIds(params: Record<string, unknown>) {