第一次提交
This commit is contained in:
@ -0,0 +1,187 @@
|
||||
'use client';
|
||||
|
||||
import { FileJson, FileOutput, FolderSearch, Image as ImageIcon, Link2, MessagesSquare } from 'lucide-react';
|
||||
|
||||
import type { ProcessArtifact, ProcessEvent, ProcessRun } from '@/types';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
|
||||
function statusLabel(status: string) {
|
||||
if (status === 'done') return '已完成';
|
||||
if (status === 'error') return '失败';
|
||||
if (status === 'cancelled') return '已取消';
|
||||
if (status === 'waiting') return '等待中';
|
||||
if (status === 'running') return '运行中';
|
||||
if (status === 'queued') return '排队中';
|
||||
return status;
|
||||
}
|
||||
|
||||
function actorTypeLabel(actorType: string) {
|
||||
if (actorType === 'mcp') return 'MCP';
|
||||
if (actorType === 'system') return '系统';
|
||||
if (actorType === 'agent') return '智能体';
|
||||
return actorType;
|
||||
}
|
||||
|
||||
function eventKindLabel(kind: string) {
|
||||
if (kind === 'run_started') return '已启动';
|
||||
if (kind === 'run_progress') return '进行中';
|
||||
if (kind === 'run_status') return '状态更新';
|
||||
if (kind === 'run_artifact') return '产物';
|
||||
if (kind === 'run_finished') return '已结束';
|
||||
if (kind === 'run_cancelled') return '已取消';
|
||||
return kind;
|
||||
}
|
||||
|
||||
function artifactIcon(type: ProcessArtifact['artifact_type']) {
|
||||
if (type === 'json') return <FileJson className="w-4 h-4" />;
|
||||
if (type === 'image') return <ImageIcon className="w-4 h-4" />;
|
||||
if (type === 'link') return <Link2 className="w-4 h-4" />;
|
||||
return <FileOutput className="w-4 h-4" />;
|
||||
}
|
||||
|
||||
function renderArtifactBody(artifact: ProcessArtifact) {
|
||||
if (artifact.artifact_type === 'json' && artifact.data !== undefined) {
|
||||
return (
|
||||
<pre className="text-[11px] leading-5 whitespace-pre-wrap break-words rounded-md bg-background/70 p-3 overflow-x-auto">
|
||||
{JSON.stringify(artifact.data, null, 2)}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
if (artifact.artifact_type === 'link' && artifact.url) {
|
||||
return (
|
||||
<a href={artifact.url} target="_blank" rel="noreferrer" className="text-sm text-sky-300 underline break-all">
|
||||
{artifact.url}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="text-xs text-foreground/90 whitespace-pre-wrap break-words">
|
||||
{artifact.content || '(空产物)'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArtifactSidebar({
|
||||
selectedRun,
|
||||
events,
|
||||
artifacts,
|
||||
}: {
|
||||
selectedRun: ProcessRun | null;
|
||||
events: ProcessEvent[];
|
||||
artifacts: ProcessArtifact[];
|
||||
}) {
|
||||
const runArtifacts = selectedRun
|
||||
? artifacts.filter((item) => item.run_id === selectedRun.run_id)
|
||||
: artifacts;
|
||||
const runEvents = selectedRun
|
||||
? events.filter((item) => item.run_id === selectedRun.run_id)
|
||||
: events.slice(-12);
|
||||
const hasContent = Boolean(
|
||||
selectedRun || runArtifacts.length > 0 || runEvents.length > 0
|
||||
);
|
||||
|
||||
if (!hasContent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full bg-card/60 flex flex-col border-l border-border">
|
||||
<div className="px-4 py-3 border-b border-border">
|
||||
<h2 className="text-sm font-semibold tracking-wide uppercase text-muted-foreground">结果面板</h2>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{selectedRun ? `当前选中: ${selectedRun.actor_name}` : '选择一个任务查看详细过程与产物'}
|
||||
</p>
|
||||
</div>
|
||||
<ScrollArea className="flex-1 px-4 py-4">
|
||||
<div className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm flex items-center gap-2">
|
||||
<FolderSearch className="w-4 h-4" />
|
||||
任务摘要
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-2 text-sm">
|
||||
{selectedRun ? (
|
||||
<>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Badge variant="outline">{actorTypeLabel(selectedRun.actor_type)}</Badge>
|
||||
<Badge variant="outline">{statusLabel(selectedRun.status)}</Badge>
|
||||
{selectedRun.source && <Badge variant="secondary">{selectedRun.source}</Badge>}
|
||||
</div>
|
||||
<div className="font-medium">{selectedRun.title}</div>
|
||||
<div className="text-muted-foreground whitespace-pre-wrap break-words">
|
||||
{selectedRun.summary || '暂时还没有最终摘要。'}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-muted-foreground text-sm">当前没有选中的任务。</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm flex items-center gap-2">
|
||||
<MessagesSquare className="w-4 h-4" />
|
||||
事件记录
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-2">
|
||||
{runEvents.length === 0 && (
|
||||
<div className="text-xs text-muted-foreground">暂时还没有结构化事件。</div>
|
||||
)}
|
||||
{runEvents.map((event, index) => (
|
||||
<div key={event.event_id}>
|
||||
<div className="rounded-md border border-border/60 px-3 py-2 bg-background/60">
|
||||
<div className="flex items-center gap-2 text-[10px] uppercase tracking-wide text-muted-foreground mb-1">
|
||||
<span>{eventKindLabel(event.kind)}</span>
|
||||
{event.status && <span>{statusLabel(event.status)}</span>}
|
||||
</div>
|
||||
<div className="text-xs whitespace-pre-wrap break-words">
|
||||
{event.text || '结构化更新'}
|
||||
</div>
|
||||
</div>
|
||||
{index < runEvents.length - 1 && <Separator className="my-2" />}
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm flex items-center gap-2">
|
||||
<FileOutput className="w-4 h-4" />
|
||||
产物列表
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 space-y-3">
|
||||
{runArtifacts.length === 0 && (
|
||||
<div className="text-xs text-muted-foreground">暂时还没有产物。</div>
|
||||
)}
|
||||
{runArtifacts.map((artifact) => (
|
||||
<div key={artifact.artifact_id} className="rounded-lg border border-border/70 bg-background/70 p-3 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center text-muted-foreground">
|
||||
{artifactIcon(artifact.artifact_type)}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm font-medium truncate">{artifact.title}</div>
|
||||
<div className="text-[11px] text-muted-foreground">
|
||||
{artifact.actor_id} · {artifact.artifact_type}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{renderArtifactBody(artifact)}
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user