feat: align frontend runtime and Edge TTS
This commit is contained in:
@@ -8,28 +8,35 @@ import { AgentPersona } from "./agent-persona";
|
||||
|
||||
type AgentCollapsedRailProps = {
|
||||
personaState?: PersonaState;
|
||||
statusLabel?: string;
|
||||
onExpand: () => void;
|
||||
};
|
||||
|
||||
export function AgentCollapsedRail({ personaState, onExpand }: AgentCollapsedRailProps) {
|
||||
export function AgentCollapsedRail({
|
||||
personaState,
|
||||
statusLabel = "Agent 已就绪",
|
||||
onExpand
|
||||
}: AgentCollapsedRailProps) {
|
||||
const expandLabel = `展开 Agent 助手面板,当前状态:${statusLabel}`;
|
||||
|
||||
return (
|
||||
<aside
|
||||
aria-label="Agent 折叠栏"
|
||||
className={cn(
|
||||
"agent-rail-enter acrylic-panel pointer-events-auto w-[136px] border p-1.5",
|
||||
"agent-rail-enter acrylic-panel pointer-events-auto w-[72px] border p-1.5",
|
||||
MAP_MAJOR_PANEL_RADIUS_CLASS_NAME
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="展开排水助手面板"
|
||||
title="展开排水助手面板"
|
||||
aria-label={expandLabel}
|
||||
title={expandLabel}
|
||||
onClick={onExpand}
|
||||
className="agent-rail-item surface-control group flex h-14 w-full items-center justify-center gap-4 rounded-xl border px-3 transition-[background-color,transform] hover:bg-white active:scale-95"
|
||||
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"
|
||||
>
|
||||
<AgentPersona className="h-12 w-12" state={personaState} />
|
||||
<span className="agent-panel-primary-icon grid h-8 w-8 shrink-0 place-items-center rounded-lg border-0! transition-transform group-hover:translate-x-0.5">
|
||||
<ChevronRight size={17} strokeWidth={2.25} aria-hidden="true" />
|
||||
<AgentPersona className="h-11 w-11" state={personaState} />
|
||||
<span className="agent-panel-primary-icon absolute bottom-0.5 right-0.5 grid h-5 w-5 place-items-center rounded-md border-0! shadow-xs transition-transform group-hover:translate-x-0.5">
|
||||
<ChevronRight size={13} strokeWidth={2.5} aria-hidden="true" />
|
||||
</span>
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
@@ -104,7 +104,7 @@ function createOperationalBrief(messages: AgentChatMessage[], statusLabel: strin
|
||||
const confirmation = pendingConfirmations > 0 ? `${pendingConfirmations} 项待确认` : "无需确认";
|
||||
const stateLabel = pendingConfirmations > 0 ? "等待人工确认" : streaming ? "Agent 分析中" : "分析完成";
|
||||
const statusDotClassName = pendingConfirmations > 0 ? "bg-orange-500" : streaming ? "bg-blue-500" : "bg-emerald-500";
|
||||
const promptContext = lastUserMessage?.content.trim() || "当前排水管网运行态势";
|
||||
const promptContext = lastUserMessage?.content.trim() || "当前供水管网运行态势";
|
||||
|
||||
return {
|
||||
task,
|
||||
|
||||
@@ -58,7 +58,11 @@ export function AgentPersona({ className, state = "idle" }: AgentPersonaProps) {
|
||||
const shouldRenderAnimation = !failed && visible;
|
||||
|
||||
return (
|
||||
<span ref={containerRef} className={cn("relative block shrink-0", className)}>
|
||||
<span
|
||||
ref={containerRef}
|
||||
aria-label="Agent 状态"
|
||||
className={cn("relative block shrink-0", className)}
|
||||
>
|
||||
{shouldRenderAnimation ? (
|
||||
<Suspense fallback={<PersonaFallback />}>
|
||||
<LazyPersona
|
||||
|
||||
@@ -20,6 +20,13 @@ describe("toTrustedMapAction", () => {
|
||||
})
|
||||
).toBeNull();
|
||||
expect(toTrustedMapAction("apply_layer_style", {})).toBeNull();
|
||||
expect(
|
||||
toTrustedMapAction("apply_layer_style", {
|
||||
layer_group_id: "pipes",
|
||||
layer_id: "scada",
|
||||
visible: true
|
||||
})
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects out-of-range coordinates and zoom", () => {
|
||||
@@ -83,6 +90,7 @@ describe("toTrustedMapAction", () => {
|
||||
it("accepts supply layer visibility and scada locate actions", () => {
|
||||
expect(toTrustedMapAction("apply_layer_style", { layer_id: "scada", visible: false })).toMatchObject({
|
||||
type: "apply_layer_style",
|
||||
layerGroupId: "scada",
|
||||
layerId: "scada",
|
||||
visible: false
|
||||
});
|
||||
@@ -92,4 +100,20 @@ describe("toTrustedMapAction", () => {
|
||||
layer: "geo_scada"
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects unknown layers and accepts known visibility controls", () => {
|
||||
expect(toTrustedMapAction("locate_features", { ids: ["P1"], layer: "geo_unknown" })).toBeNull();
|
||||
expect(toTrustedMapAction("apply_layer_style", { layer_id: "pipes", visible: true })).toMatchObject({
|
||||
type: "apply_layer_style",
|
||||
layerGroupId: "pipes",
|
||||
layerId: "pipes",
|
||||
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: "conduits", visible: true })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
@@ -165,7 +174,21 @@ const LEGACY_LOCATE_ACTION_LAYERS: Record<string, string> = {
|
||||
};
|
||||
|
||||
const RESULT_REF_PATTERN = /^res-[A-Za-z0-9_-]{8,128}$/;
|
||||
const ALLOWED_LAYER_IDS = new Set(["junctions", "pipes", "valves", "reservoirs", "scada"]);
|
||||
const SUPPLY_SOURCE_IDS = ["junctions", "pipes", "valves", "reservoirs", "scada"];
|
||||
const ALLOWED_LAYER_IDS = new Set(SUPPLY_SOURCE_IDS);
|
||||
const ALLOWED_LAYER_GROUP_IDS = new Set([...SUPPLY_SOURCE_IDS, "simulation"]);
|
||||
const ALLOWED_LOCATE_LAYERS = new Set([
|
||||
...SUPPLY_SOURCE_IDS,
|
||||
"pumps",
|
||||
"tanks",
|
||||
"geo_junctions_mat",
|
||||
"geo_pipes_mat",
|
||||
"geo_valves",
|
||||
"geo_reservoirs",
|
||||
"geo_scada",
|
||||
"geo_pumps",
|
||||
"geo_tanks"
|
||||
]);
|
||||
const WEB_MERCATOR_RADIUS = 6378137;
|
||||
|
||||
const LOCATE_ID_PARAM_KEYS = [
|
||||
|
||||
Reference in New Issue
Block a user