Files
deerflow-code/web-code/components/taskManagement/TaskConsultCreate.tsx
T
2026-05-27 19:43:15 +08:00

573 lines
26 KiB
TypeScript

import React, { useState } from 'react';
import {
ArrowLeft,
Bot,
Users,
Settings,
FileText,
Upload,
Play,
Save,
Search,
CheckCircle,
MessageSquare,
Lightbulb,
Target,
AlertCircle,
Sparkles,
} from 'lucide-react';
import { useTheme } from '@/contexts/ThemeContext';
import { PageType } from '@/components/Sidebar';
interface TaskConsultCreateProps {
onNavigate: (page: PageType) => void;
}
// 可选智能体列表
const availableAgents = [
{
id: 'goal',
name: 'GoalAgent',
role: '目标分析专家',
description: '分析业务目标,提取核心KPI和成功标准',
color: 'blue',
tags: ['目标分解', 'KPI提取'],
recommended: true,
},
{
id: 'audience',
name: 'AudienceAgent',
role: '受众分析专家',
description: '定义目标受众画像,分析用户特征和需求',
color: 'cyan',
tags: ['用户画像', '行为分析'],
recommended: true,
},
{
id: 'behavior',
name: 'BehaviorAgent',
role: '行为设计专家',
description: '设计用户行为路径和转化漏斗',
color: 'green',
tags: ['行为设计', '转化路径'],
recommended: true,
},
{
id: 'driver',
name: 'DriverAgent',
role: '驱动因素专家',
description: '分析影响用户决策的核心驱动因素',
color: 'yellow',
tags: ['驱动分析', '动机挖掘'],
recommended: false,
},
{
id: 'narrative',
name: 'NarrativeAgent',
role: '叙事策略专家',
description: '设计品牌叙事框架和内容策略',
color: 'purple',
tags: ['故事设计', '内容策略'],
recommended: true,
},
{
id: 'risk',
name: 'RiskAgent',
role: '风险评估专家',
description: '识别项目风险,制定应对策略',
color: 'red',
tags: ['风险识别', '应对策略'],
recommended: true,
},
];
const TaskConsultCreate: React.FC<TaskConsultCreateProps> = ({ onNavigate }) => {
const { theme } = useTheme();
const isDark = theme === 'dark';
// 主题颜色类
const cardBg = isDark ? 'bg-slate-800/30' : 'bg-white';
const cardBorder = isDark ? 'border-blue-800/30' : 'border-red-200';
const textPrimary = isDark ? 'text-white' : 'text-gray-900';
const textSecondary = isDark ? 'text-slate-400' : 'text-gray-600';
const textMuted = isDark ? 'text-slate-500' : 'text-gray-500';
const btnSecondary = isDark ? 'bg-slate-700/50 hover:bg-slate-600/50 border-blue-800/30 text-slate-300' : 'bg-gray-100 hover:bg-gray-200 border-gray-300 text-gray-700';
const btnPrimary = isDark ? 'bg-blue-600 hover:bg-blue-500' : 'bg-red-600 hover:bg-red-500';
const inputBg = isDark ? 'bg-slate-900/50' : 'bg-gray-50';
const inputBorder = isDark ? 'border-blue-800/30' : 'border-gray-300';
const [taskName, setTaskName] = useState('');
const [taskTheme, setTaskTheme] = useState('');
const [taskDescription, setTaskDescription] = useState('');
const [selectedAgents, setSelectedAgents] = useState<string[]>(['goal', 'audience', 'narrative', 'risk']);
const [consultMode, setConsultMode] = useState<'parallel' | 'cross' | 'human'>('parallel');
const [maxRounds, setMaxRounds] = useState(3);
const [allowAgentQuestion, setAllowAgentQuestion] = useState(true);
const [allowHumanIntervene, setAllowHumanIntervene] = useState(true);
const [autoSummary, setAutoSummary] = useState(true);
const [allowConvertToScene, setAllowConvertToScene] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const toggleAgent = (agentId: string) => {
if (selectedAgents.includes(agentId)) {
if (selectedAgents.length > 2) {
setSelectedAgents(selectedAgents.filter((id) => id !== agentId));
}
} else {
if (selectedAgents.length < 8) {
setSelectedAgents([...selectedAgents, agentId]);
}
}
};
const selectRecommended = () => {
const recommendedIds = availableAgents.filter((a) => a.recommended).map((a) => a.id);
setSelectedAgents(recommendedIds);
};
const handleStartConsult = () => {
if (taskName && selectedAgents.length >= 2) {
onNavigate('task-consult');
}
};
const filteredAgents = availableAgents.filter(
(agent) =>
agent.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
agent.role.toLowerCase().includes(searchQuery.toLowerCase())
);
const getColorClasses = (color: string) => {
const colors: Record<string, { bg: string; border: string; text: string; ring: string }> = {
blue: { bg: 'bg-blue-500/20', border: 'border-blue-500/50', text: 'text-blue-400', ring: 'ring-blue-500/50' },
cyan: { bg: 'bg-cyan-500/20', border: 'border-cyan-500/50', text: 'text-cyan-400', ring: 'ring-cyan-500/50' },
green: { bg: 'bg-green-500/20', border: 'border-green-500/50', text: 'text-green-400', ring: 'ring-green-500/50' },
yellow: { bg: 'bg-yellow-500/20', border: 'border-yellow-500/50', text: 'text-yellow-400', ring: 'ring-yellow-500/50' },
purple: { bg: 'bg-purple-500/20', border: 'border-purple-500/50', text: 'text-purple-400', ring: 'ring-purple-500/50' },
red: { bg: 'bg-red-500/20', border: 'border-red-500/50', text: 'text-red-400', ring: 'ring-red-500/50' },
};
return colors[color] || colors.blue;
};
return (
<div className="space-y-6">
{/* 顶部操作栏 */}
<div className={`${cardBg} backdrop-blur-xl ${cardBorder} rounded-xl p-4`}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<button
onClick={() => onNavigate('task-list')}
className={`flex items-center gap-2 px-3 py-2 ${btnSecondary} rounded-lg text-sm transition-colors`}
>
<ArrowLeft className="w-4 h-4" />
返回
</button>
<div className={`h-8 w-px ${isDark ? 'bg-blue-800/30' : 'bg-gray-200'}`} />
<div>
<h1 className={`${textPrimary} font-bold text-lg`}>新建多智能体会商任务</h1>
<p className={`${textSecondary} text-sm`}>多视角分析 + 人机共判 + 会商整合</p>
</div>
</div>
<div className="flex items-center gap-3">
<span className={`px-3 py-1.5 ${isDark ? 'bg-orange-500/20 text-orange-400' : 'bg-orange-100 text-orange-600'} rounded-full text-sm font-medium flex items-center gap-1.5`}>
<Users className="w-4 h-4" />
会商研判模式
</span>
</div>
</div>
</div>
<div className="grid grid-cols-12 gap-6">
{/* 左侧:基础信息和智能体选择 */}
<div className="col-span-8 space-y-6">
{/* 基础信息区 */}
<div className={`${cardBg} backdrop-blur-xl ${cardBorder} rounded-xl p-5`}>
<h3 className={`${textPrimary} font-semibold text-sm mb-4 flex items-center gap-2`}>
<FileText className="w-4 h-4 text-blue-400" />
基础信息
</h3>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className={`block ${textSecondary} text-sm mb-2`}>任务名称</label>
<input
type="text"
value={taskName}
onChange={(e) => setTaskName(e.target.value)}
placeholder="例如:五四青年节宣传策略会商"
className={`w-full px-4 py-2.5 ${inputBg} ${inputBorder} rounded-lg ${textPrimary} text-sm ${textMuted} focus:outline-none focus:border-blue-500/50 transition-colors`}
/>
</div>
<div>
<label className={`block ${textSecondary} text-sm mb-2`}>会商主题</label>
<input
type="text"
value={taskTheme}
onChange={(e) => setTaskTheme(e.target.value)}
placeholder="例如:宣传策略前期研判"
className={`w-full px-4 py-2.5 ${inputBg} ${inputBorder} rounded-lg ${textPrimary} text-sm ${textMuted} focus:outline-none focus:border-blue-500/50 transition-colors`}
/>
</div>
</div>
<div>
<label className={`block ${textSecondary} text-sm mb-2`}>任务描述</label>
<textarea
value={taskDescription}
onChange={(e) => setTaskDescription(e.target.value)}
placeholder="详细描述本次会商要解决的问题和目标..."
rows={3}
className={`w-full px-4 py-2.5 ${inputBg} ${inputBorder} rounded-lg ${textPrimary} text-sm ${textMuted} focus:outline-none focus:border-blue-500/50 transition-colors resize-none`}
/>
</div>
<div>
<label className={`block ${textSecondary} text-sm mb-2`}>背景材料上传</label>
<div className={`border-2 border-dashed ${inputBorder} rounded-lg p-6 text-center hover:border-blue-500/50 transition-colors cursor-pointer`}>
<Upload className={`w-8 h-8 ${textMuted} mx-auto mb-2`} />
<p className={`${textSecondary} text-sm`}>点击或拖拽上传文件</p>
<p className={`${textMuted} text-xs mt-1`}>支持 PDF、Word、图片等格式</p>
</div>
</div>
</div>
</div>
{/* 智能体选择区 */}
<div className={`${cardBg} backdrop-blur-xl ${cardBorder} rounded-xl p-5`}>
<div className="flex items-center justify-between mb-4">
<h3 className={`${textPrimary} font-semibold text-sm flex items-center gap-2`}>
<Bot className="w-4 h-4 text-purple-400" />
选择参与智能体
<span className={`${textMuted} font-normal text-xs ml-2`}>
(已选 {selectedAgents.length} 个,建议 3-6 个)
</span>
</h3>
<div className="flex items-center gap-2">
<button
onClick={selectRecommended}
className={`px-3 py-1.5 bg-purple-500/20 hover:bg-purple-500/30 border border-purple-500/30 text-purple-400 rounded-lg text-xs transition-colors`}
>
一键选择推荐组合
</button>
</div>
</div>
{/* 搜索和筛选 */}
<div className="relative mb-4">
<Search className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${textMuted}`} />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="搜索智能体..."
className={`w-full pl-10 pr-4 py-2 ${inputBg} ${inputBorder} rounded-lg ${textPrimary} text-sm ${textMuted} focus:outline-none focus:border-blue-500/50 transition-colors`}
/>
</div>
{/* 智能体卡片列表 */}
<div className="grid grid-cols-2 gap-3 max-h-[320px] overflow-y-auto pr-2">
{filteredAgents.map((agent) => {
const isSelected = selectedAgents.includes(agent.id);
const colors = getColorClasses(agent.color);
return (
<button
key={agent.id}
onClick={() => toggleAgent(agent.id)}
className={`p-4 rounded-xl border transition-all text-left ${
isSelected
? `${colors.bg} ${colors.border} ring-1 ${colors.ring}`
: `${inputBg} ${inputBorder} hover:border-blue-500/30`
}`}
>
<div className="flex items-start gap-3">
<div className={`w-10 h-10 rounded-lg ${colors.bg} flex items-center justify-center flex-shrink-0`}>
<Bot className={`w-5 h-5 ${colors.text}`} />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<p className={`text-sm font-medium ${isSelected ? colors.text : textPrimary}`}>
{agent.name}
</p>
{agent.recommended && (
<span className={`px-1.5 py-0.5 ${isDark ? 'bg-green-500/20 text-green-400' : 'bg-green-100 text-green-600'} rounded text-xs`}>
推荐
</span>
)}
</div>
<p className={`${textSecondary} text-xs mt-0.5`}>{agent.role}</p>
<p className={`${textMuted} text-xs mt-1 line-clamp-2`}>{agent.description}</p>
<div className="flex flex-wrap gap-1 mt-2">
{agent.tags.map((tag) => (
<span
key={tag}
className={`px-1.5 py-0.5 ${isSelected ? colors.bg : isDark ? 'bg-slate-700/50' : 'bg-gray-100'} ${
isSelected ? colors.text : textSecondary
} rounded text-xs`}
>
{tag}
</span>
))}
</div>
</div>
<div
className={`w-5 h-5 rounded border flex items-center justify-center flex-shrink-0 ${
isSelected ? `${colors.bg} ${colors.border}` : isDark ? 'border-slate-600' : 'border-gray-300'
}`}
>
{isSelected && <CheckCircle className={`w-3.5 h-3.5 ${colors.text}`} />}
</div>
</div>
</button>
);
})}
</div>
</div>
</div>
{/* 右侧:会商规则设置和预览 */}
<div className="col-span-4 space-y-6">
{/* 会商规则设置 */}
<div className={`${cardBg} backdrop-blur-xl ${cardBorder} rounded-xl p-5`}>
<h3 className={`${textPrimary} font-semibold text-sm mb-4 flex items-center gap-2`}>
<Settings className="w-4 h-4 text-green-400" />
会商规则设置
</h3>
<div className="space-y-4">
{/* 会商模式 */}
<div>
<label className={`block ${textSecondary} text-sm mb-2`}>会商模式</label>
<div className="space-y-2">
{[
{
id: 'parallel',
label: '并行独立分析',
desc: '首轮各智能体独立发言',
icon: MessageSquare,
},
{
id: 'cross',
label: '受控交叉讨论',
desc: '主持人指定智能体互相回应',
icon: Users,
},
{
id: 'human',
label: '人机共判模式',
desc: '人类主导,全程可控',
icon: Sparkles,
},
].map((mode) => (
<button
key={mode.id}
onClick={() => setConsultMode(mode.id as typeof consultMode)}
className={`w-full p-3 rounded-lg border transition-all text-left ${
consultMode === mode.id
? 'bg-green-500/20 border-green-500/50'
: `${inputBg} ${inputBorder} hover:border-blue-500/30`
}`}
>
<div className="flex items-center gap-3">
<mode.icon
className={`w-5 h-5 ${
consultMode === mode.id ? 'text-green-400' : textSecondary
}`}
/>
<div>
<p
className={`text-sm font-medium ${
consultMode === mode.id ? 'text-green-400' : textPrimary
}`}
>
{mode.label}
</p>
<p className={`${textMuted} text-xs`}>{mode.desc}</p>
</div>
</div>
</button>
))}
</div>
</div>
{/* 最大轮数 */}
<div>
<label className={`block ${textSecondary} text-sm mb-2`}>
最大讨论轮数 <span className={textMuted}>(1-10)</span>
</label>
<input
type="number"
min={1}
max={10}
value={maxRounds}
onChange={(e) => setMaxRounds(Math.min(10, Math.max(1, parseInt(e.target.value) || 1)))}
className={`w-full px-4 py-2.5 ${inputBg} ${inputBorder} rounded-lg ${textPrimary} text-sm focus:outline-none focus:border-blue-500/50 transition-colors`}
/>
</div>
{/* 开关设置 */}
<div className="space-y-3 pt-2">
<label className="flex items-center justify-between cursor-pointer">
<div>
<p className={`${textPrimary} text-sm`}>允许智能体互相提问</p>
<p className={`${textMuted} text-xs`}>智能体可主动向其他智能体发起讨论</p>
</div>
<div
onClick={() => setAllowAgentQuestion(!allowAgentQuestion)}
className={`w-11 h-6 rounded-full transition-colors relative ${
allowAgentQuestion ? 'bg-green-500' : isDark ? 'bg-slate-600' : 'bg-gray-400'
}`}
>
<div
className={`w-5 h-5 bg-white rounded-full absolute top-0.5 transition-transform ${
allowAgentQuestion ? 'translate-x-5.5' : 'translate-x-0.5'
}`}
/>
</div>
</label>
<label className="flex items-center justify-between cursor-pointer">
<div>
<p className={`${textPrimary} text-sm`}>允许人工插话</p>
<p className={`${textMuted} text-xs`}>用户可随时打断并发表意见</p>
</div>
<div
onClick={() => setAllowHumanIntervene(!allowHumanIntervene)}
className={`w-11 h-6 rounded-full transition-colors relative ${
allowHumanIntervene ? 'bg-green-500' : isDark ? 'bg-slate-600' : 'bg-gray-400'
}`}
>
<div
className={`w-5 h-5 bg-white rounded-full absolute top-0.5 transition-transform ${
allowHumanIntervene ? 'translate-x-5.5' : 'translate-x-0.5'
}`}
/>
</div>
</label>
<label className="flex items-center justify-between cursor-pointer">
<div>
<p className={`${textPrimary} text-sm`}>自动生成摘要</p>
<p className={`${textMuted} text-xs`}>每轮结束后自动提炼观点</p>
</div>
<div
onClick={() => setAutoSummary(!autoSummary)}
className={`w-11 h-6 rounded-full transition-colors relative ${
autoSummary ? 'bg-green-500' : isDark ? 'bg-slate-600' : 'bg-gray-400'
}`}
>
<div
className={`w-5 h-5 bg-white rounded-full absolute top-0.5 transition-transform ${
autoSummary ? 'translate-x-5.5' : 'translate-x-0.5'
}`}
/>
</div>
</label>
<label className="flex items-center justify-between cursor-pointer">
<div>
<p className={`${textPrimary} text-sm`}>支持转为场景任务</p>
<p className={`${textMuted} text-xs`}>会商结束后可转入场景执行</p>
</div>
<div
onClick={() => setAllowConvertToScene(!allowConvertToScene)}
className={`w-11 h-6 rounded-full transition-colors relative ${
allowConvertToScene ? 'bg-green-500' : isDark ? 'bg-slate-600' : 'bg-gray-400'
}`}
>
<div
className={`w-5 h-5 bg-white rounded-full absolute top-0.5 transition-transform ${
allowConvertToScene ? 'translate-x-5.5' : 'translate-x-0.5'
}`}
/>
</div>
</label>
</div>
</div>
</div>
{/* 会商流程预览 */}
<div className={`${cardBg} backdrop-blur-xl ${cardBorder} rounded-xl p-5`}>
<h3 className={`${textPrimary} font-semibold text-sm mb-4 flex items-center gap-2`}>
<Target className="w-4 h-4 text-orange-400" />
会商流程预览
</h3>
<div className="space-y-2">
{[
{ step: 1, label: '首轮独立发言', desc: '各智能体各抒己见' },
{ step: 2, label: '交叉讨论', desc: '按模式互动回应' },
{ step: 3, label: '观点提炼', desc: '系统自动汇总' },
{ step: 4, label: '人工整合', desc: '人类主导判断' },
{ step: 5, label: '输出结论', desc: '生成会商纪要' },
].map((item, index) => (
<div key={item.step} className="flex items-center gap-3">
<div className="flex flex-col items-center">
<div className={`w-6 h-6 rounded-full ${isDark ? 'bg-orange-500/20 border-orange-500/50' : 'bg-orange-100 border-orange-200'} flex items-center justify-center`}>
<span className={`${isDark ? 'text-orange-400' : 'text-orange-600'} text-xs font-medium`}>{item.step}</span>
</div>
{index < 4 && <div className={`w-px h-4 ${isDark ? 'bg-blue-800/30' : 'bg-gray-200'}`} />}
</div>
<div className="flex-1">
<p className={`${textPrimary} text-sm`}>{item.label}</p>
<p className={`${textMuted} text-xs`}>{item.desc}</p>
</div>
</div>
))}
</div>
</div>
{/* 选中智能体预览 */}
{selectedAgents.length > 0 && (
<div className={`${cardBg} backdrop-blur-xl ${isDark ? 'border-purple-800/30' : 'border-purple-200'} rounded-xl p-5`}>
<h3 className={`${textPrimary} font-semibold text-sm mb-3`}>
已选智能体 ({selectedAgents.length})
</h3>
<div className="flex flex-wrap gap-2">
{selectedAgents.map((agentId) => {
const agent = availableAgents.find((a) => a.id === agentId);
if (!agent) return null;
const colors = getColorClasses(agent.color);
return (
<span
key={agentId}
className={`px-2 py-1 ${colors.bg} ${colors.text} rounded-lg text-xs flex items-center gap-1`}
>
<Bot className="w-3 h-3" />
{agent.name}
</span>
);
})}
</div>
</div>
)}
{/* 操作按钮 */}
<div className="space-y-3">
<button
onClick={handleStartConsult}
disabled={!taskName || selectedAgents.length < 2}
className={`w-full flex items-center justify-center gap-2 px-4 py-3 ${btnPrimary} disabled:${isDark ? 'bg-slate-700 text-slate-500' : 'bg-gray-300 text-gray-500'} text-white rounded-xl text-sm font-medium transition-colors`}
>
<Play className="w-4 h-4" />
开始会商
</button>
<button className={`w-full flex items-center justify-center gap-2 px-4 py-3 ${btnSecondary} rounded-xl text-sm transition-colors`}>
<Save className="w-4 h-4" />
保存草稿
</button>
</div>
{/* 提示信息 */}
<div className={`${isDark ? 'bg-blue-500/10 border-blue-500/30' : 'bg-blue-50 border-blue-200'} rounded-lg p-3`}>
<div className="flex items-start gap-2">
<Lightbulb className={`w-4 h-4 text-blue-400 flex-shrink-0 mt-0.5`} />
<div className={`text-xs ${textSecondary}`}>
<p className={`${isDark ? 'text-blue-400' : 'text-blue-600'} font-medium mb-1`}>小提示</p>
<p>会商任务不追求自动闭环,而是帮助您更好地判断。建议在任务前期研判、多方案比较、争议问题讨论等场景使用。</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default TaskConsultCreate;