diff --git a/src/components/Conversation.tsx b/src/components/Conversation.tsx
index 2fcec2e..d781f95 100644
--- a/src/components/Conversation.tsx
+++ b/src/components/Conversation.tsx
@@ -4,7 +4,8 @@ import { marked } from 'marked'
import { api } from '../api'
import { store, useStore, activeRun, type LiveState, type ContextObs } from '../store'
import type { Message, Part, HITLCard } from '../contracts'
-import { UIRenderer } from './UIRenderer'
+import { UIRenderer } from '@ui'
+import { docBridge } from './Workbench'
marked.setOptions({ breaks: true })
@@ -34,7 +35,7 @@ function PartView({ part, live }: { part: Part; live?: boolean | null }) {
case 'plan':
return null // 计划在右栏 Plan 标签呈现
case 'card':
- return
+ return
case 'error':
return {part.error?.code}: {part.error?.message}
default:
@@ -154,7 +155,7 @@ function HITLCardView({ card }: { card: HITLCard }) {
)}
- {card.form && card.form.length > 0 && }
+ {card.form && card.form.length > 0 && }
)
}
diff --git a/src/components/UIRenderer.tsx b/src/components/UIRenderer.tsx
deleted file mode 100644
index 7935cf7..0000000
--- a/src/components/UIRenderer.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-// UI schema 通用渲染器(4.1 原则 3):有限词汇;插件面板/卡片/HITL 表单/设置共用。
-// 未知词汇静默忽略。插件不注入 React 组件——高级交互走 Core 契约扩展。
-import { useState } from 'react'
-import type { UINode } from '../contracts'
-import { store } from '../store'
-
-export function UIRenderer({ nodes, onAction }: { nodes: UINode[]; onAction?: (id: string) => void }) {
- return <>{nodes.map((n, i) => )}>
-}
-
-function UINodeView({ node, onAction }: { node: UINode; onAction?: (id: string) => void }) {
- const [form, setForm] = useState>({})
- switch (node.type) {
- case 'section':
- return (
-
- {node.title &&
{node.title}
}
- {node.children && }
-
- )
- case 'list':
- return (
-
- {node.title &&
{node.title}
}
-
{node.items?.map((it, i) => - {it}
)}
-
- )
- case 'kv':
- return (
-
- {node.title &&
{node.title}
}
-
{node.kv?.map(([k, v], i) => [- {k}
, - {v}
])}
-
- )
- case 'markdown':
- return
- case 'doc-ref': {
- const artifacts = store.getState().artifacts
- const art = artifacts.find(a => a.id === node.docId)
- return (
-
-
-
- )
- }
- case 'actions':
- return (
-
- {node.actions?.map(a => (
-
- ))}
-
- )
- case 'form':
- return (
-
- {node.title &&
{node.title}
}
- {node.fields?.map(f => (
-
- ))}
-
- )
- case 'chart-lite':
- return
- default:
- return null // 未知词汇静默忽略
- }
-}
-
-// 无依赖迷你条形图(chart-lite 词汇的最小实现)。
-function ChartLite({ node }: { node: UINode }) {
- const chart = node.chart
- if (!chart || chart.series.length === 0) return null
- const max = Math.max(...chart.series.map(Math.abs), 1)
- return (
-
- {node.title &&
{node.title}
}
- {chart.labels.map((label, i) => (
-
-
{label}
-
-
{chart.series[i]}
-
- ))}
-
- )
-}
-
-// marked 引入放 UIRenderer 顶部以复用(markdown 词汇渲染)。
-import { marked } from 'marked'
-marked.setOptions({ breaks: true })
-function mdSafe(s: string): string {
- return marked.parse(s) as string
-}
diff --git a/src/components/Workbench.tsx b/src/components/Workbench.tsx
index da14ed2..46bc84e 100644
--- a/src/components/Workbench.tsx
+++ b/src/components/Workbench.tsx
@@ -1,7 +1,16 @@
// 右栏工作台:Plan / Todos / Artifacts / Agents + 插件面板(4.4);tab 显隐排序见设置。
import { store, useStore, isTerminal } from '../store'
import type { Run } from '../contracts'
-import { UIRenderer } from './UIRenderer'
+import { UIRenderer, type DocBridge } from '@ui'
+
+// 工件解析/打开注入:渲染器纯展示,doc-ref 词汇经此桥接本仓状态(D29)。
+export const docBridge: DocBridge = {
+ resolveDoc: id => store.getState().artifacts.find(a => a.id === id),
+ onOpenDoc: id => {
+ const art = store.getState().artifacts.find(a => a.id === id)
+ if (art) store.viewArtifact(art)
+ },
+}
export function Workbench() {
const ui = useStore(s => s.ui)
@@ -34,7 +43,7 @@ export function Workbench() {
{ui.rightTab === 'artifacts' && }
{ui.rightTab === 'agents' && }
{ui.rightTab.startsWith('plugin:') && (
- `plugin:${p.id}` === ui.rightTab)?.body ?? []} />
+ `plugin:${p.id}` === ui.rightTab)?.body ?? []} docBridge={docBridge} />
)}
>
diff --git a/src/main.tsx b/src/main.tsx
index 8661d46..238076b 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,6 +1,8 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { App } from './App'
+import '@ui/tokens.css'
+import '@ui/ui.css'
import './styles.css'
createRoot(document.getElementById('root')!).render(
diff --git a/src/styles.css b/src/styles.css
index 7c640b3..1038ba3 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -1,37 +1,4 @@
-/* 设计令牌(4.15):组件只消费令牌;亮/暗主题切换 = data-theme 属性。 */
-:root, :root[data-theme='dark'] {
- --bg: #16181d;
- --bg-raised: #1d2026;
- --bg-panel: #191c21;
- --bg-hover: #262a31;
- --bg-active: #2d323b;
- --border: #2b2f37;
- --text: #e4e7ec;
- --text-dim: #9aa1ac;
- --text-faint: #6b7280;
- --accent: #4f8cff;
- --accent-soft: rgba(79, 140, 255, 0.14);
- --ok: #3fb972;
- --warn: #e0a83a;
- --err: #e5605c;
- --radius: 8px;
- --radius-sm: 6px;
- --font: -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'Noto Sans CJK SC', sans-serif;
- --mono: ui-monospace, 'Cascadia Code', 'SF Mono', Consolas, monospace;
-}
-:root[data-theme='light'] {
- --bg: #f7f8fa;
- --bg-raised: #ffffff;
- --bg-panel: #f2f3f6;
- --bg-hover: #e9ebf0;
- --bg-active: #dfe3ea;
- --border: #dcdfe6;
- --text: #1c2230;
- --text-dim: #5a6272;
- --text-faint: #8b93a3;
- --accent: #2f6fe4;
- --accent-soft: rgba(47, 111, 228, 0.1);
-}
+/* 设计令牌与 UI 组件样式已迁入 Agent-System-UI(@ui/tokens.css、@ui/ui.css,main.tsx 引入)。 */
* { box-sizing: border-box; }
html, body, #root { height: 100%; margin: 0; }
@@ -182,23 +149,13 @@ button { font-family: inherit; }
.effect-hot { color: var(--ok); border-color: var(--ok); }
.effect-new-run { color: var(--warn); border-color: var(--warn); }
-/* ---- UI schema 渲染器 ---- */
-.ui-section { margin: 10px 0; }
-.ui-section h5 { margin: 0 0 6px; font-size: 13px; }
-.ui-list li { margin: 3px 0; font-size: 13px; }
-.ui-kv { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 13px; }
-.ui-kv dt { color: var(--text-faint); }
-.ui-actions { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0; }
-.ui-form label { display: block; font-size: 12.5px; color: var(--text-dim); margin: 8px 0 3px; }
-.ui-form input, .ui-form select { width: 100%; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text); padding: 7px 10px; }
+/* UI schema 渲染器样式(.ui-*)与 .doc-viewer 已迁入 @ui/ui.css。 */
/* ---- 杂项 ---- */
.toast { position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%); background: var(--bg-raised); border: 1px solid var(--border); color: var(--text); padding: 9px 16px; border-radius: 20px; box-shadow: 0 8px 30px rgba(0,0,0,.35); z-index: 90; font-size: 13px; }
.conn-dot { width: 8px; height: 8px; border-radius: 50%; }
.conn-dot.on { background: var(--ok); }
.conn-dot.off { background: var(--err); }
-.doc-viewer { font-size: 13.5px; line-height: 1.7; }
-.doc-viewer pre { background: var(--bg); border: 1px solid var(--border); padding: 10px; border-radius: var(--radius-sm); overflow-x: auto; }
.doc-toolbar { display: flex; gap: 8px; margin-bottom: 10px; align-items: center; }
.search-input { background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text); padding: 6px 10px; font-family: inherit; width: 100%; }
::-webkit-scrollbar { width: 9px; height: 9px; }
diff --git a/tsconfig.json b/tsconfig.json
index 1a066cc..71e593e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,7 +12,12 @@
"skipLibCheck": true,
"isolatedModules": true,
"noEmit": true,
- "types": ["vite/client"]
+ "types": ["vite/client"],
+ "baseUrl": ".",
+ "paths": {
+ "@ui": ["../ui/src/index.ts"],
+ "@ui/*": ["../ui/src/*"]
+ }
},
"include": ["src", "test"]
}
diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo
index b044b4e..fe98e6f 100644
--- a/tsconfig.tsbuildinfo
+++ b/tsconfig.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/App.tsx","./src/api.ts","./src/auth.ts","./src/contracts.ts","./src/main.tsx","./src/store.ts","./src/components/AgentTracker.tsx","./src/components/Conversation.tsx","./src/components/DocViewer.tsx","./src/components/ProjectNav.tsx","./src/components/SettingsModal.tsx","./src/components/UIRenderer.tsx","./src/components/Workbench.tsx","./test/auth.test.ts","./test/contracts.test.ts"],"version":"5.9.3"}
\ No newline at end of file
+{"root":["./src/App.tsx","./src/api.ts","./src/auth.ts","./src/contracts.ts","./src/main.tsx","./src/store.ts","./src/components/AgentTracker.tsx","./src/components/Conversation.tsx","./src/components/DocViewer.tsx","./src/components/ProjectNav.tsx","./src/components/SettingsModal.tsx","./src/components/Workbench.tsx","./test/auth.test.ts","./test/contracts.test.ts"],"version":"5.9.3"}
\ No newline at end of file
diff --git a/vite.config.ts b/vite.config.ts
index 2e5b959..36cb30d 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,8 +1,17 @@
+import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+// UI 组件库源码组合(modules/ui,源码级别名;见 Agent-System-Documents 提案 D29)。
+const uiSrc = fileURLToPath(new URL('../ui/src', import.meta.url))
+
export default defineConfig({
plugins: [react()],
+ resolve: {
+ alias: [{ find: '@ui', replacement: uiSrc }],
+ // 源码组合下 ui 仓有自己的 node_modules;dedupe 强制最终 bundle 用 frontend 的单副本。
+ dedupe: ['react', 'react-dom', 'marked'],
+ },
server: {
port: 5173,
proxy: {