Files
Agent-System-Frontend/src/components/Workspaces.tsx
T
weichenandCursor 3c815250a0 Queue follow-up messages and pin files in the right rail
Why: a busy conversation rejected further input, and the file list lived
only behind the workspace overlay.

Evidence: npx tsc --noEmit and vitest 42 tests, including outbox
append/edit/promote. The queue uses Collapsible and design tokens.
The files tab reuses ArtifactsPanel.

Impact: while a run is active, Enter queues the draft (edit, delete, or
send-now). Finished and failed runs drain the queue. HTTP 409 also
enqueues instead of only toasting. Backend serialization is unchanged.

Refs: task feature/input-queue
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-01 04:31:50 +00:00

85 lines
3.8 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.
// 工作区弹层(提案 T4/D25):工件/代理 经 OverlayHost 呈现。
// 重设计方案 阶段②(IA 对话优先):插件面板/服务端声明工作区属系统能力,
// 只进「系统管理」分区(AdminZone),不占普通用户的 ⌘1–9;清单按角色收窄。
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 { ArtifactsPanel } from './ArtifactsPanel'
/** 工作区清单(顺序即 ⌘ 序号):内置在前,插件面板/声明工作区仅管理员可见。 */
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 <OverlayHost title="工件" icon="📦" shortcut="⌘1" badge={artifacts.length || undefined} onClose={close}>
<ArtifactsPanel />
</OverlayHost>
}
if (active === 'agents') {
const convRuns = runs.filter(r => r.kind !== 'tool')
return <OverlayHost title="代理" icon="🤖" shortcut="⌘2" onClose={close}>
<AgentsPanel runs={convRuns} />
</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)
return <OverlayHost title={ws?.title ?? '工作区'} icon={ws?.icon || '🗂'} shortcut={entry?.shortcut} 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)
}