deerflow-code/frontend-web/src/strategy-components/components/qa/QA.tsx
2026-09-07 18:24:55 +08:00

2185 lines
104 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useRef, useEffect } from 'react';
import {
Send,
Upload,
RotateCcw,
Bookmark,
History,
Sparkles,
BookOpen,
Brain,
GitCompare,
MessageSquare,
FileText,
CheckCircle,
AlertCircle,
Bot,
Database,
Layers,
Cpu,
Target,
Search,
CircleHelp,
ChevronRight,
Plus,
X,
Copy,
RefreshCw,
ArrowRight,
ArrowLeft,
Download,
Save,
ChevronDown,
ChevronUp,
MessageCircle,
Users,
FileCheck,
AlertTriangle,
Handshake,
Edit3,
ListChecks,
Play,
Pause,
UserCheck,
UserX,
Hash,
Clock,
Zap,
ClipboardList,
TrendingUp,
} from 'lucide-react';
import { useTheme } from '../../../contexts/ThemeContext';
import { useChatContext } from '../../../contexts/ChatContext';
import QATypeSwitch from './QATypeSwitch';
type QAMode = 'single-round' | 'multi-round' | 'deep-search';
type QAProvider = 'general' | 'agent' | 'multi-agent';
type ViewMode = 'single' | 'compare';
interface Agent {
id: string;
name: string;
category: string;
tags: string[];
description: string;
skills: string[];
dataSources: string[];
}
interface Message {
id: string;
role: 'user' | 'assistant';
content: string;
timestamp: Date;
sources?: SourceItem[];
agentName?: string;
skills?: string[];
dataSources?: string[];
functions?: string[];
}
interface SourceItem {
title: string;
type: string;
snippet: string;
}
interface CompareResult {
leftMode: QAMode;
rightMode: QAMode;
leftContent: string;
rightContent: string;
differences?: {
consensus: string;
differences: string;
leftAdvantage: string;
rightAdvantage: string;
};
}
// 多智能体协同分析相关类型
interface AgentResponse {
id: string;
agentId: string;
agentName: string;
agentCategory: string;
status: 'pending' | 'responding' | 'completed' | 'adopted' | 'pending-confirm';
coreJudgment: string;
content: string;
structuredSummary?: {
key: string;
value: string;
}[];
timestamp: Date;
}
// 多智能体消息类型
interface MultiAgentMessage {
id: string;
type: 'user' | 'agent';
agentName?: string;
content: string;
timestamp: Date;
isFollowUp?: boolean;
parentMessageId?: string;
}
interface AdoptedView {
id: string;
agentName: string;
content: string;
timestamp: Date;
}
interface PendingQuestion {
id: string;
content: string;
relatedAgents: string[];
timestamp: Date;
}
interface SummaryDraft {
title: string;
sections: {
title: string;
content: string;
}[];
}
// 场景模板类型
interface SceneTemplate {
id: string;
name: string;
type: string;
description: string;
agentIds: string[];
}
// 场景模板数据
const sceneTemplates: SceneTemplate[] = [
{
id: 'scene-1',
name: '宣传策略生成',
type: '策略生成',
description: '生成完整的品牌传播策略方案',
agentIds: ['goal-agent', 'audience-agent', 'brand-agent', 'channel-agent', 'content-agent', 'narrative-agent', 'behavior-agent', 'risk-agent'],
},
{
id: 'scene-2',
name: '宣传方案推演',
type: '方案推演',
description: '对现有方案进行多角度推演验证',
agentIds: ['goal-agent', 'audience-agent', 'behavior-agent', 'risk-agent', 'content-agent', 'channel-agent'],
},
{
id: 'scene-3',
name: '宣传活动复盘',
type: '效果复盘',
description: '对宣传活动效果进行多维度复盘分析',
agentIds: ['goal-agent', 'audience-agent', 'behavior-agent', 'channel-agent', 'risk-agent'],
},
{
id: 'scene-4',
name: '舆情应对策略',
type: '风险应对',
description: '制定舆情风险应对和处置策略',
agentIds: ['risk-agent', 'audience-agent', 'brand-agent', 'content-agent', 'narrative-agent', 'channel-agent', 'behavior-agent'],
},
{
id: 'scene-5',
name: '多版本策略比较',
type: '策略对比',
description: '对比分析多个策略方案的优劣',
agentIds: ['goal-agent', 'audience-agent', 'brand-agent', 'content-agent'],
},
];
// 智能体列表 - 按五大类别分类
const agents: Agent[] = [
// 规划类智能体
{
id: 'goal-agent',
name: 'GoalAgent',
category: '规划类',
tags: ['目标拆解', 'KPI设定', '优先级'],
description: '专业的目标规划智能体,能够帮助拆解任务目标并设定可衡量的KPI',
skills: ['目标拆解', 'KPI设定', '优先级排序'],
dataSources: ['知识库', '历史数据分析'],
},
{
id: 'brand-agent',
name: 'BrandAgent',
category: '规划类',
tags: ['品牌定位', '传播策略', '调性设计'],
description: '专注品牌建设与传播策略的规划智能体',
skills: ['品牌定位分析', '传播路径规划', '品牌调性设计'],
dataSources: ['知识库', '行业数据库'],
},
{
id: 'content-agent',
name: 'ContentAgent',
category: '规划类',
tags: ['文案撰写', '创意生成', '内容规划'],
description: '智能内容创作规划助手,支持多种形式的内容生成规划',
skills: ['文案撰写', '创意生成', '多形式内容'],
dataSources: ['知识库', '优秀案例库'],
},
{
id: 'channel-agent',
name: 'ChannelAgent',
category: '规划类',
tags: ['渠道选择', '投放策略', '媒介规划'],
description: '专业的渠道规划智能体,制定最优渠道组合策略',
skills: ['渠道选择', '投放策略规划', '效果评估'],
dataSources: ['Y系统', 'A系统', '行业数据'],
},
{
id: 'narrative-agent',
name: 'NarrativeAgent',
category: '规划类',
tags: ['故事线', '情感设计', '叙事策略'],
description: '专业的叙事规划智能体,构建有感染力的内容故事线',
skills: ['故事线设计', '情感共鸣设计', '内容叙事'],
dataSources: ['优秀案例库', '内容分析'],
},
// MNTY类智能体
{
id: 'mnty-agent',
name: 'MNTYAgent',
category: 'MNTY类',
tags: ['心智模型', '用户认知', '心智定位'],
description: '专注用户心智模型分析,挖掘用户深层认知和心智定位',
skills: ['心智模型构建', '认知路径分析', '心智定位设计'],
dataSources: ['用户研究数据', '心理学理论库'],
},
// 效果PG类智能体
{
id: 'behavior-agent',
name: 'BehaviorAgent',
category: '效果PG类',
tags: ['用户行为', '行为路径', '效果预测'],
description: '专注用户行为模式分析,预测和优化用户行为效果',
skills: ['用户行为分析', '行为路径设计', '转化优化'],
dataSources: ['Y系统', '行为数据'],
},
{
id: 'pg-agent',
name: 'PGAgent',
category: '效果PG类',
tags: ['效果评估', 'PG分析', '数据洞察'],
description: '专业的效果评估智能体,提供精准的数据分析和洞察',
skills: ['效果评估', '数据可视化', '趋势预测'],
dataSources: ['数据分析平台', '监控系统'],
},
// 知识分析类智能体
{
id: 'audience-agent',
name: 'AudienceAgent',
category: '知识分析类',
tags: ['受众分层', '画像抽取', '知识分析'],
description: '专业的受众知识分析智能体,识别目标受众特征并生成画像',
skills: ['受众分层', '核心受众识别', '画像抽取'],
dataSources: ['知识库', 'Y系统', 'A系统'],
},
{
id: 'knowledge-agent',
name: 'KnowledgeAgent',
category: '知识分析类',
tags: ['知识图谱', '语义分析', '关联挖掘'],
description: '专业的知识分析智能体,构建知识图谱并进行深度语义分析',
skills: ['知识图谱构建', '语义分析', '关联挖掘'],
dataSources: ['知识库', '文献数据库'],
},
// 协同管理类智能体
{
id: 'risk-agent',
name: 'RiskAgent',
category: '协同管理类',
tags: ['风险识别', '预案设计', '协同管理'],
description: '专注风险识别与协同管理的智能体,制定完善的应急预案',
skills: ['风险识别', '影响评估', '预案设计'],
dataSources: ['历史案例', '风险数据库'],
},
{
id: 'collab-agent',
name: 'CollabAgent',
category: '协同管理类',
tags: ['协同调度', '资源协调', '进度管理'],
description: '专业的协同管理智能体,统筹调度多方资源协调推进',
skills: ['协同调度', '资源协调', '进度管理'],
dataSources: ['任务管理系统', '资源数据库'],
},
];
interface QAProps {
initialMode?: 'new' | 'general' | 'agent' | 'multi-agent';
}
const QA: React.FC<QAProps> = ({ initialMode }) => {
const { theme } = useTheme();
const { addMessage } = useChatContext();
const isDark = theme === 'dark';
// 状态管理
const [viewMode, setViewMode] = useState<ViewMode>('single');
const [qaProvider, setQaProvider] = useState<QAProvider>('general');
const [qaMode, setQaMode] = useState<QAMode>('single-round');
const [selectedAgent, setSelectedAgent] = useState<Agent>(agents[0]);
const [compareLeftMode, setCompareLeftMode] = useState<QAMode>('single-round');
const [compareRightMode, setCompareRightMode] = useState<QAMode>('deep-search');
// 新建问答确认对话框
const [showNewQADialog, setShowNewQADialog] = useState(false);
// 处理 initialMode - 新建问答时清空会话
useEffect(() => {
if (initialMode === 'new') {
// 新建问答模式:清空会话,保持通用问答模式
setMessages([]);
setCompareResult(null);
setInputValue('');
setQaProvider('general');
setViewMode('single');
} else if (initialMode === 'agent') {
// 智能体问答模式
setQaProvider('agent');
} else if (initialMode === 'general') {
// 通用问答模式
setQaProvider('general');
} else if (initialMode === 'multi-agent') {
// 多智能体协同模式
setQaProvider('multi-agent');
setMessages([]);
setCompareResult(null);
setInputValue('');
}
}, [initialMode]);
// 知识范围
const [knowledgeScope, setKnowledgeScope] = useState<'all' | 'specific-kb' | 'specific-domain'>('all');
// 回答偏好
const [showSources, setShowSources] = useState(true);
const [showProcess, setShowProcess] = useState(false);
// 对比设置
const [compareLayout, setCompareLayout] = useState<'horizontal' | 'vertical'>('horizontal');
const [autoSummarizeDiff, setAutoSummarizeDiff] = useState(true);
const [recommendBetter, setRecommendBetter] = useState(true);
// 消息和结果
const [messages, setMessages] = useState<Message[]>([]);
const [inputValue, setInputValue] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [compareResult, setCompareResult] = useState<CompareResult | null>(null);
const [agentSearchQuery, setAgentSearchQuery] = useState('');
// 检索过程展示
const [searchProcess, setSearchProcess] = useState({
currentMode: '单轮RAG',
rounds: 1,
hits: 5,
queryRewrite: '',
});
// 多智能体会商状态
const [discussionStatus, setDiscussionStatus] = useState<'idle' | 'in_progress' | 'paused' | 'completed'>('idle');
const [currentRound, setCurrentRound] = useState(1);
const [currentTopic, setCurrentTopic] = useState('五四青年节宣传方向讨论');
const [speakingMode, setSpeakingMode] = useState<'broadcast' | 'targeted' | 'response'>('broadcast');
const [selectedTargetAgent, setSelectedTargetAgent] = useState<string | null>(null);
// 共识与分歧
const [consensusItems, setConsensusItems] = useState<{id: string; content: string; agents: string[]}[]>([]);
const [disagreementItems, setDisagreementItems] = useState<{id: string; content: string; agents: string[]; details: string}[]>([]);
const [conclusionDraft, setConclusionDraft] = useState('');
// 消息操作状态
const [messageAction, setMessageAction] = useState<'none' | 'adopt' | 'disagree' | 'followup' | 'respond'>('none');
const [selectedMessageId, setSelectedMessageId] = useState<string | null>(null);
// 原有状态(保持兼容性)
const [selectedMultiAgents, setSelectedMultiAgents] = useState<Agent[]>([agents[0], agents[4]]);
const [agentResponses, setAgentResponses] = useState<AgentResponse[]>([]);
const [adoptedViews, setAdoptedViews] = useState<AdoptedView[]>([]);
const [pendingQuestions, setPendingQuestions] = useState<PendingQuestion[]>([]);
const [summaryDraft, setSummaryDraft] = useState<SummaryDraft>({
title: '多智能体联合分析报告',
sections: [],
});
const [taskInfo, setTaskInfo] = useState({
name: '五四青年节宣传方向预分析',
goal: '形成一版多智能体联合分析报告',
status: '讨论中',
background: '五四青年节品牌传播活动需要明确目标受众和传播策略',
keyQuestions: ['核心受众是谁?', '如何设计参与行为?', '叙事方式如何选择?', '有哪些风险需要关注?'],
});
const [sendTarget, setSendTarget] = useState<'all' | string>('all');
const [multiAgentFilter, setMultiAgentFilter] = useState<'all' | 'completed' | 'pending' | 'adopted'>('all');
const [isMultiAgentLoading, setIsMultiAgentLoading] = useState(false);
const [multiAgentMessages, setMultiAgentMessages] = useState<MultiAgentMessage[]>([]);
const [rightPanelTab, setRightPanelTab] = useState<'structured' | 'report'>('structured');
const [messageFilter, setMessageFilter] = useState<'all' | 'user' | 'adopted'>('all');
const [agentMessageFilter, setAgentMessageFilter] = useState<string>('all');
const [messageSort, setMessageSort] = useState<'time' | 'agent'>('time');
// 左侧菜单折叠状态
const [leftPanelCollapsed, setLeftPanelCollapsed] = useState(false);
const [qaConfigCollapsed, setQaConfigCollapsed] = useState(false);
const [agentListCollapsed, setAgentListCollapsed] = useState(false);
// 多智能体协同 - 智能体选择状态
const [showAgentSelector, setShowAgentSelector] = useState(false);
const [showSceneSelector, setShowSceneSelector] = useState(false);
const [selectedSceneTemplate, setSelectedSceneTemplate] = useState<SceneTemplate | null>(null);
// 过滤智能体
const filteredAgents = agents.filter(agent =>
agent.name.toLowerCase().includes(agentSearchQuery.toLowerCase()) ||
agent.category.toLowerCase().includes(agentSearchQuery.toLowerCase()) ||
agent.tags.some(tag => tag.toLowerCase().includes(agentSearchQuery.toLowerCase()))
);
// 多智能体协同分析处理函数
const handleMultiAgentSend = () => {
if (!inputValue.trim()) return;
setIsMultiAgentLoading(true);
const targets = sendTarget === 'all' ? selectedMultiAgents : selectedMultiAgents.filter(a => a.id === sendTarget);
// 添加用户消息到消息流
const userMessageId = `user-${Date.now()}`;
const userMessage: MultiAgentMessage = {
id: userMessageId,
type: 'user',
content: inputValue,
timestamp: new Date(),
};
setMultiAgentMessages(prev => [...prev, userMessage]);
// 为每个目标智能体创建响应
const newResponses: AgentResponse[] = targets.map((agent, idx) => ({
id: `${Date.now()}-${agent.id}`,
agentId: agent.id,
agentName: agent.name,
agentCategory: agent.category,
status: 'responding' as const,
coreJudgment: '分析中...',
content: '',
timestamp: new Date(),
}));
setAgentResponses(prev => [...prev, ...newResponses]);
setInputValue('');
// 模拟智能体响应
setTimeout(() => {
const completedResponses = targets.map((agent, idx) => {
const responses: Record<string, { core: string; content: string; summary: { key: string; value: string }[] }> = {
'audience-agent': {
core: '核心受众应优先聚焦18-25岁基层青年群体',
content: '从任务背景和活动语境看,核心传播对象应聚焦能够形成参与、表达与扩散的人群,年轻基层群体更符合当前任务目标。该群体活跃于社交媒体,对节日话题有天然共鸣,是实现传播裂变的关键。',
summary: [
{ key: 'core_audience', value: '18-25岁基层青年' },
{ key: 'secondary_audience', value: '25-30岁职场新人' },
{ key: 'barriers', value: '表达门槛、品牌距离感' },
],
},
'goal-agent': {
core: '目标应分为参与层和传播层两层设计',
content: '建议将五四青年节活动目标拆解为两个层次:参与层目标是引导青年参与活动并产生内容;传播层目标是鼓励青年主动分享传播,形成话题裂变。两层目标相互关联,传播层目标的达成依赖于参与层的体验设计。',
summary: [
{ key: '参与目标', value: '引导10000+青年参与' },
{ key: '传播目标', value: '产生5000+UGC内容' },
{ key: '核心KPI', value: '话题阅读量突破500万' },
],
},
'behavior-agent': {
core: '用户行为路径应设计为"触发-参与-分享"三阶段',
content: '基于对年轻用户行为模式的分析,建议设计"触发-参与-分享"的完整行为路径。触发阶段通过话题挑战赛引发兴趣;参与阶段通过互动任务降低门槛;分享阶段通过激励机制促进主动传播。',
summary: [
{ key: '触发机制', value: '话题挑战赛' },
{ key: '参与路径', value: '任务闯关+创作征集' },
{ key: '分享激励', value: '荣誉体系+实物奖励' },
],
},
'narrative-agent': {
core: '叙事建议采用"真实青年故事"为主线',
content: '建议采用纪实风格的叙事策略,讲述真实青年人物的故事,而非虚构的营销叙事。通过真实故事的感染力和共鸣感,提升青年群体对活动的认同和参与意愿。',
summary: [
{ key: '叙事风格', value: '纪实风格' },
{ key: '故事原型', value: '真实青年人物' },
{ key: '情感基调', value: '正向、热血、有共鸣' },
],
},
'risk-agent': {
core: '主要风险在于话题敏感性和UGC内容合规',
content: '需要关注两方面风险:一是政治敏感话题的把控,需严格审核所有内容;二是UGC内容可能带来的舆论风险。建议建立完善的内容审核机制和应急预案。',
summary: [
{ key: '风险类型', value: '内容合规风险' },
{ key: '风险等级', value: '中等' },
{ key: '应对策略', value: '前置审核+应急预案' },
],
},
};
const mockData = responses[agent.id] || {
core: '该智能体已完成分析',
content: '基于专业能力进行了深入分析。',
summary: [{ key: '分析结论', value: '待补充' }],
};
const responseId = `${Date.now()}-${agent.id}`;
// 添加智能体回答到消息流
const agentMessage: MultiAgentMessage = {
id: responseId,
type: 'agent',
agentName: agent.name,
content: `${mockData.core}\n\n${mockData.content}`,
timestamp: new Date(),
};
setMultiAgentMessages(prev => [...prev, agentMessage]);
return {
id: responseId,
agentId: agent.id,
agentName: agent.name,
agentCategory: agent.category,
status: 'completed' as const,
coreJudgment: mockData.core,
content: mockData.content,
structuredSummary: mockData.summary,
timestamp: new Date(),
};
});
setAgentResponses(prev => {
const existing = prev.filter(r => !targets.find(t => `${Date.now()}-${t.id}`.includes(r.id)));
return [...existing, ...completedResponses];
});
setIsMultiAgentLoading(false);
}, 1500);
};
const handleAdoptResponse = (responseId: string) => {
const response = agentResponses.find(r => r.id === responseId);
if (response) {
setAdoptedViews(prev => [...prev, {
id: `adopted-${Date.now()}`,
agentName: response.agentName,
content: response.coreJudgment,
timestamp: new Date(),
}]);
setAgentResponses(prev => prev.map(r =>
r.id === responseId ? { ...r, status: 'adopted' as const } : r
));
}
};
const handlePendingConfirm = (responseId: string) => {
setAgentResponses(prev => prev.map(r =>
r.id === responseId ? { ...r, status: 'pending-confirm' as const } : r
));
};
const handleWriteToReport = (responseId: string) => {
const response = agentResponses.find(r => r.id === responseId);
if (response) {
setSummaryDraft(prev => ({
...prev,
sections: [...prev.sections, {
title: `${response.agentName}分析结论`,
content: response.coreJudgment + '\n\n' + response.content,
}],
}));
}
};
const handleGenerateSummary = () => {
const summaryPoints = agentResponses
.filter(r => r.status === 'completed' || r.status === 'adopted')
.map(r => `- ${r.agentName}:${r.coreJudgment}`);
setSummaryDraft(prev => ({
...prev,
sections: [
{ title: '任务背景', content: taskInfo.name },
{ title: '参与智能体', content: selectedMultiAgents.map(a => a.name).join('、') },
{ title: '综合结论', content: summaryPoints.join('\n') },
{ title: '待确认问题', content: pendingQuestions.map(q => `- ${q.content}`).join('\n') || '无' },
],
}));
};
const handleExportReport = () => {
alert('报告导出功能开发中...');
};
const handleSaveMultiAgentRecord = () => {
alert('协同分析记录已保存');
};
const handleTransferToScene = () => {
alert('转场景任务功能开发中...');
};
const handleMultiAgentFollowUp = (agentId: string) => {
setSendTarget(agentId);
};
// 添加智能体到协同分析
const handleAddAgent = (agent: Agent) => {
if (!selectedMultiAgents.find(a => a.id === agent.id)) {
setSelectedMultiAgents([...selectedMultiAgents, agent]);
}
setShowAgentSelector(false);
};
// 从协同分析中移除智能体
const handleRemoveAgent = (agentId: string) => {
setSelectedMultiAgents(selectedMultiAgents.filter(a => a.id !== agentId));
// 同时清除该智能体的响应
setAgentResponses(prev => prev.filter(r => r.agentId !== agentId));
setMultiAgentMessages(prev => prev.filter(msg => {
const response = agentResponses.find(r => r.agentId === agentId);
return !response || msg.agentName !== agents.find(a => a.id === agentId)?.name;
}));
};
// 应用场景模板
const handleApplySceneTemplate = (template: SceneTemplate) => {
const templateAgents = agents.filter(a => template.agentIds.includes(a.id));
setSelectedMultiAgents(templateAgents);
setSelectedSceneTemplate(template);
// 清空当前分析数据
setAgentResponses([]);
setMultiAgentMessages([]);
setAdoptedViews([]);
setPendingQuestions([]);
setSummaryDraft({ title: `${template.name}分析报告`, sections: [] });
setTaskInfo({
name: template.name,
goal: template.description,
status: '准备中',
background: template.description,
keyQuestions: [],
});
setShowSceneSelector(false);
};
// 获取未选择的智能体列表
const availableAgentsForSelection = agents.filter(
agent => !selectedMultiAgents.find(a => a.id === agent.id)
);
// 处理新建问答 - 清空会话并切换到指定类型
const handleNewQA = (targetProvider?: QAProvider) => {
// 清空对话
setMessages([]);
setCompareResult(null);
setInputValue('');
// 切换到指定类型
if (targetProvider) {
setQaProvider(targetProvider);
}
};
const handleSend = () => {
if (!inputValue.trim()) return;
setIsLoading(true);
const userMessage: Message = {
id: Date.now().toString(),
role: 'user',
content: inputValue,
timestamp: new Date(),
};
// 保存用户消息到聊天记录
addMessage('user', inputValue, qaProvider === 'agent' ? 'agent' : 'general', qaProvider === 'agent' ? selectedAgent.name : undefined);
setMessages([...messages, userMessage]);
setInputValue('');
setTimeout(() => {
const responseContent = qaProvider === 'agent'
? `基于${selectedAgent.name}的专业能力,我来为您分析:\n\n根据您的问题,我识别到核心受众特征如下:\n\n**核心受众识别**:\n- 主要年龄段:25-35岁\n- 职业特征:都市白领\n- 消费能力:中高端\n- 关注点:品质生活、个人成长\n\n**关键特质**:\n1. 追求个性化表达\n2. 注重社交认同\n3. 愿意为品质付费\n\n以上分析是否满足您的需求?如需调整维度,请继续补充信息。`
: `RAG(检索增强生成)技术的工作原理主要包括以下几个步骤:\n\n1. **检索阶段**:系统首先将用户问题转换为查询向量,在知识库中进行相似度搜索。\n\n2. **增强阶段**:将检索到的相关文档与原始问题组合。\n\n3. **生成阶段**:将增强后的提示词发送给大语言模型。`;
const response: Message = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: responseContent,
timestamp: new Date(),
sources: qaProvider === 'agent' ? undefined : [
{ title: 'RAG技术白皮书', type: 'PDF', snippet: 'RAG是一种结合检索和生成的技术架构...' },
],
agentName: qaProvider === 'agent' ? selectedAgent.name : undefined,
skills: qaProvider === 'agent' ? selectedAgent.skills : undefined,
dataSources: qaProvider === 'agent' ? selectedAgent.dataSources : undefined,
};
// 保存助手回复到聊天记录
addMessage('assistant', responseContent, qaProvider === 'agent' ? 'agent' : 'general', qaProvider === 'agent' ? selectedAgent.name : undefined);
setMessages(prev => [...prev, response]);
setIsLoading(false);
setSearchProcess({
currentMode: qaMode === 'single-round' ? '单轮RAG' : qaMode === 'multi-round' ? '多轮RAG' : '深度检索',
rounds: qaMode === 'multi-round' ? 3 : 1,
hits: 5,
queryRewrite: qaMode === 'deep-search' ? '原问题→分解子问题→独立检索→综合回答' : '',
});
}, 1500);
};
const handleCompare = () => {
if (!inputValue.trim()) return;
setIsLoading(true);
setTimeout(() => {
setCompareResult({
leftMode: compareLeftMode,
rightMode: compareRightMode,
leftContent: '单轮RAG回答:直接检索并生成答案,响应速度快,但可能遗漏跨文档的关联信息。适用于简单事实查询。',
rightContent: '深度检索回答:通过问题分解、多轮检索和综合分析,提供更全面的答案。包含详细的推理过程和更多的引用来源。',
differences: {
consensus: '两种方式都使用向量检索技术,都能引用原始文档',
differences: '深度检索采用多轮查询改写,覆盖更全面',
leftAdvantage: '响应更快,资源消耗更低',
rightAdvantage: '答案更完整,引用更充分',
},
});
setIsLoading(false);
}, 2000);
};
const handleSaveRecord = () => {
alert('问答记录已保存');
};
// 新建问答 - 通用/智能体模式
const handleStartNewQA = () => {
// 如果有现有消息,弹出确认对话框
if (messages.length > 0 || compareResult) {
setShowNewQADialog(true);
return;
}
// 直接新建 - 清空会话
performNewQA();
};
// 执行新建问答
const performNewQA = () => {
// 清空对话
setMessages([]);
setCompareResult(null);
setInputValue('');
setShowNewQADialog(false);
};
// 保存并新建
const handleSaveAndNewQA = () => {
handleSaveRecord();
performNewQA();
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (viewMode === 'compare') {
handleCompare();
} else {
handleSend();
}
}
};
// 获取模式标签
const getModeLabel = (mode: QAMode) => {
switch (mode) {
case 'single-round': return '单轮RAG';
case 'multi-round': return '多轮RAG';
case 'deep-search': return '深度检索';
}
};
// 空状态快捷入口
const QuickStartCards = () => (
<div className="mt-6 grid grid-cols-3 gap-3 max-w-2xl">
<button
onClick={() => handleNewQA('general')}
className={`p-4 rounded-xl border-2 border-dashed transition-all text-left ${
isDark
? 'border-slate-600 hover:border-blue-500 bg-slate-800/50 hover:bg-blue-600/10'
: 'border-gray-300 hover:border-red-400 bg-gray-50 hover:bg-red-50'
}`}
>
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${
isDark ? 'bg-blue-600/30 text-blue-400' : 'bg-red-100 text-red-500'
}`}>
<CircleHelp className="w-5 h-5" />
</div>
<div>
<div className={`font-medium text-sm ${isDark ? 'text-white' : 'text-gray-900'}`}>
通用问答
</div>
<div className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
基于知识库回答
</div>
</div>
</div>
</button>
<button
onClick={() => handleNewQA('agent')}
className={`p-4 rounded-xl border-2 border-dashed transition-all text-left ${
isDark
? 'border-slate-600 hover:border-purple-500 bg-slate-800/50 hover:bg-purple-600/10'
: 'border-gray-300 hover:border-purple-400 bg-gray-50 hover:bg-purple-50'
}`}
>
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${
isDark ? 'bg-purple-600/30 text-purple-400' : 'bg-purple-100 text-purple-500'
}`}>
<Bot className="w-5 h-5" />
</div>
<div>
<div className={`font-medium text-sm ${isDark ? 'text-white' : 'text-gray-900'}`}>
智能体问答
</div>
<div className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
专业智能体服务
</div>
</div>
</div>
</button>
<button
onClick={() => handleNewQA('multi-agent')}
className={`p-4 rounded-xl border-2 border-dashed transition-all text-left ${
isDark
? 'border-slate-600 hover:border-green-500 bg-slate-800/50 hover:bg-green-600/10'
: 'border-gray-300 hover:border-green-400 bg-gray-50 hover:bg-green-50'
}`}
>
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${
isDark ? 'bg-green-600/30 text-green-400' : 'bg-green-100 text-green-500'
}`}>
<Layers className="w-5 h-5" />
</div>
<div>
<div className={`font-medium text-sm ${isDark ? 'text-white' : 'text-gray-900'}`}>
多智能体协同
</div>
<div className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
多视角并行分析
</div>
</div>
</div>
</button>
</div>
);
return (
<div className={`h-[calc(100vh-120px)] flex theme-bg rounded-xl overflow-hidden ${isDark ? 'bg-slate-800/50' : 'bg-white'} border ${isDark ? 'border-slate-700/50' : 'border-red-100'}`}>
{/* 左侧面板 - MiniMax风格 - 可折叠 */}
<div className={`transition-all duration-300 flex-shrink-0 flex flex-col overflow-hidden ${leftPanelCollapsed ? 'w-12' : 'w-80'} ${isDark ? 'bg-slate-800/30' : 'bg-gray-50/50'} ${isDark ? 'border-slate-700/50' : 'border-red-100'}`}>
{/* 顶部标题 */}
<div className={`px-3 py-3 border-b flex items-center justify-between ${isDark ? 'border-slate-700/50' : 'border-red-100'}`}>
{!leftPanelCollapsed && (
<div className="flex items-center gap-2">
<Bot className={`w-5 h-5 ${isDark ? 'text-blue-400' : 'text-red-500'}`} />
<h2 className={`font-medium text-sm ${isDark ? 'text-white' : 'text-gray-900'}`}>
{qaProvider === 'agent' ? '智能体列表' : '问答配置'}
</h2>
</div>
)}
<button
onClick={() => setLeftPanelCollapsed(!leftPanelCollapsed)}
className={`p-1.5 rounded-lg transition-colors ${isDark ? 'hover:bg-slate-700 text-slate-400' : 'hover:bg-gray-200 text-gray-500'}`}
title={leftPanelCollapsed ? '展开菜单' : '收起菜单'}
>
{leftPanelCollapsed ? (
<ChevronRight className="w-4 h-4" />
) : (
<ChevronDown className="w-4 h-4" />
)}
</button>
</div>
{/* 左侧面板折叠内容 */}
{!leftPanelCollapsed && (
<>
{/* 通用问答配置 */}
{qaProvider === 'general' && (
<div className="flex-1 overflow-y-auto p-4 space-y-6 custom-scrollbar">
{/* 问答模式选择 - 卡片式 */}
<div>
<h3 className={`text-xs font-medium mb-2 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
问答模式
</h3>
<div className="space-y-2">
{[
{ value: 'single-round', label: '单轮RAG', icon: Sparkles, desc: '快速直接' },
{ value: 'multi-round', label: '多轮RAG', icon: Brain, desc: '深入理解' },
{ value: 'deep-search', label: '深度检索', icon: BookOpen, desc: '全面分析' },
].map((mode) => (
<button
key={mode.value}
onClick={() => setQaMode(mode.value as QAMode)}
className={`w-full flex items-center gap-3 p-3 rounded-xl transition-all text-left ${
qaMode === mode.value
? isDark
? 'bg-blue-600/20 border-2 border-blue-500'
: 'bg-red-50 border-2 border-red-300'
: isDark
? 'bg-slate-700/50 border border-transparent hover:bg-slate-700'
: 'bg-white border border-gray-200 hover:border-gray-300'
}`}
>
<div className={`w-9 h-9 rounded-lg flex items-center justify-center ${
qaMode === mode.value
? isDark
? 'bg-blue-600 text-white'
: 'bg-red-500 text-white'
: isDark
? 'bg-slate-600 text-slate-300'
: 'bg-gray-100 text-gray-600'
}`}>
<mode.icon className="w-4 h-4" />
</div>
<div className="flex-1">
<div className={`text-sm font-medium ${isDark ? 'text-white' : 'text-gray-900'}`}>
{mode.label}
</div>
<div className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
{mode.desc}
</div>
</div>
{qaMode === mode.value && (
<CheckCircle className={`w-5 h-5 ${isDark ? 'text-blue-400' : 'text-red-500'}`} />
)}
</button>
))}
</div>
</div>
{/* 知识范围 */}
<div>
<h3 className={`text-xs font-medium mb-2 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
知识范围
</h3>
<div className={`p-3 rounded-xl space-y-2 ${isDark ? 'bg-slate-700/50' : 'bg-white'}`}>
{[
{ value: 'all', label: '全部知识源' },
{ value: 'specific-kb', label: '指定知识库' },
{ value: 'specific-domain', label: '指定资源域' },
].map((option) => (
<label
key={option.value}
className="flex items-center gap-2 py-1.5 cursor-pointer"
>
<div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center transition-all ${
knowledgeScope === option.value
? isDark
? 'border-blue-500 bg-blue-500'
: 'border-red-500 bg-red-500'
: isDark
? 'border-slate-500'
: 'border-gray-300'
}`}>
{knowledgeScope === option.value && (
<div className="w-1.5 h-1.5 rounded-full bg-white" />
)}
</div>
<input
type="radio"
name="knowledge-scope"
checked={knowledgeScope === option.value}
onChange={() => setKnowledgeScope(option.value as any)}
className="sr-only"
/>
<span className={`text-sm ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
{option.label}
</span>
</label>
))}
</div>
</div>
{/* 回答偏好 */}
<div>
<h3 className={`text-xs font-medium mb-2 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
回答偏好
</h3>
<div className={`p-3 rounded-xl space-y-2 ${isDark ? 'bg-slate-700/50' : 'bg-white'}`}>
{[
{ checked: showSources, onChange: () => setShowSources(!showSources), label: '展示引用来源' },
{ checked: showProcess, onChange: () => setShowProcess(!showProcess), label: '展示检索过程' },
].map((item, idx) => (
<label key={idx} className="flex items-center gap-2 py-1.5 cursor-pointer">
<div className={`w-4 h-4 rounded-md border-2 flex items-center justify-center transition-all ${
item.checked
? isDark
? 'border-blue-500 bg-blue-500'
: 'border-red-500 bg-red-500'
: isDark
? 'border-slate-500'
: 'border-gray-300'
}`}>
{item.checked && (
<svg className="w-2.5 h-2.5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg>
)}
</div>
<input
type="checkbox"
checked={item.checked}
onChange={item.onChange}
className="sr-only"
/>
<span className={`text-sm ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
{item.label}
</span>
</label>
))}
</div>
</div>
{/* 检索过程 */}
{showProcess && messages.length > 0 && (
<div>
<h3 className={`text-xs font-medium mb-2 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
检索过程
</h3>
<div className={`p-3 rounded-xl space-y-2 ${isDark ? 'bg-slate-700/50' : 'bg-white'}`}>
<div className="flex items-center justify-between">
<span className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>当前模式</span>
<span className={`text-xs font-medium ${isDark ? 'text-blue-400' : 'text-blue-600'}`}>
{searchProcess.currentMode}
</span>
</div>
<div className="flex items-center justify-between">
<span className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>检索轮次</span>
<span className={`text-xs ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>{searchProcess.rounds}</span>
</div>
<div className="flex items-center justify-between">
<span className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>命中数</span>
<span className={`text-xs ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>{searchProcess.hits}</span>
</div>
</div>
</div>
)}
</div>
)}
{/* 智能体问答配置 - MiniMax单专家风格优化 */}
{qaProvider === 'agent' && (
<div className="flex-1 overflow-hidden flex flex-col">
{/* 简洁的智能体选择区 */}
<div className="px-4 py-4">
{/* 当前选中智能体 - 大卡片突出显示 */}
<div className={`p-4 rounded-2xl mb-4 ${isDark ? 'bg-gradient-to-br from-blue-600/20 to-purple-600/20 border border-blue-500/30' : 'bg-gradient-to-br from-blue-50 to-purple-50 border border-blue-200'}`}>
<div className="flex items-center gap-3 mb-3">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${
isDark ? 'bg-blue-600/40 text-blue-300' : 'bg-blue-500/20 text-blue-600'
}`}>
<Bot className="w-6 h-6" />
</div>
<div className="flex-1">
<div className={`font-semibold text-base ${isDark ? 'text-white' : 'text-gray-900'}`}>
{selectedAgent.name}
</div>
<div className={`text-xs mt-0.5 ${isDark ? 'text-blue-400' : 'text-blue-600'}`}>
{selectedAgent.category}
</div>
</div>
</div>
<div className={`text-sm leading-relaxed ${isDark ? 'text-slate-300' : 'text-gray-600'}`}>
{selectedAgent.description}
</div>
</div>
{/* 智能体列表 - MiniMax风格网格 */}
<div className={`text-xs font-medium mb-2 flex items-center gap-1.5 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
<Sparkles className="w-3 h-3" />
选择其他智能体
</div>
<div className="grid grid-cols-2 gap-2">
{agents.slice(0, 6).map((agent) => (
<button
key={agent.id}
onClick={() => setSelectedAgent(agent)}
className={`p-2.5 rounded-xl text-left transition-all ${
selectedAgent.id === agent.id
? isDark
? 'bg-blue-600/30 border border-blue-500'
: 'bg-blue-100 border border-blue-300'
: isDark
? 'bg-slate-700/50 border border-transparent hover:border-slate-600'
: 'bg-white border border-gray-200 hover:border-gray-300'
}`}
>
<div className="flex items-center gap-2">
<div className={`w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 ${
selectedAgent.id === agent.id
? isDark ? 'bg-blue-600 text-white' : 'bg-blue-500 text-white'
: isDark ? 'bg-slate-600 text-slate-300' : 'bg-gray-100 text-gray-500'
}`}>
<Bot className="w-4 h-4" />
</div>
<div className="min-w-0 flex-1">
<div className={`text-xs font-medium truncate ${isDark ? 'text-white' : 'text-gray-800'}`}>
{agent.name}
</div>
<div className={`text-xs truncate ${isDark ? 'text-slate-400' : 'text-gray-400'}`}>
{agent.category}
</div>
</div>
</div>
</button>
))}
</div>
</div>
{/* 智能体能力标签 - 底部展示 */}
<div className={`mt-auto p-4 border-t ${isDark ? 'border-slate-700/50 bg-slate-800/30' : 'border-gray-100 bg-gray-50/50'}`}>
{/* 技能标签 */}
<div className="mb-3">
<div className={`text-xs font-medium mb-2 flex items-center gap-1 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
<Sparkles className="w-3 h-3" />
核心技能
</div>
<div className="flex flex-wrap gap-1">
{selectedAgent.skills.slice(0, 4).map((skill, idx) => (
<span
key={idx}
className={`px-2 py-1 rounded-lg text-xs font-medium ${
isDark ? 'bg-blue-600/20 text-blue-400 border border-blue-500/30' : 'bg-blue-50 text-blue-600 border border-blue-200'
}`}
>
{skill}
</span>
))}
</div>
</div>
{/* 数据源标签 */}
<div>
<div className={`text-xs font-medium mb-2 flex items-center gap-1 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
<Database className="w-3 h-3" />
数据源
</div>
<div className="flex flex-wrap gap-1">
{selectedAgent.dataSources.slice(0, 3).map((source, idx) => (
<span
key={idx}
className={`px-2 py-1 rounded-lg text-xs ${
isDark ? 'bg-green-600/20 text-green-400 border border-green-500/30' : 'bg-green-50 text-green-600 border border-green-200'
}`}
>
{source}
</span>
))}
</div>
</div>
</div>
</div>
)}
{/* 底部按钮 - 所有模式通用 */}
<div className={`p-4 border-t ${isDark ? 'border-slate-700/50' : 'border-red-100'}`}>
<button
className={`w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl 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>
{/* 右侧面板 - 对话区 */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* 顶部模式切换 */}
<div className={`px-6 py-3 border-b flex items-center justify-between ${isDark ? 'border-slate-700/50 bg-slate-800/30' : 'border-red-100 bg-gray-50/50'}`}>
<QATypeSwitch
qaProvider={qaProvider}
setQaProvider={setQaProvider}
selectedAgentName={selectedAgent.name}
viewMode={viewMode}
setViewMode={setViewMode}
/>
{/* 右侧 - 新建问答按钮(仅通用/智能体模式显示) */}
{qaProvider !== 'multi-agent' && (
<button
onClick={handleStartNewQA}
className={`px-4 py-2 rounded-xl text-sm font-medium transition-all flex items-center gap-2 ${
isDark
? 'bg-slate-700/50 hover:bg-slate-600 text-slate-300 border border-slate-600'
: 'bg-white hover:bg-gray-100 text-gray-700 border border-gray-200'
}`}
>
<Plus className="w-4 h-4" />
新建问答
</button>
)}
</div>
{/* 消息区域 */}
<div className="flex-1 overflow-y-auto p-0 custom-scrollbar">
{/* 多智能体协同分析面板 - 正确设计 */}
{qaProvider === 'multi-agent' && (
<div className="h-full flex flex-col">
{/* 顶部标题栏 - 简洁版 */}
<div className={`flex-shrink-0 px-6 py-4 border-b ${isDark ? 'bg-slate-800/50 border-slate-700/50' : 'bg-white border-gray-200'}`}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<button
className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-all ${
isDark ? 'bg-slate-700/50 hover:bg-slate-600/50 text-slate-300' : 'bg-gray-100 hover:bg-gray-200 text-gray-700'
}`}
>
<ArrowLeft className="w-4 h-4" />
返回
</button>
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-xl flex items-center justify-center ${isDark ? 'bg-green-600/20' : 'bg-green-100'}`}>
<Layers className={`w-5 h-5 ${isDark ? 'text-green-400' : 'text-green-600'}`} />
</div>
<div>
<h2 className={`font-bold text-lg ${isDark ? 'text-white' : 'text-gray-900'}`}>
多智能体协同分析
</h2>
<div className={`flex items-center gap-3 mt-0.5 text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
<span className="font-medium">{taskInfo.name}</span>
<span>•</span>
<span>{taskInfo.goal}</span>
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${
isDark ? 'bg-green-600/30 text-green-400' : 'bg-green-100 text-green-600'
}`}>
{taskInfo.status}
</span>
</div>
</div>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={handleGenerateSummary}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all flex items-center gap-2 ${
isDark ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'bg-blue-500 hover:bg-blue-600 text-white'
}`}
>
<Sparkles className="w-4 h-4" />
生成汇总
</button>
<button
onClick={handleExportReport}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all flex items-center gap-2 ${
isDark ? 'bg-slate-700 hover:bg-slate-600 text-white' : 'bg-gray-200 hover:bg-gray-300 text-gray-700'
}`}
>
<Download className="w-4 h-4" />
导出报告
</button>
<button
onClick={handleSaveMultiAgentRecord}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all flex items-center gap-2 ${
isDark ? 'bg-slate-700 hover:bg-slate-600 text-white' : 'bg-gray-200 hover:bg-gray-300 text-gray-700'
}`}
>
<Save className="w-4 h-4" />
保存记录
</button>
<button
onClick={() => alert('分析已结束')}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all flex items-center gap-2 ${
isDark ? 'bg-red-600/80 hover:bg-red-600 text-white' : 'bg-red-500 hover:bg-red-600 text-white'
}`}
>
<X className="w-4 h-4" />
结束分析
</button>
</div>
</div>
</div>
{/* 三栏布局主体 */}
<div className="flex-1 flex min-h-0">
{/* 左侧:参与智能体列表 */}
<div className={`w-72 flex-shrink-0 flex flex-col border-r overflow-hidden ${
isDark ? 'bg-slate-800/30 border-slate-700/50' : 'bg-gray-50/50 border-gray-200'
}`}>
{/* 场景模板选择 */}
<div className={`p-3 border-b ${isDark ? 'border-slate-700/50' : 'border-gray-200'}`}>
<div className="relative">
<button
onClick={() => { setShowSceneSelector(!showSceneSelector); setShowAgentSelector(false); }}
className={`w-full flex items-center justify-between px-3 py-2 rounded-lg text-sm font-medium transition-all ${
selectedSceneTemplate
? (isDark ? 'bg-green-600/20 border border-green-500/50 text-green-400' : 'bg-green-50 border border-green-200 text-green-600')
: (isDark ? 'bg-slate-700/50 border border-transparent hover:border-slate-600 text-slate-300' : 'bg-white border border-gray-200 hover:border-gray-300 text-gray-700')
}`}
>
<div className="flex items-center gap-2">
<Layers className="w-4 h-4" />
<span>{selectedSceneTemplate ? selectedSceneTemplate.name : '应用场景模板'}</span>
</div>
<ChevronDown className={`w-4 h-4 transition-transform ${showSceneSelector ? 'rotate-180' : ''}`} />
</button>
{/* 场景模板选择下拉 */}
{showSceneSelector && (
<div className={`absolute top-full left-0 right-0 mt-1 z-20 rounded-lg shadow-xl overflow-hidden ${
isDark ? 'bg-slate-800 border border-slate-700' : 'bg-white border border-gray-200'
}`}>
<div className={`p-2 text-xs font-medium ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
选择场景模板自动配置智能体
</div>
{sceneTemplates.map((template) => (
<button
key={template.id}
onClick={() => handleApplySceneTemplate(template)}
className={`w-full px-3 py-2 text-left hover:${
isDark ? 'bg-slate-700/50' : 'bg-gray-50'
} ${isDark ? 'hover:bg-slate-700/50' : 'hover:bg-gray-50'}`}
>
<div className={`text-sm font-medium ${isDark ? 'text-white' : 'text-gray-900'}`}>{template.name}</div>
<div className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>{template.description}</div>
</button>
))}
</div>
)}
</div>
</div>
{/* 智能体列表头部 */}
<div className="flex items-center justify-between px-4 py-3">
<h3 className={`text-sm font-medium flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<Users className={`w-4 h-4 ${isDark ? 'text-green-400' : 'text-green-500'}`} />
参与智能体 ({selectedMultiAgents.length})
</h3>
<button
onClick={() => { setShowAgentSelector(!showAgentSelector); setShowSceneSelector(false); }}
className={`p-1.5 rounded-lg transition-all ${
isDark ? 'hover:bg-slate-700 text-slate-400 hover:text-white' : 'hover:bg-gray-200 text-gray-500 hover:text-gray-700'
}`}
title="添加智能体"
>
<Plus className="w-4 h-4" />
</button>
</div>
{/* 智能体选择下拉 - 按五大类别分组 */}
{showAgentSelector && (
<div className={`mx-3 mb-2 p-3 rounded-lg ${
isDark ? 'bg-slate-700/50 border border-slate-600' : 'bg-white border border-gray-200'
}`}>
<div className={`text-xs font-medium mb-2 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
选择要添加的智能体
</div>
{availableAgentsForSelection.length === 0 ? (
<div className={`text-xs ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>所有智能体已添加</div>
) : (
<div className="max-h-64 overflow-y-auto space-y-3">
{/* 按类别分组显示 */}
{['规划类', 'MNTY类', '效果PG类', '知识分析类', '协同管理类'].map((category) => {
const categoryAgents = availableAgentsForSelection.filter(a => a.category === category);
if (categoryAgents.length === 0) return null;
return (
<div key={category}>
<div className={`text-xs font-medium mb-1.5 flex items-center gap-1.5 ${
category === '规划类' ? (isDark ? 'text-blue-400' : 'text-blue-600') :
category === 'MNTY类' ? (isDark ? 'text-purple-400' : 'text-purple-600') :
category === '效果PG类' ? (isDark ? 'text-amber-400' : 'text-amber-600') :
category === '知识分析类' ? (isDark ? 'text-cyan-400' : 'text-cyan-600') :
(isDark ? 'text-green-400' : 'text-green-600')
}`}>
{category === '规划类' && <Target className="w-3 h-3" />}
{category === 'MNTY类' && <Brain className="w-3 h-3" />}
{category === '效果PG类' && <TrendingUp className="w-3 h-3" />}
{category === '知识分析类' && <Database className="w-3 h-3" />}
{category === '协同管理类' && <Users className="w-3 h-3" />}
{category}
</div>
<div className="space-y-1">
{categoryAgents.map((agent) => (
<button
key={agent.id}
onClick={() => handleAddAgent(agent)}
className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-lg text-left transition-all ${
isDark ? 'hover:bg-slate-600/50' : 'hover:bg-gray-100'
}`}
>
<Bot className={`w-3.5 h-3.5 ${isDark ? 'text-blue-400' : 'text-blue-500'}`} />
<div className="flex-1 min-w-0">
<div className={`text-xs font-medium ${isDark ? 'text-white' : 'text-gray-900'}`}>{agent.name}</div>
<div className={`text-xs truncate ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>{agent.tags.slice(0, 2).join(' / ')}</div>
</div>
<Plus className={`w-3.5 h-3.5 ${isDark ? 'text-green-400' : 'text-green-500'}`} />
</button>
))}
</div>
</div>
);
})}
</div>
)}
</div>
)}
{/* 参与智能体列表 */}
<div className="flex-1 overflow-y-auto px-3 pb-3 custom-scrollbar">
<div className="space-y-2">
{selectedMultiAgents.map((agent) => {
const response = agentResponses.find(r => r.agentId === agent.id);
const status = response?.status || 'pending';
return (
<div key={agent.id} className={`p-2.5 rounded-lg ${isDark ? 'bg-slate-700/50' : 'bg-white border border-gray-200'}`}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 flex-1 min-w-0">
<Bot className={`w-4 h-4 ${isDark ? 'text-blue-400' : 'text-blue-500'}`} />
<div className="min-w-0 flex-1">
<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>
</div>
<div className="flex items-center gap-1.5">
<span className={`px-2 py-0.5 rounded text-xs ${
status === 'pending' ? (isDark ? 'bg-slate-600/50 text-slate-400' : 'bg-gray-200 text-gray-500') :
status === 'responding' ? (isDark ? 'bg-blue-600/30 text-blue-400' : 'bg-blue-100 text-blue-500') :
(isDark ? 'bg-green-600/30 text-green-400' : 'bg-green-100 text-green-500')
}`}>
{status === 'pending' ? '待回答' : status === 'responding' ? '回答中' : '已回答'}
</span>
<button
onClick={() => handleRemoveAgent(agent.id)}
className={`p-1 rounded transition-all ${
isDark ? 'hover:bg-slate-600 text-slate-500 hover:text-red-400' : 'hover:bg-gray-200 text-gray-400 hover:text-red-500'
}`}
title="移除智能体"
>
<X className="w-3.5 h-3.5" />
</button>
</div>
</div>
</div>
);
})}
{selectedMultiAgents.length === 0 && (
<div className={`text-center py-6 ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
<Bot className="w-8 h-8 mx-auto mb-2 opacity-50" />
<p className="text-xs">请添加参与分析的智能体</p>
</div>
)}
</div>
</div>
{/* 历史记录入口 */}
<div className={`p-4 border-t ${isDark ? 'border-slate-700/50' : 'border-gray-200'}`}>
<button
className={`w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium transition-all ${
isDark ? 'bg-slate-700/50 hover:bg-slate-600/50 text-slate-300' : 'bg-white hover:bg-gray-100 text-gray-600 border border-gray-200'
}`}
>
<History className="w-4 h-4" />
查看历史记录
</button>
</div>
</div>
{/* 中间:问答消息流 */}
<div className="flex-1 flex flex-col min-h-0 bg-transparent">
{/* 工具条 */}
<div className={`flex-shrink-0 px-4 py-3 border-b items-center gap-3 ${isDark ? 'border-slate-700/50 bg-slate-800/30' : 'border-gray-200 bg-gray-50'}`}>
<button className={`px-3 py-1.5 rounded-lg text-xs font-medium ${
messageFilter === 'all' ? (isDark ? 'bg-green-600 text-white' : 'bg-green-500 text-white') : (isDark ? 'text-slate-400' : 'text-gray-500')
}`} onClick={() => setMessageFilter('all')}>
全部消息
</button>
<select
value={agentMessageFilter}
onChange={(e) => setAgentMessageFilter(e.target.value)}
className={`px-2 py-1.5 rounded-lg text-xs border ${isDark ? 'bg-slate-700 border-slate-600 text-slate-300' : 'bg-white border-gray-200 text-gray-600'}`}
>
<option value="all">全部智能体</option>
{selectedMultiAgents.map(agent => (
<option key={agent.id} value={agent.id}>{agent.name}</option>
))}
</select>
<button className={`px-3 py-1.5 rounded-lg text-xs font-medium ${
messageFilter === 'user' ? (isDark ? 'bg-green-600 text-white' : 'bg-green-500 text-white') : (isDark ? 'text-slate-400' : 'text-gray-500')
}`} onClick={() => setMessageFilter('user')}>
用户提问
</button>
<button className={`px-3 py-1.5 rounded-lg text-xs font-medium ${
messageFilter === 'adopted' ? (isDark ? 'bg-green-600 text-white' : 'bg-green-500 text-white') : (isDark ? 'text-slate-400' : 'text-gray-500')
}`} onClick={() => setMessageFilter('adopted')}>
已采纳
</button>
</div>
{/* 消息列表 */}
<div className="flex-1 overflow-y-auto p-4 space-y-4 custom-scrollbar">
{multiAgentMessages.length === 0 ? (
<div className={`h-full flex flex-col items-center justify-center ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
<MessageSquare className="w-16 h-16 mb-4 opacity-30" />
<p className="text-base font-medium mb-2">尚未开始提问</p>
<p className="text-sm text-center max-w-md">
请在底部输入你希望多个智能体分析的问题<br/>系统将按所选对象返回回答
</p>
</div>
) : (
multiAgentMessages.map((msg) => {
const isAdopted = msg.type === 'agent' && adoptedViews.some(v => v.agentName === msg.agentName);
return (
<div key={msg.id}>
{msg.type === 'user' ? (
<div className="flex gap-3 justify-end">
<div className={`max-w-[70%] rounded-2xl px-4 py-3 ${isDark ? 'bg-blue-600 text-white' : 'bg-blue-500 text-white'}`}>
<div className={`text-xs mb-1 opacity-75`}>用户</div>
<p className="text-sm whitespace-pre-wrap">{msg.content}</p>
</div>
<div className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${isDark ? 'bg-slate-700 text-slate-300' : 'bg-gray-200 text-gray-600'}`}>
<div className="w-3 h-3 rounded-full bg-current" />
</div>
</div>
) : (
<div className={`p-4 rounded-xl border ${isAdopted ? (isDark ? 'bg-green-600/10 border-green-500/30' : 'bg-green-50 border-green-200') : (isDark ? 'bg-slate-800/30 border-slate-700/50' : 'bg-white border-gray-200')}`}>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Bot className={`w-4 h-4 ${isDark ? 'text-blue-400' : 'text-blue-500'}`} />
<span className={`text-sm font-medium ${isDark ? 'text-white' : 'text-gray-900'}`}>{msg.agentName}</span>
{isAdopted && (
<span className={`px-2 py-0.5 rounded text-xs font-medium ${isDark ? 'bg-green-600/30 text-green-400' : 'bg-green-100 text-green-600'}`}>已采纳</span>
)}
</div>
<span className={`text-xs ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>{msg.timestamp.toLocaleTimeString()}</span>
</div>
<p className={`text-sm leading-relaxed whitespace-pre-wrap ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>{msg.content}</p>
<div className="flex items-center gap-2 mt-3 pt-3 border-t border-opacity-30">
<button
onClick={() => handleAdoptResponse(msg.id)}
className={`px-3 py-1 rounded-lg text-xs font-medium flex items-center gap-1.5 ${
isAdopted ? (isDark ? 'bg-green-600/30 text-green-400' : 'bg-green-100 text-green-600') : (isDark ? 'bg-slate-700/50 text-slate-300' : 'bg-gray-100 text-gray-600')
}`}
>
<CheckCircle className="w-3.5 h-3.5" />
采纳
</button>
<button className={`px-3 py-1 rounded-lg text-xs font-medium flex items-center gap-1.5 ${isDark ? 'bg-slate-700/50 text-slate-300' : 'bg-gray-100 text-gray-600'}`}>
<AlertCircle className="w-3.5 h-3.5" />
待确认
</button>
<button
onClick={() => { setSendTarget(msg.agentName || 'all'); setInputValue(''); }}
className={`px-3 py-1 rounded-lg text-xs font-medium flex items-center gap-1.5 ${isDark ? 'bg-slate-700/50 text-slate-300' : 'bg-gray-100 text-gray-600'}`}
>
<MessageCircle className="w-3.5 h-3.5" />
追问
</button>
</div>
</div>
)}
</div>
);
})
)}
{isMultiAgentLoading && (
<div className="flex items-center gap-3 p-4">
<RefreshCw className={`w-5 h-5 animate-spin ${isDark ? 'text-blue-400' : 'text-blue-500'}`} />
<span className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>智能体正在分析中...</span>
</div>
)}
</div>
{/* 底部统一输入区 */}
<div className={`flex-shrink-0 p-4 border-t ${isDark ? 'bg-white/5 border-slate-700/50' : 'bg-white border-gray-200'}`}>
<div className={`flex items-center gap-3 mb-3 ${isDark ? 'text-slate-400' : 'text-gray-600'}`}>
<span className="text-sm">发送对象:</span>
<div className={`flex rounded-lg p-1 ${isDark ? 'bg-slate-700/50' : 'bg-gray-100'}`}>
<button
onClick={() => setSendTarget('all')}
className={`px-3 py-1 rounded text-xs font-medium transition-all ${
sendTarget === 'all' ? (isDark ? 'bg-green-600 text-white' : 'bg-green-500 text-white') : (isDark ? 'text-slate-400' : 'text-gray-500')
}`}
>
全部智能体
</button>
{selectedMultiAgents.map((agent) => (
<button
key={agent.id}
onClick={() => setSendTarget(agent.id)}
className={`px-3 py-1 rounded text-xs font-medium transition-all ${
sendTarget === agent.id ? (isDark ? 'bg-green-600 text-white' : 'bg-green-500 text-white') : (isDark ? 'text-slate-400' : 'text-gray-500')
}`}
>
{agent.name}
</button>
))}
</div>
</div>
<div className={`flex items-end gap-3 rounded-2xl p-4 ${isDark ? 'bg-slate-700/50' : 'bg-gray-50'}`}>
<textarea
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleMultiAgentSend();
}
}}
placeholder="请输入希望智能体分析的问题、补充要求或追问内容..."
rows={2}
className={`flex-1 bg-transparent resize-none focus:outline-none text-base ${
isDark ? 'text-white placeholder-slate-500' : 'text-gray-800 placeholder-gray-400'
}`}
/>
<div className="flex items-center gap-2">
<button className={`p-2.5 rounded-xl ${isDark ? 'hover:bg-slate-600 text-slate-400' : 'hover:bg-gray-200 text-gray-500'}`} title="上传文件">
<Upload className="w-5 h-5" />
</button>
<button
onClick={handleMultiAgentSend}
disabled={!inputValue.trim() || isMultiAgentLoading}
className={`px-5 py-2.5 rounded-xl font-medium transition-all flex items-center gap-2 ${
inputValue.trim() && !isMultiAgentLoading
? (isDark ? 'bg-green-600 hover:bg-green-700 text-white' : 'bg-green-500 hover:bg-green-600 text-white')
: (isDark ? 'bg-slate-600 text-slate-400' : 'bg-gray-300 text-gray-500')
}`}
>
{isMultiAgentLoading ? <RefreshCw className="w-4 h-4 animate-spin" /> : <Send className="w-4 h-4" />}
发送
</button>
</div>
</div>
</div>
</div>
{/* 右侧:分析结果 */}
<div className={`w-80 flex-shrink-0 flex flex-col border-l overflow-hidden ${
isDark ? 'bg-slate-800/30 border-slate-700/50' : 'bg-gray-50/50 border-gray-200'
}`}>
{/* 标签切换 */}
<div className={`flex border-b ${isDark ? 'border-slate-700/50' : 'border-gray-200'}`}>
<button
onClick={() => setRightPanelTab('structured')}
className={`flex-1 py-3 text-sm font-medium transition-all ${
rightPanelTab === 'structured'
? isDark ? 'bg-slate-800/50 text-white border-b-2 border-green-500' : 'bg-white text-gray-900 border-b-2 border-green-500'
: isDark ? 'text-slate-400' : 'text-gray-500'
}`}
>
结构化结果
</button>
<button
onClick={() => setRightPanelTab('report')}
className={`flex-1 py-3 text-sm font-medium transition-all ${
rightPanelTab === 'report'
? isDark ? 'bg-slate-800/50 text-white border-b-2 border-green-500' : 'bg-white text-gray-900 border-b-2 border-green-500'
: isDark ? 'text-slate-400' : 'text-gray-500'
}`}
>
文本报告
</button>
</div>
{/* 内容区 */}
<div className="flex-1 overflow-y-auto p-4 custom-scrollbar">
{rightPanelTab === 'structured' ? (
<div className="space-y-4">
{/* 汇总结论 */}
<div>
<div className={`text-xs font-medium mb-2 ${isDark ? 'text-green-400' : 'text-green-600'}`}>汇总结论</div>
<div className={`p-3 rounded-lg ${isDark ? 'bg-slate-700/50' : 'bg-white'}`}>
{agentResponses.filter(r => r.status === 'completed' || r.status === 'adopted').length === 0 ? (
<p className={`text-sm ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>暂无分析结论</p>
) : (
<div className="space-y-2">
{agentResponses.filter(r => r.status === 'completed' || r.status === 'adopted').map(r => (
<div key={r.id} className="text-sm">
<span className={`font-medium ${isDark ? 'text-blue-400' : 'text-blue-600'}`}>{r.agentName}:</span>
<span className={isDark ? 'text-slate-300' : 'text-gray-700'}>{r.coreJudgment}</span>
</div>
))}
</div>
)}
</div>
</div>
{/* 已采纳观点 */}
<div>
<div className={`text-xs font-medium mb-2 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>已采纳观点 ({adoptedViews.length})</div>
{adoptedViews.length === 0 ? (
<p className={`text-sm ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>暂无采纳观点</p>
) : (
<div className="space-y-2">
{adoptedViews.map(view => (
<div key={view.id} className={`p-3 rounded-lg ${isDark ? 'bg-slate-700/50' : 'bg-white'}`}>
<span className={`font-medium ${isDark ? 'text-green-400' : 'text-green-600'}`}>{view.agentName}</span>
<div className={`text-xs mt-1 ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>{view.content}</div>
</div>
))}
</div>
)}
</div>
{/* 待确认问题 */}
<div>
<div className={`text-xs font-medium mb-2 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>待确认问题</div>
<div className={`p-3 rounded-lg ${isDark ? 'bg-slate-700/50' : 'bg-white'}`}>
{pendingQuestions.length === 0 ? (
<p className={`text-sm ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>无待确认问题</p>
) : (
<div className="space-y-1">
{pendingQuestions.map(q => (
<div key={q.id} className={`text-sm flex items-start gap-2 ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
<span className={isDark ? 'text-amber-400' : 'text-amber-500'}>•</span>
<span>{q.content}</span>
</div>
))}
</div>
)}
</div>
</div>
</div>
) : (
<div className="space-y-4">
<div className={`text-sm font-medium ${isDark ? 'text-white' : 'text-gray-900'}`}>{summaryDraft.title || '五四青年节宣传方向分析报告'}</div>
<div className={`p-3 rounded-lg ${isDark ? 'bg-slate-700/50' : 'bg-white'}`}>
<div className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>背景</div>
<div className={`text-sm mt-1 ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>{taskInfo.background}</div>
</div>
<div className={`p-3 rounded-lg ${isDark ? 'bg-slate-700/50' : 'bg-white'}`}>
<div className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>参与智能体</div>
<div className={`text-sm mt-1 ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>{selectedMultiAgents.map(a => a.name).join(' / ')}</div>
</div>
<div className={`p-3 rounded-lg ${isDark ? 'bg-slate-700/50' : 'bg-white'}`}>
<div className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>联合结论</div>
<div className={`text-sm mt-1 ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
{summaryDraft.sections.length > 0 ? summaryDraft.sections.map(s => s.content).join('\n') : '点击"生成汇总"自动生成联合结论'}
</div>
</div>
</div>
)}
</div>
</div>
</div>
</div>
)}
{viewMode === 'single' && (
messages.length === 0 ? (
<div className={`h-full flex flex-col items-center justify-center ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
<MessageSquare className="w-16 h-16 mb-4 opacity-50" />
<p className="text-lg">
{qaProvider === 'agent' ? `开始与${selectedAgent.name}对话` : '开始你的问答'}
</p>
<p className="text-sm mt-2">
{qaProvider === 'agent'
? '基于智能体专业能力获取精准回答'
: '输入问题,获取基于知识库的智能回答'}
</p>
{/* 快捷入口 - 仅在通用问答且无消息时显示 */}
{qaProvider === 'general' && (
<QuickStartCards />
)}
</div>
) : (
<div className="h-full flex flex-col">
{/* 消息列表 - 居中显示,MiniMax风格 */}
<div className="flex-1 overflow-y-auto px-4 py-6 custom-scrollbar">
<div className="max-w-3xl mx-auto space-y-6">
{messages.map((msg) => (
<div key={msg.id} className="space-y-3">
{/* 用户消息 - 右侧显示 */}
{msg.role === 'user' && (
<div className="flex gap-3 justify-end">
<div className={`max-w-[75%] rounded-2xl px-5 py-3.5 ${
isDark
? 'bg-gradient-to-br from-blue-600 to-blue-700 text-white shadow-lg shadow-blue-600/20'
: 'bg-gradient-to-br from-red-500 to-red-600 text-white shadow-lg shadow-red-500/20'
}`}>
<p className="whitespace-pre-wrap leading-relaxed text-[15px]">{msg.content}</p>
</div>
<div className={`w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 ${
isDark ? 'bg-slate-700 text-slate-300' : 'bg-gray-200 text-gray-600'
}`}>
<div className="w-5 h-5 rounded-full bg-current" />
</div>
</div>
)}
{/* AI/智能体回复 - 左侧显示 */}
{msg.role === 'assistant' && (
<div className="flex gap-3">
<div className={`w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0 ${
qaProvider === 'agent'
? (isDark ? 'bg-gradient-to-br from-blue-600/40 to-purple-600/40 text-blue-400' : 'bg-gradient-to-br from-blue-100 to-purple-100 text-blue-600')
: (isDark ? 'bg-slate-700/80 text-slate-300' : 'bg-gray-100 text-gray-600')
}`}>
{qaProvider === 'agent' ? <Bot className="w-5 h-5" /> : <Brain className="w-5 h-5" />}
</div>
<div className="flex-1 min-w-0">
{/* 智能体名称标识 - 仅智能体模式 */}
{qaProvider === 'agent' && (
<div className={`text-xs font-medium mb-1.5 ${isDark ? 'text-blue-400' : 'text-blue-600'}`}>
{selectedAgent.name}
</div>
)}
<div className={`rounded-2xl px-5 py-3.5 ${
isDark
? 'bg-slate-800/80 text-slate-200 border border-slate-700/50'
: 'bg-white text-gray-800 border border-gray-200 shadow-sm'
}`}>
<p className="whitespace-pre-wrap leading-relaxed text-[15px]">{msg.content}</p>
</div>
{/* 来源信息 - MiniMax风格折叠 */}
{msg.sources && showSources && (
<div className={`mt-3 ml-1 p-4 rounded-xl ${isDark ? 'bg-slate-800/40 border border-slate-700/30' : 'bg-gray-50 border border-gray-200'}`}>
<div className={`text-xs font-medium mb-3 flex items-center gap-1.5 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
<FileText className="w-3.5 h-3.5" />
引用来源 ({msg.sources.length})
</div>
<div className="space-y-2">
{msg.sources.slice(0, 3).map((source, idx) => (
<div key={idx} className={`p-2.5 rounded-lg ${isDark ? 'bg-slate-700/50' : 'bg-white'}`}>
<div className="flex items-center gap-2">
<span className={`font-medium text-sm ${isDark ? 'text-slate-200' : 'text-gray-700'}`}>
{source.title}
</span>
<span className={`px-1.5 py-0.5 rounded text-xs ${
isDark ? 'bg-slate-600 text-slate-400' : 'bg-gray-100 text-gray-500'
}`}>
{source.type}
</span>
</div>
<p className={`text-xs mt-1.5 leading-relaxed ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
{source.snippet.substring(0, 100)}...
</p>
</div>
))}
</div>
</div>
)}
{/* 智能体执行详情 */}
{qaProvider === 'agent' && msg.skills && (
<div className={`mt-3 ml-1 p-4 rounded-xl ${isDark ? 'bg-slate-800/40 border border-slate-700/30' : 'bg-gray-50 border border-gray-200'}`}>
<div className="grid grid-cols-2 gap-4">
<div>
<h4 className={`text-xs font-medium mb-2 flex items-center gap-1.5 ${isDark ? 'text-blue-400' : 'text-blue-600'}`}>
<Sparkles className="w-3.5 h-3.5" />
启用技能
</h4>
<div className="flex flex-wrap gap-1">
{msg.skills.map((skill, idx) => (
<span key={idx} className={`px-2 py-1 rounded-lg text-xs font-medium ${
isDark ? 'bg-blue-600/30 text-blue-400' : 'bg-blue-100 text-blue-600'
}`}>
{skill}
</span>
))}
</div>
</div>
<div>
<h4 className={`text-xs font-medium mb-2 flex items-center gap-1.5 ${isDark ? 'text-green-400' : 'text-green-600'}`}>
<Database className="w-3.5 h-3.5" />
数据源
</h4>
<div className="flex flex-wrap gap-1">
{msg.dataSources?.map((source, idx) => (
<span key={idx} className={`px-2 py-1 rounded-lg text-xs ${
isDark ? 'bg-green-600/30 text-green-400' : 'bg-green-100 text-green-600'
}`}>
{source}
</span>
))}
</div>
</div>
</div>
</div>
)}
</div>
</div>
)}
</div>
))}
</div>
</div>
{/* 加载动画 */}
{isLoading && (
<div className="flex gap-3 max-w-3xl mx-auto px-4">
<div className={`w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0 ${
qaProvider === 'agent'
? (isDark ? 'bg-gradient-to-br from-blue-600/40 to-purple-600/40 text-blue-400' : 'bg-gradient-to-br from-blue-100 to-purple-100 text-blue-600')
: (isDark ? 'bg-slate-700/80 text-slate-300' : 'bg-gray-100 text-gray-600')
}`}>
{qaProvider === 'agent' ? <Bot className="w-5 h-5" /> : <Brain className="w-5 h-5" />}
</div>
<div className={`rounded-2xl px-5 py-4 ${isDark ? 'bg-slate-800/80 border border-slate-700/50' : 'bg-white border border-gray-200 shadow-sm'}`}>
<div className="flex gap-1.5 items-center">
<span className={`w-2 h-2 rounded-full animate-bounce ${isDark ? 'bg-blue-400' : 'bg-blue-500'}`} style={{ animationDelay: '0ms' }} />
<span className={`w-2 h-2 rounded-full animate-bounce ${isDark ? 'bg-blue-400' : 'bg-blue-500'}`} style={{ animationDelay: '150ms' }} />
<span className={`w-2 h-2 rounded-full animate-bounce ${isDark ? 'bg-blue-400' : 'bg-blue-500'}`} style={{ animationDelay: '300ms' }} />
</div>
</div>
</div>
)}
</div>
)
)}
</div>
{/* 对比模式视图 */}
{viewMode === 'compare' && (
<div className="h-full">
{compareResult ? (
compareLayout === 'horizontal' ? (
<div className="grid grid-cols-2 gap-6 h-full">
<div className={`rounded-2xl border overflow-hidden ${isDark ? 'border-slate-700/50' : 'border-red-200'}`}>
<div className={`px-4 py-3 ${isDark ? 'bg-slate-800/50 border-b border-slate-700/50' : 'bg-red-50/50 border-b border-red-100'}`}>
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
isDark ? 'bg-blue-600/30 text-blue-400' : 'bg-blue-100 text-blue-600'
}`}>
{getModeLabel(compareResult.leftMode)}
</span>
</div>
<div className="p-5">
<p className={`whitespace-pre-wrap ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
{compareResult.leftContent}
</p>
</div>
</div>
<div className={`rounded-2xl border overflow-hidden ${isDark ? 'border-slate-700/50' : 'border-red-200'}`}>
<div className={`px-4 py-3 ${isDark ? 'bg-slate-800/50 border-b border-slate-700/50' : 'bg-purple-50/50 border-b border-purple-100'}`}>
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
isDark ? 'bg-purple-600/30 text-purple-400' : 'bg-purple-100 text-purple-600'
}`}>
{getModeLabel(compareResult.rightMode)}
</span>
</div>
<div className="p-5">
<p className={`whitespace-pre-wrap ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
{compareResult.rightContent}
</p>
</div>
</div>
</div>
) : (
<div className="space-y-6">
<div className={`rounded-2xl border overflow-hidden ${isDark ? 'border-slate-700/50' : 'border-red-200'}`}>
<div className={`px-4 py-3 ${isDark ? 'bg-slate-800/50 border-b border-slate-700/50' : 'bg-red-50/50 border-b border-red-100'}`}>
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
isDark ? 'bg-blue-600/30 text-blue-400' : 'bg-blue-100 text-blue-600'
}`}>
{getModeLabel(compareResult.leftMode)}
</span>
</div>
<div className="p-5">
<p className={`whitespace-pre-wrap ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
{compareResult.leftContent}
</p>
</div>
</div>
<div className={`rounded-2xl border overflow-hidden ${isDark ? 'border-slate-700/50' : 'border-red-200'}`}>
<div className={`px-4 py-3 ${isDark ? 'bg-slate-800/50 border-b border-slate-700/50' : 'bg-purple-50/50 border-b border-purple-100'}`}>
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
isDark ? 'bg-purple-600/30 text-purple-400' : 'bg-purple-100 text-purple-600'
}`}>
{getModeLabel(compareResult.rightMode)}
</span>
</div>
<div className="p-5">
<p className={`whitespace-pre-wrap ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
{compareResult.rightContent}
</p>
</div>
</div>
</div>
)
) : (
<div className={`h-full flex flex-col items-center justify-center ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
<GitCompare className="w-16 h-16 mb-4 opacity-50" />
<p className="text-lg">对比模式</p>
<p className="text-sm mt-2">输入问题,对比不同模式的回答效果</p>
</div>
)}
</div>
)}
{/* 新建问答确认对话框 */}
{showNewQADialog && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className={`w-96 rounded-2xl shadow-2xl overflow-hidden ${
isDark ? 'bg-slate-800' : 'bg-white'
}`}>
<div className={`px-6 py-5 border-b ${isDark ? 'border-slate-700' : 'border-gray-100'}`}>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-full flex items-center justify-center ${
isDark ? 'bg-amber-600/30 text-amber-400' : 'bg-amber-100 text-amber-600'
}`}>
<AlertCircle className="w-6 h-6" />
</div>
<div>
<div className={`text-lg font-medium ${isDark ? 'text-white' : 'text-gray-900'}`}>
是否保存当前会话?
</div>
<div className={`text-sm mt-1 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
当前有 {messages.length} 条对话记录
</div>
</div>
</div>
</div>
<div className="p-5 space-y-3">
<button
onClick={handleSaveAndNewQA}
className={`w-full flex items-center justify-center gap-2 px-4 py-3.5 rounded-xl text-sm font-medium transition-all ${
isDark
? 'bg-blue-600 hover:bg-blue-700 text-white'
: 'bg-red-500 hover:bg-red-600 text-white'
}`}
>
<Bookmark className="w-4 h-4" />
保存并新建
</button>
<button
onClick={performNewQA}
className={`w-full flex items-center justify-center gap-2 px-4 py-3.5 rounded-xl text-sm font-medium transition-all ${
isDark
? 'bg-slate-700 hover:bg-slate-600 text-white'
: 'bg-gray-100 hover:bg-gray-200 text-gray-700'
}`}
>
<X className="w-4 h-4" />
直接新建(不保存)
</button>
<button
onClick={() => setShowNewQADialog(false)}
className={`w-full flex items-center justify-center gap-2 px-4 py-3.5 rounded-xl text-sm font-medium transition-all ${
isDark
? 'bg-transparent hover:bg-slate-700/50 text-slate-400'
: 'bg-transparent hover:bg-gray-50 text-gray-500'
}`}
>
取消
</button>
</div>
</div>
</div>
)}
{/* 底部输入区 - 仅在非多智能体模式显示 */}
{qaProvider !== 'multi-agent' && (
<div className={`p-4 border-t ${isDark ? 'border-slate-700/50' : 'border-red-100'}`}>
<div className={`flex items-end gap-3 rounded-2xl p-4 ${isDark ? 'bg-slate-700/50' : 'bg-gray-100'}`}>
<textarea
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={
viewMode === 'compare'
? '输入问题进行对比分析...'
: '输入你的问题...'
}
rows={1}
className={`flex-1 bg-transparent resize-none focus:outline-none text-base ${
isDark ? 'text-white placeholder-slate-500' : 'text-gray-800 placeholder-gray-400'
}`}
/>
<div className="flex items-center gap-2">
<button className={`p-2.5 rounded-xl transition-colors ${
isDark ? 'hover:bg-slate-600 text-slate-400 hover:text-white' : 'hover:bg-gray-200 text-gray-500'
}`}>
<Upload className="w-5 h-5" />
</button>
<button
onClick={() => {
setMessages([]);
setCompareResult(null);
}}
className={`p-2.5 rounded-xl transition-colors ${
isDark ? 'hover:bg-slate-600 text-slate-400 hover:text-white' : 'hover:bg-gray-200 text-gray-500'
}`}
title="重新提问"
>
<RotateCcw className="w-5 h-5" />
</button>
<button
onClick={handleSaveRecord}
className={`p-2.5 rounded-xl transition-colors ${
isDark ? 'hover:bg-slate-600 text-slate-400 hover:text-white' : 'hover:bg-gray-200 text-gray-500'
}`}
title="保存记录"
>
<Bookmark className="w-5 h-5" />
</button>
<button
onClick={
viewMode === 'compare'
? handleCompare
: handleSend
}
disabled={!inputValue.trim() || isLoading}
className={`px-5 py-2.5 rounded-xl font-medium transition-all flex items-center gap-2 ${
inputValue.trim() && !isLoading
? isDark
? 'bg-blue-600 hover:bg-blue-700 text-white'
: 'bg-red-500 hover:bg-red-600 text-white'
: isDark
? 'bg-slate-600 text-slate-400'
: 'bg-gray-300 text-gray-500'
}`}
>
<Send className="w-4 h-4" />
发送
</button>
</div>
</div>
</div>
)}
</div>
{/* 自定义滚动条样式 */}
<style>{`
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: ${isDark ? 'rgba(148, 163, 184, 0.3)' : 'rgba(0, 0, 0, 0.15)'};
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: ${isDark ? 'rgba(148, 163, 184, 0.5)' : 'rgba(0, 0, 0, 0.25)'};
}
`}</style>
</div>
);
};
export default QA;