2185 lines
104 KiB
TypeScript
2185 lines
104 KiB
TypeScript
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;
|