Files
beaver_project/app-instance/frontend/components/chat-workbench/ArtifactSidebar.tsx
steven_li 30ab74ffb2 feat(engine): 添加MCP连接管理和工具集成功能
- 集成MCP连接管理器,支持MCP服务器连接
- 添加多种内置工具:ClarifyTool、CronTool、DelegateTool、ExecuteCodeTool、
  PatchFileTool、ProcessTool、SendMessageTool、SpawnTool、TerminalTool、
  TodoTool、WebFetchTool、WebSearchTool、WriteFileTool等
- 实现工具注册和装配功能
- 添加技能选择上下文参数
- 支持思考模式控制参数thinking_enabled

feat(coordinator): 重构任务执行计划器参数命名

- 将learning_candidate_enabled重命名为allow_candidate_generation
- 更新TeamGraphScheduler中的参数传递
- 修改LocalAgentRunner中的相关参数处理
- 更新README文档中的相应描述

refactor(context): 标准化工具调用参数格式

- 添加_json导入用于参数序列化
- 实现_provider_tool_calls方法标准化OpenAI兼容的工具调用载荷
- 修复工具调用中参数非字符串类型的序列化问题

refactor(session): 优化消息历史记录过滤逻辑

- 修改get_messages_as_conversation为基于运行状态过滤消息
- 排除未完成、失败或错误结束的运行记录
- 改进对话历史的可见性控制机制

fix(store): 修复FTS索引重建逻辑

- 添加异常处理防止FTS索引创建失败
- 实现_rebuild_fts_index方法重新构建全文搜索索引
- 优化索引触发器和表的维护流程
2026-05-14 09:43:48 +08:00

167 lines
7.3 KiB
TypeScript

'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';
import { appActorTypeLabel, appEventKindLabel, appStatusLabel } from '@/lib/i18n/common';
import { pickAppText } from '@/lib/i18n/core';
import { useAppI18n } from '@/lib/i18n/provider';
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, locale: 'zh-CN' | 'en-US') {
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-[#5F5550] underline break-all">
{artifact.url}
</a>
);
}
return (
<div className="text-xs text-foreground/90 whitespace-pre-wrap break-words">
{artifact.content || pickAppText(locale, '(空产物)', '(Empty artifact)')}
</div>
);
}
export function ArtifactSidebar({
selectedRun,
events,
artifacts,
}: {
selectedRun: ProcessRun | null;
events: ProcessEvent[];
artifacts: ProcessArtifact[];
}) {
const { locale } = useAppI18n();
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">{pickAppText(locale, '结果面板', 'Results')}</h2>
<p className="text-xs text-muted-foreground mt-1">
{selectedRun
? pickAppText(locale, `当前选中: ${selectedRun.actor_name}`, `Selected: ${selectedRun.actor_name}`)
: pickAppText(locale, '选择一个任务查看详细过程与产物', 'Select a task to inspect its process and artifacts')}
</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" />
{pickAppText(locale, '任务摘要', 'Task summary')}
</CardTitle>
</CardHeader>
<CardContent className="pt-0 space-y-2 text-sm">
{selectedRun ? (
<>
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="outline">{appActorTypeLabel(selectedRun.actor_type, locale)}</Badge>
<Badge variant="outline">{appStatusLabel(selectedRun.status, locale)}</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 || pickAppText(locale, '暂时还没有最终摘要。', 'No final summary yet.')}
</div>
</>
) : (
<div className="text-muted-foreground text-sm">{pickAppText(locale, '当前没有选中的任务。', 'No task is selected right now.')}</div>
)}
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm flex items-center gap-2">
<MessagesSquare className="w-4 h-4" />
{pickAppText(locale, '事件记录', 'Events')}
</CardTitle>
</CardHeader>
<CardContent className="pt-0 space-y-2">
{runEvents.length === 0 && (
<div className="text-xs text-muted-foreground">{pickAppText(locale, '暂时还没有结构化事件。', 'No structured events yet.')}</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>{appEventKindLabel(event.kind, locale)}</span>
{event.status && <span>{appStatusLabel(event.status, locale)}</span>}
</div>
<div className="text-xs whitespace-pre-wrap break-words">
{event.text || pickAppText(locale, '结构化更新', 'Structured update')}
</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" />
{pickAppText(locale, '产物列表', 'Artifacts')}
</CardTitle>
</CardHeader>
<CardContent className="pt-0 space-y-3">
{runArtifacts.length === 0 && (
<div className="text-xs text-muted-foreground">{pickAppText(locale, '暂时还没有产物。', 'No artifacts yet.')}</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, locale)}
</div>
))}
</CardContent>
</Card>
</div>
</ScrollArea>
</div>
);
}