Files
socaichat/frontend/components/workspace/cards/SandboxResultCard.tsx
T
gongzhiyongandClaude Sonnet 4.6 0a2b339550 feat: implement Generative UI — Agent Workspace + workspace_card SSE protocol
## Backend
- Add workspace_card SSE event protocol: {id, name, props, merge}
- Add _extract_llm_text / _maybe_emit_workspace_card helpers in chat.py
- Refactor all tools to dual-output format: {llm_text, ui: {name, props}}
  - kb_search → KnowledgeResultCard
  - ticket_list/detail → TicketSummaryCard / TicketDetailCard
  - web_search → SearchResultCard
  - generate_document → DocumentResultCard
  - sandbox_run → SandboxResultCard
- Update SYSTEM_PROMPT: instruct LLM not to repeat tool data (UI shows it)

## Frontend
- Three-column layout: sidebar + chat + Agent Workspace (360px right panel)
- WorkspaceSession state model with ActivityNode + WorkspaceCard
- New components/workspace/: AgentWorkspace, ActivityTimeline, WorkspaceCardRenderer
- 6 card components: Knowledge/Ticket/Search/Document/Sandbox/ErrorCard
- GeminiChat: workspace state management, SSE routing for workspace_card events
- GeminiMessage: replace TracePanel with lightweight activity summary line
- lib/api.ts: add WorkspaceSession/ActivityNode/WorkspaceCard types

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 02:57:09 +08:00

44 lines
1.7 KiB
TypeScript

"use client";
import { Terminal, CheckCircle, XCircle } from "lucide-react";
interface SandboxResultCardProps {
language: string;
exit_code: number;
stdout: string;
has_more: boolean;
duration_ms?: number;
}
export function SandboxResultCard({ language, exit_code, stdout, has_more, duration_ms }: SandboxResultCardProps) {
const success = exit_code === 0;
return (
<div className="rounded-xl border border-[var(--gem-border)] bg-[var(--gem-surface)] p-4">
<div className="flex items-center gap-2 mb-3">
<Terminal size={15} className="text-[var(--gem-blue,#4285f4)]" />
<span className="text-sm font-medium text-[var(--gem-text)]">代码执行</span>
<span className="ml-auto flex items-center gap-1 text-xs">
{success
? <CheckCircle size={13} className="text-green-400" />
: <XCircle size={13} className="text-red-400" />
}
<span className={success ? "text-green-400" : "text-red-400"}>
{success ? "成功" : `退出码 ${exit_code}`}
</span>
</span>
</div>
<div className="flex items-center gap-2 mb-2">
<span className="text-[10px] text-[var(--gem-text-muted)] bg-[var(--gem-surface-2)] px-2 py-0.5 rounded">{language}</span>
{duration_ms != null && (
<span className="text-[10px] text-[var(--gem-text-muted)]">{duration_ms}ms</span>
)}
</div>
{stdout && (
<pre className="text-xs text-[var(--gem-text-secondary)] bg-[var(--gem-surface-2)] rounded-lg p-3 overflow-x-auto whitespace-pre-wrap max-h-48 overflow-y-auto font-mono">
{stdout}
{has_more && "\n… (输出已截断)"}
</pre>
)}
</div>
);
}