Files
sundynix-agentix/sundynix-desktop/frontend/src/studio/Inspector.tsx
T
Blizzard 61c1177eba feat(desktop): MVP 驾驶舱外壳 + 类型化节点 Studio + 运行抽屉
按 desktop-ui-plan.md 落 MVP:五区外壳 + 编排 Studio + 底部抽屉 + 健康灯。

- shell: TopBar(垂直切换/健康灯[Gateway/DB 实时,余规划]/身份会话) +
  LeftNav(BUILD/RUN/MANAGE 分组,未就绪模块灰显) + BottomDrawer(输出/轨迹/工具调用/引用/评测)
- studio: 类型化节点目录(输入/检索RAG/Agent/工具/记忆/分支/并行/汇聚/渲染/输出,
  按类配色) + 自定义 TypedNode(状态徽标) + Inspector(按类型渲染配置表单) +
  校验(孤立节点/必填项) + 运行
- views: MemoryView(复用偏好面板) + Placeholder(规划中模块,露出 IA 与依赖)
- lib: run(运行状态机) + health(轮询 billing) + dsl(导出类型化 DSL + validate)
- 删旧 AgentCanvas(被 StudioView 取代)

验证: npm run build(tsc+vite)✓; 真实浏览器跑通——加类型化节点→校验(标出孤立)→运行
→SSE 注入画像(老王)+历史 流入抽屉, 健康灯 Gateway/DB 实时绿

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:00:32 +08:00

95 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Node } from "@xyflow/react";
import { NODE_KINDS } from "./nodeCatalog";
// 右检查器:按选中节点的类型渲染配置表单;空选时显示图级提示。
export function Inspector({
node,
onChange,
onDelete,
}: {
node: Node | null;
onChange: (id: string, patch: Record<string, unknown>) => void;
onDelete: (id: string) => void;
}) {
if (!node) {
return (
<div className="p-4 text-xs text-gray-400">
/
<br />
线
</div>
);
}
const data = node.data as { kind: string; label?: string; config?: Record<string, unknown> };
const k = NODE_KINDS[data.kind] ?? NODE_KINDS.output;
const config = data.config ?? {};
const setConfig = (key: string, value: unknown) =>
onChange(node.id, { config: { ...config, [key]: value } });
return (
<div className="flex h-full flex-col">
<div className="flex items-center justify-between border-b p-3">
<span className={`rounded px-1.5 py-0.5 text-[11px] font-medium ${k.badge}`}>{k.label}</span>
<button onClick={() => onDelete(node.id)} className="text-xs text-rose-500 hover:underline">
</button>
</div>
<div className="flex flex-col gap-3 overflow-auto p-3">
<label className="text-xs text-gray-500">
<input
className="mt-1 w-full rounded border px-2 py-1 text-sm text-gray-900"
value={data.label ?? ""}
onChange={(e) => onChange(node.id, { label: e.target.value })}
/>
</label>
{k.fields.map((f) => {
const v = config[f.key];
return (
<label key={f.key} className="text-xs text-gray-500">
{f.label}
{f.required && <span className="text-rose-500"> *</span>}
{f.type === "select" ? (
<select
className="mt-1 w-full rounded border px-2 py-1 text-sm text-gray-900"
value={String(v ?? "")}
onChange={(e) => setConfig(f.key, e.target.value)}
>
{f.options?.map((o) => (
<option key={o}>{o}</option>
))}
</select>
) : f.type === "textarea" ? (
<textarea
className="mt-1 h-16 w-full resize-none rounded border px-2 py-1 text-sm text-gray-900"
value={String(v ?? "")}
placeholder={f.placeholder}
onChange={(e) => setConfig(f.key, e.target.value)}
/>
) : f.type === "checkbox" ? (
<input
type="checkbox"
className="ml-2 align-middle"
checked={Boolean(v)}
onChange={(e) => setConfig(f.key, e.target.checked)}
/>
) : (
<input
type={f.type === "number" ? "number" : "text"}
className="mt-1 w-full rounded border px-2 py-1 text-sm text-gray-900"
value={String(v ?? "")}
placeholder={f.placeholder}
onChange={(e) =>
setConfig(f.key, f.type === "number" ? Number(e.target.value) : e.target.value)
}
/>
)}
</label>
);
})}
</div>
</div>
);
}