190 lines
8.3 KiB
TypeScript
190 lines
8.3 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { CheckCircle, ChevronLeft, ChevronRight, History, MessageCircle, Users } from 'lucide-react';
|
|
import { useTheme } from '../../contexts/ThemeContext';
|
|
import { PageType } from '../../components/Sidebar';
|
|
import { ChatMessage, EmptyChatState, QAComposer, TypingIndicator } from './QACommon';
|
|
|
|
interface CollaborativeQAProps {
|
|
onNavigate: (page: PageType) => void;
|
|
}
|
|
|
|
interface CollaborativeMessage {
|
|
id: string;
|
|
role: 'user' | 'assistant';
|
|
content: string;
|
|
timestamp: Date;
|
|
agentName?: string;
|
|
}
|
|
|
|
const collaborativeAgents = [
|
|
{ id: 'goal-agent', name: 'GoalAgent', category: '目标规划', color: 'blue' },
|
|
{ id: 'audience-agent', name: 'AudienceAgent', category: '受众分析', color: 'green' },
|
|
{ id: 'risk-agent', name: 'RiskAgent', category: '风险识别', color: 'purple' },
|
|
];
|
|
|
|
/**
|
|
* 多智能体协同问答组件
|
|
* 多个智能体从不同视角联合分析问题,生成综合建议
|
|
* @param onNavigate - 页面导航函数
|
|
*/
|
|
const CollaborativeQA: React.FC<CollaborativeQAProps> = ({ onNavigate }) => {
|
|
const { theme } = useTheme();
|
|
const isDark = theme === 'dark';
|
|
const [messages, setMessages] = useState<CollaborativeMessage[]>([]);
|
|
const [inputValue, setInputValue] = useState('');
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [selectedAgents, setSelectedAgents] = useState(collaborativeAgents.map((agent) => agent.id));
|
|
const [isLeftSidebarCollapsed, setIsLeftSidebarCollapsed] = useState(false);
|
|
|
|
const activeAgents = collaborativeAgents.filter((agent) => selectedAgents.includes(agent.id));
|
|
|
|
const handleSend = () => {
|
|
if (!inputValue.trim()) return;
|
|
|
|
const userMessage: CollaborativeMessage = {
|
|
id: `user-${Date.now()}`,
|
|
role: 'user',
|
|
content: inputValue,
|
|
timestamp: new Date(),
|
|
};
|
|
|
|
setMessages((prev) => [...prev, userMessage]);
|
|
setInputValue('');
|
|
setIsLoading(true);
|
|
|
|
setTimeout(() => {
|
|
const agentNames = activeAgents.map((agent) => agent.name).join('、');
|
|
const assistantMessage: CollaborativeMessage = {
|
|
id: `assistant-${Date.now()}`,
|
|
role: 'assistant',
|
|
content: `已组织 ${agentNames || '默认协同组'} 完成一轮联合研判:\n\n1. GoalAgent 建议先明确任务目标与阶段性 KPI,避免讨论发散。\n2. AudienceAgent 认为应优先锁定核心受众画像,并补充行为触点数据。\n3. RiskAgent 提醒需要同步评估内容合规、舆情扩散和资源约束。\n\n综合建议:先形成“目标-受众-风险”三列表,再进入方案细化与执行拆解。`,
|
|
timestamp: new Date(),
|
|
agentName: '协同编排助手',
|
|
};
|
|
setMessages((prev) => [...prev, assistantMessage]);
|
|
setIsLoading(false);
|
|
}, 1800);
|
|
};
|
|
|
|
const handleStop = () => {
|
|
setIsLoading(false);
|
|
};
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
handleSend();
|
|
}
|
|
};
|
|
|
|
const toggleAgent = (agentId: string) => {
|
|
setSelectedAgents((prev) =>
|
|
prev.includes(agentId) ? prev.filter((id) => id !== agentId) : [...prev, agentId]
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className={`h-[calc(100vh-120px)] flex flex-col theme-bg rounded-xl ${isDark ? 'bg-slate-800/50' : 'bg-white'} border ${isDark ? 'border-slate-700/50' : 'border-red-100'}`}>
|
|
<div className={`flex items-center justify-between px-6 py-4 border-b ${isDark ? 'border-slate-700/50' : 'border-red-100'}`}>
|
|
<div className="flex items-center gap-4">
|
|
<h1 className={`text-lg font-semibold ${isDark ? 'text-white' : 'text-gray-900'}`}>多智能体协同问答</h1>
|
|
<span className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>多角色联合研判、共识汇总与风险补充</span>
|
|
</div>
|
|
<button
|
|
onClick={() => onNavigate('qa-records')}
|
|
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all ${
|
|
isDark ? 'bg-slate-700/50 text-slate-300 hover:bg-slate-600/50' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
}`}
|
|
>
|
|
<History className="w-4 h-4" />
|
|
历史记录
|
|
</button>
|
|
</div>
|
|
|
|
<div className="flex-1 flex overflow-hidden">
|
|
<aside className={`${isLeftSidebarCollapsed ? 'w-14 p-2' : 'w-72 p-4'} border-r flex-shrink-0 overflow-y-auto transition-all duration-300 ${isDark ? 'border-slate-700/50 bg-slate-800/30' : 'border-red-100 bg-gray-50/50'}`}>
|
|
<div className={`flex items-center ${isLeftSidebarCollapsed ? 'justify-center' : 'justify-between'} mb-4`}>
|
|
{!isLeftSidebarCollapsed && (
|
|
<h3 className={`text-sm font-medium ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>参与智能体</h3>
|
|
)}
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsLeftSidebarCollapsed(!isLeftSidebarCollapsed)}
|
|
className={`p-2 rounded-lg transition-all ${
|
|
isDark
|
|
? 'text-slate-400 hover:text-white hover:bg-slate-700/50'
|
|
: 'text-gray-500 hover:text-red-600 hover:bg-red-50'
|
|
}`}
|
|
title={isLeftSidebarCollapsed ? '展开侧边栏' : '收起侧边栏'}
|
|
>
|
|
{isLeftSidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
|
|
</button>
|
|
</div>
|
|
|
|
{!isLeftSidebarCollapsed && (<>
|
|
<div className="space-y-2">
|
|
{collaborativeAgents.map((agent) => {
|
|
const active = selectedAgents.includes(agent.id);
|
|
return (
|
|
<button
|
|
key={agent.id}
|
|
onClick={() => toggleAgent(agent.id)}
|
|
className={`w-full p-3 rounded-xl text-left border transition-all ${
|
|
active
|
|
? isDark ? 'bg-blue-600/20 border-blue-500/50' : 'bg-red-50 border-red-200'
|
|
: isDark ? 'bg-slate-700/30 border-transparent hover:bg-slate-700/50' : 'bg-white border-gray-200 hover:bg-gray-50'
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div className={`w-10 h-10 rounded-2xl flex items-center justify-center ${active ? 'bg-gradient-to-br from-red-500 to-orange-400 text-white' : isDark ? 'bg-slate-600 text-slate-300' : 'bg-gray-100 text-gray-600'}`}>
|
|
<Users className="w-5 h-5" />
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<div className={`text-sm font-medium truncate ${isDark ? 'text-white' : 'text-gray-900'}`}>{agent.name}</div>
|
|
<div className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>{agent.category}</div>
|
|
</div>
|
|
{active && <CheckCircle className={`w-4 h-4 ${isDark ? 'text-blue-400' : 'text-red-500'}`} />}
|
|
</div>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</>)}
|
|
</aside>
|
|
|
|
<section className="flex-1 flex flex-col overflow-hidden">
|
|
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
{messages.length === 0 ? (
|
|
<EmptyChatState
|
|
icon={<MessageCircle className="w-11 h-11 opacity-60" />}
|
|
title="发起一次协同会商"
|
|
description="输入任务背景,多个智能体会从不同视角生成联合建议"
|
|
/>
|
|
) : (
|
|
messages.map((message) => (
|
|
<ChatMessage key={message.id} message={message} assistantLabel={message.agentName || '协同编排助手'} />
|
|
))
|
|
)}
|
|
{isLoading && <TypingIndicator label="多智能体正在协同分析并汇总观点" assistantLabel="协同编排助手" />}
|
|
</div>
|
|
|
|
<QAComposer
|
|
value={inputValue}
|
|
onChange={setInputValue}
|
|
onKeyDown={handleKeyDown}
|
|
onSubmit={handleSend}
|
|
onStop={handleStop}
|
|
onSave={() => alert('问答记录已保存')}
|
|
onCollect={() => alert('已添加到收藏')}
|
|
isLoading={isLoading}
|
|
placeholder="输入协同研判主题或任务背景..."
|
|
showUpload={false}
|
|
/>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CollaborativeQA;
|