Why: implement the reviewed I2 increment - statistics band, structure tree with lazy section pages, panorama center (project overview + node summary), shared selection/filter, snapshot-consistency banner and honest states, per the frozen contract (Dev docs/superpowers 2026-10-16, rollout section 7). Evidence: vitest 314/314 green (14 new pure-logic tests covering capability gate, true-zero vs unavailable stats, filter toggle, snapshot equality and update notice, context key, selection survival, honest badges); tsc -b and vite production build green. Impact: new workspace entry (rdm-cockpit, gated on the seven cockpit commands); no changes to existing surfaces; reader view and right panel land in I3. Refs: rollout I2
84 lines
3.8 KiB
TypeScript
84 lines
3.8 KiB
TypeScript
// 工作区弹层(提案 T4/D25):深读经 OverlayHost 单层呈现。
|
||
// 服务端声明的工作区(领域插件槽位)对所有用户可见;插件管理面板仍只在系统管理区。
|
||
// 文件浏览已并入第三栏「文件」入口,本弹层只承载代理与领域工作区。
|
||
import { store, useStore } from '../store'
|
||
import type { Run } from '../contracts'
|
||
import { OverlayHost, type DocBridge } from '@ui'
|
||
import { docBridge } from './Workbench'
|
||
import { AgentRow } from './Workbench'
|
||
import { buildWorkspaceEntries, type WorkspaceEntry } from '../workspaces'
|
||
import { RdmAuditWorkspace } from './RdmAuditWorkspace'
|
||
import { DocumentCockpit } from './DocumentCockpit'
|
||
|
||
/** 体系工具(顺序即 ⌘ 序号)。服务端工作区全员可见;插件管理面板只在系统管理区。
|
||
* 追溯审计按命令面能力显隐(增量 2):store.rdmAuditAvailable 为 true 才入列。 */
|
||
export function workspaceEntries(): WorkspaceEntry[] {
|
||
const s = store.getState()
|
||
return buildWorkspaceEntries(s.serverWorkspaces, { rdmAudit: s.rdmAuditAvailable, rdmCockpit: s.rdmCockpitAvailable })
|
||
}
|
||
|
||
export function Workspaces() {
|
||
const active = useStore(s => s.ui.activeWorkspace)
|
||
const runs = useStore(s => s.runs)
|
||
const pluginPanels = useStore(s => s.pluginPanels)
|
||
const serverWorkspaces = useStore(s => s.serverWorkspaces)
|
||
if (!active) return null
|
||
const entry = workspaceEntries().find(e => e.key === active)
|
||
const close = () => store.closeWorkspace()
|
||
|
||
if (active === 'agents') {
|
||
const convRuns = runs.filter(r => r.kind !== 'tool')
|
||
return <OverlayHost title="代理" icon="🤖" shortcut={entry?.shortcut ?? '⌘2'} sayHint="打开代理" onClose={close}>
|
||
<AgentsPanel runs={convRuns} />
|
||
</OverlayHost>
|
||
}
|
||
if (active === 'rdm-audit') {
|
||
return <OverlayHost title="追溯审计" icon="🔍" shortcut={entry?.shortcut} sayHint="打开追溯审计" onClose={close}>
|
||
<RdmAuditWorkspace />
|
||
</OverlayHost>
|
||
}
|
||
if (active === 'rdm-cockpit') {
|
||
return <OverlayHost title="文档驾驶舱" icon="📋" shortcut={entry?.shortcut} sayHint="打开文档驾驶舱" onClose={close}>
|
||
<DocumentCockpit />
|
||
</OverlayHost>
|
||
}
|
||
if (active.startsWith('plugin:')) {
|
||
const panel = pluginPanels.find(p => `plugin:${p.id}` === active)
|
||
return <OverlayHost title={panel?.title ?? '插件面板'} icon="🧩" onClose={close}
|
||
nodes={panel?.body ?? []} docBridge={docBridge as DocBridge} onRefresh={() => store.refreshPlugins()} />
|
||
}
|
||
if (active.startsWith('ws:')) {
|
||
const ws = serverWorkspaces.find(w => `ws:${w.id}` === active)
|
||
const title = ws?.title ?? '工作区'
|
||
return <OverlayHost title={title} icon={ws?.icon || '🗂'} shortcut={entry?.shortcut} sayHint={`打开${title}`} onClose={close}
|
||
nodes={ws?.body ?? []} docBridge={docBridge as DocBridge} onRefresh={() => store.refreshPlugins()} />
|
||
}
|
||
return null
|
||
}
|
||
|
||
/** 代理面板(任务树;点击进 AgentTracker 深读)。 */
|
||
export function AgentsPanel({ runs }: { runs: Run[] }) {
|
||
if (runs.length === 0) return <div className="empty-note">暂无运行——多代理任务会在此形成任务树(mock:#agents)</div>
|
||
const roots = runs.filter(r => !r.parentRunId)
|
||
return <div className="subagent-tree">
|
||
{roots.map(r => (
|
||
<div key={r.id}>
|
||
<AgentRow run={r} depth={0} />
|
||
{runs.filter(c => c.parentRunId === r.id).map(c => <AgentRow key={c.id} run={c} depth={1} />)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
}
|
||
|
||
/** ⌘1–9 / Ctrl+1–9 全局快捷键(挂载于 App)。 */
|
||
export function workspaceShortcuts(e: KeyboardEvent) {
|
||
if (!(e.metaKey || e.ctrlKey)) return
|
||
const n = parseInt(e.key, 10)
|
||
if (!Number.isInteger(n) || n < 1 || n > 9) return
|
||
const entries = workspaceEntries()
|
||
const hit = entries[n - 1]
|
||
if (!hit) return
|
||
e.preventDefault()
|
||
store.openWorkspace(hit.key)
|
||
}
|