新增内部Task系统,包括验证、反馈门控机制,实现自动质量验证 (通过率>=0.75)和用户反馈闭环(satisfied/revise/abandon)。 实现Agent Team v1协调器,支持sequence/parallel/dag执行策略, sub-agent复用主AgentLoop,每个run使用独立memory snapshot。 建立Skill学习pipeline,包含draft/审核/发布/回滚完整生命周期, 通过Task验证通过且用户满意才生成学习候选。 重构目录结构,移除third_party依赖,建立统一engine内核, 所有agent共享运行时基础组件。 更新ContextBuilder清理provider消息字段,增强SkillContext版本管理, 集成TaskExecutionPlanner和TaskSkillResolver实现技能解析机制。
174 lines
5.5 KiB
TypeScript
174 lines
5.5 KiB
TypeScript
'use client';
|
|
|
|
import React from 'react';
|
|
|
|
import type { ChatMessage, ProcessArtifact, ProcessEvent, ProcessRun } from '@/types';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
import { MessageList } from '@/components/chat-workbench/MessageList';
|
|
import { ArtifactSidebar } from '@/components/chat-workbench/ArtifactSidebar';
|
|
import { ProcessLane } from '@/components/chat-workbench/ProcessLane';
|
|
import { pickAppText } from '@/lib/i18n/core';
|
|
import { useAppI18n } from '@/lib/i18n/provider';
|
|
|
|
export function ChatWorkbench({
|
|
messages,
|
|
isThinking,
|
|
messagesEndRef,
|
|
messageViewportRef,
|
|
processRuns,
|
|
processEvents,
|
|
processArtifacts,
|
|
selectedRunId,
|
|
onSelectRun,
|
|
onCancelRun,
|
|
onFeedback,
|
|
}: {
|
|
messages: ChatMessage[];
|
|
isThinking: boolean;
|
|
messagesEndRef: React.RefObject<HTMLDivElement>;
|
|
messageViewportRef: React.RefObject<HTMLDivElement>;
|
|
processRuns: ProcessRun[];
|
|
processEvents: ProcessEvent[];
|
|
processArtifacts: ProcessArtifact[];
|
|
selectedRunId: string | null;
|
|
onSelectRun: (runId: string) => void;
|
|
onCancelRun: (runId: string) => void;
|
|
onFeedback: (runId: string, feedbackType: 'satisfied' | 'revise' | 'abandon') => void;
|
|
}) {
|
|
const { locale } = useAppI18n();
|
|
const [isDesktop, setIsDesktop] = React.useState(() =>
|
|
typeof window === 'undefined' ? true : window.matchMedia('(min-width: 1024px)').matches
|
|
);
|
|
|
|
React.useEffect(() => {
|
|
if (typeof window === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
const mediaQuery = window.matchMedia('(min-width: 1024px)');
|
|
const updateLayout = () => setIsDesktop(mediaQuery.matches);
|
|
updateLayout();
|
|
|
|
if (typeof mediaQuery.addEventListener === 'function') {
|
|
mediaQuery.addEventListener('change', updateLayout);
|
|
return () => mediaQuery.removeEventListener('change', updateLayout);
|
|
}
|
|
|
|
mediaQuery.addListener(updateLayout);
|
|
return () => mediaQuery.removeListener(updateLayout);
|
|
}, []);
|
|
|
|
const selectedRun = selectedRunId
|
|
? processRuns.find((item) => item.run_id === selectedRunId) || null
|
|
: null;
|
|
const selectedRunEvents = selectedRun
|
|
? processEvents.filter((item) => item.run_id === selectedRun.run_id)
|
|
: [];
|
|
const selectedRunArtifacts = selectedRun
|
|
? processArtifacts.filter((item) => item.run_id === selectedRun.run_id)
|
|
: [];
|
|
const hasResultsPanel = Boolean(
|
|
selectedRun &&
|
|
(
|
|
selectedRun.summary ||
|
|
selectedRunEvents.length > 0 ||
|
|
selectedRunArtifacts.length > 0
|
|
)
|
|
);
|
|
const hasProcessPanel = processRuns.length > 0;
|
|
const desktopColumns = hasProcessPanel && hasResultsPanel
|
|
? 'grid-cols-[minmax(0,1fr)_340px_360px]'
|
|
: hasProcessPanel
|
|
? 'grid-cols-[minmax(0,1fr)_340px]'
|
|
: hasResultsPanel
|
|
? 'grid-cols-[minmax(0,1fr)_360px]'
|
|
: 'grid-cols-[minmax(0,1fr)]';
|
|
|
|
const messageList = (
|
|
<MessageList
|
|
messages={messages}
|
|
isThinking={isThinking}
|
|
messagesEndRef={messagesEndRef}
|
|
viewportRef={messageViewportRef}
|
|
processRuns={processRuns}
|
|
processEvents={processEvents}
|
|
processArtifacts={processArtifacts}
|
|
selectedRunId={selectedRun?.run_id || null}
|
|
onSelectRun={onSelectRun}
|
|
onCancelRun={onCancelRun}
|
|
onFeedback={onFeedback}
|
|
/>
|
|
);
|
|
|
|
if (isDesktop) {
|
|
return (
|
|
<div className={`grid h-full ${desktopColumns}`}>
|
|
<div className="min-h-0">
|
|
{messageList}
|
|
</div>
|
|
{hasProcessPanel && (
|
|
<div className="min-h-0">
|
|
<ProcessLane
|
|
runs={processRuns}
|
|
events={processEvents}
|
|
selectedRunId={selectedRun?.run_id || null}
|
|
onSelectRun={onSelectRun}
|
|
onCancelRun={onCancelRun}
|
|
/>
|
|
</div>
|
|
)}
|
|
{hasResultsPanel && (
|
|
<div className="min-h-0">
|
|
<ArtifactSidebar
|
|
selectedRun={selectedRun}
|
|
events={processEvents}
|
|
artifacts={processArtifacts}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="h-full">
|
|
{!hasResultsPanel && !hasProcessPanel ? (
|
|
messageList
|
|
) : (
|
|
<Tabs defaultValue="chat" className="h-full flex flex-col">
|
|
<div className="px-4 pt-3 border-b border-border">
|
|
<TabsList className={`grid w-full ${hasResultsPanel ? 'grid-cols-3' : 'grid-cols-2'}`}>
|
|
<TabsTrigger value="chat">{pickAppText(locale, '聊天', 'Chat')}</TabsTrigger>
|
|
<TabsTrigger value="process">{pickAppText(locale, '过程', 'Process')}</TabsTrigger>
|
|
{hasResultsPanel && (
|
|
<TabsTrigger value="results">{pickAppText(locale, '结果', 'Results')}</TabsTrigger>
|
|
)}
|
|
</TabsList>
|
|
</div>
|
|
<TabsContent value="chat" className="flex-1 min-h-0 mt-0">
|
|
{messageList}
|
|
</TabsContent>
|
|
<TabsContent value="process" className="flex-1 min-h-0 mt-0">
|
|
<ProcessLane
|
|
runs={processRuns}
|
|
events={processEvents}
|
|
selectedRunId={selectedRun?.run_id || null}
|
|
onSelectRun={onSelectRun}
|
|
onCancelRun={onCancelRun}
|
|
/>
|
|
</TabsContent>
|
|
{hasResultsPanel && (
|
|
<TabsContent value="results" className="flex-1 min-h-0 mt-0">
|
|
<ArtifactSidebar
|
|
selectedRun={selectedRun}
|
|
events={processEvents}
|
|
artifacts={processArtifacts}
|
|
/>
|
|
</TabsContent>
|
|
)}
|
|
</Tabs>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|