// 工作区弹层(提案 T4/D25):工件/代理 经 OverlayHost 呈现。 // 重设计方案 阶段②(IA 对话优先):插件面板/服务端声明工作区属系统能力, // 只进「系统管理」分区(AdminZone),不占普通用户的 ⌘1–9;清单按角色收窄。 import { useState } from 'react' import { store, useStore } from '../store' import type { Run } from '../contracts' import { api } from '../api' import { OverlayHost, Icon, type DocBridge } from '@ui' import { docBridge } from './Workbench' import { AgentRow } from './Workbench' import { artifactIcon, humanSize } from '../messageparts' /** 工作区清单(顺序即 ⌘ 序号):内置在前,插件面板/声明工作区仅管理员可见。 */ export function workspaceEntries(): { key: string; title: string; icon: string; shortcut: string }[] { const s = store.getState() const admin = !!s.me?.isAdmin const out = [ { key: 'artifacts', title: '工件', icon: '📦', shortcut: '⌘1' }, { key: 'agents', title: '代理', icon: '🤖', shortcut: '⌘2' }, ...(admin ? [ ...s.pluginPanels.map((p, i) => ({ key: `plugin:${p.id}`, title: p.title, icon: '🧩', shortcut: `⌘${i + 3}` })), ...s.serverWorkspaces.map((w, i) => ({ key: `ws:${w.id}`, title: w.title, icon: w.icon || '🗂', shortcut: w.shortcut ? `⌘${w.shortcut}` : `⌘${i + 3 + s.pluginPanels.length}` })), ] : []), ] return out.slice(0, 9) } export function Workspaces() { const active = useStore(s => s.ui.activeWorkspace) const artifacts = useStore(s => s.artifacts) 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 === 'artifacts') { return } if (active === 'agents') { const convRuns = runs.filter(r => r.kind !== 'tool') return } if (active.startsWith('plugin:')) { const panel = pluginPanels.find(p => `plugin:${p.id}` === active) return store.refreshPlugins()} /> } if (active.startsWith('ws:')) { const ws = serverWorkspaces.find(w => `ws:${w.id}` === active) return store.refreshPlugins()} /> } return null } /** 工件面板(工作区文件管理):搜索 + kind 过滤 + 上传;行内 深预览/右栏预览/下载。 * 删除是保留能力:DELETE /api/artifacts/{id} 与 api.deleteArtifact 均在(插件可用), * 系统默认不提供删除入口(提案 2026-09-30-file-preview-and-reserved-capabilities §2.2)。 */ export function ArtifactsPanel() { const artifacts = useStore(s => s.artifacts) const [q, setQ] = useState('') const [kind, setKind] = useState<'all' | 'document' | 'data' | 'image'>('all') const kinds = ['all', 'document', 'data', 'image'] as const const visible = artifacts.filter(a => (kind === 'all' || a.kind === kind) && a.name.toLowerCase().includes(q.toLowerCase())) return (
setQ(e.target.value)} /> {kinds.map(k => ( ))}
{artifacts.length === 0 ?
暂无文件——上传附件或让模型产出文档(mock:#doc);这里可浏览、预览、下载
: visible.length === 0 ?
没有匹配「{q}」的文件
: visible.map(a => (
store.requestPreview(a)} title={`预览 ${a.name}(右栏,边聊边看)`}> {a.name} {a.kind} · {humanSize(a.size) || '—'} · {shortTime(a.updatedAt)} e.stopPropagation()}>
))}
) } function shortTime(iso: string): string { try { const d = new Date(iso) const now = new Date() return d.toDateString() === now.toDateString() ? d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : d.toLocaleDateString([], { month: '2-digit', day: '2-digit' }) } catch { return '' } } /** 代理面板(任务树;点击进 AgentTracker 深读)。 */ export function AgentsPanel({ runs }: { runs: Run[] }) { if (runs.length === 0) return
暂无运行——多代理任务会在此形成任务树(mock:#agents)
const roots = runs.filter(r => !r.parentRunId) return
{roots.map(r => (
{runs.filter(c => c.parentRunId === r.id).map(c => )}
))}
} /** ⌘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) }