代码修改

This commit is contained in:
fengxiangdebaozi committed 2026-05-27 19:43:15 +08:00
1 parent 8f5af46ec2
commit 8f75e12871
168 files changed
+76575

No files matched your search

@@ -0,0 +1,209 @@
import { useTheme } from '@/contexts/ThemeContext';
import { actionColumnItem, ActionListItem, TaskListItem, taskStatusList } from '@/pages/TaskManagement';
import { Bot, ChevronRight, Download, Edit, Eye, FileText, Layers, MoreHorizontal, Play, Search, User, Users, Workflow } from 'lucide-react';
import React, { useEffect, useRef, useState } from 'react';
import { PageType } from '../Sidebar';
import { format } from 'date-fns';
import { useNavigate } from 'react-router-dom';
import { Col, Empty, Loading, Pagination, Progress, Row } from 'tdesign-react';
import { requestCrudService } from '@/service/request-axios-service';
import { ActionColumn } from './ActionColumn';
interface ActionAnalyzesProps {
TaskType?: string;
title: string;
category: string;
subCategory: string;
onNavigate: (page: PageType) => void;
}
const service = requestCrudService;
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
export const ActionAnalyzes: React.FC<ActionAnalyzesProps> = ({ title, category, subCategory, 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-100';
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-400';
const inputBg = isDark ? 'bg-slate-900/50' : 'bg-white';
const navigate = useNavigate();
const [searchQuery, setSearchQuery] = useState<string>('');
const [taskList, setTaskList] = useState<TaskListItem[]>([]); // 任务列表
const [taskId, setTaskId] = useState<number>(0); // 任务
const [actionColumnList, setActionColumnList] = useState<actionColumnItem[]>([]);
const [taskLoading, setTaskLoading] = useState<boolean>(false);
const [actionLoading, setActionLoading] = useState<boolean>(false);
// 当前页码,默认为第 1 页
const [pageNum, setPageNum] = useState<number>(1);
// 每页显示数量,默认为 10 条
const [pageSize, setPageSize] = useState<number>(10); // 每页显示 10 条
const [total, setTotal] = useState<number>(0); // 查询任务总数
const state = useRef({
pageIndex: 1,
pageSize: 10
})
// 回车处理
const handleKeyDown = async (e) => {
if ((e.metaKey || e.ctrlKey || e.shiftKey) && e.key === "Enter") {
e.preventDefault();
} else if (e.keyCode === 13) {
e.preventDefault();
await taskListSearch()
} else if (e.key === "/") {
e.preventDefault();
}
};
/**
* 页码变化时的回调函数
* 当用户切换页码时触发,更新页码并重新获取数据
* @param value - 新的页码值
*/
const pageIndexChange = async (value: number) => {
// 更新 ref 中存储的页码值
// 更新页码状态,触发组件重新渲染
setPageNum(value);
state.current.pageIndex = value;
await taskListSearch();
}
// 任务查询
const taskListSearch = async () => {
const param = {
keyword: '',
pageNum: state.current.pageIndex,
pageSize: state.current.pageSize
}
setTaskLoading(true);
const res = await service.post(`${zncmUrl}/action/cop-action-tasks`, param);
setTaskLoading(false);
console.log('任务搜索', res);
const result = res.data;
result.forEach((item: TaskListItem) => {
item.progress = item.endCount / item.sumAll || 0; // 获取已完成和未完成的占比
item.taskStatusData = taskStatusList[item.taskStatus] // 根据任务状态码获取相关信息
})
setTaskList(result || []); // 设置任务列表页
const totalRes = await service.post(`${zncmUrl}/action/cop-action-tasks-count`, param); // 查询任务总数
console.log('totalRes', totalRes)
setTotal(totalRes.data || 0); // 设置总数
if (result.length > 0) {
setTaskId(result[0].id);
await actionListSearch(result[0].id);
}
}
// 行动查询
const actionListSearch = async (taskId: number) => {
const param = {
taskId: taskId,
}
setActionLoading(true);
const res = await service.get(`${zncmUrl}/action/cop-task-actions`, param);
console.log('行动查询', res);
setActionLoading(false);
const result = res.data;
setActionColumnList([
{ columnName: '待分析', icon: '待CEH.svg', actionList: result.collect || [] },
{ columnName: 'CEH中', icon: '进行中.svg', actionList: result.collect1 || [] },
{ columnName: 'CEH完成', icon: '完成.svg', actionList: result.collect2 || [] },
{ columnName: '已终止', icon: '已终止.svg', actionList: result.endDate || [] },
]);
}
const taskDataChange = async (taskId: number) => {
setTaskId(taskId);
await actionListSearch(taskId);
}
useEffect(() => {
taskListSearch();
}, [])
return (
<div className="h-full flex flex-col">
<Row className="h-full flex flex-col">
<Col span={2} className="h-full">
<div className={`h-[calc(100vh-100px)] p-[10px] ${isDark ? 'bg-[rgba(3,38,80,0.6)]' : 'bg-white'} shadow-[2px_0px_6px_rgba(0,0,0,0.12)]`} >
{/* <div className="flex justify-between">
<span>筛选</span>
<span className="flex items-center">
<span>下发时间排序</span>
</span>
</div> */}
<div className="relative flex-1 max-w-md">
<Search className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${textMuted}`} />
<input
type="text"
placeholder="搜索任务名称"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={(e) => handleKeyDown(e)}
className={`w-full pl-10 pr-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-500/20 transition-colors ${inputBg} ${cardBorder} ${textPrimary} placeholder:${textMuted}`}
/>
</div>
{/* 任务列表 */}
<Loading loading={taskLoading}>
<div className="h-[calc(100vh-200px)] overflow-y-auto pr-[10px]">
{
taskList.map((item: TaskListItem, index: number) => (
<div key={index} className={`${isDark ? 'bg-[#0062d91a] border-[#55a6fe1a] shadow-[0_3px_4px_#8b8b8b0f] dark-theme' : 'bg-white border-[#f5f5f5] shadow-[2px_0px_6px_rgba(0,0,0,0.08)] task-item-active'} p-[10px] mt-[10px] mb-[10px] rounded-[4px] border ${taskId === item.id && 'task-active'} cursor-pointer`} onClick={() => taskDataChange(item.id)}>
<div className="text-[var(--text-color)] text-[17px] mb-[5px] font-[500]">{item.taskName}</div>
<div className="text-[#7f7f7f] mb-[5px]">{format(new Date(item.taskStartDate), 'yyyy-MM-dd HH:mm:ss')}</div>
<div className="flex items-center mb-[8px]">
<img src={`/icon/${item?.taskStatusData?.icon}`} alt="" />
<span className="ml-[5px] leading-[16px]">{item?.taskStatusData?.label}</span>
</div>
<div className="pb-[10px] pt-[10px]">
<div className="border-b border-[#e8e8e8]"></div>
</div>
<div className="flex items-center justify-between">
<div className="h-[24px] item-active pl-[10px] pr-[10px] task-tab-color leading-[24px] rounded-[50px] bg-[var(--bg-color)]">
完成{item.endCount}/{item.sumAll}
</div>
<div className="pl-[20px] flex-1">
<Progress percentage={item.progress} label={false} color={item?.taskStatusData?.color} />
</div>
</div>
</div>
))
}
</div>
</Loading>
{/* 分页 */}
<div>
<Pagination
total={total}
size="small"
theme="simple"
current={pageNum}
pageSize={pageSize}
showPageSize={false}
onCurrentChange={(page) => pageIndexChange(Number(page))}
/>
</div>
</div>
</Col>
<Col span={10} className="h-full">
<div className="h-full flex flex-col p-[0_10px]">
<Loading loading={actionLoading}>
<div className="flex grid grid-cols-4 gap-[10px] pl-[10px]">
{
actionColumnList.map((item: actionColumnItem, index: number) => (
<ActionColumn key={index} icon={item.icon} columnIndex={index} columnName={item.columnName} actionCount={item.actionList.length} actionList={item.actionList} title={title} category={category} subCategory={subCategory} />
))
}
{
actionColumnList.length === 0 && <Empty/>
}
</div>
</Loading>
</div>
</Col>
</Row>
</div>
);
}
@@ -0,0 +1,114 @@
import { useTheme } from '@/contexts/ThemeContext';
import { ActionListItem, TaskListItem, taskStatusList } from '@/pages/TaskManagement';
import { Bot, ChevronRight, Download, Edit, Eye, FileText, Layers, MoreHorizontal, Play, Search, User, Users, Workflow } from 'lucide-react';
import React, { useEffect, useRef, useState } from 'react';
import { PageType } from '../Sidebar';
import { format } from 'date-fns';
import { useNavigate } from 'react-router-dom';
import { Col, Empty, Pagination, Progress, Row } from 'tdesign-react';
import { requestCrudService } from '@/service/request-axios-service';
interface ActionColumnProps {
TaskType?: string;
title: string;
category: string;
subCategory: string;
icon: string;
columnIndex: number;
columnName: string;
actionCount: number;
actionList: ActionListItem[];
}
const service = requestCrudService;
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
export const ActionColumn: React.FC<ActionColumnProps> = ({ title, category, subCategory, icon, columnName, columnIndex, actionCount, actionList }) => {
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-100';
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-400';
const inputBg = isDark ? 'bg-slate-900/50' : 'bg-white';
const navigate = useNavigate();
const [searchQuery, setSearchQuery] = useState<string>('');
// 当前页码,默认为第 1 页
const [pageNum, setPageNum] = useState<number>(1);
// 每页显示数量,默认为 10 条
const [pageSize, setPageSize] = useState<number>(10); // 每页显示 10 条
const [total, setTotal] = useState<number>(0); // 查询任务总数
const state = useRef({
pageIndex: 1,
pageSize: 10
})
/**
* 页码变化时的回调函数
* 当用户切换页码时触发,更新页码并重新获取数据
* @param value - 新的页码值
*/
const pageIndexChange = async (value: number) => {
// 更新 ref 中存储的页码值
// 更新页码状态,触发组件重新渲染
setPageNum(value);
state.current.pageIndex = value;
}
const actionRouterChange = (item: ActionListItem) => {
if (item.actionStatus !== 0) {
window.open(`https://ch1.b.uat.4.cn/web/#/action/item-aiceh/step-analysis-three?id=${item.id}`);
} else {
window.open(`https://ch1.b.uat.4.cn/web/#/action/item-aiceh/step-action-topic?id=${item.id}&isPassApply=false`);
}
}
return (
<div className={`h-[calc(100vh-100px)] p-[10px] flex-1 ${isDark? 'dark-action-bg' : `bg-white action-bg${columnIndex} shadow-[2px_0px_6px_rgba(0,0,0,0.12)]`} rounded-[4px]`}>
<div className="mb-[10px]">
<div className="flex items-center mb-[20px]">
<img src={`/icon/${icon}`} className="w-[24px] mr-[10px]" alt="" />
<span className="text[#222222] text-[18px] font-bold mr-[20px]">{columnName}</span>
<span className="text[#222222] text-[18px] font-bold mr-[20px]">{actionCount}</span>
</div>
</div>
<div className="h-[calc(100vh-200px)] overflow-y-auto pr-[10px]">
{
actionList.map((item: ActionListItem, index: number) => (
<div key={index} className={`${isDark? 'dark-action-item' : 'bg-white border border-[#f5f5f5] shadow-[0_1px_16px_#0000000f] action-item'} rounded-md px-4 py-2.5 mb-[10px] hover:boder-[var(--card-border)]`} onClick={() => actionRouterChange(item)}>
<div className="line-clamp-4 overflow-hidden mb-[10px]">
{
item.actionName
}
</div>
<div className="flex items-center mb-[10px]">
<span className={`${isDark? 'bg-[#0062d94d] text-[#aaceff]' :'bg-[#f5f5f5]'} leading-[16px] p-[5px_10px] rounded-[2px]`}>
{item.level === 1 ? '主要行动' : '次要行动'}
</span>
</div>
<div className="flex justify-between">
<span className={`${isDark? 'bg-[#0062d94d] text-[#aaceff]' :'bg-[#f5f5f5]'} leading-[16px] p-[5px_10px] rounded-[2px]`}>
{item.deptName}
</span>
<span className="border border-[var(--color-main)] text-[var(--color-main)] cursor-pointer leading-[16px] ml-[10px] p-[5px_10px] rounded-[2px]">
{
item.actionStatus === 0 ? <span>XD签收</span> : <span>开始CEH</span>
}
</span>
</div>
</div>
))
}
{
actionList.length === 0 && <Empty image={
<img
src="/icon/empty-list.png"
alt="empty"
className="w-[135px]"
/>
} />
}
</div>
</div>
);
}
@@ -0,0 +1,35 @@
import React, { useEffect, useState } from "react";
import "./css/iframe.css";
import { useTheme } from "@/contexts/ThemeContext";
import { PageType } from "@/components/Sidebar";
interface FanganTyPageProps {
title: string;
category: string;
subCategory: string;
onNavigate: (page: PageType) => void;
}
const TYURL = import.meta.env.VITE_AIPI_B2ACTOR_URL || "";
const FanganTyPage: React.FC<FanganTyPageProps> = ({ onNavigate }) => {
const { theme } = useTheme();
const isDark = theme === "dark";
const [iframeUrl, setIframeUrl] = useState("");
useEffect(() => {
initIframeUrl();
}, []);
const initIframeUrl = () => {
const url = `${TYURL}/fromB/toPage?defaultPage=iframeFaty`;
console.log({ iframeUrl: url });
setIframeUrl(url);
};
return (
<div className="iframe-container">
<iframe src={iframeUrl} className="full-size-iframe"></iframe>
</div>
);
};
export default FanganTyPage;
@@ -0,0 +1,92 @@
import { useTheme } from '@/contexts/ThemeContext';
import { TaskListItem } from '@/pages/TaskManagement';
import { ChevronRight, FileText } from 'lucide-react';
import React, { useEffect, useState } from 'react';
import { PageType } from '../Sidebar';
import { TaskCard } from './TaskCard';
import { Pagination } from 'tdesign-react';
interface KanbanColumnProps {
title: string;
count: number;
color: string;
tasks: TaskListItem[];
taskType: string;
TaskDataType?: string; // 区分来源类型
onNavigate: (page: PageType) => void;
pageIndexSearch: (taskType: string, pageIndex: number) => void;
handleStartAnalysis: (task: TaskListItem) => void
}
export const KanbanColumn: React.FC<KanbanColumnProps> = ({ title, count, color, tasks,taskType, TaskDataType = 'task' , onNavigate, pageIndexSearch, handleStartAnalysis }) => {
const { theme } = useTheme();
const isDark = theme === 'dark';
const textPrimary = isDark ? 'text-white' : 'text-gray-900';
const textMuted = isDark ? 'text-slate-500' : 'text-gray-400';
// 当前页码,默认为第 1 页
const [pageNum, setPageNum] = useState<number>(1);
// 每页显示数量,默认为 10 条
const [pageSize, setPageSize] = useState<number>(10); // 每页显示 10 条
/**
* 页码变化时的回调函数
* 当用户切换页码时触发,更新页码并重新获取数据
* @param value - 新的页码值
*/
const pageIndexChange = async (value: number) => {
// 更新 ref 中存储的页码值
// 更新页码状态,触发组件重新渲染
console.log('taskType', taskType)
setPageNum(value);
await pageIndexSearch(taskType, value);
}
/**
* 每页显示数量变化时的回调函数
* 当用户切换每页显示条数时触发,更新每页数量并重新获取数据
* @param value - 新的每页显示数量
*/
const pageSizeChange = async (value: number) => {
// 更新 ref 中存储的每页显示数量
// 更新每页显示数量状态,触发组件重新渲染
setPageSize(value);
}
return (
<div className="flex flex-col min-h-0 flex-1">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${color}`} />
<h2 className={`font-semibold text-sm ${textPrimary}`}>{title}</h2>
<span className={`px-2 py-0.5 rounded-full text-xs ${isDark ? 'bg-slate-700/50 text-slate-400' : 'bg-red-50 text-red-500'
}`}>{count}</span>
</div>
{/* <button className={`p-1 rounded transition-colors ${isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`}>
<ChevronRight className={`w-4 h-4 ${textMuted}`} />
</button> */}
<div>
<Pagination
total={count}
size="small"
theme="simple"
current={pageNum}
pageSize={pageSize}
maxPageBtn={3}
showPageSize={false}
totalContent={false}
onCurrentChange={(page) => pageIndexChange(Number(page))}
/>
</div>
</div>
<div className="flex-1 overflow-y-auto space-y-3 pr-1 scrollbar-thin">
{tasks.length === 0 ? (
<div className={`flex flex-col items-center justify-center py-8 ${textMuted}`}>
<FileText className="w-8 h-8 mb-2 opacity-50" />
<p className="text-sm">暂无任务</p>
</div>
) : (
tasks.map(task => <TaskCard key={task.id} task={task} TaskType={TaskDataType} onNavigate={onNavigate} handleStartAnalysis={handleStartAnalysis}/>)
)}
</div>
</div>
);
}
@@ -0,0 +1,401 @@
import React, { useEffect, useRef, useState } from 'react';
import {
Plus,
Search,
Eye,
Play,
Pause,
Clock,
User,
ChevronRight,
Filter,
FileText,
AlertTriangle,
CheckCircle,
XCircle,
MoreHorizontal,
Edit,
Download,
Bot,
Users,
Workflow,
X,
ArrowRight,
Target,
Layers,
} from 'lucide-react';
import { useTheme } from '@/contexts/ThemeContext';
import { PageType } from '@/components/Sidebar';
// 导入请求服务,用于与后端 API 交互
import { requestCrudService } from '@/service/request-axios-service';
import { KanbanColumn } from './KanbanColumn';
import { TaskListItem } from '@/pages/TaskManagement';
import { DateRangePicker, DateRangePickerProps, DateRangeValue, Loading } from 'tdesign-react';
import dayjs from 'dayjs';
import { useChatContext } from '@/contexts/ChatContext';
import { useNavigate } from 'react-router-dom';
interface TaskListPageProps {
title: string;
category: string;
subCategory: string;
onNavigate: (page: PageType) => void;
}
interface stateItem {
startTime: string,
endTime: string,
task: TaskListItem,
}
type TaskType = 'single' | 'consultation' | 'scene' | 'multi-agent' | 'task-search';
type TaskStatus = 'draft' | 'pending' | 'running' | 'paused' | 'blocked' | 'completed';
type ColumnType = 'unanalyzed' | 'analyzing' | 'completed';
const service = requestCrudService;
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategory, onNavigate }) => {
const { theme } = useTheme();
const isDark = theme === 'dark';
const router = useNavigate();
// 主题颜色变量
const cardBg = isDark ? 'bg-slate-800/30' : 'bg-white';
const cardBorder = isDark ? 'border-blue-800/30' : 'border-red-100';
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-400';
const inputBg = isDark ? 'bg-slate-900/50' : 'bg-white';
const { setStartConfig } = useChatContext();
const [searchQuery, setSearchQuery] = useState('');
const [modeFilter, setModeFilter] = useState<string>('all');
const [showAnalysisModal, setShowAnalysisModal] = useState(false);
const [selectedTaskForAnalysis, setSelectedTaskForAnalysis] = useState<TaskListItem | null>(null);
const [unanalyzedTotal, setUnanalyzedTotal] = useState<number>(0); // 未分析
const [runningTotal, setRunningTotal] = useState<number>(0); // 分析中
const [completedTotal, setCompletedTotal] = useState<number>(0); // 已完成
const [unanalyzedList, setUnanalyzedList] = useState<TaskListItem[]>([]); // 未分析
const [runningList, setRunningList] = useState<TaskListItem[]>([]); // 分析中
const [completedList, setCompletedList] = useState<TaskListItem[]>([]); // 已完成
const [unanalyzedLoading, setUnanalyzedLoading] = useState<boolean>(false); // 未分析
const [runningLoading, setRunningLoading] = useState<boolean>(false); // 分析中
const [completedLoading, setCompletedLoading] = useState<boolean>(false); // 已完成
const [presets] = useState<DateRangePickerProps['presets']>({
最近7天: [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()],
最近3天: [dayjs().subtract(2, 'day').toDate(), dayjs().toDate()],
今天: [dayjs().toDate(), dayjs().toDate()],
});
const [range, setRange] = useState<DateRangeValue>([]);
const state = useRef<stateItem>({
startTime: '',
endTime: '',
task: {} as TaskListItem,
})
// 时间日期选择器查询
const setRangeChange = async (val) => {
setRange(val);
console.log('时间日期', val);
state.current.startTime = '';
state.current.endTime = '';
if (val.length > 0) {
state.current.startTime = val[0];
state.current.endTime = val[1];
}
await taskDataChange();
}
// 处理开始分析按钮点击
const handleStartAnalysis = (task: TaskListItem) => {
setSelectedTaskForAnalysis(task);
state.current.task = task;
setShowAnalysisModal(true);
};
// 处理分析模式选择
const handleSelectAnalysisMode = (mode: TaskType) => {
setShowAnalysisModal(false);
console.log('mode', mode);
if (mode === 'single') {
onNavigate('task-single');
} else if (mode === 'scene') {
onNavigate('task-scene');
} else if (mode === 'consultation') {
console.log('state.current.task', state.current.task)
const sceneItem = JSON.parse(state.current.task.scene);
if (sceneItem.taskJson) {
sceneItem.taskJson = JSON.parse(sceneItem.taskJson);
setStartConfig({
...sceneItem.taskJson,
llm_preset_id: 'BAILING',
consistency_mode: 'soft',
phase_config: { diverge_rounds: 3 },
task_context: state.current.task.overview
});
router('/qa-center/mope-chat')
}
console.log('sceneItem', sceneItem);
// onNavigate('task-consult-create');
// window.open(`http://ch1.b.uat.4.cn/mopeweb/#/mope?taskOverview=${state.current.task.overview}`)
} else if (mode === 'multi-agent') {
onNavigate('multi-agent-qa');
} else if (mode === 'task-search') {
window.open(`https://ch1.b.uat.4.cn/web/#/task/item-aicoh/step-info-search?id=${state.current.task.id}`)
}
};
// 处理卡片点击
const taskListSearch = async (taskType: string, pageNum: number = 1, pageSize: number = 10) => {
let taskStatus: number[] = []
if (taskType === 'unanalyzed') { // 未分析
taskStatus = [0, 10];
setUnanalyzedLoading(true);
} else if (taskType === 'running') { // 分析中
taskStatus = [20];
setRunningLoading(true);
} else if (taskType === 'completed') { // 已完成
taskStatus = [25, 30, 40, 60, 70, 80, 90, 100, 110];
setCompletedLoading(true);
}
const param = {
content: searchQuery,
pageNum: pageNum,
pageSize: pageSize,
taskStatus: taskStatus,
startTime: state.current.startTime,
endTime: state.current.endTime,
taskDirection: ''
}
const res = await service.post(`${zncmUrl}/three/cop-three-tasks`, param);
console.log('任务列表', res);
const result = res.data;
result.records.forEach((item: TaskListItem) => {
item.column = taskType;
if (!item.taskLevel) item.taskLevel = 1;
})
if (taskType === 'unanalyzed') {
setUnanalyzedLoading(false);
setUnanalyzedTotal(result.total);
setUnanalyzedList(result.records);
} else if (taskType === 'running') {
setRunningLoading(false);
setRunningTotal(result.total);
setRunningList(result.records);
} else if (taskType === 'completed') {
setCompletedLoading(false);
setCompletedTotal(result.total);
setCompletedList(result.records);
}
}
/**
* 页码变化时的回调函数
* 当用户切换页码时触发,更新页码并重新获取数据
* @param value - 新的页码值
*/
const pageIndexChange = async (type: string, pageIndex: number) => {
// 更新 ref 中存储的页码值
// 更新页码状态,触发组件重新渲染
await taskListSearch(type, pageIndex);
}
const taskDataChange = async (pageNum: number = 1, pageSize: number = 10) => {
taskListSearch('unanalyzed', pageNum, pageSize);
taskListSearch('running', pageNum, pageSize);
taskListSearch('completed', pageNum, pageSize);
}
// 回车处理
const handleKeyDown = async (e) => {
if ((e.metaKey || e.ctrlKey || e.shiftKey) && e.key === "Enter") {
e.preventDefault();
} else if (e.keyCode === 13) {
e.preventDefault();
await taskDataChange();
} else if (e.key === "/") {
e.preventDefault();
}
};
useEffect(() => {
taskDataChange();
}, []);
// 渲染看板列
return (
<div className="h-full flex flex-col">
{/* 顶部工具栏 */}
<div className={`rounded-xl p-4 flex-shrink-0 mb-4 ${cardBg} border ${cardBorder}`}>
<div className="flex items-center justify-between mb-4">
<div>
<div className="flex items-center gap-2 mb-1">
<span className={`px-2 py-0.5 rounded text-xs ${isDark ? 'bg-blue-500/20 text-blue-400' : 'bg-red-100 text-red-600'}`}>
{category}
</span>
<span className={`text-xs ${textMuted}`}>/</span>
<span className={`px-2 py-0.5 rounded text-xs ${isDark ? 'bg-cyan-500/20 text-cyan-400' : 'bg-red-100 text-red-600'}`}>
{subCategory}
</span>
</div>
<h1 className={`text-xl font-bold ${textPrimary}`}>任务中心</h1>
</div>
<button
onClick={() => onNavigate('task-create')}
className={`flex items-center gap-2 px-4 py-2 text-white rounded-lg text-sm font-medium transition-colors ${isDark
? 'bg-blue-600 hover:bg-blue-500'
: 'bg-red-600 hover:bg-red-500 shadow-md'
}`}
>
<Plus className="w-4 h-4" />
新建任务
</button>
</div>
{/* 搜索和筛选 */}
<div className="flex items-center gap-4">
<div className="relative flex-1 max-w-md">
<Search className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${textMuted}`} />
<input
type="text"
placeholder="搜索任务名称"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={(e) => handleKeyDown(e)}
className={`w-full pl-10 pr-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-500/20 transition-colors ${inputBg} ${cardBorder} ${textPrimary} placeholder:${textMuted}`}
/>
</div>
{/* <select
value={modeFilter}
onChange={(e) => setModeFilter(e.target.value)}
className={`px-4 py-2 border rounded-lg text-sm focus:outline-none cursor-pointer ${inputBg} ${cardBorder} ${textSecondary}`}
>
<option value="all">全部模式</option>
<option value="single">单智能体</option>
<option value="multi-agent">多智能体协同</option>
<option value="consultation">多智能体会商</option>
<option value="scene">场景执行</option>
</select> */}
{/* <button className={`flex items-center gap-2 px-4 py-2 border rounded-lg text-sm transition-colors ${inputBg} ${cardBorder} ${textSecondary}`}>
<Clock className="w-4 h-4" />
时间范围
</button> */}
<DateRangePicker value={range} presets={presets} onChange={(val) => setRangeChange(val)} enableTimePicker={true} clearable />
</div>
</div>
{/* 统计概览栏 */}
<div className="grid grid-cols-4 gap-4 mb-4 flex-shrink-0">
<button
onClick={() => { setModeFilter('all'); }}
className={`p-4 rounded-xl border transition-all ${cardBg} ${cardBorder} hover:border-red-400`}
>
<p className={`text-2xl font-bold ${textPrimary}`}>{unanalyzedTotal + runningTotal + completedTotal}</p>
<p className={`text-sm ${textSecondary}`}>全部任务</p>
</button>
<button
className={`p-4 rounded-xl border transition-all ${cardBg} ${cardBorder} hover:border-red-400`}
>
<p className={`text-2xl font-bold ${textPrimary}`}>{unanalyzedTotal}</p>
<p className={`text-sm ${textSecondary}`}>未分析</p>
</button>
<button
className={`p-4 rounded-xl border transition-all ${cardBg} ${cardBorder} hover:border-red-400`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-blue-400' : 'text-red-600'}`}>{runningTotal}</p>
<p className={`text-sm ${textSecondary}`}>分析中</p>
</button>
<button
className={`p-4 rounded-xl border transition-all ${cardBg} ${cardBorder} hover:border-red-400`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-green-400' : 'text-green-600'}`}>{completedTotal}</p>
<p className={`text-sm ${textSecondary}`}>已完成</p>
</button>
</div>
{/* 看板区 */}
<div className="flex-1 grid grid-cols-3 gap-4 min-h-0 overflow-hidden">
<Loading loading={unanalyzedLoading}>
<KanbanColumn title="未分析" taskType={"unanalyzed"} TaskDataType={'sentiment'} count={unanalyzedTotal} color={isDark ? 'bg-yellow-400' : 'bg-yellow-400'} tasks={unanalyzedList} onNavigate={onNavigate} pageIndexSearch={pageIndexChange} handleStartAnalysis={handleStartAnalysis} />
</Loading>
<Loading loading={runningLoading}>
<KanbanColumn title="分析中" taskType="running" TaskDataType={'sentiment'} count={runningTotal} color={isDark ? 'bg-blue-400' : 'bg-red-400'} tasks={runningList} onNavigate={onNavigate} pageIndexSearch={pageIndexChange} handleStartAnalysis={handleStartAnalysis} />
</Loading>
<Loading loading={completedLoading}>
<KanbanColumn title="已完成" taskType="completed" TaskDataType={'sentiment'} count={completedTotal} color={isDark ? 'bg-green-400' : 'bg-green-400'} tasks={completedList} onNavigate={onNavigate} pageIndexSearch={pageIndexChange} handleStartAnalysis={handleStartAnalysis} />
</Loading>
</div>
{/* 选择分析模式弹窗 */}
{showAnalysisModal && selectedTaskForAnalysis && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50" onClick={() => setShowAnalysisModal(false)}>
<div
className={`w-full max-w-lg rounded-2xl p-6 ${cardBg} border ${cardBorder} shadow-xl`}
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between mb-6">
<div>
<h2 className={`text-xl font-bold ${textPrimary}`}>选择分析模式</h2>
<p className={`text-sm ${textMuted} mt-1`}>为任务「{selectedTaskForAnalysis.taskName}」选择分析方式</p>
</div>
<button
onClick={() => setShowAnalysisModal(false)}
className={`p-2 rounded-lg hover:bg-slate-700/50 ${textSecondary}`}
>
<X className="w-5 h-5" />
</button>
</div>
<div className="space-y-3">
<button
onClick={() => handleSelectAnalysisMode('task-search')}
className={`w-full p-4 rounded-xl border transition-all duration-200 text-left hover:shadow-md ${isDark
? 'bg-slate-700/50 border-blue-800/30 hover:border-green-500/50'
: 'bg-white border-red-100 hover:border-red-300 hover:shadow-md'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${isDark ? 'bg-green-500/20' : 'bg-green-100'
}`}>
<Layers className={`w-6 h-6 ${isDark ? 'text-green-400' : 'text-green-600'}`} />
</div>
<div className="flex-1">
<h3 className={`font-semibold ${textPrimary}`}>查看任务详情</h3>
<p className={`text-sm ${textSecondary}`}>进入b1系统查看任务详情</p>
</div>
<ArrowRight className={`w-5 h-5 ${textMuted}`} />
</div>
</button>
{/* 多智能体会商 */}
<button
onClick={() => handleSelectAnalysisMode('consultation')}
className={`w-full p-4 rounded-xl border transition-all duration-200 text-left hover:shadow-md ${isDark
? 'bg-slate-700/50 border-blue-800/30 hover:border-orange-500/50'
: 'bg-white border-red-100 hover:border-red-300 hover:shadow-md'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${isDark ? 'bg-orange-500/20' : 'bg-red-100'
}`}>
<Users className={`w-6 h-6 ${isDark ? 'text-orange-400' : 'text-red-500'}`} />
</div>
<div className="flex-1">
<h3 className={`font-semibold ${textPrimary}`}>多智能体会商</h3>
<p className={`text-sm ${textSecondary}`}>多个AI智能体从不同视角开展讨论并形成共识</p>
</div>
<ArrowRight className={`w-5 h-5 ${textMuted}`} />
</div>
</button>
</div>
</div>
</div>
)}
</div>
);
};
export default SentimentAnalyzes;
@@ -0,0 +1,678 @@
import React, { useEffect, useRef, useState } from 'react';
import {
Plus,
Search,
Eye,
Play,
Pause,
Clock,
User,
ChevronRight,
Filter,
FileText,
AlertTriangle,
CheckCircle,
XCircle,
MoreHorizontal,
Edit,
Download,
Bot,
Users,
Workflow,
X,
ArrowRight,
Target,
Layers,
} from 'lucide-react';
import { useTheme } from '@/contexts/ThemeContext';
import { PageType } from '@/components/Sidebar';
// 导入请求服务,用于与后端 API 交互
import { requestCrudService } from '@/service/request-axios-service';
import { KanbanColumn } from './KanbanColumn';
import { TaskListItem } from '@/pages/TaskManagement';
import { DateRangePicker, DateRangePickerProps, DateRangeValue, Loading } from 'tdesign-react';
import { useNavigate } from 'react-router-dom';
import dayjs from 'dayjs';
import { useChatContext } from '@/contexts/ChatContext';
interface TaskListPageProps {
title: string;
category: string;
subCategory: string;
onNavigate: (page: PageType) => void;
}
type TaskType = 'single' | 'consultation' | 'scene' | 'multi-agent' | 'task-search';
type TaskStatus = 'draft' | 'pending' | 'running' | 'paused' | 'blocked' | 'completed';
type ColumnType = 'unanalyzed' | 'analyzing' | 'completed';
interface Task {
id: string;
name: string;
type: TaskType;
status: TaskStatus;
agent?: string;
scene?: string;
participants?: string[];
currentPhase?: string;
currentRound?: number;
summary?: string;
creator: string;
createTime: string;
updateTime: string;
column: ColumnType;
}
interface stateItem {
startTime: string,
endTime: string,
task: TaskListItem,
}
const service = requestCrudService;
const zncmUrl = import.meta.env.VITE_API_BASE_URL || '';
const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategory, onNavigate }) => {
const { theme } = useTheme();
const { setStartConfig } = useChatContext();
const router = useNavigate();
const isDark = theme === 'dark';
// 主题颜色变量
const cardBg = isDark ? 'bg-slate-800/30' : 'bg-white';
const cardBorder = isDark ? 'border-blue-800/30' : 'border-red-100';
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-400';
const inputBg = isDark ? 'bg-slate-900/50' : 'bg-white';
// 生成模拟数据
const [tasks] = useState<Task[]>([
// 未分析列
{
id: '2026-0320-001',
name: '端午节品牌传播方案',
type: 'scene',
scene: '宣传策略生成',
status: 'draft',
column: 'unanalyzed',
summary: '针对端午节品牌活动制定完整传播策略方案',
creator: '李策划',
createTime: '2026-03-20 10:00',
updateTime: '2026-03-20 10:00',
},
{
id: '2026-0320-002',
name: '新品上市用户调研',
type: 'single',
agent: 'AudienceAgent',
status: 'pending',
column: 'unanalyzed',
summary: '对新品目标用户进行画像分析',
creator: '王运营',
createTime: '2026-03-20 09:30',
updateTime: '2026-03-20 09:30',
},
// 分析中列
{
id: '2026-0319-001',
name: '五四青年节宣传活动策略',
type: 'scene',
scene: '宣传策略生成',
status: 'running',
currentPhase: '行为分析',
column: 'analyzing',
summary: '正在按场景模板执行目标、受众、行为、叙事等分析...',
creator: '张策略',
createTime: '2026-03-19 10:30',
updateTime: '2026-03-20 09:45',
},
{
id: '2026-0319-002',
name: '青年传播方向会商分析',
type: 'consultation',
participants: ['GoalAgent', 'AudienceAgent', 'NarrativeAgent'],
status: 'running',
currentRound: 2,
column: 'analyzing',
summary: '围绕宣传方向、叙事方式和受众切入点开展多视角会商...',
creator: '王策划',
createTime: '2026-03-19 14:00',
updateTime: '2026-03-20 09:00',
},
{
id: '2026-0318-001',
name: '年度品牌升级方案推演',
type: 'scene',
scene: '宣传方案推演',
status: 'paused',
currentPhase: '叙事分析',
column: 'analyzing',
summary: '任务已暂停,等待人工介入确认叙事方向',
creator: '王策划',
createTime: '2026-03-18 14:00',
updateTime: '2026-03-20 08:30',
},
// 已完成列
{
id: '2026-0318-002',
name: '核心受众画像分析',
type: 'single',
agent: 'AudienceAgent',
status: 'completed',
column: 'completed',
summary: '已完成目标受众识别、关切提炼和阻力分析',
creator: '赵运营',
createTime: '2026-03-18 09:00',
updateTime: '2026-03-18 16:30',
},
{
id: '2026-0319-003',
name: '五四青年节宣传策略',
type: 'scene',
scene: '宣传策略生成',
status: 'completed',
column: 'completed',
summary: '已完成宣传策略方案生成',
creator: '张策略',
createTime: '2026-03-19 10:00',
updateTime: '2026-03-19 18:00',
},
{
id: '2026-0319-004',
name: '青年品牌传播路径会商',
type: 'consultation',
participants: ['GoalAgent', 'NarrativeAgent', 'RiskAgent'],
status: 'completed',
column: 'completed',
summary: '已完成多智能体会商,形成结论与分歧报告',
creator: '王策划',
createTime: '2026-03-19 14:30',
updateTime: '2026-03-19 17:00',
},
]);
const [searchQuery, setSearchQuery] = useState('');
const [modeFilter, setModeFilter] = useState<string>('all');
const [showAnalysisModal, setShowAnalysisModal] = useState(false);
const [selectedTaskForAnalysis, setSelectedTaskForAnalysis] = useState<TaskListItem | null>(null);
const [unanalyzedTotal, setUnanalyzedTotal] = useState<number>(0); // 未分析
const [runningTotal, setRunningTotal] = useState<number>(0); // 分析中
const [completedTotal, setCompletedTotal] = useState<number>(0); // 已完成
const [unanalyzedList, setUnanalyzedList] = useState<TaskListItem[]>([]); // 未分析
const [runningList, setRunningList] = useState<TaskListItem[]>([]); // 分析中
const [completedList, setCompletedList] = useState<TaskListItem[]>([]); // 已完成
const [unanalyzedLoading, setUnanalyzedLoading] = useState<boolean>(false); // 未分析
const [runningLoading, setRunningLoading] = useState<boolean>(false); // 分析中
const [completedLoading, setCompletedLoading] = useState<boolean>(false); // 已完成
const [presets] = useState<DateRangePickerProps['presets']>({
最近7天: [dayjs().subtract(6, 'day').toDate(), dayjs().toDate()],
最近3天: [dayjs().subtract(2, 'day').toDate(), dayjs().toDate()],
今天: [dayjs().toDate(), dayjs().toDate()],
});
const [range, setRange] = useState<DateRangeValue>([]);
const state = useRef<stateItem>({
startTime: '',
endTime: '',
task: {} as TaskListItem,
})
// 时间日期选择器查询
const setRangeChange = async (val) => {
setRange(val);
console.log('时间日期', val);
state.current.startTime = '';
state.current.endTime = '';
if (val.length > 0) {
state.current.startTime = val[0];
state.current.endTime = val[1];
}
await taskDataChange();
}
// 获取任务类型配置
const getTypeConfig = (type: TaskType) => {
if (isDark) {
switch (type) {
case 'single':
return { label: '单智能体', color: 'bg-cyan-500/20 text-cyan-400', icon: Bot };
case 'scene':
return { label: '场景执行', color: 'bg-purple-500/20 text-purple-400', icon: Workflow };
case 'consultation':
return { label: '多智能体会商', color: 'bg-orange-500/20 text-orange-400', icon: Users };
case 'multi-agent':
return { label: '多智能体协同', color: 'bg-green-500/20 text-green-400', icon: Layers };
}
} else {
switch (type) {
case 'single':
return { label: '单智能体', color: 'bg-blue-100 text-blue-600', icon: Bot };
case 'scene':
return { label: '场景执行', color: 'bg-purple-100 text-purple-600', icon: Workflow };
case 'consultation':
return { label: '多智能体会商', color: 'bg-orange-100 text-orange-600', icon: Users };
case 'multi-agent':
return { label: '多智能体协同', color: 'bg-green-100 text-green-600', icon: Layers };
}
}
};
// 获取状态配置
const getStatusConfig = (status: TaskStatus, column: ColumnType) => {
if (column === 'unanalyzed') {
switch (status) {
case 'draft':
return { label: '草稿', color: isDark ? 'bg-slate-500/20 text-slate-400' : 'bg-gray-100 text-gray-600' };
case 'pending':
return { label: '待启动', color: isDark ? 'bg-slate-500/20 text-slate-400' : 'bg-gray-100 text-gray-600' };
default:
return { label: '待配置', color: isDark ? 'bg-yellow-500/20 text-yellow-400' : 'bg-yellow-100 text-yellow-600' };
}
} else if (column === 'analyzing') {
switch (status) {
case 'running':
return { label: '运行中', color: isDark ? 'bg-blue-500/20 text-blue-400' : 'bg-red-100 text-red-600' };
case 'paused':
return { label: '已暂停', color: isDark ? 'bg-yellow-500/20 text-yellow-400' : 'bg-yellow-100 text-yellow-600' };
case 'blocked':
return { label: '阻塞中', color: isDark ? 'bg-red-500/20 text-red-400' : 'bg-orange-100 text-orange-600' };
default:
return { label: '待处理', color: isDark ? 'bg-purple-500/20 text-purple-400' : 'bg-purple-100 text-purple-600' };
}
} else {
return { label: '已完成', color: isDark ? 'bg-green-500/20 text-green-400' : 'bg-green-100 text-green-600' };
}
};
// 筛选任务
const filteredTasks = tasks.filter(task => {
const matchesSearch = task.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
task.id.toLowerCase().includes(searchQuery.toLowerCase());
const matchesMode = modeFilter === 'all' || task.type === modeFilter;
return matchesSearch && matchesMode;
});
const unanalyzedTasks = filteredTasks.filter(t => t.column === 'unanalyzed');
const analyzingTasks = filteredTasks.filter(t => t.column === 'analyzing');
const completedTasks = filteredTasks.filter(t => t.column === 'completed');
// 统计数据
const stats = {
total: tasks.length,
unanalyzed: unanalyzedTasks.length,
analyzing: analyzingTasks.length,
completed: completedTasks.length,
};
// 处理开始分析按钮点击
const handleStartAnalysis = (task: TaskListItem) => {
setSelectedTaskForAnalysis(task);
state.current.task = task;
setShowAnalysisModal(true);
};
// 处理分析模式选择
const handleSelectAnalysisMode = (mode: TaskType) => {
setShowAnalysisModal(false);
console.log('mode', mode);
if (mode === 'single') {
onNavigate('task-single');
} else if (mode === 'scene') {
onNavigate('task-scene');
} else if (mode === 'consultation') {
console.log('state.current.task', state.current.task)
const sceneItem = JSON.parse(state.current.task.scene);
if (sceneItem.taskJson) {
sceneItem.taskJson = JSON.parse(sceneItem.taskJson);
setStartConfig({
...sceneItem.taskJson,
llm_preset_id: 'BAILING',
consistency_mode: 'soft',
phase_config: {diverge_rounds: 3},
task_context: state.current.task.overview
});
router('/qa-center/mope-chat')
}
console.log('sceneItem', sceneItem);
// onNavigate('task-consult-create');
// window.open(`http://ch1.b.uat.4.cn/mopeweb/#/mope?taskOverview=${state.current.task.overview}`)
} else if (mode === 'multi-agent') {
onNavigate('multi-agent-qa');
} else if (mode === 'task-search') {
window.open(`https://ch1.b.uat.4.cn/web/#/task/item-aicoh/step-info-search?id=${state.current.task.id}`)
}
};
// 处理卡片点击
const handleTaskClick = (task: Task) => {
if (task.column === 'completed') {
if (task.type === 'consultation') {
onNavigate('task-consult-result');
} else {
onNavigate('strategy-result');
}
} else if (task.column === 'analyzing') {
if (task.type === 'scene') {
onNavigate('task-scene');
} else if (task.type === 'consultation') {
onNavigate('task-consult');
} else {
onNavigate('task-single');
}
}
};
const taskListSearch = async (taskType: string, pageNum: number = 1, pageSize: number = 10) => {
let taskStatus: number[] = []
if (taskType === 'unanalyzed') { // 未分析
taskStatus = [0, 10];
setUnanalyzedLoading(true);
} else if (taskType === 'running') { // 分析中
taskStatus = [20];
setRunningLoading(true);
} else if (taskType === 'completed') { // 已完成
taskStatus = [25, 30, 40, 60, 70, 80, 90, 100, 110];
setCompletedLoading(true);
}
const param = {
content: searchQuery,
pageNum: pageNum,
pageSize: pageSize,
taskStatus: taskStatus,
startTime: state.current.startTime,
endTime: state.current.endTime,
taskDirection: ''
}
const res = await service.post(`${zncmUrl}/task/cop-tasks`, param);
console.log('任务列表', res);
const result = res.data;
result.records.forEach((item: TaskListItem) => {
item.column = taskType;
if (!item.taskLevel) item.taskLevel = 1;
})
if (taskType === 'unanalyzed') {
setUnanalyzedLoading(false);
setUnanalyzedTotal(result.total);
setUnanalyzedList(result.records);
} else if (taskType === 'running') {
setRunningLoading(false);
setRunningTotal(result.total);
setRunningList(result.records);
} else if (taskType === 'completed') {
setCompletedLoading(false);
setCompletedTotal(result.total);
setCompletedList(result.records);
}
}
/**
* 页码变化时的回调函数
* 当用户切换页码时触发,更新页码并重新获取数据
* @param value - 新的页码值
*/
const pageIndexChange = async (type: string, pageIndex: number) => {
// 更新 ref 中存储的页码值
// 更新页码状态,触发组件重新渲染
await taskListSearch(type, pageIndex);
}
const taskDataChange = async (pageNum: number = 1, pageSize: number = 10) => {
taskListSearch('unanalyzed', pageNum, pageSize);
taskListSearch('running', pageNum, pageSize);
taskListSearch('completed', pageNum, pageSize);
}
// 回车处理
const handleKeyDown = async (e) => {
if ((e.metaKey || e.ctrlKey || e.shiftKey) && e.key === "Enter") {
e.preventDefault();
} else if (e.keyCode === 13) {
e.preventDefault();
await taskDataChange();
} else if (e.key === "/") {
e.preventDefault();
}
};
useEffect(() => {
taskDataChange();
}, []);
// 渲染看板列
return (
<div className="h-full flex flex-col">
{/* 顶部工具栏 */}
<div className={`rounded-xl p-4 flex-shrink-0 mb-4 ${cardBg} border ${cardBorder}`}>
<div className="flex items-center justify-between mb-4">
<div>
<div className="flex items-center gap-2 mb-1">
<span className={`px-2 py-0.5 rounded text-xs ${isDark ? 'bg-blue-500/20 text-blue-400' : 'bg-red-100 text-red-600'}`}>
{category}
</span>
<span className={`text-xs ${textMuted}`}>/</span>
<span className={`px-2 py-0.5 rounded text-xs ${isDark ? 'bg-cyan-500/20 text-cyan-400' : 'bg-red-100 text-red-600'}`}>
{subCategory}
</span>
</div>
<h1 className={`text-xl font-bold ${textPrimary}`}>任务中心</h1>
</div>
<button
onClick={() => onNavigate('task-create')}
className={`flex items-center gap-2 px-4 py-2 text-white rounded-lg text-sm font-medium transition-colors ${isDark
? 'bg-blue-600 hover:bg-blue-500'
: 'bg-red-600 hover:bg-red-500 shadow-md'
}`}
>
<Plus className="w-4 h-4" />
新建任务
</button>
</div>
{/* 搜索和筛选 */}
<div className="flex items-center gap-4">
<div className="relative flex-1 max-w-md">
<Search className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${textMuted}`} />
<input
type="text"
placeholder="搜索任务名称"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={(e) => handleKeyDown(e)}
className={`w-full pl-10 pr-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-500/20 transition-colors ${inputBg} ${cardBorder} ${textPrimary} placeholder:${textMuted}`}
/>
</div>
{/* <select
value={modeFilter}
onChange={(e) => setModeFilter(e.target.value)}
className={`px-4 py-2 border rounded-lg text-sm focus:outline-none cursor-pointer ${inputBg} ${cardBorder} ${textSecondary}`}
>
<option value="all">全部模式</option>
<option value="single">单智能体</option>
<option value="multi-agent">多智能体协同</option>
<option value="consultation">多智能体会商</option>
<option value="scene">场景执行</option>
</select> */}
{/* <button className={`flex items-center gap-2 px-4 py-2 border rounded-lg text-sm transition-colors ${inputBg} ${cardBorder} ${textSecondary}`}>
<Clock className="w-4 h-4" />
时间范围
</button> */}
<DateRangePicker value={range} presets={presets} onChange={(val) => setRangeChange(val)} enableTimePicker={true} clearable />
</div>
</div>
{/* 统计概览栏 */}
<div className="grid grid-cols-4 gap-4 mb-4 flex-shrink-0">
<button
onClick={() => { setModeFilter('all'); }}
className={`p-4 rounded-xl border transition-all ${cardBg} ${cardBorder} hover:border-red-400`}
>
<p className={`text-2xl font-bold ${textPrimary}`}>{unanalyzedTotal + runningTotal + completedTotal}</p>
<p className={`text-sm ${textSecondary}`}>全部任务</p>
</button>
<button
className={`p-4 rounded-xl border transition-all ${cardBg} ${cardBorder} hover:border-red-400`}
>
<p className={`text-2xl font-bold ${textPrimary}`}>{unanalyzedTotal}</p>
<p className={`text-sm ${textSecondary}`}>未分析</p>
</button>
<button
className={`p-4 rounded-xl border transition-all ${cardBg} ${cardBorder} hover:border-red-400`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-blue-400' : 'text-red-600'}`}>{runningTotal}</p>
<p className={`text-sm ${textSecondary}`}>分析中</p>
</button>
<button
className={`p-4 rounded-xl border transition-all ${cardBg} ${cardBorder} hover:border-red-400`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-green-400' : 'text-green-600'}`}>{completedTotal}</p>
<p className={`text-sm ${textSecondary}`}>已完成</p>
</button>
</div>
{/* 看板区 */}
<div className="flex-1 grid grid-cols-3 gap-4 min-h-0 overflow-hidden">
<Loading loading={unanalyzedLoading}>
<KanbanColumn title="未分析" taskType={"unanalyzed"} count={unanalyzedTotal} color={isDark ? 'bg-yellow-400' : 'bg-yellow-400'} tasks={unanalyzedList} onNavigate={onNavigate} pageIndexSearch={pageIndexChange} handleStartAnalysis={handleStartAnalysis}/>
</Loading>
<Loading loading={runningLoading}>
<KanbanColumn title="分析中" taskType="running" count={runningTotal} color={isDark ? 'bg-blue-400' : 'bg-red-400'} tasks={runningList} onNavigate={onNavigate} pageIndexSearch={pageIndexChange} handleStartAnalysis={handleStartAnalysis}/>
</Loading>
<Loading loading={completedLoading}>
<KanbanColumn title="已完成" taskType="completed" count={completedTotal} color={isDark ? 'bg-green-400' : 'bg-green-400'} tasks={completedList} onNavigate={onNavigate} pageIndexSearch={pageIndexChange} handleStartAnalysis={handleStartAnalysis}/>
</Loading>
</div>
{/* 选择分析模式弹窗 */}
{showAnalysisModal && selectedTaskForAnalysis && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50" onClick={() => setShowAnalysisModal(false)}>
<div
className={`w-full max-w-lg rounded-2xl p-6 ${cardBg} border ${cardBorder} shadow-xl`}
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between mb-6">
<div>
<h2 className={`text-xl font-bold ${textPrimary}`}>选择分析模式</h2>
<p className={`text-sm ${textMuted} mt-1`}>为任务「{selectedTaskForAnalysis.taskName}」选择分析方式</p>
</div>
<button
onClick={() => setShowAnalysisModal(false)}
className={`p-2 rounded-lg hover:bg-slate-700/50 ${textSecondary}`}
>
<X className="w-5 h-5" />
</button>
</div>
<div className="space-y-3">
{/* 单智能体分析 */}
{/* <button
onClick={() => handleSelectAnalysisMode('single')}
className={`w-full p-4 rounded-xl border transition-all duration-200 text-left hover:shadow-md ${isDark
? 'bg-slate-700/50 border-blue-800/30 hover:border-cyan-500/50'
: 'bg-white border-red-100 hover:border-red-300 hover:shadow-md'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${isDark ? 'bg-cyan-500/20' : 'bg-red-100'
}`}>
<Bot className={`w-6 h-6 ${isDark ? 'text-cyan-400' : 'text-red-500'}`} />
</div>
<div className="flex-1">
<h3 className={`font-semibold ${textPrimary}`}>单智能体分析</h3>
<p className={`text-sm ${textSecondary}`}>选择一个AI智能体完成单一维度的专项分析</p>
</div>
<ArrowRight className={`w-5 h-5 ${textMuted}`} />
</div>
</button> */}
{/* 多智能体协同分析 */}
{/* <button
onClick={() => handleSelectAnalysisMode('multi-agent')}
className={`w-full p-4 rounded-xl border transition-all duration-200 text-left hover:shadow-md ${isDark
? 'bg-slate-700/50 border-blue-800/30 hover:border-green-500/50'
: 'bg-white border-red-100 hover:border-red-300 hover:shadow-md'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${isDark ? 'bg-green-500/20' : 'bg-green-100'
}`}>
<Layers className={`w-6 h-6 ${isDark ? 'text-green-400' : 'text-green-600'}`} />
</div>
<div className="flex-1">
<h3 className={`font-semibold ${textPrimary}`}>多智能体协同分析</h3>
<p className={`text-sm ${textSecondary}`}>多个AI智能体并行分析,输出各自的判断和结论</p>
</div>
<ArrowRight className={`w-5 h-5 ${textMuted}`} />
</div>
</button> */}
<button
onClick={() => handleSelectAnalysisMode('task-search')}
className={`w-full p-4 rounded-xl border transition-all duration-200 text-left hover:shadow-md ${isDark
? 'bg-slate-700/50 border-blue-800/30 hover:border-green-500/50'
: 'bg-white border-red-100 hover:border-red-300 hover:shadow-md'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${isDark ? 'bg-green-500/20' : 'bg-green-100'
}`}>
<Layers className={`w-6 h-6 ${isDark ? 'text-green-400' : 'text-green-600'}`} />
</div>
<div className="flex-1">
<h3 className={`font-semibold ${textPrimary}`}>查看任务详情</h3>
<p className={`text-sm ${textSecondary}`}>进入b1系统查看任务详情</p>
</div>
<ArrowRight className={`w-5 h-5 ${textMuted}`} />
</div>
</button>
{/* 多智能体会商 */}
<button
onClick={() => handleSelectAnalysisMode('consultation')}
className={`w-full p-4 rounded-xl border transition-all duration-200 text-left hover:shadow-md ${isDark
? 'bg-slate-700/50 border-blue-800/30 hover:border-orange-500/50'
: 'bg-white border-red-100 hover:border-red-300 hover:shadow-md'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${isDark ? 'bg-orange-500/20' : 'bg-red-100'
}`}>
<Users className={`w-6 h-6 ${isDark ? 'text-orange-400' : 'text-red-500'}`} />
</div>
<div className="flex-1">
<h3 className={`font-semibold ${textPrimary}`}>多智能体会商</h3>
<p className={`text-sm ${textSecondary}`}>多个AI智能体从不同视角开展讨论并形成共识</p>
</div>
<ArrowRight className={`w-5 h-5 ${textMuted}`} />
</div>
</button>
{/* 场景执行 */}
{/* <button
onClick={() => handleSelectAnalysisMode('scene')}
className={`w-full p-4 rounded-xl border transition-all duration-200 text-left hover:shadow-md ${isDark
? 'bg-slate-700/50 border-blue-800/30 hover:border-purple-500/50'
: 'bg-white border-red-100 hover:border-red-300 hover:shadow-md'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${isDark ? 'bg-purple-500/20' : 'bg-red-100'
}`}>
<Workflow className={`w-6 h-6 ${isDark ? 'text-purple-400' : 'text-red-500'}`} />
</div>
<div className="flex-1">
<h3 className={`font-semibold ${textPrimary}`}>场景执行</h3>
<p className={`text-sm ${textSecondary}`}>按照预设场景模板自动执行多阶段策略分析</p>
</div>
<ArrowRight className={`w-5 h-5 ${textMuted}`} />
</div>
</button> */}
</div>
</div>
</div>
)}
</div>
);
};
export default TaskAnalyzes;
@@ -0,0 +1,299 @@
import { useTheme } from '@/contexts/ThemeContext';
import { TaskListItem } from '@/pages/TaskManagement';
import { Bot, ChevronRight, Download, Edit, Eye, FileText, Layers, MoreHorizontal, Play, User, Users, Workflow } from 'lucide-react';
import React, { useEffect, useState } from 'react';
import { PageType } from '../Sidebar';
import { format } from 'date-fns';
import { useNavigate } from 'react-router-dom';
import { useChatContext } from '@/contexts/ChatContext';
interface KanbanColumnProps {
TaskType?: string;
task: TaskListItem;
onNavigate: (page: PageType) => void;
handleStartAnalysis: (task: TaskListItem) => void
}
export const TaskCard: React.FC<KanbanColumnProps> = ({ task, TaskType = 'task', onNavigate, handleStartAnalysis }) => {
const { theme } = useTheme();
const { setStartConfig } = useChatContext();
const isDark = theme === 'dark';
const textPrimary = isDark ? 'text-white' : 'text-gray-900';
const textMuted = isDark ? 'text-slate-500' : 'text-gray-400';
const textSecondary = isDark ? 'text-slate-400' : 'text-gray-600';
const navigate = useNavigate();
const handleStartChange = (task: TaskListItem) => {
// if (TaskType === 'task') {
// handleStartAnalysis(task);
// } else {
// sceneDataChange(task);
// }
handleStartAnalysis(task);
}
const sceneDataChange = (task: TaskListItem) => {
const sceneItem = JSON.parse(task.scene);
if (sceneItem.taskJson) {
sceneItem.taskJson = JSON.parse(sceneItem.taskJson);
setStartConfig({
...sceneItem.taskJson,
llm_preset_id: 'BAILING',
consistency_mode: 'soft',
phase_config: { diverge_rounds: 3 },
task_context: task.overview
});
navigate('/qa-center/mope-chat')
}
console.log('sceneItem', sceneItem);
}
// 处理卡片点击
const handleTaskClick = (task: TaskListItem) => {
if (TaskType === 'sentiment') {
console.log('点击了', task)
sceneDataChange(task);
// window.open(`http://ch1.b.uat.4.cn/taskcop/#/situation-overview/action/sentiment?taskId=${task.id}`)
} else {
if (task.column === 'completed') {
// if (task.type === 'consultation') {
// onNavigate('task-consult-result');
// } else {
// onNavigate('strategy-result');
// }
navigate('/strategy-result')
} else if (task.column === 'running') {
// navigate('/task-management/task-consult')
sceneDataChange(task);
// window.open(`http://ch1.b.uat.4.cn/mopeweb/#/mope?taskOverview=${task.overview}`)
// if (task.type === 'scene') {
// onNavigate('task-scene');
// } else if (task.type === 'consultation') {
// onNavigate('task-consult');
// } else {
// onNavigate('task-single');
// }
}
}
};
// 获取任务类型配置
const getTypeConfig = (type: string) => {
if (isDark) {
switch (type) {
case 'single':
return { label: '单智能体', color: 'bg-cyan-500/20 text-cyan-400', icon: Bot };
case 'scene':
return { label: '场景执行', color: 'bg-purple-500/20 text-purple-400', icon: Workflow };
case 'consultation':
return { label: '多智能体会商', color: 'bg-orange-500/20 text-orange-400', icon: Users };
case 'multi-agent':
return { label: '多智能体协同', color: 'bg-green-500/20 text-green-400', icon: Layers };
}
} else {
switch (type) {
case 'single':
return { label: '单智能体', color: 'bg-blue-100 text-blue-600', icon: Bot };
case 'scene':
return { label: '场景执行', color: 'bg-purple-100 text-purple-600', icon: Workflow };
case 'consultation':
return { label: '多智能体会商', color: 'bg-orange-100 text-orange-600', icon: Users };
case 'multi-agent':
return { label: '多智能体协同', color: 'bg-green-100 text-green-600', icon: Layers };
}
}
};
const typeConfig = getTypeConfig('single');
// 获取任务等级
const getLevel = (type: number) => {
if (isDark) {
switch (type) {
case 1:
return { label: '一般', color: 'bg-cyan-500/20 text-cyan-400', icon: Bot };
case 2:
return { label: '紧急', color: 'bg-purple-500/20 text-purple-400', icon: Workflow };
case 3:
return { label: '加急', color: 'bg-orange-500/20 text-orange-400', icon: Users };
}
} else {
switch (type) {
case 1:
return { label: '一般', color: 'bg-blue-100 text-blue-600', icon: Bot };
case 2:
return { label: '紧急', color: 'bg-purple-100 text-purple-600', icon: Workflow };
case 3:
return { label: '加急', color: 'bg-orange-100 text-orange-600', icon: Users };
}
}
};
const levelConfig = getLevel(task.taskLevel);
const LevelIcon = levelConfig?.icon || Bot;
// 获取状态配置
const getStatusConfig = (column: string) => {
if (column === 'unanalyzed') {
return { label: '待分析', color: isDark ? 'bg-yellow-500/20 text-yellow-400' : 'bg-yellow-100 text-yellow-600' };
} else if (column === 'running') {
return { label: '运行中', color: isDark ? 'bg-blue-500/20 text-blue-400' : 'bg-red-100 text-red-600' };
} else {
return { label: '已完成', color: isDark ? 'bg-green-500/20 text-green-400' : 'bg-green-100 text-green-600' };
}
};
const statusConfig = getStatusConfig(task.column);
return (
<div
className={`rounded-xl p-4 hover:shadow-md transition-all duration-200 cursor-pointer group theme-card border ${isDark
? 'bg-slate-800/40 backdrop-blur-xl border-blue-800/30 hover:border-blue-500/50'
: 'bg-white border-red-100 hover:shadow-md shadow-red-100'
}`}
onClick={() => handleTaskClick(task)}
>
{/* 卡片头部:模式标签 + 状态标签 */}
<div className="flex items-center justify-between mb-2">
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-medium ${levelConfig.color}`}>
{LevelIcon && <LevelIcon className="w-3 h-3" />}
{levelConfig.label}
</span>
<span></span>
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${statusConfig.color}`}>
{statusConfig.label}
</span>
</div>
{/* 任务名称 */}
<h3 className={`font-semibold text-sm mb-1 line-clamp-2 group-hover:text-red-500 transition-colors ${textPrimary}`}>
{task.taskName}
</h3>
{/* 任务编号 */}
<p className={`text-xs mb-2 ${textMuted}`}>TASK-{task.id}</p>
{/* 任务摘要 */}
<p className={`text-xs mb-3 line-clamp-2 ${textSecondary}`}>{task.overview}</p>
{/* 补充信息区 */}
{/* <div className="space-y-1 mb-3">
{task.type === 'single' && task.agent && (
<div className="flex items-center gap-2 text-xs">
<Bot className={`w-3 h-3 ${isDark ? 'text-cyan-400' : 'text-red-500'}`} />
<span className={textSecondary}>执行智能体:</span>
<span className={isDark ? 'text-cyan-400' : 'text-red-600'}>{task.agent}</span>
</div>
)}
{task.type === 'scene' && task.scene && (
<div className="flex items-center gap-2 text-xs">
<Workflow className={`w-3 h-3 ${isDark ? 'text-purple-400' : 'text-red-500'}`} />
<span className={textSecondary}>关联场景:</span>
<span className={isDark ? 'text-purple-400' : 'text-red-600'}>{task.scene}</span>
</div>
)}
{task.type === 'consultation' && (
<>
<div className="flex items-center gap-2 text-xs">
<Users className={`w-3 h-3 ${isDark ? 'text-orange-400' : 'text-red-500'}`} />
<span className={textSecondary}>参与智能体:</span>
<span className={isDark ? 'text-orange-400' : 'text-red-600'}>{task.participants?.length || 0}个</span>
</div>
{task.currentRound && (
<div className="flex items-center gap-2 text-xs">
<Clock className={`w-3 h-3 ${textMuted}`} />
<span className={textSecondary}>当前轮次:</span>
<span className={textPrimary}>第{task.currentRound}轮</span>
</div>
)}
</>
)}
{task.column === 'analyzing' && task.currentPhase && (
<div className="flex items-center gap-2 text-xs">
<Target className={`w-3 h-3 ${textMuted}`} />
<span className={textSecondary}>当前阶段:</span>
<span className={textPrimary}>{task.currentPhase}</span>
</div>
)}
</div> */}
{/* 时间信息 */}
<div className={`flex items-center justify-between text-xs pt-3 border-t ${isDark ? 'border-blue-800/30 text-slate-500' : 'border-red-100 text-gray-400'
}`}>
<div className="flex items-center gap-1.5">
<User className="w-3 h-3" />
<span>{task.taskDirection}</span>
</div>
<span>{format(new Date(task.updateTime), 'yyyy-MM-dd HH:mm:ss')}</span>
</div>
{/* 操作按钮区 */}
<div className={`flex items-center gap-2 mt-3 pt-3 border-t ${isDark ? 'border-blue-800/30' : 'border-red-100'
}`}>
{task.column === 'unanalyzed' && (
<>
<button
onClick={(e) => { e.stopPropagation(); handleStartChange(task); }}
className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-white rounded-lg text-xs font-medium transition-colors ${isDark
? 'bg-blue-600 hover:bg-blue-500'
: 'bg-red-600 hover:bg-red-500 shadow-md'
}`}
>
<Play className="w-3 h-3" />
开始分析
</button>
{/* <button
onClick={(e) => { e.stopPropagation(); onNavigate('task-create'); }}
className={`p-1.5 rounded-lg transition-colors ${isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`}
title="编辑"
>
<Edit className={`w-4 h-4 ${isDark ? 'text-slate-400' : 'text-red-400'}`} />
</button> */}
</>
)}
{task.column === 'running' && (
<>
<button
onClick={(e) => { e.stopPropagation(); handleStartChange(task); }}
className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-white rounded-lg text-xs font-medium transition-colors ${isDark
? 'bg-blue-600 hover:bg-blue-500'
: 'bg-red-600 hover:bg-red-500 shadow-md'
}`}
>
<Play className="w-3 h-3" />
开始分析
</button>
{/* <button
onClick={(e) => { e.stopPropagation(); handleStartChange(task); }}
className={`px-2 py-1.5 rounded-lg text-xs transition-colors ${isDark
? 'hover:bg-slate-700/50 border border-blue-800/30 text-slate-300'
: 'hover:bg-red-50 border border-red-200 text-red-600'
}`}
>
查看进展
</button> */}
</>
)}
{task.column === 'completed' && (
<>
<button
onClick={(e) => { e.stopPropagation(); handleStartChange(task); }}
className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-white rounded-lg text-xs font-medium transition-colors ${isDark
? 'bg-green-600 hover:bg-green-500'
: 'bg-green-600 hover:bg-green-500 shadow-md'
}`}
>
<Eye className="w-3 h-3" />
开始分析
</button>
{/* <button className={`px-2 py-1.5 rounded-lg text-xs transition-colors ${isDark
? 'hover:bg-slate-700/50 border border-blue-800/30 text-slate-300'
: 'hover:bg-red-50 border border-red-200 text-red-600'
}`}>
<Download className="w-4 h-4" />
</button> */}
</>
)}
{/* <button className={`p-1.5 rounded-lg transition-colors ${isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`} title="更多">
<MoreHorizontal className={`w-4 h-4 ${isDark ? 'text-slate-400' : 'text-red-400'}`} />
</button> */}
</div>
</div>
);
}
@@ -0,0 +1,626 @@
import React, { useState } from 'react';
import {
ArrowLeft,
Bot,
Users,
Play,
Pause,
Download,
CheckCircle,
ChevronRight,
MessageSquare,
Send,
Star,
AlertCircle,
FileText,
Settings,
Lightbulb,
Zap,
Check,
X,
Minus,
} from 'lucide-react';
import { useTheme } from '@/contexts/ThemeContext';
import { PageType } from '@/components/Sidebar';
interface TaskConsultProps {
onNavigate: (page: PageType) => void;
}
// 模拟参与智能体
const participants = [
{
id: 'goal',
name: 'GoalAgent',
role: '目标分析专家',
color: 'blue',
status: 'idle' as const,
lastOpinion: '建议聚焦提升品牌认知度这一核心目标',
},
{
id: 'audience',
name: 'AudienceAgent',
role: '受众分析专家',
color: 'cyan',
status: 'speaking' as const,
lastOpinion: '核心受众应为18-25岁基层青年群体',
},
{
id: 'narrative',
name: 'NarrativeAgent',
role: '叙事策略专家',
color: 'purple',
status: 'idle' as const,
lastOpinion: '应采用真实、有态度的青年故事叙事',
},
{
id: 'risk',
name: 'RiskAgent',
role: '风险评估专家',
color: 'red',
status: 'idle' as const,
lastOpinion: '需注意UGC质量不稳定和话题热度维持的风险',
},
];
// 模拟讨论记录
const discussionRecords = [
{
id: 1,
agentId: 'goal',
agentName: 'GoalAgent',
agentRole: '目标分析专家',
color: 'blue',
content: `从目标分析角度看,这次五四青年节宣传活动应聚焦以下核心目标:
**主目标**:提升品牌在Z世代用户中的认知度
**量化指标建议**:
- 曝光量:500万+
- 互动率:5%+
- UGC数量:1000+
**优先级判断**:
建议优先聚焦品牌认知度提升,短期内不追求直接转化。这样可以更好地与目标受众建立情感连接。`,
time: '14:30',
tags: ['核心目标', '品牌认知'],
markedAs: null as string | null,
},
{
id: 2,
agentId: 'audience',
agentName: 'AudienceAgent',
agentRole: '受众分析专家',
color: 'cyan',
content: `基于受众分析,我建议:
**核心受众画像**:
- 年龄:18-25岁
- 身份:学生、基层青年工作者
- 地域:一二线城市为主
**关键洞察**:
这部分群体是社交媒体重度用户,注重"真实感"和"态度感",反感过度营销。需要用平等对话的方式,而非说教式传播。
**内容偏好**:
真实故事 > 精致包装
态度表达 > 信息堆砌
参与互动 > 被动观看`,
time: '14:32',
tags: ['受众画像', '内容偏好'],
markedAs: 'valuable' as string | null,
},
{
id: 3,
agentId: 'narrative',
agentName: 'NarrativeAgent',
agentRole: '叙事策略专家',
color: 'purple',
content: `从叙事策略角度,我建议采用以下框架:
**叙事主题**:真实、有态度的青年故事
**叙事结构**:
1. 讲述真实的人物故事
2. 引发情感共鸣
3. 传递品牌价值观
4. 激发用户参与
**关键要素**:
- 真实感 > 精致感
- 态度感 > 说教感
- 参与感 > 观看感
**内容类型建议**:
- 人物专访型:真实青年故事
- 事件记录型:青年节活动现场
- 观点表达型:青年态度宣言`,
time: '14:35',
tags: ['叙事框架', '内容类型'],
markedAs: null as string | null,
},
{
id: 4,
agentId: 'risk',
agentName: 'RiskAgent',
agentRole: '风险评估专家',
color: 'red',
content: `我需要提醒以下风险:
**高风险项**:
1. 内容质量不稳定
- 风险:UGC质量参差不齐
- 对策:建立内容审核机制
**中风险项**:
2. 预算控制难度大
- 风险:投放效果不可控
- 对策:分阶段预算分配
3. 青年群体注意力分散
- 风险:话题热度难维持
- 对策:多波次内容规划
**建议**:重点关注内容质量风险,建立审核机制。`,
time: '14:38',
tags: ['风险识别', '应对建议'],
markedAs: 'pending' as string | null,
},
];
const TaskConsult: React.FC<TaskConsultProps> = ({ onNavigate }) => {
const { theme } = useTheme();
const isDark = theme === 'dark';
const [isPaused, setIsPaused] = useState(false);
const [currentRound, setCurrentRound] = useState(2);
const [currentTopic, setCurrentTopic] = useState('宣传策略方向选择');
const [humanInput, setHumanInput] = useState('');
const [consensusPoints] = useState([
'核心受众应聚焦18-25岁基层青年',
'内容应以真实、有态度为核心',
]);
const [disputedPoints] = useState([
'叙事角度:事件叙事 vs 人物叙事',
'传播渠道:短视频平台 vs 社交媒体',
]);
const [humanNotes, setHumanNotes] = useState('');
const getColorClasses = (color: string) => {
const colors: Record<string, { bg: string; border: string; text: string }> = {
blue: { bg: isDark ? 'bg-blue-500/20' : 'bg-blue-50', border: isDark ? 'border-blue-500/50' : 'border-blue-200', text: isDark ? 'text-blue-400' : 'text-blue-600' },
cyan: { bg: isDark ? 'bg-cyan-500/20' : 'bg-cyan-50', border: isDark ? 'border-cyan-500/50' : 'border-cyan-200', text: isDark ? 'text-cyan-400' : 'text-cyan-600' },
green: { bg: isDark ? 'bg-green-500/20' : 'bg-green-50', border: isDark ? 'border-green-500/50' : 'border-green-200', text: isDark ? 'text-green-400' : 'text-green-600' },
yellow: { bg: isDark ? 'bg-yellow-500/20' : 'bg-yellow-50', border: isDark ? 'border-yellow-500/50' : 'border-yellow-200', text: isDark ? 'text-yellow-400' : 'text-yellow-600' },
purple: { bg: isDark ? 'bg-purple-500/20' : 'bg-purple-50', border: isDark ? 'border-purple-500/50' : 'border-purple-200', text: isDark ? 'text-purple-400' : 'text-purple-600' },
red: { bg: isDark ? 'bg-red-500/20' : 'bg-red-50', border: isDark ? 'border-red-500/50' : 'border-red-200', text: isDark ? 'text-red-400' : 'text-red-600' },
};
return colors[color] || colors.blue;
};
const getStatusConfig = (status: string) => {
switch (status) {
case 'speaking':
return { label: '发言中', color: isDark ? 'bg-green-500/20 text-green-400' : 'bg-green-100 text-green-700', dot: isDark ? 'bg-green-400' : 'bg-green-500' };
case 'completed':
return { label: '已完成', color: isDark ? 'bg-slate-500/20 text-slate-400' : 'bg-gray-100 text-gray-600', dot: isDark ? 'bg-slate-400' : 'bg-gray-400' };
default:
return { label: '待发言', color: isDark ? 'bg-slate-500/20 text-slate-400' : 'bg-gray-100 text-gray-600', dot: isDark ? 'bg-slate-400' : 'bg-gray-400' };
}
};
const handleNextRound = () => {
setCurrentRound(currentRound + 1);
};
const handleFinishConsult = () => {
onNavigate('task-consult-result');
};
// 主题颜色类
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';
return (
<div className="h-[calc(100vh-140px)] flex flex-col space-y-4">
{/* 顶部信息栏 */}
<div className={`${cardBg} backdrop-blur-xl border ${cardBorder} rounded-xl p-4 flex-shrink-0`}>
<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-red-200'}`} />
<div>
<h1 className={`${textPrimary} font-bold text-lg`}>五四青年节宣传策略会商</h1>
<p className={`${textSecondary} text-sm`}>多智能体会商任务 · 第 {currentRound} 轮</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-4 text-sm">
<span className={textSecondary}>
模式:<span className={isDark ? 'text-purple-400' : 'text-purple-600'}>受控交叉讨论</span>
</span>
<span className={textSecondary}>
参与:<span className={isDark ? 'text-cyan-400' : 'text-cyan-600'}>{participants.length} 个智能体</span>
</span>
<span className={textSecondary}>
创建:<span className={textPrimary}>2026-03-19 14:30</span>
</span>
</div>
<div className={`h-8 w-px ${isDark ? 'bg-blue-800/30' : 'bg-red-200'}`} />
<div className="flex items-center gap-2">
<button
onClick={() => setIsPaused(!isPaused)}
className={`flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm transition-colors ${
isPaused
? (isDark ? 'bg-green-500/20 hover:bg-green-500/30 border-green-500/50 text-green-400' : 'bg-green-100 hover:bg-green-200 border-green-300 text-green-700')
: (isDark ? 'bg-yellow-500/20 hover:bg-yellow-500/30 border-yellow-500/50 text-yellow-400' : 'bg-yellow-100 hover:bg-yellow-200 border-yellow-300 text-yellow-700')
}`}
>
{isPaused ? <Play className="w-4 h-4" /> : <Pause className="w-4 h-4" />}
{isPaused ? '继续会商' : '暂停会商'}
</button>
<button className={`flex items-center gap-2 px-3 py-1.5 ${btnSecondary} rounded-lg text-sm transition-colors`}>
<Download className="w-4 h-4" />
导出纪要
</button>
<button
onClick={handleFinishConsult}
className="flex items-center gap-2 px-3 py-1.5 bg-orange-600 hover:bg-orange-500 text-white rounded-lg text-sm transition-colors"
>
<CheckCircle className="w-4 h-4" />
结束并整合
</button>
</div>
</div>
</div>
</div>
{/* 三栏主体区域 */}
<div className="flex-1 grid grid-cols-12 gap-4 min-h-0">
{/* 左栏:参与智能体与议题导航 */}
<div className="col-span-3 flex flex-col gap-4 min-h-0">
{/* 参与智能体 */}
<div className={`${cardBg} backdrop-blur-xl border ${cardBorder} rounded-xl p-4 flex-1 overflow-hidden flex flex-col`}>
<h3 className={`${textPrimary} font-semibold text-sm mb-3 flex items-center gap-2`}>
<Users className={`w-4 h-4 ${isDark ? 'text-purple-400' : 'text-purple-600'}`} />
参与智能体
</h3>
<div className="space-y-2 flex-1 overflow-y-auto">
{participants.map((agent) => {
const colors = getColorClasses(agent.color);
const statusConfig = getStatusConfig(agent.status);
return (
<div
key={agent.id}
className={`p-3 rounded-lg border transition-all ${
agent.status === 'speaking'
? `${colors.bg} ${colors.border}`
: isDark ? 'bg-slate-900/50 border-blue-800/30' : 'bg-gray-50 border-gray-200'
}`}
>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<div className={`w-8 h-8 rounded-lg ${colors.bg} flex items-center justify-center`}>
<Bot className={`w-4 h-4 ${colors.text}`} />
</div>
<div>
<p className={`text-sm font-medium ${agent.status === 'speaking' ? colors.text : textPrimary}`}>
{agent.name}
</p>
<p className={`${textMuted} text-xs`}>{agent.role}</p>
</div>
</div>
<span className={`px-1.5 py-0.5 rounded text-xs ${statusConfig.color}`}>
{statusConfig.label}
</span>
</div>
{agent.lastOpinion && (
<p className={`${textSecondary} text-xs line-clamp-2 pl-10`}>{agent.lastOpinion}</p>
)}
<button
className={`mt-2 w-full px-2 py-1.5 rounded text-xs transition-colors ${
agent.status === 'speaking'
? (isDark ? 'bg-slate-700/50 text-slate-400 cursor-not-allowed' : 'bg-gray-200 text-gray-500 cursor-not-allowed')
: (isDark ? 'bg-purple-500/20 hover:bg-purple-500/30 text-purple-400' : 'bg-purple-100 hover:bg-purple-200 text-purple-700')
}`}
disabled={agent.status === 'speaking'}
>
点名发言
</button>
</div>
);
})}
</div>
</div>
{/* 会商议题导航 */}
<div className={`${cardBg} backdrop-blur-xl border ${cardBorder} rounded-xl p-4`}>
<h3 className={`${textPrimary} font-semibold text-sm mb-3 flex items-center gap-2`}>
<MessageSquare className={`w-4 h-4 ${isDark ? 'text-blue-400' : 'text-blue-600'}`} />
会商议题
</h3>
<div className="space-y-2">
<div className={`p-3 rounded-lg border ${isDark ? 'bg-green-500/20 border-green-500/50' : 'bg-green-50 border-green-300'}`}>
<p className={`text-sm font-medium ${isDark ? 'text-green-400' : 'text-green-700'}`}>当前议题</p>
<p className={`${textPrimary} text-sm mt-1`}>{currentTopic}</p>
</div>
<div className={`p-3 rounded-lg border ${isDark ? 'bg-slate-900/50 border-blue-800/30' : 'bg-gray-50 border-gray-200'}`}>
<p className={`${textMuted} text-xs mb-1`}>待讨论</p>
<p className={isDark ? 'text-slate-300 text-sm' : 'text-gray-700 text-sm'}>渠道策略选择</p>
<p className={isDark ? 'text-slate-300 text-sm' : 'text-gray-700 text-sm'}>预算分配方案</p>
</div>
<div className={`p-3 rounded-lg border ${isDark ? 'bg-slate-900/50 border-blue-800/30' : 'bg-gray-50 border-gray-200'}`}>
<p className={`${textMuted} text-xs mb-1`}>已讨论</p>
<p className={`${textMuted} text-sm line-through`}>受众定位</p>
<p className={`${textMuted} text-sm line-through`}>目标设定</p>
</div>
</div>
<div className="mt-3 flex gap-2">
<button className={`flex-1 px-2 py-1.5 ${btnSecondary} rounded-lg text-xs transition-colors`}>
切换议题
</button>
<button className={`flex-1 px-2 py-1.5 ${btnSecondary} rounded-lg text-xs transition-colors`}>
结束议题
</button>
</div>
</div>
</div>
{/* 中栏:会商讨论区 */}
<div className="col-span-6 flex flex-col gap-4 min-h-0">
{/* 当前议题卡 */}
<div className={`${cardBg} backdrop-blur-xl border ${cardBorder} rounded-xl p-4 flex-shrink-0`}>
<div className="flex items-center justify-between">
<div>
<p className={`${textSecondary} text-xs`}>当前议题</p>
<p className={`${textPrimary} font-medium mt-0.5`}>{currentTopic}</p>
</div>
<div className="flex items-center gap-3">
<span className={`px-2 py-1 rounded text-xs ${isDark ? 'bg-purple-500/20 text-purple-400' : 'bg-purple-100 text-purple-700'}`}>
受控交叉讨论模式
</span>
<span className={`px-2 py-1 rounded text-xs ${isDark ? 'bg-blue-500/20 text-blue-400' : 'bg-blue-100 text-blue-700'}`}>
第 {currentRound} 轮
</span>
</div>
</div>
</div>
{/* 讨论流 */}
<div className={`${cardBg} backdrop-blur-xl border ${cardBorder} rounded-xl overflow-hidden flex flex-col`}>
<div className={`p-4 border-b ${isDark ? 'border-blue-800/30' : 'border-gray-200'} flex items-center justify-between`}>
<h3 className={`${textPrimary} font-semibold text-sm`}>讨论记录</h3>
<button
onClick={handleNextRound}
className={`flex items-center gap-2 px-3 py-1.5 ${btnPrimary} bg-opacity-20 hover:bg-opacity-30 ${isDark ? 'border border-blue-500/30 text-blue-400' : 'border border-red-400 text-red-600'} rounded-lg text-xs transition-colors`}
>
<ChevronRight className="w-4 h-4" />
下一轮
</button>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{discussionRecords.map((record) => {
const colors = getColorClasses(record.color);
return (
<div
key={record.id}
className={`p-4 rounded-xl border ${
record.markedAs === 'valuable'
? (isDark ? 'bg-green-500/10 border-green-500/30' : 'bg-green-50 border-green-300')
: record.markedAs === 'pending'
? (isDark ? 'bg-yellow-500/10 border-yellow-500/30' : 'bg-yellow-50 border-yellow-300')
: (isDark ? 'bg-slate-900/50 border-blue-800/30' : 'bg-gray-50 border-gray-200')
}`}
>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-lg ${colors.bg} flex items-center justify-center`}>
<Bot className={`w-5 h-5 ${colors.text}`} />
</div>
<div>
<div className="flex items-center gap-2">
<p className={`text-sm font-medium ${colors.text}`}>{record.agentName}</p>
{record.markedAs === 'valuable' && (
<span className={`px-1.5 py-0.5 rounded text-xs flex items-center gap-0.5 ${isDark ? 'bg-green-500/20 text-green-400' : 'bg-green-100 text-green-700'}`}>
<Star className="w-3 h-3" /> 有价值
</span>
)}
{record.markedAs === 'pending' && (
<span className={`px-1.5 py-0.5 rounded text-xs flex items-center gap-0.5 ${isDark ? 'bg-yellow-500/20 text-yellow-400' : 'bg-yellow-100 text-yellow-700'}`}>
<AlertCircle className="w-3 h-3" /> 待验证
</span>
)}
</div>
<p className={`${textMuted} text-xs`}>{record.agentRole} · {record.time}</p>
</div>
</div>
<div className="flex items-center gap-1">
<button
className={`p-1.5 rounded transition-colors ${isDark ? 'hover:bg-green-500/20' : 'hover:bg-green-100'}`}
title="标记为有价值"
>
<Star className={`w-4 h-4 ${isDark ? 'text-slate-400 hover:text-green-400' : 'text-gray-500 hover:text-green-600'}`} />
</button>
<button
className={`p-1.5 rounded transition-colors ${isDark ? 'hover:bg-yellow-500/20' : 'hover:bg-yellow-100'}`}
title="标记为待验证"
>
<AlertCircle className={`w-4 h-4 ${isDark ? 'text-slate-400 hover:text-yellow-400' : 'text-gray-500 hover:text-yellow-600'}`} />
</button>
<button
className={`p-1.5 rounded transition-colors ${isDark ? 'hover:bg-red-500/20' : 'hover:bg-red-100'}`}
title="暂不采纳"
>
<X className={`w-4 h-4 ${isDark ? 'text-slate-400 hover:text-red-400' : 'text-gray-500 hover:text-red-600'}`} />
</button>
</div>
</div>
<div className="pl-13">
<div className={`${isDark ? 'text-slate-300' : 'text-gray-700'} text-sm whitespace-pre-wrap leading-relaxed`}>
{record.content}
</div>
<div className="flex items-center gap-2 mt-3">
{record.tags.map((tag) => (
<span key={tag} className={`px-2 py-0.5 rounded text-xs ${isDark ? 'bg-blue-500/20 text-blue-400' : 'bg-blue-100 text-blue-700'}`}>
{tag}
</span>
))}
</div>
<div className="flex items-center gap-2 mt-3">
<button className={`flex items-center gap-1 px-2 py-1 ${btnSecondary} rounded text-xs transition-colors`}>
<FileText className="w-3 h-3" />
引用到整合区
</button>
</div>
</div>
</div>
);
})}
</div>
</div>
{/* 人工发言区 */}
<div className={`${cardBg} backdrop-blur-xl border ${cardBorder} rounded-xl p-4 flex-shrink-0`}>
<div className="flex items-start gap-3">
<div className={`w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 ${isDark ? 'bg-blue-500/20' : 'bg-blue-100'}`}>
<Users className={`w-5 h-5 ${isDark ? 'text-blue-400' : 'text-blue-600'}`} />
</div>
<div className="flex-1">
<textarea
value={humanInput}
onChange={(e) => setHumanInput(e.target.value)}
placeholder="输入您的评论、问题或指令... (可点名某个智能体:@GoalAgent)"
rows={2}
className={`w-full px-4 py-2.5 ${inputBg} border ${inputBorder} rounded-lg ${textPrimary} text-sm ${isDark ? 'placeholder-slate-500' : 'placeholder-gray-400'} focus:outline-none ${isDark ? 'focus:border-blue-500/50' : 'focus:border-red-400'} transition-colors resize-none`}
/>
<div className="flex items-center justify-between mt-2">
<div className="flex items-center gap-2">
<button className={`px-2 py-1 ${btnSecondary} rounded text-xs transition-colors`}>
点名提问
</button>
<button className={`px-2 py-1 ${btnSecondary} rounded text-xs transition-colors`}>
发起对比问题
</button>
<button className={`px-2 py-1 ${btnSecondary} rounded text-xs transition-colors`}>
要求补充说明
</button>
</div>
<button
disabled={!humanInput.trim()}
className={`flex items-center gap-2 px-4 py-2 ${btnPrimary} disabled:${isDark ? 'bg-slate-700 text-slate-500' : 'bg-gray-300 text-gray-500'} text-white rounded-lg text-sm transition-colors`}
>
<Send className="w-4 h-4" />
发送
</button>
</div>
</div>
</div>
</div>
</div>
{/* 右栏:观点整合区 */}
<div className="col-span-3 flex flex-col gap-4 min-h-0">
{/* 各智能体核心观点 */}
<div className={`${cardBg} backdrop-blur-xl border ${cardBorder} rounded-xl p-4 flex-1 overflow-hidden flex flex-col`}>
<h3 className={`${textPrimary} font-semibold text-sm mb-3 flex items-center gap-2`}>
<Bot className={`w-4 h-4 ${isDark ? 'text-cyan-400' : 'text-cyan-600'}`} />
各方观点
</h3>
<div className="space-y-3 flex-1 overflow-y-auto">
{participants.map((agent) => {
const colors = getColorClasses(agent.color);
return (
<div key={agent.id} className={`p-3 rounded-lg ${isDark ? 'bg-slate-900/50 border-blue-800/30' : 'bg-gray-50 border-gray-200'}`}>
<div className="flex items-center gap-2 mb-2">
<Bot className={`w-4 h-4 ${colors.text}`} />
<span className={`text-sm font-medium ${colors.text}`}>{agent.name}</span>
</div>
<p className={`${textSecondary} text-xs line-clamp-3`}>{agent.lastOpinion}</p>
</div>
);
})}
</div>
</div>
{/* 共识观点 */}
<div className={`${cardBg} backdrop-blur-xl border ${isDark ? 'border-green-800/30' : 'border-green-300'} rounded-xl p-4`}>
<h3 className={`${textPrimary} font-semibold text-sm mb-3 flex items-center gap-2`}>
<Check className={`w-4 h-4 ${isDark ? 'text-green-400' : 'text-green-600'}`} />
共识观点
<span className={`px-1.5 py-0.5 rounded text-xs ml-auto ${isDark ? 'bg-green-500/20 text-green-400' : 'bg-green-100 text-green-700'}`}>
{consensusPoints.length} 条
</span>
</h3>
<div className="space-y-2">
{consensusPoints.map((point, index) => (
<div key={index} className={`p-2 rounded-lg ${isDark ? 'bg-green-500/10 border border-green-500/30' : 'bg-green-50 border border-green-200'}`}>
<p className={isDark ? 'text-green-300 text-sm' : 'text-green-800 text-sm'}>{point}</p>
</div>
))}
</div>
</div>
{/* 分歧观点 */}
<div className={`${cardBg} backdrop-blur-xl border ${isDark ? 'border-yellow-800/30' : 'border-yellow-300'} rounded-xl p-4`}>
<h3 className={`${textPrimary} font-semibold text-sm mb-3 flex items-center gap-2`}>
<Minus className={`w-4 h-4 ${isDark ? 'text-yellow-400' : 'text-yellow-600'}`} />
分歧观点
<span className={`px-1.5 py-0.5 rounded text-xs ml-auto ${isDark ? 'bg-yellow-500/20 text-yellow-400' : 'bg-yellow-100 text-yellow-700'}`}>
{disputedPoints.length} 条
</span>
</h3>
<div className="space-y-2">
{disputedPoints.map((point, index) => (
<div key={index} className={`p-2 rounded-lg ${isDark ? 'bg-yellow-500/10 border border-yellow-500/30' : 'bg-yellow-50 border border-yellow-200'}`}>
<p className={isDark ? 'text-yellow-300 text-sm' : 'text-yellow-800 text-sm'}>{point}</p>
</div>
))}
</div>
</div>
{/* 人工整合区 */}
<div className={`${cardBg} backdrop-blur-xl border ${isDark ? 'border-purple-800/30' : 'border-purple-300'} rounded-xl p-4 flex-1 overflow-hidden flex flex-col`}>
<h3 className={`${textPrimary} font-semibold text-sm mb-3 flex items-center gap-2`}>
<Zap className={`w-4 h-4 ${isDark ? 'text-purple-400' : 'text-purple-600'}`} />
人工整合
</h3>
<textarea
value={humanNotes}
onChange={(e) => setHumanNotes(e.target.value)}
placeholder="记录您的判断、暂定方向、待确认事项..."
className={`flex-1 min-h-[120px] px-3 py-2 ${inputBg} border ${inputBorder} rounded-lg ${textPrimary} text-sm ${isDark ? 'placeholder-slate-500' : 'placeholder-gray-400'} focus:outline-none ${isDark ? 'focus:border-blue-500/50' : 'focus:border-red-400'} transition-colors resize-none`}
/>
<div className="flex flex-col gap-2 mt-3">
<button className={`w-full flex items-center justify-center gap-2 px-3 py-2 ${isDark ? 'bg-purple-600 hover:bg-purple-500' : 'bg-purple-600 hover:bg-purple-500'} text-white rounded-lg text-xs font-medium transition-colors`}>
<Lightbulb className="w-4 h-4" />
生成会商结论
</button>
<button className={`w-full flex items-center justify-center gap-2 px-3 py-2 ${btnSecondary} rounded-lg text-xs transition-colors`}>
<FileText className="w-4 h-4" />
转为任务摘要
</button>
<button className={`w-full flex items-center justify-center gap-2 px-3 py-2 ${btnSecondary} rounded-lg text-xs transition-colors`}>
<Settings className="w-4 h-4" />
转为场景任务输入
</button>
</div>
</div>
</div>
</div>
</div>
);
};
export default TaskConsult;
@@ -0,0 +1,572 @@
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;
@@ -0,0 +1,431 @@
import React, { useState } from 'react';
import { useTheme } from '@/contexts/ThemeContext';
import {
ArrowLeft,
Bot,
CheckCircle,
Download,
FileText,
ChevronRight,
Zap,
AlertCircle,
Check,
Target,
X,
RefreshCw,
} from 'lucide-react';
import { PageType } from '@/components/Sidebar';
interface TaskConsultResultProps {
onNavigate: (page: PageType) => void;
}
const TaskConsultResult: React.FC<TaskConsultResultProps> = ({ 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 [activeTab, setActiveTab] = useState<'conclusion' | 'opinions' | 'disputes'>('conclusion');
// 会商结论数据
const conclusion = {
mainConclusion: '五四青年节宣传活动应聚焦"真实、有态度的青年故事"这一叙事方向,以18-25岁基层青年为核心受众,优先在短视频平台建立品牌认知。',
recommendedDirection: [
'以真实人物故事为核心叙事内容',
'采用平等对话而非说教式的传播语气',
'优先布局抖音、小红书等短视频平台',
'建立UGC激励机制促进用户参与',
],
notRecommendedDirection: [
'过度包装的精致化内容',
'单向输出的宣传式传播',
'纯文字为主的长图文形式',
],
pendingIssues: [
'叙事角度:事件叙事 vs 人物叙事(待进一步验证)',
'渠道策略:短视频为主 vs 社交媒体为主',
'预算在内容制作和达人投放的分配比例',
],
nextSteps: [
'建议由 NarrativeAgent 细化叙事框架',
'建议由 AudienceAgent 补充平台选择建议',
'可考虑转入"宣传策略生成"场景任务进行完整方案输出',
],
};
// 观点汇总数据
const agentOpinions = [
{
agentId: 'goal',
agentName: 'GoalAgent',
agentRole: '目标分析专家',
color: 'blue',
coreOpinion: '建议聚焦提升品牌认知度这一核心目标,短期内不追求直接转化',
adopted: true,
notes: '与整体策略方向一致',
},
{
agentId: 'audience',
agentName: 'AudienceAgent',
agentRole: '受众分析专家',
color: 'cyan',
coreOpinion: '核心受众应为18-25岁基层青年群体,注重"真实感"和"态度感"',
adopted: true,
notes: '已采纳为策略基础',
},
{
agentId: 'narrative',
agentName: 'NarrativeAgent',
agentRole: '叙事策略专家',
color: 'purple',
coreOpinion: '应采用"真实、有态度的青年故事"叙事框架',
adopted: true,
notes: '核心建议,已采纳',
},
{
agentId: 'risk',
agentName: 'RiskAgent',
agentRole: '风险评估专家',
color: 'red',
coreOpinion: '需注意UGC质量不稳定和话题热度维持的风险',
adopted: true,
notes: '需在执行阶段重点关注',
},
];
// 分歧与待确认数据
const disputes = [
{
id: 1,
issue: '叙事角度选择',
description: '人物叙事 vs 事件叙事',
viewpoints: [
{ agent: 'NarrativeAgent', opinion: '人物叙事更有情感穿透力', position: '支持人物叙事' },
{ agent: 'GoalAgent', opinion: '事件叙事更聚焦目标达成', position: '支持事件叙事' },
],
status: '待确认',
},
{
id: 2,
issue: '渠道策略选择',
description: '短视频平台 vs 社交媒体',
viewpoints: [
{ agent: 'AudienceAgent', opinion: '核心受众在短视频平台活跃度更高', position: '支持短视频平台' },
{ agent: 'GoalAgent', opinion: '品牌传播需要社交媒体的裂变效应', position: '支持社交媒体' },
],
status: '待确认',
},
{
id: 3,
issue: '风险应对方案',
description: '内容质量控制机制',
viewpoints: [
{ agent: 'RiskAgent', opinion: '需要建立严格的内容审核机制', position: '强调审核' },
{ agent: 'NarrativeAgent', opinion: '审核流程可能影响内容时效性', position: '担忧时效' },
],
status: '需进一步讨论',
},
];
const getColorClasses = (color: string) => {
const colors: Record<string, { bg: string; border: string; text: string }> = {
blue: { bg: isDark ? 'bg-blue-500/20' : 'bg-blue-50', border: isDark ? 'border-blue-500/50' : 'border-blue-200', text: isDark ? 'text-blue-400' : 'text-blue-600' },
cyan: { bg: isDark ? 'bg-cyan-500/20' : 'bg-cyan-50', border: isDark ? 'border-cyan-500/50' : 'border-cyan-200', text: isDark ? 'text-cyan-400' : 'text-cyan-600' },
green: { bg: isDark ? 'bg-green-500/20' : 'bg-green-50', border: isDark ? 'border-green-500/50' : 'border-green-200', text: isDark ? 'text-green-400' : 'text-green-600' },
yellow: { bg: isDark ? 'bg-yellow-500/20' : 'bg-yellow-50', border: isDark ? 'border-yellow-500/50' : 'border-yellow-200', text: isDark ? 'text-yellow-400' : 'text-yellow-600' },
purple: { bg: isDark ? 'bg-purple-500/20' : 'bg-purple-50', border: isDark ? 'border-purple-500/50' : 'border-purple-200', text: isDark ? 'text-purple-400' : 'text-purple-600' },
red: { bg: isDark ? 'bg-red-500/20' : 'bg-red-50', border: isDark ? 'border-red-500/50' : 'border-red-200', text: isDark ? 'text-red-400' : 'text-red-600' },
};
return colors[color] || colors.blue;
};
return (
<div className="space-y-6">
{/* 顶部总览区 */}
<div className={`${cardBg} backdrop-blur-xl ${cardBorder} rounded-xl p-5`}>
<div className="flex items-center justify-between mb-4">
<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-red-200'}`} />
<div>
<h1 className={`${textPrimary} font-bold text-lg`}>五四青年节宣传策略会商</h1>
<p className={`${textMuted} text-sm`}>会商结果 · 已完成</p>
</div>
</div>
<div className="flex items-center gap-3">
<span className={`px-3 py-1.5 ${isDark ? 'bg-green-500/20 text-green-400' : 'bg-green-100 text-green-700'} rounded-full text-sm font-medium flex items-center gap-1.5`}>
<CheckCircle className="w-4 h-4" />
会商完成
</span>
</div>
</div>
{/* 基本信息 */}
<div className={`grid grid-cols-4 gap-4 p-4 ${inputBg} rounded-lg`}>
<div className="text-center">
<p className={`${textMuted} text-xs mb-1`}>参与智能体</p>
<p className={`${textPrimary} font-medium text-lg`}>4</p>
</div>
<div className="text-center">
<p className={`${textMuted} text-xs mb-1`}>会商轮数</p>
<p className={`${textPrimary} font-medium text-lg`}>3</p>
</div>
<div className="text-center">
<p className={`${textMuted} text-xs mb-1`}>完成时间</p>
<p className={`${textPrimary} font-medium text-lg`}>2026-03-19</p>
</div>
<div className="text-center">
<p className={`${textMuted} text-xs mb-1`}>采纳观点</p>
<p className={`${isDark ? 'text-green-400' : 'text-green-600'} font-medium text-lg`}>4/4</p>
</div>
</div>
{/* 操作按钮 */}
<div className="flex items-center gap-3 mt-4">
<button className={`flex items-center gap-2 px-4 py-2 ${btnSecondary} rounded-lg text-sm transition-colors`}>
<Download className="w-4 h-4" />
导出纪要
</button>
<button className={`flex items-center gap-2 px-4 py-2 ${btnSecondary} rounded-lg text-sm transition-colors`}>
<FileText className="w-4 h-4" />
导出结论
</button>
<button className={`flex items-center gap-2 px-4 py-2 ${isDark ? 'bg-cyan-600/20 hover:bg-cyan-600/30 border-cyan-500/50 text-cyan-400' : 'bg-cyan-100 hover:bg-cyan-200 border-cyan-300 text-cyan-700'} rounded-lg text-sm transition-colors`}>
<Bot className="w-4 h-4" />
转为单智能体任务
</button>
<button className={`flex items-center gap-2 px-4 py-2 ${isDark ? 'bg-purple-600/20 hover:bg-purple-600/30 border-purple-500/50 text-purple-400' : 'bg-purple-100 hover:bg-purple-200 border-purple-300 text-purple-700'} rounded-lg text-sm transition-colors`}>
<Zap className="w-4 h-4" />
转为场景任务
</button>
<button className={`flex items-center gap-2 px-4 py-2 ${btnPrimary} text-white rounded-lg text-sm transition-colors ml-auto`}>
<RefreshCw className="w-4 h-4" />
重新发起会商
</button>
</div>
</div>
{/* Tab切换 */}
<div className={`flex items-center gap-1 ${cardBg} p-1 rounded-lg w-fit`}>
<button
onClick={() => setActiveTab('conclusion')}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
activeTab === 'conclusion'
? `${btnPrimary} text-white`
: `${textSecondary} ${isDark ? 'hover:text-white hover:bg-slate-700/50' : 'hover:text-gray-900 hover:bg-gray-200'}`
}`}
>
会商结论
</button>
<button
onClick={() => setActiveTab('opinions')}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
activeTab === 'opinions'
? `${btnPrimary} text-white`
: `${textSecondary} ${isDark ? 'hover:text-white hover:bg-slate-700/50' : 'hover:text-gray-900 hover:bg-gray-200'}`
}`}
>
观点汇总
</button>
<button
onClick={() => setActiveTab('disputes')}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
activeTab === 'disputes'
? `${btnPrimary} text-white`
: `${textSecondary} ${isDark ? 'hover:text-white hover:bg-slate-700/50' : 'hover:text-gray-900 hover:bg-gray-200'}`
}`}
>
分歧与待确认
</button>
</div>
{/* 内容区 */}
{activeTab === 'conclusion' && (
<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`}>
<CheckCircle className={`w-4 h-4 ${isDark ? 'text-green-400' : 'text-green-600'}`} />
本次会商结论
</h3>
<div className={`p-4 ${isDark ? 'bg-green-500/10 border-green-500/30' : 'bg-green-50 border-green-200'} rounded-lg`}>
<p className={`${isDark ? 'text-green-300' : 'text-green-800'} leading-relaxed`}>{conclusion.mainConclusion}</p>
</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`}>
<Check className={`w-4 h-4 ${isDark ? 'text-green-400' : 'text-green-600'}`} />
推荐采用方向
</h3>
<div className="space-y-2">
{conclusion.recommendedDirection.map((item, index) => (
<div key={index} className={`flex items-center gap-3 p-3 ${isDark ? 'bg-green-500/10 border-green-500/30' : 'bg-green-50 border-green-200'} rounded-lg`}>
<div className={`w-6 h-6 rounded-full ${isDark ? 'bg-green-500/20' : 'bg-green-100'} flex items-center justify-center`}>
<Check className={`w-4 h-4 ${isDark ? 'text-green-400' : 'text-green-600'}`} />
</div>
<p className={`${isDark ? 'text-green-300' : 'text-green-800'} text-sm`}>{item}</p>
</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`}>
<X className={`w-4 h-4 ${isDark ? 'text-red-400' : 'text-red-600'}`} />
不建议采用方向
</h3>
<div className="space-y-2">
{conclusion.notRecommendedDirection.map((item, index) => (
<div key={index} className={`flex items-center gap-3 p-3 ${isDark ? 'bg-red-500/10 border-red-500/30' : 'bg-red-50 border-red-200'} rounded-lg`}>
<div className={`w-6 h-6 rounded-full ${isDark ? 'bg-red-500/20' : 'bg-red-100'} flex items-center justify-center`}>
<X className={`w-4 h-4 ${isDark ? 'text-red-400' : 'text-red-600'}`} />
</div>
<p className={`${isDark ? 'text-red-300' : 'text-red-800'} text-sm`}>{item}</p>
</div>
))}
</div>
</div>
</div>
{/* 侧边栏 */}
<div className="col-span-4 space-y-6">
<div className={`${cardBg} backdrop-blur-xl ${isDark ? 'border-yellow-800/30' : 'border-yellow-200'} rounded-xl p-5`}>
<h3 className={`${textPrimary} font-semibold text-sm mb-4 flex items-center gap-2`}>
<AlertCircle className={`w-4 h-4 ${isDark ? 'text-yellow-400' : 'text-yellow-600'}`} />
待确认问题
</h3>
<div className="space-y-2">
{conclusion.pendingIssues.map((item, index) => (
<div key={index} className={`p-3 ${isDark ? 'bg-yellow-500/10 border-yellow-500/30' : 'bg-yellow-50 border-yellow-200'} rounded-lg`}>
<p className={`${isDark ? 'text-yellow-300' : 'text-yellow-800'} text-sm`}>{item}</p>
</div>
))}
</div>
</div>
<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-4 flex items-center gap-2`}>
<ChevronRight className={`w-4 h-4 ${isDark ? 'text-purple-400' : 'text-purple-600'}`} />
推荐下一步动作
</h3>
<div className="space-y-2">
{conclusion.nextSteps.map((item, index) => (
<div key={index} className={`p-3 ${isDark ? 'bg-purple-500/10 border-purple-500/30' : 'bg-purple-50 border-purple-200'} rounded-lg`}>
<p className={`${isDark ? 'text-purple-300' : 'text-purple-800'} text-sm`}>{item}</p>
</div>
))}
</div>
<button className={`w-full mt-4 flex items-center justify-center gap-2 px-4 py-3 ${btnPrimary} text-white rounded-xl text-sm font-medium transition-colors`}>
<Zap className="w-4 h-4" />
转入宣传策略生成场景
</button>
</div>
</div>
</div>
)}
{activeTab === 'opinions' && (
<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`}>
<Bot className={`w-4 h-4 ${isDark ? 'text-cyan-400' : 'text-cyan-600'}`} />
各智能体观点汇总
</h3>
<div className="space-y-4">
{agentOpinions.map((agent) => {
const colors = getColorClasses(agent.color);
return (
<div key={agent.agentId} className={`p-4 ${inputBg} ${inputBorder} rounded-xl`}>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-lg ${colors.bg} flex items-center justify-center`}>
<Bot className={`w-5 h-5 ${colors.text}`} />
</div>
<div>
<div className="flex items-center gap-2">
<p className={`text-sm font-medium ${colors.text}`}>{agent.agentName}</p>
{agent.adopted ? (
<span className={`px-1.5 py-0.5 ${isDark ? 'bg-green-500/20 text-green-400' : 'bg-green-100 text-green-700'} rounded text-xs`}>
已采纳
</span>
) : (
<span className={`px-1.5 py-0.5 ${isDark ? 'bg-slate-500/20 text-slate-400' : 'bg-gray-200 text-gray-600'} rounded text-xs`}>
未采纳
</span>
)}
</div>
<p className={`${textMuted} text-xs`}>{agent.agentRole}</p>
</div>
</div>
</div>
<div className="pl-13">
<p className={`${textSecondary} text-sm mb-2`}>{agent.coreOpinion}</p>
<p className={`${textMuted} text-xs`}>备注:{agent.notes}</p>
</div>
</div>
);
})}
</div>
</div>
)}
{activeTab === 'disputes' && (
<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`}>
<AlertCircle className={`w-4 h-4 ${isDark ? 'text-yellow-400' : 'text-yellow-600'}`} />
争议点与待确认事项
</h3>
<div className="space-y-4">
{disputes.map((dispute) => (
<div key={dispute.id} className={`p-4 ${inputBg} ${isDark ? 'border-yellow-800/30' : 'border-yellow-200'} rounded-xl`}>
<div className="flex items-center justify-between mb-3">
<div>
<p className={`${textPrimary} font-medium text-sm`}>{dispute.issue}</p>
<p className={`${textMuted} text-xs mt-0.5`}>{dispute.description}</p>
</div>
<span className={`px-2 py-1 ${isDark ? 'bg-yellow-500/20 text-yellow-400' : 'bg-yellow-100 text-yellow-700'} rounded text-xs`}>
{dispute.status}
</span>
</div>
<div className="space-y-2">
{dispute.viewpoints.map((viewpoint, index) => (
<div key={index} className={`p-3 ${isDark ? 'bg-yellow-500/5 border-yellow-500/20' : 'bg-yellow-50 border-yellow-200'} rounded-lg`}>
<div className="flex items-center gap-2 mb-1">
<Bot className={`w-4 h-4 ${textMuted}`} />
<span className={`${textMuted} text-xs`}>{viewpoint.agent}</span>
</div>
<p className={`${isDark ? 'text-yellow-300' : 'text-yellow-800'} text-sm`}>{viewpoint.opinion}</p>
</div>
))}
</div>
<button className={`mt-3 flex items-center gap-1 px-3 py-1.5 ${isDark ? 'bg-yellow-500/20 hover:bg-yellow-500/30 border-yellow-500/50 text-yellow-400' : 'bg-yellow-100 hover:bg-yellow-200 border-yellow-300 text-yellow-700'} rounded text-xs transition-colors`}>
<Target className="w-3 h-3" />
发起单智能体分析
</button>
</div>
))}
</div>
</div>
)}
</div>
);
};
export default TaskConsultResult;
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,931 @@
import React, { useState } from "react";
import {
FileText,
Search,
Filter,
Download,
Target,
Clock,
CheckCircle,
AlertTriangle,
Plus,
TrendingUp,
TrendingDown,
ChevronRight,
User,
Eye,
} from "lucide-react";
import { useTheme } from "@/contexts/ThemeContext";
import { PageType } from "@/components/Sidebar";
interface TaskDashboardProps {
onNavigate: (page: PageType) => void;
}
const TaskDashboard: React.FC<TaskDashboardProps> = ({ 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-100";
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-400";
const inputBg = isDark ? "bg-slate-900/50" : "bg-white";
const [timeRange, setTimeRange] = useState<string>("month");
const [categoryFilter, setCategoryFilter] = useState<string>("all");
const [statusFilter, setStatusFilter] = useState<string>("all");
// 统计数据
const stats = {
total: 1286,
inProgress: 326,
completed: 842,
delayed: 57,
weeklyNew: 93,
priorityPending: 41,
};
// 二级任务分类数据
const categoryData = [
{ name: "XK任务", count: 320, percentage: 25 },
{ name: "规划任务", count: 240, percentage: 19 },
{ name: "MNTY任务", count: 280, percentage: 22 },
{ name: "效果PG任务", count: 300, percentage: 23 },
];
// 三级任务数据
const thirdLevelData = [
{ name: "任务JC", count: 120 },
{ name: "任务调度", count: 200 },
{ name: "任务FX", count: 140 },
{ name: "行动CE", count: 100 },
{ name: "XDTY任务", count: 70 },
{ name: "方案TY任务", count: 120 },
{ name: "XXTY任务", count: 90 },
{ name: "任务PG", count: 160 },
{ name: "行动PG", count: 80 },
{ name: "信息PG", count: 130 },
{ name: "账号PG", count: 60 },
];
// 预警任务列表
const warningTasks = [
{
id: "001",
name: "五四宣传执行任务",
category: "规划任务 / 行动CE",
status: "已延期",
dueDate: "04-20",
riskLevel: "high",
},
{
id: "002",
name: "青年传播调度任务",
category: "XK任务 / 任务调度",
status: "待处理",
dueDate: "04-18",
riskLevel: "medium",
},
{
id: "003",
name: "效果复盘信息任务",
category: "效果PG / 信息PG",
status: "阻塞中",
dueDate: "04-17",
riskLevel: "high",
},
];
// 我的待办
const myTodos = {
pending: 8,
pendingConfirm: 3,
pendingReview: 2,
};
// Top 5 排行
const top5ByVolume = [
{ name: "任务PG", count: 160 },
{ name: "信息PG", count: 130 },
{ name: "任务FX", count: 120 },
{ name: "任务调度", count: 100 },
{ name: "方案TY任务", count: 90 },
];
const top5ByDelay = [
{ name: "行动CE", count: 15 },
{ name: "任务调度", count: 12 },
{ name: "信息PG", count: 10 },
{ name: "XDTY任务", count: 8 },
{ name: "账号PG", count: 6 },
];
// 完成率对比
const completionRates = [
{ name: "XK任务", rate: 72 },
{ name: "规划任务", rate: 61 },
{ name: "MNTY任务", rate: 68 },
{ name: "效果PG任务", rate: 54 },
];
return (
<div className="h-full flex flex-col">
{/* 顶部筛选区 */}
<div
className={`rounded-xl p-4 flex-shrink-0 mb-4 ${cardBg} border ${cardBorder}`}
>
<div className="flex items-center justify-between">
<div>
<h1 className={`text-xl font-bold ${textPrimary}`}>任务统计总览</h1>
<p className={`text-sm ${textMuted}`}>
面向分级分类任务体系的全局监控看板
</p>
</div>
<div className="flex items-center gap-3">
<select
value={timeRange}
onChange={(e) => setTimeRange(e.target.value)}
className={`px-4 py-2 border rounded-lg text-sm cursor-pointer ${inputBg} ${cardBorder} ${textSecondary}`}
>
<option value="day">今日</option>
<option value="week">本周</option>
<option value="month">本月</option>
<option value="quarter">本季度</option>
<option value="custom">自定义</option>
</select>
<select
value={categoryFilter}
onChange={(e) => setCategoryFilter(e.target.value)}
className={`px-4 py-2 border rounded-lg text-sm cursor-pointer ${inputBg} ${cardBorder} ${textSecondary}`}
>
<option value="all">全部分类</option>
<option value="xk">XK任务</option>
<option value="plan">规划任务</option>
<option value="mnty">MNTY任务</option>
<option value="effect">效果PG任务</option>
</select>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className={`px-4 py-2 border rounded-lg text-sm cursor-pointer ${inputBg} ${cardBorder} ${textSecondary}`}
>
<option value="all">全部状态</option>
<option value="pending">未开始</option>
<option value="in-progress">进行中</option>
<option value="completed">已完成</option>
<option value="delayed">延期</option>
</select>
<button
className={`flex items-center gap-2 px-4 py-2 border rounded-lg text-sm transition-colors ${
isDark
? "border-blue-800/30 hover:bg-slate-700/50 text-slate-300"
: "border-red-200 hover:bg-red-50 text-red-600"
}`}
>
<Download className="w-4 h-4" />
导出统计
</button>
<button
onClick={() => onNavigate("plan-task-fx")}
className={`flex items-center gap-2 px-4 py-2 text-white rounded-lg text-sm font-medium transition-colors ${
isDark
? "bg-blue-600 hover:bg-blue-500"
: "bg-red-600 hover:bg-red-500"
}`}
>
查看全部任务
</button>
</div>
</div>
</div>
{/* 第一行:核心指标卡 */}
<div className="grid grid-cols-6 gap-4 mb-4 flex-shrink-0">
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<div className="flex items-center gap-2 mb-2">
<div
className={`w-8 h-8 rounded-lg flex items-center justify-center ${
isDark ? "bg-blue-500/20" : "bg-red-100"
}`}
>
<Target
className={`w-4 h-4 ${
isDark ? "text-blue-400" : "text-red-500"
}`}
/>
</div>
<span className={`text-sm ${textSecondary}`}>任务总数</span>
</div>
<p className={`text-3xl font-bold ${textPrimary}`}>1,286</p>
<div className="flex items-center gap-1 mt-1">
<TrendingUp
className={`w-3 h-3 ${
isDark ? "text-green-400" : "text-green-500"
}`}
/>
<span
className={`text-xs ${
isDark ? "text-green-400" : "text-green-500"
}`}
>
较上周 +8%
</span>
</div>
</div>
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<div className="flex items-center gap-2 mb-2">
<div
className={`w-8 h-8 rounded-lg flex items-center justify-center ${
isDark ? "bg-cyan-500/20" : "bg-red-100"
}`}
>
<Clock
className={`w-4 h-4 ${
isDark ? "text-cyan-400" : "text-red-500"
}`}
/>
</div>
<span className={`text-sm ${textSecondary}`}>进行中</span>
</div>
<p className={`text-3xl font-bold ${textPrimary}`}>326</p>
<div className="flex items-center gap-1 mt-1">
<span className={`text-xs ${textMuted}`}>占比 25.4%</span>
</div>
</div>
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<div className="flex items-center gap-2 mb-2">
<div
className={`w-8 h-8 rounded-lg flex items-center justify-center ${
isDark ? "bg-green-500/20" : "bg-green-100"
}`}
>
<CheckCircle
className={`w-4 h-4 ${
isDark ? "text-green-400" : "text-green-500"
}`}
/>
</div>
<span className={`text-sm ${textSecondary}`}>已完成</span>
</div>
<p className={`text-3xl font-bold ${textPrimary}`}>842</p>
<div className="flex items-center gap-1 mt-1">
<span
className={`text-xs ${
isDark ? "text-green-400" : "text-green-500"
}`}
>
完成率 65.5%
</span>
</div>
</div>
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<div className="flex items-center gap-2 mb-2">
<div
className={`w-8 h-8 rounded-lg flex items-center justify-center ${
isDark ? "bg-red-500/20" : "bg-orange-100"
}`}
>
<AlertTriangle
className={`w-4 h-4 ${
isDark ? "text-red-400" : "text-orange-500"
}`}
/>
</div>
<span className={`text-sm ${textSecondary}`}>延期任务</span>
</div>
<p
className={`text-3xl font-bold ${
isDark ? "text-red-400" : "text-orange-600"
}`}
>
57
</p>
<div className="flex items-center gap-1 mt-1">
<span
className={`text-xs ${
isDark ? "text-red-400" : "text-orange-500"
}`}
>
需重点关注
</span>
</div>
</div>
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<div className="flex items-center gap-2 mb-2">
<div
className={`w-8 h-8 rounded-lg flex items-center justify-center ${
isDark ? "bg-purple-500/20" : "bg-red-100"
}`}
>
<Plus
className={`w-4 h-4 ${
isDark ? "text-purple-400" : "text-red-500"
}`}
/>
</div>
<span className={`text-sm ${textSecondary}`}>本周新增</span>
</div>
<p className={`text-3xl font-bold ${textPrimary}`}>93</p>
<div className="flex items-center gap-1 mt-1">
<TrendingUp
className={`w-3 h-3 ${
isDark ? "text-green-400" : "text-green-500"
}`}
/>
<span
className={`text-xs ${
isDark ? "text-green-400" : "text-green-500"
}`}
>
较上周 +12
</span>
</div>
</div>
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<div className="flex items-center gap-2 mb-2">
<div
className={`w-8 h-8 rounded-lg flex items-center justify-center ${
isDark ? "bg-yellow-500/20" : "bg-yellow-100"
}`}
>
<AlertTriangle
className={`w-4 h-4 ${
isDark ? "text-yellow-400" : "text-yellow-600"
}`}
/>
</div>
<span className={`text-sm ${textSecondary}`}>重点待处理</span>
</div>
<p className={`text-3xl font-bold ${textPrimary}`}>41</p>
<div className="flex items-center gap-1 mt-1">
<span className={`text-xs ${textMuted}`}>高优先级 12</span>
</div>
</div>
</div>
{/* 第二行:分类分布区 */}
<div className="grid grid-cols-3 gap-4 mb-4 flex-shrink-0">
{/* 左侧:二级任务分类分布 */}
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<h3 className={`font-semibold mb-4 ${textPrimary}`}>
二级任务分类分布
</h3>
<div className="space-y-3">
{categoryData.map((item, index) => (
<div key={index} className="space-y-1">
<div className="flex items-center justify-between">
<span className={`text-sm ${textSecondary}`}>
{item.name}
</span>
<span className={`text-sm font-medium ${textPrimary}`}>
{item.count}
</span>
</div>
<div
className={`h-2 rounded-full overflow-hidden ${
isDark ? "bg-slate-700" : "bg-gray-200"
}`}
>
<div
className={`h-full rounded-full transition-all ${
index === 0
? isDark
? "bg-blue-500"
: "bg-red-500"
: index === 1
? isDark
? "bg-cyan-500"
: "bg-red-400"
: index === 2
? isDark
? "bg-purple-500"
: "bg-red-300"
: isDark
? "bg-green-500"
: "bg-red-200"
}`}
style={{ width: `${item.percentage}%` }}
/>
</div>
</div>
))}
</div>
</div>
{/* 中间:三级任务分布(点击下钻) */}
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<div className="flex items-center justify-between mb-4">
<h3 className={`font-semibold ${textPrimary}`}>三级任务分布</h3>
<select
className={`text-xs px-2 py-1 rounded ${inputBg} ${cardBorder} ${textMuted}`}
>
<option value="plan">规划任务</option>
<option value="xk">XK任务</option>
<option value="mnty">MNTY任务</option>
<option value="effect">效果PG任务</option>
</select>
</div>
<div className="space-y-2">
{thirdLevelData.slice(0, 5).map((item, index) => (
<div key={index} className="flex items-center gap-2">
<span className={`text-xs w-16 ${textMuted}`}>{item.name}</span>
<div
className={`flex-1 h-4 rounded overflow-hidden ${
isDark ? "bg-slate-700" : "bg-gray-200"
}`}
>
<div
className={`h-full rounded ${
index === 0
? isDark
? "bg-blue-500"
: "bg-red-500"
: index === 1
? isDark
? "bg-cyan-500"
: "bg-red-400"
: index === 2
? isDark
? "bg-purple-500"
: "bg-red-300"
: index === 3
? isDark
? "bg-green-500"
: "bg-red-200"
: isDark
? "bg-yellow-500"
: "bg-red-100"
}`}
style={{ width: `${(item.count / 200) * 100}%` }}
/>
</div>
<span
className={`text-xs font-medium w-8 text-right ${textPrimary}`}
>
{item.count}
</span>
</div>
))}
</div>
</div>
{/* 右侧:三级任务数量Top5 */}
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<h3 className={`font-semibold mb-4 ${textPrimary}`}>
三级任务数量 Top 5
</h3>
<div className="space-y-2">
{top5ByVolume.map((item, index) => (
<div key={index} className="flex items-center gap-2">
<span
className={`w-5 h-5 rounded-full flex items-center justify-center text-xs font-bold ${
index === 0
? isDark
? "bg-yellow-500/20 text-yellow-400"
: "bg-yellow-100 text-yellow-600"
: index === 1
? isDark
? "bg-slate-500/20 text-slate-400"
: "bg-gray-200 text-gray-600"
: index === 2
? isDark
? "bg-orange-500/20 text-orange-400"
: "bg-orange-100 text-orange-600"
: isDark
? "bg-slate-600/20 text-slate-500"
: "bg-gray-100 text-gray-500"
}`}
>
{index + 1}
</span>
<span className={`flex-1 text-sm ${textSecondary}`}>
{item.name}
</span>
<span className={`text-sm font-medium ${textPrimary}`}>
{item.count}
</span>
</div>
))}
</div>
</div>
</div>
{/* 第三行:状态与趋势分析区 */}
<div className="grid grid-cols-3 gap-4 mb-4 flex-shrink-0">
{/* 左侧:任务状态分布 */}
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<h3 className={`font-semibold mb-4 ${textPrimary}`}>任务状态分布</h3>
<div className="space-y-3">
{categoryData.map((item, index) => (
<div key={index}>
<div className="flex items-center justify-between mb-1">
<span className={`text-xs ${textMuted}`}>{item.name}</span>
</div>
<div className="flex gap-1">
<div
className={`h-3 flex-1 rounded-l ${
isDark ? "bg-slate-700" : "bg-gray-200"
}`}
style={{ width: "30%" }}
/>
<div
className={`h-3 flex-1 ${
isDark ? "bg-blue-500" : "bg-red-400"
}`}
style={{ width: "25%" }}
/>
<div
className={`h-3 flex-1 ${
isDark ? "bg-green-500" : "bg-green-400"
}`}
style={{ width: "35%" }}
/>
<div
className={`h-3 flex-1 rounded-r ${
isDark ? "bg-red-500" : "bg-orange-400"
}`}
style={{ width: "10%" }}
/>
</div>
</div>
))}
</div>
<div className="flex items-center justify-center gap-4 mt-4 pt-4 border-t ${cardBorder}">
<div className="flex items-center gap-1">
<div
className={`w-2 h-2 rounded ${
isDark ? "bg-slate-600" : "bg-gray-400"
}`}
/>
<span className={`text-xs ${textMuted}`}>未开始</span>
</div>
<div className="flex items-center gap-1">
<div
className={`w-2 h-2 rounded ${
isDark ? "bg-blue-500" : "bg-red-400"
}`}
/>
<span className={`text-xs ${textMuted}`}>进行中</span>
</div>
<div className="flex items-center gap-1">
<div
className={`w-2 h-2 rounded ${
isDark ? "bg-green-500" : "bg-green-400"
}`}
/>
<span className={`text-xs ${textMuted}`}>已完成</span>
</div>
<div className="flex items-center gap-1">
<div
className={`w-2 h-2 rounded ${
isDark ? "bg-red-500" : "bg-orange-400"
}`}
/>
<span className={`text-xs ${textMuted}`}>延期</span>
</div>
</div>
</div>
{/* 中间:任务趋势图(简化版) */}
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<div className="flex items-center justify-between mb-4">
<h3 className={`font-semibold ${textPrimary}`}>
任务趋势(近30天)
</h3>
<div className="flex gap-2">
<button
className={`px-2 py-1 rounded text-xs ${
timeRange === "7d"
? isDark
? "bg-blue-500/20 text-blue-400"
: "bg-red-100 text-red-600"
: textMuted
}`}
>
近7天
</button>
<button
className={`px-2 py-1 rounded text-xs ${
timeRange === "30d"
? isDark
? "bg-blue-500/20 text-blue-400"
: "bg-red-100 text-red-600"
: textMuted
}`}
>
近30天
</button>
</div>
</div>
<div className="h-32 flex items-end justify-between gap-2">
{[65, 72, 68, 75, 80, 78, 85, 82, 88, 92, 87, 95].map(
(value, index) => (
<div
key={index}
className="flex-1 flex flex-col items-center gap-1"
>
<div
className={`w-full rounded-t transition-all ${
isDark ? "bg-blue-500/50" : "bg-red-300"
}`}
style={{ height: `${value}%` }}
/>
</div>
)
)}
</div>
<div className="flex items-center justify-center gap-4 mt-2">
<div className="flex items-center gap-1">
<div
className={`w-3 h-1 rounded ${
isDark ? "bg-blue-500" : "bg-red-500"
}`}
/>
<span className={`text-xs ${textMuted}`}>新增任务</span>
</div>
<div className="flex items-center gap-1">
<div
className={`w-3 h-1 rounded ${
isDark ? "bg-green-500" : "bg-green-500"
}`}
/>
<span className={`text-xs ${textMuted}`}>完成任务</span>
</div>
</div>
</div>
{/* 右侧:分类完成率对比 */}
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<h3 className={`font-semibold mb-4 ${textPrimary}`}>
分类完成率对比
</h3>
<div className="space-y-4">
{completionRates.map((item, index) => (
<div key={index}>
<div className="flex items-center justify-between mb-1">
<span className={`text-sm ${textSecondary}`}>
{item.name}
</span>
<span
className={`text-sm font-medium ${
isDark ? "text-blue-400" : "text-red-600"
}`}
>
{item.rate}%
</span>
</div>
<div
className={`h-2 rounded-full overflow-hidden ${
isDark ? "bg-slate-700" : "bg-gray-200"
}`}
>
<div
className={`h-full rounded-full transition-all ${
isDark ? "bg-blue-500" : "bg-red-500"
}`}
style={{ width: `${item.rate}%` }}
/>
</div>
</div>
))}
</div>
</div>
</div>
{/* 第四行:预警与行动区 */}
<div className="grid grid-cols-3 gap-4 flex-shrink-0">
{/* 左侧:预警任务列表 */}
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<div className="flex items-center justify-between mb-4">
<h3 className={`font-semibold ${textPrimary}`}>预警任务</h3>
<button
className={`text-xs ${
isDark
? "text-blue-400 hover:text-blue-300"
: "text-red-500 hover:text-red-600"
}`}
>
查看全部
</button>
</div>
<div className="space-y-3">
{warningTasks.map((task) => (
<div
key={task.id}
className={`p-3 rounded-lg border cursor-pointer transition-all hover:shadow-md ${
task.riskLevel === "high"
? isDark
? "bg-red-500/10 border-red-500/30"
: "bg-orange-50 border-orange-200"
: isDark
? "bg-yellow-500/10 border-yellow-500/30"
: "bg-yellow-50 border-yellow-200"
}`}
>
<div className="flex items-start justify-between mb-1">
<div className="flex items-center gap-2">
<AlertTriangle
className={`w-4 h-4 ${
task.riskLevel === "high"
? isDark
? "text-red-400"
: "text-orange-500"
: isDark
? "text-yellow-400"
: "text-yellow-500"
}`}
/>
<span className={`text-sm font-medium ${textPrimary}`}>
{task.name}
</span>
</div>
<span className={`text-xs ${textMuted}`}>
截止:{task.dueDate}
</span>
</div>
<div className="flex items-center gap-2">
<span
className={`text-xs px-2 py-0.5 rounded ${
isDark
? "bg-slate-700/50 text-slate-400"
: "bg-gray-100 text-gray-500"
}`}
>
{task.category}
</span>
<span
className={`text-xs ${
task.status === "已延期"
? isDark
? "text-red-400"
: "text-orange-500"
: textMuted
}`}
>
{task.status}
</span>
</div>
</div>
))}
</div>
</div>
{/* 中间:重点分类排行 */}
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<h3 className={`font-semibold mb-4 ${textPrimary}`}>重点排行</h3>
<div className="space-y-4">
<div>
<div className="flex items-center gap-2 mb-2">
<TrendingUp
className={`w-4 h-4 ${
isDark ? "text-blue-400" : "text-red-500"
}`}
/>
<span className={`text-sm font-medium ${textSecondary}`}>
任务量 Top 5
</span>
</div>
<div className="space-y-1">
{top5ByVolume.map((item, index) => (
<div key={index} className="flex items-center gap-2">
<span className={`w-4 text-xs ${textMuted}`}>
{index + 1}.
</span>
<span className={`flex-1 text-xs ${textSecondary}`}>
{item.name}
</span>
<span className={`text-xs font-medium ${textPrimary}`}>
{item.count}
</span>
</div>
))}
</div>
</div>
<div className={`pt-3 border-t ${cardBorder}`}>
<div className="flex items-center gap-2 mb-2">
<AlertTriangle
className={`w-4 h-4 ${
isDark ? "text-red-400" : "text-orange-500"
}`}
/>
<span className={`text-sm font-medium ${textSecondary}`}>
延期量 Top 5
</span>
</div>
<div className="space-y-1">
{top5ByDelay.map((item, index) => (
<div key={index} className="flex items-center gap-2">
<span className={`w-4 text-xs ${textMuted}`}>
{index + 1}.
</span>
<span className={`flex-1 text-xs ${textSecondary}`}>
{item.name}
</span>
<span
className={`text-xs font-medium ${
isDark ? "text-red-400" : "text-orange-500"
}`}
>
{item.count}
</span>
</div>
))}
</div>
</div>
</div>
</div>
{/* 右侧:我的待办/快捷入口 */}
<div className={`rounded-xl p-4 border ${cardBg} ${cardBorder}`}>
<h3 className={`font-semibold mb-4 ${textPrimary}`}>我的待办</h3>
<div className="grid grid-cols-3 gap-3 mb-4">
<div
className={`p-3 rounded-lg text-center ${
isDark ? "bg-slate-700/50" : "bg-red-50"
}`}
>
<p className={`text-2xl font-bold ${textPrimary}`}>
{myTodos.pending}
</p>
<p className={`text-xs ${textMuted}`}>待处理</p>
</div>
<div
className={`p-3 rounded-lg text-center ${
isDark ? "bg-slate-700/50" : "bg-yellow-50"
}`}
>
<p className={`text-2xl font-bold ${textPrimary}`}>
{myTodos.pendingConfirm}
</p>
<p className={`text-xs ${textMuted}`}>待确认</p>
</div>
<div
className={`p-3 rounded-lg text-center ${
isDark ? "bg-slate-700/50" : "bg-blue-50"
}`}
>
<p className={`text-2xl font-bold ${textPrimary}`}>
{myTodos.pendingReview}
</p>
<p className={`text-xs ${textMuted}`}>待复核</p>
</div>
</div>
<div className="space-y-2">
<button
onClick={() => onNavigate("task-create")}
className={`w-full flex items-center justify-center gap-2 px-4 py-2 text-white rounded-lg text-sm font-medium transition-colors ${
isDark
? "bg-blue-600 hover:bg-blue-500"
: "bg-red-600 hover:bg-red-500"
}`}
>
<Plus className="w-4 h-4" />
新建任务
</button>
<button
onClick={() => onNavigate("plan-task-fx")}
className={`w-full flex items-center justify-center gap-2 px-4 py-2 border rounded-lg text-sm font-medium transition-colors ${
isDark
? "border-blue-800/30 hover:bg-slate-700/50 text-slate-300"
: "border-red-200 hover:bg-red-50 text-red-600"
}`}
>
<FileText className="w-4 h-4" />
进入任务中心
</button>
<button
className={`w-full flex items-center justify-center gap-2 px-4 py-2 border rounded-lg text-sm font-medium transition-colors ${
isDark
? "border-blue-800/30 hover:bg-slate-700/50 text-slate-300"
: "border-red-200 hover:bg-red-50 text-red-600"
}`}
>
<AlertTriangle className="w-4 h-4" />
查看延期任务
</button>
<button
className={`w-full flex items-center justify-center gap-2 px-4 py-2 border rounded-lg text-sm font-medium transition-colors ${
isDark
? "border-blue-800/30 hover:bg-slate-700/50 text-slate-300"
: "border-red-200 hover:bg-red-50 text-red-600"
}`}
>
<User className="w-4 h-4" />
查看我的任务
</button>
</div>
</div>
</div>
</div>
);
};
export default TaskDashboard;
@@ -0,0 +1,602 @@
import React, { useState } from 'react';
import { Plus, Search, Eye, Play, Pause, Trash2, Target, Clock, User, ChevronRight, Filter, Bot, Workflow, Users, MoreHorizontal, Edit, Copy, FileText } from 'lucide-react';
import { useTheme } from '@/contexts/ThemeContext';
import { PageType } from '@/components/Sidebar';
interface TaskListProps {
onViewWorkspace: (id: string) => void;
onNavigate: (page: PageType) => void;
}
interface Task {
id: string;
name: string;
type: 'single' | 'scene' | 'consultation';
agent?: string;
scene?: string;
participants?: string[];
status: 'draft' | 'pending' | 'running' | 'paused' | 'blocked' | 'completed';
currentPhase: string;
summary?: string;
creator: string;
createTime: string;
updateTime: string;
column: 'unanalyzed' | 'analyzing' | 'completed';
}
const TaskList: React.FC<TaskListProps> = ({ onViewWorkspace, onNavigate }) => {
const { theme } = useTheme();
const isDark = theme === 'dark';
const [tasks] = useState<Task[]>([
// 未分析列
{
id: '8',
name: '端午节品牌传播方案',
type: 'scene',
scene: '宣传策略生成',
status: 'draft',
currentPhase: '待配置',
summary: '针对端午节品牌活动制定完整传播策略方案',
creator: '李策划',
createTime: '2026-03-19 16:00',
updateTime: '2026-03-19 16:00',
column: 'unanalyzed',
},
{
id: '9',
name: '新品上市用户调研',
type: 'single',
agent: 'AudienceAgent',
status: 'pending',
currentPhase: '待启动',
summary: '对新品目标用户进行画像分析',
creator: '王运营',
createTime: '2026-03-19 14:30',
updateTime: '2026-03-19 15:00',
column: 'unanalyzed',
},
// 分析中列
{
id: '1',
name: '五四青年节宣传活动策略',
type: 'scene',
scene: '宣传策略生成',
status: 'running',
currentPhase: '行为分析',
summary: '正在按场景模板执行目标、受众、行为、叙事等分析...',
creator: '张策略',
createTime: '2026-03-19 10:30',
updateTime: '2026-03-19 15:45',
column: 'analyzing',
},
{
id: '7',
name: '品牌升级叙事方向研讨',
type: 'consultation',
participants: ['GoalAgent', 'NarrativeAgent', 'RiskAgent'],
status: 'running',
currentPhase: '第2轮讨论中',
summary: '围绕宣传方向、叙事方式和受众切入点开展多视角会商...',
creator: '王策划',
createTime: '2026-03-19 15:00',
updateTime: '2026-03-19 16:30',
column: 'analyzing',
},
{
id: '3',
name: '年度品牌升级方案推演',
type: 'scene',
scene: '宣传方案推演',
status: 'paused',
currentPhase: '叙事分析',
summary: '任务已暂停,等待人工介入确认叙事方向',
creator: '王策划',
createTime: '2026-03-17 14:00',
updateTime: '2026-03-19 11:30',
column: 'analyzing',
},
// 已完成列
{
id: '2',
name: '新产品发布会舆情应对预案',
type: 'scene',
scene: '舆情应对策略',
status: 'completed',
currentPhase: '已完成',
summary: '已完成舆情应对策略方案生成',
creator: '李分析师',
createTime: '2026-03-18 09:00',
updateTime: '2026-03-18 16:20',
column: 'completed',
},
{
id: '4',
name: '五四青年节受众快速分析',
type: 'single',
agent: 'AudienceAgent',
status: 'completed',
currentPhase: '已完成',
summary: '已完成目标受众识别、关切提炼和阻力分析',
creator: '赵运营',
createTime: '2026-03-19 09:00',
updateTime: '2026-03-19 09:15',
column: 'completed',
},
{
id: '6',
name: '五四青年节宣传策略会商',
type: 'consultation',
participants: ['GoalAgent', 'AudienceAgent', 'NarrativeAgent', 'RiskAgent'],
status: 'completed',
currentPhase: '已完成',
summary: '已完成多智能体会商,形成结论与分歧报告',
creator: '赵运营',
createTime: '2026-03-19 14:30',
updateTime: '2026-03-19 16:00',
column: 'completed',
},
]);
const [activeTab, setActiveTab] = useState<'all' | 'unanalyzed' | 'analyzing' | 'completed'>('all');
const [searchQuery, setSearchQuery] = useState('');
const [modeFilter, setModeFilter] = useState<'all' | 'single' | 'scene' | 'consultation'>('all');
const getTypeConfig = (type: string) => {
if (isDark) {
switch (type) {
case 'single':
return { label: '单智能体', color: 'bg-cyan-500/20 text-cyan-400', icon: Bot };
case 'scene':
return { label: '场景执行', color: 'bg-purple-500/20 text-purple-400', icon: Workflow };
case 'consultation':
return { label: '多智能体会商', color: 'bg-orange-500/20 text-orange-400', icon: Users };
default:
return { label: '未知', color: 'bg-slate-500/20 text-slate-400', icon: Bot };
}
} else {
switch (type) {
case 'single':
return { label: '单智能体', color: 'bg-red-100 text-red-600', icon: Bot };
case 'scene':
return { label: '场景执行', color: 'bg-red-100 text-red-600', icon: Workflow };
case 'consultation':
return { label: '多智能体会商', color: 'bg-red-100 text-red-600', icon: Users };
default:
return { label: '未知', color: 'bg-gray-100 text-gray-600', icon: Bot };
}
}
};
const getStatusConfig = (status: string) => {
if (isDark) {
switch (status) {
case 'draft':
return { label: '草稿', color: 'bg-slate-500/20 text-slate-400' };
case 'pending':
return { label: '待启动', color: 'bg-slate-500/20 text-slate-400' };
case 'running':
return { label: '运行中', color: 'bg-blue-500/20 text-blue-400' };
case 'paused':
return { label: '已暂停', color: 'bg-yellow-500/20 text-yellow-400' };
case 'blocked':
return { label: '阻塞中', color: 'bg-red-500/20 text-red-400' };
case 'completed':
return { label: '已完成', color: 'bg-green-500/20 text-green-400' };
default:
return { label: '未知', color: 'bg-slate-500/20 text-slate-400' };
}
} else {
switch (status) {
case 'draft':
return { label: '草稿', color: 'bg-gray-100 text-gray-600' };
case 'pending':
return { label: '待启动', color: 'bg-gray-100 text-gray-600' };
case 'running':
return { label: '运行中', color: 'bg-red-100 text-red-600' };
case 'paused':
return { label: '已暂停', color: 'bg-yellow-100 text-yellow-600' };
case 'blocked':
return { label: '阻塞中', color: 'bg-red-100 text-red-600' };
case 'completed':
return { label: '已完成', color: 'bg-green-100 text-green-600' };
default:
return { label: '未知', color: 'bg-gray-100 text-gray-600' };
}
}
};
const filteredTasks = tasks.filter(task => {
const matchesTab = activeTab === 'all' || task.column === activeTab;
const matchesSearch = task.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
task.id.toLowerCase().includes(searchQuery.toLowerCase());
const matchesMode = modeFilter === 'all' || task.type === modeFilter;
return matchesTab && matchesSearch && matchesMode;
});
const unanalyzedTasks = filteredTasks.filter(t => t.column === 'unanalyzed');
const analyzingTasks = filteredTasks.filter(t => t.column === 'analyzing');
const completedTasks = filteredTasks.filter(t => t.column === 'completed');
const totalCount = tasks.length;
const unanalyzedCount = tasks.filter(t => t.column === 'unanalyzed').length;
const analyzingCount = tasks.filter(t => t.column === 'analyzing').length;
const completedCount = tasks.filter(t => t.column === 'completed').length;
const handleTaskClick = (task: Task) => {
if (task.column === 'completed') {
if (task.type === 'consultation') {
onNavigate('task-consult-result');
} else {
onNavigate('strategy-result');
}
} else if (task.column === 'analyzing') {
if (task.type === 'scene') {
onNavigate('task-scene');
} else if (task.type === 'consultation') {
onNavigate('task-consult');
} else {
onNavigate('task-single');
}
} else {
// 未分析 - 进入创建/配置
onNavigate('task-create');
}
};
const TaskCard = ({ task }: { task: Task }) => {
const typeConfig = getTypeConfig(task.type);
const statusConfig = getStatusConfig(task.status);
const TypeIcon = typeConfig.icon;
return (
<div
className={`rounded-xl p-4 hover:border-red-400 transition-all duration-200 group cursor-pointer theme-card border ${
isDark
? 'bg-slate-800/40 backdrop-blur-xl border-blue-800/30 hover:border-blue-500/50'
: 'bg-white border-red-100 hover:shadow-md shadow-red-100'
}`}
onClick={() => handleTaskClick(task)}
>
{/* 卡片头部:模式标签 + 状态标签 */}
<div className="flex items-center justify-between mb-3">
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-medium ${typeConfig.color}`}>
<TypeIcon className="w-3 h-3" />
{typeConfig.label}
</span>
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${statusConfig.color}`}>
{statusConfig.label}
</span>
</div>
{/* 任务名称 */}
<h3 className={`font-semibold text-sm mb-1 line-clamp-2 group-hover:text-red-500 transition-colors ${
isDark ? 'text-white' : 'text-gray-900'
}`}>
{task.name}
</h3>
{/* 任务编号 */}
<p className={`text-xs mb-2 ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>TASK-{task.id.padStart(4, '0')}</p>
{/* 任务摘要 */}
<p className={`text-xs mb-3 line-clamp-2 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>{task.summary}</p>
{/* 补充信息 */}
<div className="space-y-1.5 mb-3">
{task.type === 'single' && (
<div className="flex items-center gap-2 text-xs">
<Bot className={`w-3 h-3 ${isDark ? 'text-cyan-400' : 'text-red-500'}`} />
<span className={isDark ? 'text-slate-400' : 'text-gray-500'}>执行智能体:</span>
<span className={isDark ? 'text-cyan-400' : 'text-red-600'}>{task.agent}</span>
</div>
)}
{task.type === 'scene' && (
<div className="flex items-center gap-2 text-xs">
<Workflow className={`w-3 h-3 ${isDark ? 'text-purple-400' : 'text-red-500'}`} />
<span className={isDark ? 'text-slate-400' : 'text-gray-500'}>关联场景:</span>
<span className={isDark ? 'text-purple-400' : 'text-red-600'}>{task.scene}</span>
</div>
)}
{task.type === 'consultation' && (
<>
<div className="flex items-center gap-2 text-xs">
<Users className={`w-3 h-3 ${isDark ? 'text-orange-400' : 'text-red-500'}`} />
<span className={isDark ? 'text-slate-400' : 'text-gray-500'}>参与智能体:</span>
<span className={isDark ? 'text-orange-400' : 'text-red-600'}>{task.participants?.length || 0}个</span>
</div>
<div className="flex items-center gap-2 text-xs">
<Clock className={`w-3 h-3 ${isDark ? 'text-slate-500' : 'text-gray-400'}`} />
<span className={isDark ? 'text-slate-400' : 'text-gray-500'}>当前轮次:</span>
<span className={isDark ? 'text-slate-300' : 'text-gray-700'}>{task.currentPhase}</span>
</div>
</>
)}
{task.column !== 'unanalyzed' && task.column !== 'completed' && (
<div className="flex items-center gap-2 text-xs">
<Target className={`w-3 h-3 ${isDark ? 'text-slate-500' : 'text-gray-400'}`} />
<span className={isDark ? 'text-slate-400' : 'text-gray-500'}>当前阶段:</span>
<span className={isDark ? 'text-slate-300' : 'text-gray-700'}>{task.currentPhase}</span>
</div>
)}
</div>
{/* 时间信息 */}
<div className={`flex items-center justify-between text-xs pt-3 border-t ${
isDark
? 'text-slate-500 border-blue-800/30'
: 'text-gray-400 border-red-100'
}`}>
<div className="flex items-center gap-1.5">
<User className="w-3 h-3" />
<span>{task.creator}</span>
</div>
<span>{task.column === 'completed' ? `完成于 ${task.updateTime}` : `更新于 ${task.updateTime}`}</span>
</div>
{/* 操作按钮区 */}
<div className={`flex items-center gap-2 mt-3 pt-3 border-t ${
isDark ? 'border-blue-800/30' : 'border-red-100'
}`}>
{task.column === 'unanalyzed' && (
<>
<button className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-white rounded-lg text-xs font-medium transition-colors ${
isDark
? 'bg-blue-600 hover:bg-blue-500'
: 'bg-red-600 hover:bg-red-500 shadow-md'
}`}>
<Play className="w-3 h-3" />
开始分析
</button>
<button className={`p-1.5 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`} title="编辑">
<Edit className={`w-4 h-4 ${isDark ? 'text-slate-400' : 'text-red-400'}`} />
</button>
</>
)}
{(task.column === 'analyzing') && (
<>
<button className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-white rounded-lg text-xs font-medium transition-colors ${
isDark
? 'bg-blue-600 hover:bg-blue-500'
: 'bg-red-600 hover:bg-red-500 shadow-md'
}`}>
<Play className="w-3 h-3" />
继续处理
</button>
<button className={`px-2 py-1.5 rounded-lg text-xs transition-colors ${
isDark
? 'hover:bg-slate-700/50 border border-blue-800/30 text-slate-300'
: 'hover:bg-red-50 border border-red-200 text-red-600'
}`}>
查看进展
</button>
</>
)}
{task.column === 'completed' && (
<>
<button className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-white rounded-lg text-xs font-medium transition-colors ${
isDark
? 'bg-green-600 hover:bg-green-500'
: 'bg-green-600 hover:bg-green-500 shadow-md'
}`}>
<Eye className="w-3 h-3" />
查看结果
</button>
<button className={`px-2 py-1.5 rounded-lg text-xs transition-colors ${
isDark
? 'hover:bg-slate-700/50 border border-blue-800/30 text-slate-300'
: 'hover:bg-red-50 border border-red-200 text-red-600'
}`}>
导出
</button>
</>
)}
<button className={`p-1.5 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`} title="更多">
<MoreHorizontal className={`w-4 h-4 ${isDark ? 'text-slate-400' : 'text-red-400'}`} />
</button>
</div>
</div>
);
};
const ColumnHeader = ({ title, count, color }: { title: string; count: number; color: string }) => (
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${color}`} />
<h2 className={`font-semibold text-sm ${isDark ? 'text-white' : 'text-gray-900'}`}>{title}</h2>
<span className={`px-2 py-0.5 rounded-full text-xs ${
isDark ? 'bg-slate-700/50 text-slate-400' : 'bg-red-50 text-red-500'
}`}>{count}</span>
</div>
<button className={`p-1 rounded transition-colors ${
isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`}>
<ChevronRight className={`w-4 h-4 ${isDark ? 'text-slate-500' : 'text-red-400'}`} />
</button>
</div>
);
return (
<div className="h-full flex flex-col">
{/* 顶部工具栏 */}
<div className={`rounded-xl p-4 flex-shrink-0 mb-4 theme-card border ${
isDark
? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30'
: 'bg-white border-red-100 shadow-sm'
}`}>
<div className="flex items-center justify-between mb-4">
<h1 className={`text-xl font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>任务中心</h1>
<button
onClick={() => onNavigate('task-create')}
className={`flex items-center gap-2 px-4 py-2 text-white rounded-lg text-sm font-medium transition-colors ${
isDark
? 'bg-blue-600 hover:bg-blue-500'
: 'bg-red-600 hover:bg-red-500 shadow-md'
}`}
>
<Plus className="w-4 h-4" />
新建任务
</button>
</div>
{/* 搜索和筛选 */}
<div className="flex items-center gap-4">
<div className="relative flex-1 max-w-md">
<Search className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${isDark ? 'text-slate-500' : 'text-red-400'}`} />
<input
type="text"
placeholder="搜索任务名称 / 任务编号 / 关键词"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className={`w-full pl-10 pr-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-500/20 transition-colors theme-transition ${
isDark
? 'bg-slate-900/50 border-blue-800/30 text-white placeholder-slate-500'
: 'bg-white border-red-200 text-gray-900 placeholder-red-300'
}`}
/>
</div>
<select
value={modeFilter}
onChange={(e) => setModeFilter(e.target.value as any)}
className={`px-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-500/20 cursor-pointer theme-transition ${
isDark
? 'bg-slate-900/50 border-blue-800/30 text-slate-300'
: 'bg-white border-red-200 text-gray-700'
}`}
>
<option value="all">全部模式</option>
<option value="single">单智能体</option>
<option value="scene">场景执行</option>
<option value="consultation">多智能体会商</option>
</select>
<button className={`flex items-center gap-2 px-4 py-2 border rounded-lg text-sm transition-colors theme-transition ${
isDark
? 'bg-slate-800/50 border-blue-800/30 text-slate-300 hover:bg-slate-700/50'
: 'bg-white border-red-200 text-gray-700 hover:bg-red-50'
}`}>
<Clock className="w-4 h-4" />
时间范围
</button>
</div>
</div>
{/* 统计概览栏 */}
<div className="grid grid-cols-4 gap-4 mb-4 flex-shrink-0">
<button
onClick={() => setActiveTab('all')}
className={`p-4 rounded-xl border transition-all duration-200 ${
activeTab === 'all'
? isDark
? 'bg-blue-600/20 border-blue-500/50'
: 'bg-red-50 border-red-300'
: isDark
? 'bg-slate-800/30 border-blue-800/30 hover:border-blue-500/30'
: 'bg-white border-red-100 hover:border-red-300'
}`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>{totalCount}</p>
<p className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>全部任务</p>
</button>
<button
onClick={() => setActiveTab('unanalyzed')}
className={`p-4 rounded-xl border transition-all duration-200 ${
activeTab === 'unanalyzed'
? isDark
? 'bg-yellow-500/20 border-yellow-500/50'
: 'bg-yellow-50 border-yellow-300'
: isDark
? 'bg-slate-800/30 border-blue-800/30 hover:border-blue-500/30'
: 'bg-white border-red-100 hover:border-red-300'
}`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>{unanalyzedCount}</p>
<p className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>未分析</p>
</button>
<button
onClick={() => setActiveTab('analyzing')}
className={`p-4 rounded-xl border transition-all duration-200 ${
activeTab === 'analyzing'
? isDark
? 'bg-blue-500/20 border-blue-500/50'
: 'bg-red-50 border-red-300'
: isDark
? 'bg-slate-800/30 border-blue-800/30 hover:border-blue-500/30'
: 'bg-white border-red-100 hover:border-red-300'
}`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>{analyzingCount}</p>
<p className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>分析中</p>
</button>
<button
onClick={() => setActiveTab('completed')}
className={`p-4 rounded-xl border transition-all duration-200 ${
activeTab === 'completed'
? isDark
? 'bg-green-500/20 border-green-500/50'
: 'bg-green-50 border-green-300'
: isDark
? 'bg-slate-800/30 border-blue-800/30 hover:border-blue-500/30'
: 'bg-white border-red-100 hover:border-red-300'
}`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>{completedCount}</p>
<p className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>已完成</p>
</button>
</div>
{/* 看板区 */}
<div className="flex-1 grid grid-cols-3 gap-4 min-h-0 overflow-hidden">
{/* 未分析列 */}
<div className="flex flex-col min-h-0">
<ColumnHeader title="未分析" count={unanalyzedTasks.length} color={isDark ? 'bg-yellow-400' : 'bg-yellow-400'} />
<div className="flex-1 overflow-y-auto space-y-3 pr-1 scrollbar-thin">
{unanalyzedTasks.length === 0 ? (
<div className={`flex flex-col items-center justify-center py-8 ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
<FileText className="w-8 h-8 mb-2 opacity-50" />
<p className="text-sm">暂无待分析任务</p>
</div>
) : (
unanalyzedTasks.map(task => <TaskCard key={task.id} task={task} />)
)}
</div>
</div>
{/* 分析中列 */}
<div className="flex flex-col min-h-0">
<ColumnHeader title="分析中" count={analyzingTasks.length} color={isDark ? 'bg-blue-400' : 'bg-red-400'} />
<div className="flex-1 overflow-y-auto space-y-3 pr-1 scrollbar-thin">
{analyzingTasks.length === 0 ? (
<div className={`flex flex-col items-center justify-center py-8 ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
<Clock className="w-8 h-8 mb-2 opacity-50" />
<p className="text-sm">暂无分析中任务</p>
</div>
) : (
analyzingTasks.map(task => <TaskCard key={task.id} task={task} />)
)}
</div>
</div>
{/* 已完成列 */}
<div className="flex flex-col min-h-0">
<ColumnHeader title="已完成" count={completedTasks.length} color={isDark ? 'bg-green-400' : 'bg-green-400'} />
<div className="flex-1 overflow-y-auto space-y-3 pr-1 scrollbar-thin">
{completedTasks.length === 0 ? (
<div className={`flex flex-col items-center justify-center py-8 ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>
<Target className="w-8 h-8 mb-2 opacity-50" />
<p className="text-sm">暂无已完成任务</p>
</div>
) : (
completedTasks.map(task => <TaskCard key={task.id} task={task} />)
)}
</div>
</div>
</div>
</div>
);
};
export default TaskList;
@@ -0,0 +1,714 @@
import React, { useState } from 'react';
import {
Plus,
Search,
Eye,
Play,
Pause,
Clock,
User,
ChevronRight,
Filter,
FileText,
AlertTriangle,
CheckCircle,
XCircle,
MoreHorizontal,
Edit,
Download,
Bot,
Users,
Workflow,
X,
ArrowRight,
Target,
Layers,
} from 'lucide-react';
import { useTheme } from '@/contexts/ThemeContext';
import { PageType } from '@/components/Sidebar';
interface TaskListPageProps {
title: string;
category: string;
subCategory: string;
onNavigate: (page: PageType) => void;
}
type TaskType = 'single' | 'consultation' | 'scene' | 'multi-agent';
type TaskStatus = 'draft' | 'pending' | 'running' | 'paused' | 'blocked' | 'completed';
type ColumnType = 'unanalyzed' | 'analyzing' | 'completed';
interface Task {
id: string;
name: string;
type: TaskType;
status: TaskStatus;
agent?: string;
scene?: string;
participants?: string[];
currentPhase?: string;
currentRound?: number;
summary?: string;
creator: string;
createTime: string;
updateTime: string;
column: ColumnType;
}
const TaskListPage: React.FC<TaskListPageProps> = ({ title, category, subCategory, 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-100';
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-400';
const inputBg = isDark ? 'bg-slate-900/50' : 'bg-white';
// 生成模拟数据
const [tasks] = useState<Task[]>([
// 未分析列
{
id: '2026-0320-001',
name: '端午节品牌传播方案',
type: 'scene',
scene: '宣传策略生成',
status: 'draft',
column: 'unanalyzed',
summary: '针对端午节品牌活动制定完整传播策略方案',
creator: '李策划',
createTime: '2026-03-20 10:00',
updateTime: '2026-03-20 10:00',
},
{
id: '2026-0320-002',
name: '新品上市用户调研',
type: 'single',
agent: 'AudienceAgent',
status: 'pending',
column: 'unanalyzed',
summary: '对新品目标用户进行画像分析',
creator: '王运营',
createTime: '2026-03-20 09:30',
updateTime: '2026-03-20 09:30',
},
// 分析中列
{
id: '2026-0319-001',
name: '五四青年节宣传活动策略',
type: 'scene',
scene: '宣传策略生成',
status: 'running',
currentPhase: '行为分析',
column: 'analyzing',
summary: '正在按场景模板执行目标、受众、行为、叙事等分析...',
creator: '张策略',
createTime: '2026-03-19 10:30',
updateTime: '2026-03-20 09:45',
},
{
id: '2026-0319-002',
name: '青年传播方向会商分析',
type: 'consultation',
participants: ['GoalAgent', 'AudienceAgent', 'NarrativeAgent'],
status: 'running',
currentRound: 2,
column: 'analyzing',
summary: '围绕宣传方向、叙事方式和受众切入点开展多视角会商...',
creator: '王策划',
createTime: '2026-03-19 14:00',
updateTime: '2026-03-20 09:00',
},
{
id: '2026-0318-001',
name: '年度品牌升级方案推演',
type: 'scene',
scene: '宣传方案推演',
status: 'paused',
currentPhase: '叙事分析',
column: 'analyzing',
summary: '任务已暂停,等待人工介入确认叙事方向',
creator: '王策划',
createTime: '2026-03-18 14:00',
updateTime: '2026-03-20 08:30',
},
// 已完成列
{
id: '2026-0318-002',
name: '核心受众画像分析',
type: 'single',
agent: 'AudienceAgent',
status: 'completed',
column: 'completed',
summary: '已完成目标受众识别、关切提炼和阻力分析',
creator: '赵运营',
createTime: '2026-03-18 09:00',
updateTime: '2026-03-18 16:30',
},
{
id: '2026-0319-003',
name: '五四青年节宣传策略',
type: 'scene',
scene: '宣传策略生成',
status: 'completed',
column: 'completed',
summary: '已完成宣传策略方案生成',
creator: '张策略',
createTime: '2026-03-19 10:00',
updateTime: '2026-03-19 18:00',
},
{
id: '2026-0319-004',
name: '青年品牌传播路径会商',
type: 'consultation',
participants: ['GoalAgent', 'NarrativeAgent', 'RiskAgent'],
status: 'completed',
column: 'completed',
summary: '已完成多智能体会商,形成结论与分歧报告',
creator: '王策划',
createTime: '2026-03-19 14:30',
updateTime: '2026-03-19 17:00',
},
]);
const [searchQuery, setSearchQuery] = useState('');
const [modeFilter, setModeFilter] = useState<string>('all');
const [showAnalysisModal, setShowAnalysisModal] = useState(false);
const [selectedTaskForAnalysis, setSelectedTaskForAnalysis] = useState<Task | null>(null);
// 获取任务类型配置
const getTypeConfig = (type: TaskType) => {
if (isDark) {
switch (type) {
case 'single':
return { label: '单智能体', color: 'bg-cyan-500/20 text-cyan-400', icon: Bot };
case 'scene':
return { label: '场景执行', color: 'bg-purple-500/20 text-purple-400', icon: Workflow };
case 'consultation':
return { label: '多智能体会商', color: 'bg-orange-500/20 text-orange-400', icon: Users };
case 'multi-agent':
return { label: '多智能体协同', color: 'bg-green-500/20 text-green-400', icon: Layers };
}
} else {
switch (type) {
case 'single':
return { label: '单智能体', color: 'bg-blue-100 text-blue-600', icon: Bot };
case 'scene':
return { label: '场景执行', color: 'bg-purple-100 text-purple-600', icon: Workflow };
case 'consultation':
return { label: '多智能体会商', color: 'bg-orange-100 text-orange-600', icon: Users };
case 'multi-agent':
return { label: '多智能体协同', color: 'bg-green-100 text-green-600', icon: Layers };
}
}
};
// 获取状态配置
const getStatusConfig = (status: TaskStatus, column: ColumnType) => {
if (column === 'unanalyzed') {
switch (status) {
case 'draft':
return { label: '草稿', color: isDark ? 'bg-slate-500/20 text-slate-400' : 'bg-gray-100 text-gray-600' };
case 'pending':
return { label: '待启动', color: isDark ? 'bg-slate-500/20 text-slate-400' : 'bg-gray-100 text-gray-600' };
default:
return { label: '待配置', color: isDark ? 'bg-yellow-500/20 text-yellow-400' : 'bg-yellow-100 text-yellow-600' };
}
} else if (column === 'analyzing') {
switch (status) {
case 'running':
return { label: '运行中', color: isDark ? 'bg-blue-500/20 text-blue-400' : 'bg-red-100 text-red-600' };
case 'paused':
return { label: '已暂停', color: isDark ? 'bg-yellow-500/20 text-yellow-400' : 'bg-yellow-100 text-yellow-600' };
case 'blocked':
return { label: '阻塞中', color: isDark ? 'bg-red-500/20 text-red-400' : 'bg-orange-100 text-orange-600' };
default:
return { label: '待处理', color: isDark ? 'bg-purple-500/20 text-purple-400' : 'bg-purple-100 text-purple-600' };
}
} else {
return { label: '已完成', color: isDark ? 'bg-green-500/20 text-green-400' : 'bg-green-100 text-green-600' };
}
};
// 筛选任务
const filteredTasks = tasks.filter(task => {
const matchesSearch = task.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
task.id.toLowerCase().includes(searchQuery.toLowerCase());
const matchesMode = modeFilter === 'all' || task.type === modeFilter;
return matchesSearch && matchesMode;
});
const unanalyzedTasks = filteredTasks.filter(t => t.column === 'unanalyzed');
const analyzingTasks = filteredTasks.filter(t => t.column === 'analyzing');
const completedTasks = filteredTasks.filter(t => t.column === 'completed');
// 统计数据
const stats = {
total: tasks.length,
unanalyzed: unanalyzedTasks.length,
analyzing: analyzingTasks.length,
completed: completedTasks.length,
};
// 处理开始分析按钮点击
const handleStartAnalysis = (task: Task) => {
setSelectedTaskForAnalysis(task);
setShowAnalysisModal(true);
};
// 处理分析模式选择
const handleSelectAnalysisMode = (mode: TaskType) => {
setShowAnalysisModal(false);
if (mode === 'single') {
onNavigate('task-single');
} else if (mode === 'scene') {
onNavigate('task-scene');
} else if (mode === 'consultation') {
onNavigate('task-consult-create');
} else if (mode === 'multi-agent') {
onNavigate('multi-agent-qa');
}
};
// 处理卡片点击
const handleTaskClick = (task: Task) => {
if (task.column === 'completed') {
if (task.type === 'consultation') {
onNavigate('task-consult-result');
} else {
onNavigate('strategy-result');
}
} else if (task.column === 'analyzing') {
if (task.type === 'scene') {
onNavigate('task-scene');
} else if (task.type === 'consultation') {
onNavigate('task-consult');
} else {
onNavigate('task-single');
}
}
};
// 渲染任务卡片
const TaskCard = ({ task }: { task: Task }) => {
const typeConfig = getTypeConfig(task.type);
const statusConfig = getStatusConfig(task.status, task.column);
const TypeIcon = typeConfig.icon;
return (
<div
className={`rounded-xl p-4 hover:shadow-md transition-all duration-200 cursor-pointer group theme-card border ${
isDark
? 'bg-slate-800/40 backdrop-blur-xl border-blue-800/30 hover:border-blue-500/50'
: 'bg-white border-red-100 hover:shadow-md shadow-red-100'
}`}
onClick={() => handleTaskClick(task)}
>
{/* 卡片头部:模式标签 + 状态标签 */}
<div className="flex items-center justify-between mb-2">
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs font-medium ${typeConfig.color}`}>
<TypeIcon className="w-3 h-3" />
{typeConfig.label}
</span>
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${statusConfig.color}`}>
{statusConfig.label}
</span>
</div>
{/* 任务名称 */}
<h3 className={`font-semibold text-sm mb-1 line-clamp-2 group-hover:text-red-500 transition-colors ${textPrimary}`}>
{task.name}
</h3>
{/* 任务编号 */}
<p className={`text-xs mb-2 ${textMuted}`}>TASK-{task.id}</p>
{/* 任务摘要 */}
<p className={`text-xs mb-3 line-clamp-2 ${textSecondary}`}>{task.summary}</p>
{/* 补充信息区 */}
<div className="space-y-1 mb-3">
{task.type === 'single' && task.agent && (
<div className="flex items-center gap-2 text-xs">
<Bot className={`w-3 h-3 ${isDark ? 'text-cyan-400' : 'text-red-500'}`} />
<span className={textSecondary}>执行智能体:</span>
<span className={isDark ? 'text-cyan-400' : 'text-red-600'}>{task.agent}</span>
</div>
)}
{task.type === 'scene' && task.scene && (
<div className="flex items-center gap-2 text-xs">
<Workflow className={`w-3 h-3 ${isDark ? 'text-purple-400' : 'text-red-500'}`} />
<span className={textSecondary}>关联场景:</span>
<span className={isDark ? 'text-purple-400' : 'text-red-600'}>{task.scene}</span>
</div>
)}
{task.type === 'consultation' && (
<>
<div className="flex items-center gap-2 text-xs">
<Users className={`w-3 h-3 ${isDark ? 'text-orange-400' : 'text-red-500'}`} />
<span className={textSecondary}>参与智能体:</span>
<span className={isDark ? 'text-orange-400' : 'text-red-600'}>{task.participants?.length || 0}个</span>
</div>
{task.currentRound && (
<div className="flex items-center gap-2 text-xs">
<Clock className={`w-3 h-3 ${textMuted}`} />
<span className={textSecondary}>当前轮次:</span>
<span className={textPrimary}>第{task.currentRound}轮</span>
</div>
)}
</>
)}
{task.column === 'analyzing' && task.currentPhase && (
<div className="flex items-center gap-2 text-xs">
<Target className={`w-3 h-3 ${textMuted}`} />
<span className={textSecondary}>当前阶段:</span>
<span className={textPrimary}>{task.currentPhase}</span>
</div>
)}
</div>
{/* 时间信息 */}
<div className={`flex items-center justify-between text-xs pt-3 border-t ${
isDark ? 'border-blue-800/30 text-slate-500' : 'border-red-100 text-gray-400'
}`}>
<div className="flex items-center gap-1.5">
<User className="w-3 h-3" />
<span>{task.creator}</span>
</div>
<span>{task.column === 'completed' ? `完成于 ${task.updateTime}` : `更新于 ${task.updateTime}`}</span>
</div>
{/* 操作按钮区 */}
<div className={`flex items-center gap-2 mt-3 pt-3 border-t ${
isDark ? 'border-blue-800/30' : 'border-red-100'
}`}>
{task.column === 'unanalyzed' && (
<>
<button
onClick={(e) => { e.stopPropagation(); handleStartAnalysis(task); }}
className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-white rounded-lg text-xs font-medium transition-colors ${
isDark
? 'bg-blue-600 hover:bg-blue-500'
: 'bg-red-600 hover:bg-red-500 shadow-md'
}`}
>
<Play className="w-3 h-3" />
开始分析
</button>
<button
onClick={(e) => { e.stopPropagation(); onNavigate('task-create'); }}
className={`p-1.5 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`}
title="编辑"
>
<Edit className={`w-4 h-4 ${isDark ? 'text-slate-400' : 'text-red-400'}`} />
</button>
</>
)}
{task.column === 'analyzing' && (
<>
<button
onClick={(e) => { e.stopPropagation(); handleTaskClick(task); }}
className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-white rounded-lg text-xs font-medium transition-colors ${
isDark
? 'bg-blue-600 hover:bg-blue-500'
: 'bg-red-600 hover:bg-red-500 shadow-md'
}`}
>
<Play className="w-3 h-3" />
继续处理
</button>
<button
onClick={(e) => { e.stopPropagation(); handleTaskClick(task); }}
className={`px-2 py-1.5 rounded-lg text-xs transition-colors ${
isDark
? 'hover:bg-slate-700/50 border border-blue-800/30 text-slate-300'
: 'hover:bg-red-50 border border-red-200 text-red-600'
}`}
>
查看进展
</button>
</>
)}
{task.column === 'completed' && (
<>
<button
onClick={(e) => { e.stopPropagation(); handleTaskClick(task); }}
className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-white rounded-lg text-xs font-medium transition-colors ${
isDark
? 'bg-green-600 hover:bg-green-500'
: 'bg-green-600 hover:bg-green-500 shadow-md'
}`}
>
<Eye className="w-3 h-3" />
查看结果
</button>
<button className={`px-2 py-1.5 rounded-lg text-xs transition-colors ${
isDark
? 'hover:bg-slate-700/50 border border-blue-800/30 text-slate-300'
: 'hover:bg-red-50 border border-red-200 text-red-600'
}`}>
<Download className="w-4 h-4" />
</button>
</>
)}
<button className={`p-1.5 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`} title="更多">
<MoreHorizontal className={`w-4 h-4 ${isDark ? 'text-slate-400' : 'text-red-400'}`} />
</button>
</div>
</div>
);
};
// 渲染看板列
const KanbanColumn = ({ title, count, color, tasks }: { title: string; count: number; color: string; tasks: Task[] }) => (
<div className="flex flex-col min-h-0 flex-1">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${color}`} />
<h2 className={`font-semibold text-sm ${textPrimary}`}>{title}</h2>
<span className={`px-2 py-0.5 rounded-full text-xs ${
isDark ? 'bg-slate-700/50 text-slate-400' : 'bg-red-50 text-red-500'
}`}>{count}</span>
</div>
<button className={`p-1 rounded transition-colors ${
isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`}>
<ChevronRight className={`w-4 h-4 ${textMuted}`} />
</button>
</div>
<div className="flex-1 overflow-y-auto space-y-3 pr-1 scrollbar-thin">
{tasks.length === 0 ? (
<div className={`flex flex-col items-center justify-center py-8 ${textMuted}`}>
<FileText className="w-8 h-8 mb-2 opacity-50" />
<p className="text-sm">暂无任务</p>
</div>
) : (
tasks.map(task => <TaskCard key={task.id} task={task} />)
)}
</div>
</div>
);
return (
<div className="h-full flex flex-col">
{/* 顶部工具栏 */}
<div className={`rounded-xl p-4 flex-shrink-0 mb-4 ${cardBg} border ${cardBorder}`}>
<div className="flex items-center justify-between mb-4">
<div>
<div className="flex items-center gap-2 mb-1">
<span className={`px-2 py-0.5 rounded text-xs ${isDark ? 'bg-blue-500/20 text-blue-400' : 'bg-red-100 text-red-600'}`}>
{category}
</span>
<span className={`text-xs ${textMuted}`}>/</span>
<span className={`px-2 py-0.5 rounded text-xs ${isDark ? 'bg-cyan-500/20 text-cyan-400' : 'bg-red-100 text-red-600'}`}>
{subCategory}
</span>
</div>
<h1 className={`text-xl font-bold ${textPrimary}`}>任务中心</h1>
</div>
<button
onClick={() => onNavigate('task-create')}
className={`flex items-center gap-2 px-4 py-2 text-white rounded-lg text-sm font-medium transition-colors ${
isDark
? 'bg-blue-600 hover:bg-blue-500'
: 'bg-red-600 hover:bg-red-500 shadow-md'
}`}
>
<Plus className="w-4 h-4" />
新建任务
</button>
</div>
{/* 搜索和筛选 */}
<div className="flex items-center gap-4">
<div className="relative flex-1 max-w-md">
<Search className={`absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 ${textMuted}`} />
<input
type="text"
placeholder="搜索任务名称 / 任务编号 / 关键词"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className={`w-full pl-10 pr-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-500/20 transition-colors ${inputBg} ${cardBorder} ${textPrimary} placeholder:${textMuted}`}
/>
</div>
<select
value={modeFilter}
onChange={(e) => setModeFilter(e.target.value)}
className={`px-4 py-2 border rounded-lg text-sm focus:outline-none cursor-pointer ${inputBg} ${cardBorder} ${textSecondary}`}
>
<option value="all">全部模式</option>
<option value="single">单智能体</option>
<option value="multi-agent">多智能体协同</option>
<option value="consultation">多智能体会商</option>
<option value="scene">场景执行</option>
</select>
<button className={`flex items-center gap-2 px-4 py-2 border rounded-lg text-sm transition-colors ${inputBg} ${cardBorder} ${textSecondary}`}>
<Clock className="w-4 h-4" />
时间范围
</button>
</div>
</div>
{/* 统计概览栏 */}
<div className="grid grid-cols-4 gap-4 mb-4 flex-shrink-0">
<button
onClick={() => { setModeFilter('all'); }}
className={`p-4 rounded-xl border transition-all ${cardBg} ${cardBorder} hover:border-red-400`}
>
<p className={`text-2xl font-bold ${textPrimary}`}>{stats.total}</p>
<p className={`text-sm ${textSecondary}`}>全部任务</p>
</button>
<button
className={`p-4 rounded-xl border transition-all ${cardBg} ${cardBorder} hover:border-red-400`}
>
<p className={`text-2xl font-bold ${textPrimary}`}>{stats.unanalyzed}</p>
<p className={`text-sm ${textSecondary}`}>未分析</p>
</button>
<button
className={`p-4 rounded-xl border transition-all ${cardBg} ${cardBorder} hover:border-red-400`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-blue-400' : 'text-red-600'}`}>{stats.analyzing}</p>
<p className={`text-sm ${textSecondary}`}>分析中</p>
</button>
<button
className={`p-4 rounded-xl border transition-all ${cardBg} ${cardBorder} hover:border-red-400`}
>
<p className={`text-2xl font-bold ${isDark ? 'text-green-400' : 'text-green-600'}`}>{stats.completed}</p>
<p className={`text-sm ${textSecondary}`}>已完成</p>
</button>
</div>
{/* 看板区 */}
<div className="flex-1 grid grid-cols-3 gap-4 min-h-0 overflow-hidden">
<KanbanColumn title="未分析" count={unanalyzedTasks.length} color={isDark ? 'bg-yellow-400' : 'bg-yellow-400'} tasks={unanalyzedTasks} />
<KanbanColumn title="分析中" count={analyzingTasks.length} color={isDark ? 'bg-blue-400' : 'bg-red-400'} tasks={analyzingTasks} />
<KanbanColumn title="已完成" count={completedTasks.length} color={isDark ? 'bg-green-400' : 'bg-green-400'} tasks={completedTasks} />
</div>
{/* 选择分析模式弹窗 */}
{showAnalysisModal && selectedTaskForAnalysis && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50" onClick={() => setShowAnalysisModal(false)}>
<div
className={`w-full max-w-lg rounded-2xl p-6 ${cardBg} border ${cardBorder} shadow-xl`}
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between mb-6">
<div>
<h2 className={`text-xl font-bold ${textPrimary}`}>选择分析模式</h2>
<p className={`text-sm ${textMuted} mt-1`}>为任务「{selectedTaskForAnalysis.name}」选择分析方式</p>
</div>
<button
onClick={() => setShowAnalysisModal(false)}
className={`p-2 rounded-lg hover:bg-slate-700/50 ${textSecondary}`}
>
<X className="w-5 h-5" />
</button>
</div>
<div className="space-y-3">
{/* 单智能体分析 */}
<button
onClick={() => handleSelectAnalysisMode('single')}
className={`w-full p-4 rounded-xl border transition-all duration-200 text-left hover:shadow-md ${
isDark
? 'bg-slate-700/50 border-blue-800/30 hover:border-cyan-500/50'
: 'bg-white border-red-100 hover:border-red-300 hover:shadow-md'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${
isDark ? 'bg-cyan-500/20' : 'bg-red-100'
}`}>
<Bot className={`w-6 h-6 ${isDark ? 'text-cyan-400' : 'text-red-500'}`} />
</div>
<div className="flex-1">
<h3 className={`font-semibold ${textPrimary}`}>单智能体分析</h3>
<p className={`text-sm ${textSecondary}`}>选择一个AI智能体完成单一维度的专项分析</p>
</div>
<ArrowRight className={`w-5 h-5 ${textMuted}`} />
</div>
</button>
{/* 多智能体协同分析 */}
<button
onClick={() => handleSelectAnalysisMode('multi-agent')}
className={`w-full p-4 rounded-xl border transition-all duration-200 text-left hover:shadow-md ${
isDark
? 'bg-slate-700/50 border-blue-800/30 hover:border-green-500/50'
: 'bg-white border-red-100 hover:border-red-300 hover:shadow-md'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${
isDark ? 'bg-green-500/20' : 'bg-green-100'
}`}>
<Layers className={`w-6 h-6 ${isDark ? 'text-green-400' : 'text-green-600'}`} />
</div>
<div className="flex-1">
<h3 className={`font-semibold ${textPrimary}`}>多智能体协同分析</h3>
<p className={`text-sm ${textSecondary}`}>多个AI智能体并行分析,输出各自的判断和结论</p>
</div>
<ArrowRight className={`w-5 h-5 ${textMuted}`} />
</div>
</button>
{/* 多智能体会商 */}
<button
onClick={() => handleSelectAnalysisMode('consultation')}
className={`w-full p-4 rounded-xl border transition-all duration-200 text-left hover:shadow-md ${
isDark
? 'bg-slate-700/50 border-blue-800/30 hover:border-orange-500/50'
: 'bg-white border-red-100 hover:border-red-300 hover:shadow-md'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${
isDark ? 'bg-orange-500/20' : 'bg-red-100'
}`}>
<Users className={`w-6 h-6 ${isDark ? 'text-orange-400' : 'text-red-500'}`} />
</div>
<div className="flex-1">
<h3 className={`font-semibold ${textPrimary}`}>多智能体会商</h3>
<p className={`text-sm ${textSecondary}`}>多个AI智能体从不同视角开展讨论并形成共识</p>
</div>
<ArrowRight className={`w-5 h-5 ${textMuted}`} />
</div>
</button>
{/* 场景执行 */}
<button
onClick={() => handleSelectAnalysisMode('scene')}
className={`w-full p-4 rounded-xl border transition-all duration-200 text-left hover:shadow-md ${
isDark
? 'bg-slate-700/50 border-blue-800/30 hover:border-purple-500/50'
: 'bg-white border-red-100 hover:border-red-300 hover:shadow-md'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${
isDark ? 'bg-purple-500/20' : 'bg-red-100'
}`}>
<Workflow className={`w-6 h-6 ${isDark ? 'text-purple-400' : 'text-red-500'}`} />
</div>
<div className="flex-1">
<h3 className={`font-semibold ${textPrimary}`}>场景执行</h3>
<p className={`text-sm ${textSecondary}`}>按照预设场景模板自动执行多阶段策略分析</p>
</div>
<ArrowRight className={`w-5 h-5 ${textMuted}`} />
</div>
</button>
</div>
</div>
</div>
)}
</div>
);
};
export default TaskListPage;
@@ -0,0 +1,26 @@
import { Outlet, useNavigate } from 'react-router-dom';
import React, { useState, useEffect, useRef } from 'react';
import { useSearchParams } from 'react-router-dom';
import { requestCrudService } from '@/service/request-axios-service';
const service = requestCrudService;
const userInfo = localStorage.getItem('userInfo');
const pgLinkUrl = import.meta.env.VITE_AIPI_PGLINKTO_URL;
interface TaskPgFromProps {
gopath: string
}
const TaskPgFrom: React.FC<TaskPgFromProps> = ({gopath}) => {
const [pgUrl, setPgUrl] = useState(pgLinkUrl);
useEffect(() => {
if(userInfo) {
const userData = JSON.parse(userInfo);
setPgUrl(`${pgUrl}?access_token=${userData.accessToken}&goPath=${gopath}`);
}
}, [gopath]);
return (
<div className="h-full flex flex-col">
<iframe className="h-[calc(100vh-92px)]" src={pgUrl}></iframe>
</div>
);
}
export default TaskPgFrom
@@ -0,0 +1,715 @@
import React, { useState } from 'react';
import {
ArrowLeft,
Bot,
CheckCircle,
Clock,
User,
Download,
FileText,
Send,
Zap,
Sparkles,
Lightbulb,
Copy,
Edit3,
ChevronDown,
ChevronRight,
Database,
Brain,
MessageSquare,
ExternalLink,
BookOpen,
Info,
Eye,
} from 'lucide-react';
import { useTheme } from '@/contexts/ThemeContext';
import { PageType } from '@/components/Sidebar';
interface TaskSingleProps {
onNavigate: (page: PageType) => void;
}
interface ChatMessage {
id: string;
role: 'user' | 'agent';
content: string;
timestamp: string;
citations?: { title: string; source: string; page?: number }[];
}
const TaskSingle: React.FC<TaskSingleProps> = ({ onNavigate }) => {
const { theme } = useTheme();
const isDark = theme === 'dark';
const [taskStatus, setTaskStatus] = useState<'pending' | 'running' | 'completed'>('completed');
const [input, setInput] = useState('请分析五四青年节宣传活动的目标受众');
const [isRunning, setIsRunning] = useState(false);
const [showCitations, setShowCitations] = useState<string | null>(null);
const [isEditing, setIsEditing] = useState(false);
const [editedContent, setEditedContent] = useState('');
const [messages, setMessages] = useState<ChatMessage[]>([
{
id: '1',
role: 'user',
content: '请分析五四青年节宣传活动的目标受众',
timestamp: '14:00',
},
{
id: '2',
role: 'agent',
content: `根据您的任务要求,我对五四青年节宣传活动的目标受众进行了全面分析,以下是详细报告:
## 一、核心受众画像
**基本信息**
- 年龄段:18-25岁
- 身份定位:基层青年(学生、职场新人)
- 地域分布:一二线城市为主
**用户特征**
- 社交媒体重度用户,日均使用时长超过3小时
- 短视频内容消费主力,偏好竖屏内容
- 注重"真实感"和"态度感",反感过度营销
- 追求个性化表达和群体认同
## 二、行为洞察
**媒体习惯**
- 主要平台:抖音、小红书、微博、B站
- 内容偏好:真实故事、情感共鸣、实用价值
- 互动方式:点赞、评论、转发、UGC创作
**决策因素**
- 社交认同 > 品牌声誉 > 产品功能
- 性价比 > 品牌溢价
- 真实体验 > 官方宣传
## 三、核心需求
1. **归属感需求** - 希望被看见、被认可,参与活动获得身份认同
2. **表达欲驱动** - 有态度的内容激发创作冲动
3. **社交认同激励** - 优质内容获得曝光和认可
## 结论
核心受众应聚焦18-25岁基层青年群体,这部分用户与品牌年轻化战略高度契合。`,
timestamp: '14:03',
citations: [
{ title: 'Z世代消费行为研究报告', source: '市场研究数据库', page: 23 },
{ title: '短视频平台用户画像', source: 'Y系统-用户标签', page: 5 },
{ title: '品牌年轻化营销案例集', source: '宣传知识库', page: 45 },
],
},
]);
const agentConfig = {
name: 'AudienceAgent',
role: '受众分析专家',
description: '分析目标受众画像、行为特征和核心需求,为策略制定提供数据支持',
skills: [
{ name: '受众分层', type: '分析类', status: 'enabled' },
{ name: '核心受众识别', type: '推断类', status: 'enabled' },
{ name: '受众画像抽取', type: '抽取类', status: 'enabled' },
{ name: '阻力因素提炼', type: '分析类', status: 'enabled' },
],
databases: [
{ name: '宣传知识库', type: '知识库', access: '品牌案例、活动方法论', status: 'connected' },
{ name: 'Y系统', type: '业务系统', access: '用户标签、传播数据', status: 'connected' },
{ name: '市场数据库', type: '外部数据源', access: '市场报告、用户研究', status: 'connected' },
],
outputFields: ['core_audience', 'key_traits', 'concerns', 'barriers'],
};
const analysisResult = {
title: '五四青年节宣传活动目标受众分析报告',
agent: 'AudienceAgent',
completionTime: '2026-03-19 14:03',
status: '已完成',
summary: '核心受众聚焦18-25岁基层青年群体,需用"真实、有态度"的内容打动',
};
const handleSend = () => {
if (!input.trim()) return;
const userMessage: ChatMessage = {
id: Date.now().toString(),
role: 'user',
content: input,
timestamp: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }),
};
setMessages([...messages, userMessage]);
setInput('');
setIsRunning(true);
setTimeout(() => {
const agentMessage: ChatMessage = {
id: (Date.now() + 1).toString(),
role: 'agent',
content: '分析结果已生成,请查看上方报告详情。',
timestamp: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }),
citations: [
{ title: '青年消费趋势报告', source: '市场数据库', page: 12 },
],
};
setMessages(prev => [...prev, agentMessage]);
setIsRunning(false);
setTaskStatus('completed');
}, 2000);
};
const handleCopyResult = () => {
const content = messages.filter(m => m.role === 'agent').map(m => m.content).join('\n\n');
navigator.clipboard.writeText(content);
};
const handleEditResult = () => {
if (isEditing) {
setIsEditing(false);
} else {
setEditedContent(messages.filter(m => m.role === 'agent').map(m => m.content).join('\n\n'));
setIsEditing(true);
}
};
const handleExport = () => {
const content = `# ${analysisResult.title}\n\n智能体:${analysisResult.agent}\n完成时间:${analysisResult.completionTime}\n状态:${analysisResult.status}\n\n---\n\n${messages.filter(m => m.role === 'agent').map(m => m.content).join('\n\n')}`;
const blob = new Blob([content], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'audience_analysis_report.md';
a.click();
URL.revokeObjectURL(url);
};
return (
<div className="h-[calc(100vh-140px)] flex flex-col">
{/* 顶部工具栏 */}
<div className={`rounded-xl p-4 flex-shrink-0 theme-card border ${
isDark
? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30'
: 'bg-white border-red-100 shadow-sm'
}`}>
<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 rounded-lg text-sm transition-colors ${
isDark
? 'bg-slate-700/50 hover:bg-slate-600/50 border border-blue-800/30 text-slate-300'
: 'bg-red-50 hover:bg-red-100 border border-red-200 text-red-600'
}`}
>
<ArrowLeft className="w-4 h-4" />
返回
</button>
<div className={`h-8 w-px ${isDark ? 'bg-blue-800/30' : 'bg-red-200'}`} />
<div>
<h1 className={`font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>五四青年节受众分析</h1>
<p className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>单智能体任务 · AudienceAgent</p>
</div>
</div>
<div className="flex items-center gap-2">
<span className={`px-3 py-1.5 rounded-full text-xs font-medium flex items-center gap-1.5 ${
taskStatus === 'completed'
? isDark
? 'bg-green-500/20 text-green-400'
: 'bg-green-100 text-green-600'
: taskStatus === 'running'
? isDark
? 'bg-blue-500/20 text-blue-400'
: 'bg-red-100 text-red-600'
: isDark
? 'bg-slate-500/20 text-slate-400'
: 'bg-gray-100 text-gray-600'
}`}>
{taskStatus === 'completed' ? (
<><CheckCircle className="w-3 h-3" /> 已完成</>
) : taskStatus === 'running' ? (
<><Clock className="w-3 h-3" /> 运行中</>
) : (
<><Clock className="w-3 h-3" /> 待运行</>
)}
</span>
</div>
</div>
</div>
{/* 主内容区:三栏布局 - 中间栏加宽,右侧栏变窄 */}
<div className="flex-1 grid grid-cols-12 gap-4 mt-4 min-h-0">
{/* 左侧栏 (15%) - 任务信息 + 智能体配置 */}
<div className={`col-span-2 flex flex-col gap-4 min-h-0 overflow-y-auto ${
isDark ? '' : 'pr-2'
}`}>
{/* 任务信息卡片 */}
<div className={`rounded-xl p-4 flex-shrink-0 border ${
isDark
? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30'
: 'bg-white border-red-100 shadow-sm'
}`}>
<h3 className={`font-semibold text-sm mb-3 flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<span className={isDark ? 'text-blue-400' : 'text-red-500'}>
<FileText className="w-4 h-4" />
</span>
任务信息
</h3>
<div className="space-y-3 text-sm">
<div className="flex items-center justify-between">
<span className={isDark ? 'text-slate-500' : 'text-gray-500'}>任务类型</span>
<span className={`px-2 py-0.5 rounded text-xs ${
isDark
? 'bg-blue-500/20 text-blue-400'
: 'bg-red-100 text-red-600'
}`}>单智能体</span>
</div>
<div className="flex items-center justify-between">
<span className={isDark ? 'text-slate-500' : 'text-gray-500'}>执行智能体</span>
<span className={isDark ? 'text-cyan-400' : 'text-red-600'}>AudienceAgent</span>
</div>
<div className="flex items-center justify-between">
<span className={isDark ? 'text-slate-500' : 'text-gray-500'}>创建时间</span>
<span className={isDark ? 'text-slate-400' : 'text-gray-600'}>14:00</span>
</div>
<div className="flex items-center justify-between">
<span className={isDark ? 'text-slate-500' : 'text-gray-500'}>完成时间</span>
<span className={isDark ? 'text-slate-400' : 'text-gray-600'}>14:03</span>
</div>
</div>
</div>
{/* 智能体配置卡片 */}
<div className={`rounded-xl p-4 flex-1 overflow-y-auto border theme-card scrollbar-thin ${
isDark
? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30'
: 'bg-white border-red-100 shadow-sm'
}`}>
<h3 className={`font-semibold text-sm mb-3 flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<span className={isDark ? 'text-cyan-400' : 'text-red-500'}>
<Bot className="w-4 h-4" />
</span>
智能体配置
</h3>
{/* 智能体简介 */}
<div className={`mb-4 p-3 rounded-lg ${
isDark ? 'bg-slate-900/50' : 'bg-red-50'
}`}>
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-cyan-400' : 'text-red-600'}`}>{agentConfig.name}</p>
<p className={`text-xs mb-2 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>{agentConfig.role}</p>
<p className={`text-xs ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>{agentConfig.description}</p>
</div>
{/* 技能列表 */}
<div className="mb-4">
<div className="flex items-center gap-2 mb-2">
<Brain className={`w-4 h-4 ${isDark ? 'text-purple-400' : 'text-red-500'}`} />
<span className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>启用技能</span>
</div>
<div className="space-y-1.5">
{agentConfig.skills.map((skill, index) => (
<div key={index} className={`flex items-center justify-between p-2 rounded ${
isDark ? 'bg-slate-900/50' : 'bg-red-50'
}`}>
<span className={`text-xs ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>{skill.name}</span>
<span className={`px-1.5 py-0.5 rounded text-[10px] ${
isDark
? 'bg-purple-500/20 text-purple-400'
: 'bg-red-100 text-red-600'
}`}>{skill.type}</span>
</div>
))}
</div>
</div>
{/* 数据源列表 */}
<div>
<div className="flex items-center gap-2 mb-2">
<Database className={`w-4 h-4 ${isDark ? 'text-green-400' : 'text-red-500'}`} />
<span className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>可调用数据源</span>
</div>
<div className="space-y-1.5">
{agentConfig.databases.map((db, index) => (
<div key={index} className={`p-2 rounded ${
isDark ? 'bg-slate-900/50' : 'bg-red-50'
}`}>
<div className="flex items-center justify-between mb-1">
<span className={`text-xs ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>{db.name}</span>
<span className={`w-1.5 h-1.5 rounded-full ${
db.status === 'connected'
? isDark ? 'bg-green-400' : 'bg-red-500'
: 'bg-slate-500'
}`} />
</div>
<p className={`text-[10px] ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>{db.access}</p>
</div>
))}
</div>
</div>
</div>
</div>
{/* 中间栏 (65%) - 对话式问答 - 加宽 */}
<div className="col-span-7 flex flex-col min-h-0">
<div className={`rounded-xl flex-1 flex flex-col min-h-0 border theme-card ${
isDark
? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30'
: 'bg-white border-red-100 shadow-sm'
}`}>
{/* 对话区域头部 */}
<div className={`p-4 border-b flex-shrink-0 ${
isDark ? 'border-blue-800/30' : 'border-red-100'
}`}>
<h3 className={`font-semibold flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<span className={isDark ? 'text-blue-400' : 'text-red-500'}>
<MessageSquare className="w-4 h-4" />
</span>
对话分析
</h3>
</div>
{/* 消息列表 */}
<div className="flex-1 overflow-y-auto p-4 space-y-4 scrollbar-thin">
{messages.map((msg) => (
<div key={msg.id} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[85%] rounded-xl p-4 border ${
msg.role === 'user'
? isDark
? 'bg-blue-600/30 border-blue-500/30'
: 'bg-red-50 border-red-200'
: isDark
? 'bg-slate-900/50 border-blue-800/30'
: 'bg-gray-50 border-gray-200'
}`}>
<div className="flex items-center gap-2 mb-2">
{msg.role === 'agent' ? (
<Bot className={`w-4 h-4 ${isDark ? 'text-cyan-400' : 'text-red-500'}`} />
) : (
<User className={`w-4 h-4 ${isDark ? 'text-blue-400' : 'text-red-500'}`} />
)}
<span className={`text-xs font-medium ${
msg.role === 'user'
? isDark ? 'text-blue-400' : 'text-red-600'
: isDark ? 'text-cyan-400' : 'text-red-600'
}`}>
{msg.role === 'user' ? '你' : 'AudienceAgent'}
</span>
<span className={`text-[10px] ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>{msg.timestamp}</span>
</div>
<p className={`text-sm whitespace-pre-wrap ${
isDark ? 'text-slate-300' : 'text-gray-700'
}`}>{msg.content}</p>
{/* 引用信息 */}
{msg.citations && msg.citations.length > 0 && (
<div className={`mt-3 pt-3 border-t ${
isDark ? 'border-blue-800/30' : 'border-red-200'
}`}>
<button
onClick={() => setShowCitations(showCitations === msg.id ? null : msg.id)}
className={`flex items-center gap-2 text-xs transition-colors ${
isDark ? 'text-slate-400 hover:text-blue-400' : 'text-gray-500 hover:text-red-500'
}`}
>
<BookOpen className="w-3 h-3" />
查看引用 ({msg.citations.length})
{showCitations === msg.id ? <ChevronDown className="w-3 h-3" /> : <ChevronRight className="w-3 h-3" />}
</button>
{showCitations === msg.id && (
<div className="mt-2 space-y-2">
{msg.citations.map((citation, index) => (
<div className={`p-2 rounded flex items-start gap-2 ${
isDark ? 'bg-slate-800/50' : 'bg-white border border-gray-200'
}`}>
<ExternalLink className={`w-3 h-3 mt-0.5 ${isDark ? 'text-slate-500' : 'text-gray-400'}`} />
<div>
<p className={`text-xs ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>{citation.title}</p>
<p className={`text-[10px] ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>{citation.source} {citation.page && `· 第${citation.page}页`}</p>
</div>
</div>
))}
</div>
)}
</div>
)}
</div>
</div>
))}
{isRunning && (
<div className="flex justify-start">
<div className={`rounded-xl p-4 border ${
isDark ? 'bg-slate-900/50 border-blue-800/30' : 'bg-gray-50 border-gray-200'
}`}>
<div className="flex items-center gap-2">
<Bot className={`w-4 h-4 ${isDark ? 'text-cyan-400' : 'text-red-500'}`} />
<span className={`text-sm ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>分析中...</span>
<div className="flex gap-1">
<span className={`w-1.5 h-1.5 rounded-full animate-pulse ${
isDark ? 'bg-cyan-400' : 'bg-red-500'
}`} />
<span className={`w-1.5 h-1.5 rounded-full animate-pulse ${
isDark ? 'bg-cyan-400' : 'bg-red-500'
}`} style={{ animationDelay: '100ms' }} />
<span className={`w-1.5 h-1.5 rounded-full animate-pulse ${
isDark ? 'bg-cyan-400' : 'bg-red-500'
}`} style={{ animationDelay: '200ms' }} />
</div>
</div>
</div>
</div>
)}
</div>
{/* 输入区域 */}
<div className={`p-4 border-t flex-shrink-0 ${
isDark ? 'border-blue-800/30' : 'border-red-100'
}`}>
<div className="flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
placeholder="输入问题或任务要求..."
className={`flex-1 px-4 py-2.5 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-500/20 transition-colors ${
isDark
? 'bg-slate-900/50 border border-blue-800/30 text-white placeholder-slate-500'
: 'bg-white border border-red-200 text-gray-900 placeholder-gray-400'
}`}
/>
<button
onClick={handleSend}
disabled={isRunning}
className={`px-4 py-2.5 rounded-lg text-sm transition-colors flex items-center gap-2 ${
isDark
? 'bg-blue-600 hover:bg-blue-500 disabled:bg-slate-700 text-white'
: 'bg-red-600 hover:bg-red-500 disabled:bg-gray-400 text-white shadow-md'
}`}
>
<Send className="w-4 h-4" />
发送
</button>
</div>
</div>
</div>
</div>
{/* 右侧栏 (20%) - 分析结果 - 变窄 */}
<div className="col-span-3 flex flex-col min-h-0">
<div className={`rounded-xl flex-1 flex flex-col min-h-0 border theme-card ${
isDark
? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30'
: 'bg-white border-red-100 shadow-sm'
}`}>
{/* 结果区域头部 */}
<div className={`p-4 border-b flex-shrink-0 ${
isDark ? 'border-blue-800/30' : 'border-red-100'
}`}>
<div className="flex items-center justify-between">
<h3 className={`font-semibold flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<span className={isDark ? 'text-purple-400' : 'text-red-500'}>
<Sparkles className="w-4 h-4" />
</span>
分析结果
</h3>
<div className="flex items-center gap-1">
<button
onClick={handleCopyResult}
className={`p-2 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`}
title="复制结果"
>
<Copy className={`w-4 h-4 ${isDark ? 'text-slate-400' : 'text-red-400'}`} />
</button>
<button
onClick={handleEditResult}
className={`p-2 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`}
title="编辑结果"
>
<Edit3 className={`w-4 h-4 ${isEditing ? (isDark ? 'text-blue-400' : 'text-red-500') : (isDark ? 'text-slate-400' : 'text-red-400')}`} />
</button>
<button
onClick={handleExport}
className={`p-2 rounded-lg transition-colors ${
isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'
}`}
title="导出结果"
>
<Download className={`w-4 h-4 ${isDark ? 'text-slate-400' : 'text-red-400'}`} />
</button>
</div>
</div>
</div>
{/* 结果内容 */}
<div className="flex-1 overflow-y-auto p-4 scrollbar-thin">
{/* 结果概览 */}
<div className={`mb-4 p-4 rounded-xl border ${
isDark
? 'bg-gradient-to-br from-purple-600/20 to-blue-600/20 border-purple-500/30'
: 'bg-gradient-to-br from-red-50 to-red-100 border-red-200'
}`}>
<h4 className={`font-medium text-sm mb-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>{analysisResult.title}</h4>
<div className={`flex items-center gap-4 text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
<span className="flex items-center gap-1">
<Bot className="w-3 h-3" />
{analysisResult.agent}
</span>
<span className="flex items-center gap-1">
<Clock className="w-3 h-3" />
{analysisResult.completionTime}
</span>
</div>
</div>
{/* 结论摘要 */}
<div className="mb-4">
<h4 className={`text-xs mb-2 flex items-center gap-2 ${
isDark ? 'text-slate-400' : 'text-gray-500'
}`}>
<Lightbulb className={`w-3 h-3 ${isDark ? 'text-yellow-400' : 'text-yellow-500'}`} />
核心结论
</h4>
<div className={`p-3 rounded-lg border ${
isDark
? 'bg-blue-500/10 border-blue-500/30'
: 'bg-red-50 border-red-200'
}`}>
<p className={`text-sm ${isDark ? 'text-blue-300' : 'text-red-600'}`}>{analysisResult.summary}</p>
</div>
</div>
{/* 关键洞察 */}
<div className="mb-4">
<h4 className={`text-xs mb-2 flex items-center gap-2 ${
isDark ? 'text-slate-400' : 'text-gray-500'
}`}>
<Eye className={`w-3 h-3 ${isDark ? 'text-green-400' : 'text-green-500'}`} />
关键洞察
</h4>
<div className="space-y-2">
<div className={`p-3 rounded-lg border ${
isDark ? 'bg-slate-900/50 border-blue-800/30' : 'bg-gray-50 border-gray-200'
}`}>
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-white' : 'text-gray-900'}`}>核心受众</p>
<p className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>18-25岁基层青年群体</p>
</div>
<div className={`p-3 rounded-lg border ${
isDark ? 'bg-slate-900/50 border-blue-800/30' : 'bg-gray-50 border-gray-200'
}`}>
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-white' : 'text-gray-900'}`}>核心驱动</p>
<p className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>社交认同与真实态度</p>
</div>
<div className={`p-3 rounded-lg border ${
isDark ? 'bg-slate-900/50 border-blue-800/30' : 'bg-gray-50 border-gray-200'
}`}>
<p className={`text-sm font-medium mb-1 ${isDark ? 'text-white' : 'text-gray-900'}`}>内容策略</p>
<p className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>真实故事 + 情感共鸣 + 实用价值</p>
</div>
</div>
</div>
{/* 详细信息预览 */}
<div>
<h4 className={`text-xs mb-2 flex items-center gap-2 ${
isDark ? 'text-slate-400' : 'text-gray-500'
}`}>
<Info className={`w-3 h-3 ${isDark ? 'text-slate-500' : 'text-gray-400'}`} />
详细报告
</h4>
{isEditing ? (
<textarea
value={editedContent}
onChange={(e) => setEditedContent(e.target.value)}
className={`w-full h-48 p-3 rounded-lg text-xs resize-none focus:outline-none focus:ring-2 focus:ring-red-500/20 ${
isDark
? 'bg-slate-900/50 border border-blue-800/30 text-slate-300'
: 'bg-white border border-gray-200 text-gray-700'
}`}
/>
) : (
<div className={`p-3 rounded-lg max-h-48 overflow-y-auto ${
isDark ? 'bg-slate-900/50 border border-blue-800/30' : 'bg-gray-50 border border-gray-200'
}`}>
<div className={`space-y-3 text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>
<p>## 核心受众画像</p>
<p className="pl-2">• 年龄段:18-25岁</p>
<p className="pl-2">• 身份定位:基层青年(学生、职场新人)</p>
<p className="pl-2">• 地域分布:一二线城市为主</p>
<p className="pt-2">## 用户特征</p>
<p className="pl-2">• 社交媒体重度用户</p>
<p className="pl-2">• 注重"真实感"和"态度感"</p>
<p className="pl-2">• 追求个性化表达</p>
<p className={`pt-2 ${isDark ? 'text-blue-400' : 'text-red-500'}`}>点击编辑按钮可修改报告内容</p>
</div>
</div>
)}
</div>
{/* 引用来源 */}
<div className="mt-4">
<h4 className={`text-xs mb-2 flex items-center gap-2 ${
isDark ? 'text-slate-400' : 'text-gray-500'
}`}>
<BookOpen className={`w-3 h-3 ${isDark ? 'text-slate-500' : 'text-gray-400'}`} />
引用来源
</h4>
<div className="space-y-1.5">
{[
{ title: 'Z世代消费行为研究报告', source: '市场研究数据库' },
{ title: '短视频平台用户画像', source: 'Y系统-用户标签' },
{ title: '品牌年轻化营销案例集', source: '宣传知识库' },
].map((item, index) => (
<div key={index} className={`flex items-center gap-2 p-2 rounded ${
isDark ? 'bg-slate-900/30' : 'bg-white border border-gray-100'
}`}>
<ExternalLink className={`w-3 h-3 ${isDark ? 'text-slate-500' : 'text-gray-400'}`} />
<div>
<p className={`text-xs ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>{item.title}</p>
<p className={`text-[10px] ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>{item.source}</p>
</div>
</div>
))}
</div>
</div>
</div>
{/* 底部操作 */}
<div className={`p-4 border-t flex-shrink-0 ${
isDark ? 'border-blue-800/30' : 'border-red-100'
}`}>
<div className="flex gap-2">
<button
onClick={handleExport}
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg text-sm transition-colors ${
isDark
? 'bg-blue-600 hover:bg-blue-500 text-white'
: 'bg-red-600 hover:bg-red-500 text-white shadow-md'
}`}
>
<Download className="w-4 h-4" />
导出报告
</button>
<button className={`flex-1 flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg text-sm transition-colors ${
isDark
? 'bg-slate-700/50 hover:bg-slate-600/50 border border-blue-800/30 text-slate-300'
: 'bg-gray-100 hover:bg-gray-200 border border-gray-200 text-gray-700'
}`}>
<Zap className="w-4 h-4" />
存入知识库
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default TaskSingle;
@@ -0,0 +1,935 @@
import React, { useState } from 'react';
import {
Target,
Clock,
User,
ChevronRight,
Play,
RotateCcw,
Download,
MessageSquare,
AlertTriangle,
CheckCircle,
Bot,
FileText,
Lightbulb,
ArrowRight,
RefreshCw,
Pause,
Eye,
Zap,
Shield,
AlertCircle,
UserCheck,
CheckCircle2,
XCircle,
Send,
Plus,
AtSign,
FileInput,
} from 'lucide-react';
import { useTheme } from '@/contexts/ThemeContext';
import { PageType } from '@/components/Sidebar';
interface TaskWorkspaceProps {
onNavigate: (page: PageType) => void;
}
const TaskWorkspace: React.FC<TaskWorkspaceProps> = ({ onNavigate }) => {
const { theme } = useTheme();
const isDark = theme === 'dark';
const [currentPhase, setCurrentPhase] = useState(3);
const [showHumanIntervention, setShowHumanIntervention] = useState(false);
const [inputType, setInputType] = useState('question');
const [sendTarget, setSendTarget] = useState('current');
const [inputMessage, setInputMessage] = useState('');
// 逻辑链一致性检查数据
const logicChainChecks = [
{ id: 'goal-behavior', label: '目标-行为', status: 'passed', detail: '行为与目标高度一致' },
{ id: 'behavior-driver', label: '行为-驱动', status: 'passed', detail: '驱动有效支撑行为' },
{ id: 'driver-narrative', label: '驱动-叙事', status: 'passed', detail: '叙事与驱动匹配' },
{ id: 'narrative-audience', label: '叙事-受众', status: 'warning', detail: '叙事需进一步收窄以匹配核心受众' },
];
// 待确认问题列表
const pendingQuestions = [
{ id: 1, type: 'field', title: '受众范围收窄', description: '是否聚焦基层青年而非全体青年?', priority: 'high' },
{ id: 2, type: 'conflict', title: '叙事方向待定', description: '真实性与话题性如何平衡?', priority: 'medium' },
];
const phases = [
{ id: 1, name: '目标分析', agent: 'GoalAgent', status: 'completed' },
{ id: 2, name: '受众分析', agent: 'AudienceAgent', status: 'completed' },
{ id: 3, name: '行为分析', agent: 'BehaviorAgent', status: 'active' },
{ id: 4, name: '驱动分析', agent: 'DriverAgent', status: 'pending' },
{ id: 5, name: '叙事分析', agent: 'NarrativeAgent', status: 'pending' },
{ id: 6, name: '逻辑链收敛', agent: 'LogicChainAgent', status: 'pending' },
{ id: 7, name: '风险评估', agent: 'RiskAgent', status: 'pending' },
{ id: 8, name: '方案生成', agent: 'DraftAgent', status: 'pending' },
];
const discussionCards = [
{
id: 1,
agent: 'GoalAgent',
phase: '目标分析',
time: '10:32',
type: 'judgment',
coreJudgment: '核心目标是提升品牌在年轻群体中的认知度和好感度',
outputFields: {
primary_goal: '提升品牌在Z世代用户中的认知度',
secondary_goal: '建立品牌年轻化形象',
},
releaseStatus: 'passed',
riskWarnings: [],
},
{
id: 2,
agent: 'AudienceAgent',
phase: '受众分析',
time: '11:15',
type: 'judgment',
coreJudgment: '核心受众应聚焦18-25岁基层青年群体,次级受众为25-30岁职场新人',
outputFields: {
core_audience: '18-25岁基层青年',
secondary_audience: '25-30岁职场新人',
audience_insights: ['社交媒体重度用户', '注重性价比', '偏好真实、有态度的品牌'],
},
releaseStatus: 'passed',
riskWarnings: ['受众范围可能过宽,建议进一步细化'],
},
{
id: 3,
agent: 'BehaviorAgent',
phase: '行为分析',
time: '14:20',
type: 'response',
coreJudgment: '目标行为是促使受众主动参与互动并生成UGC内容',
outputFields: {
target_behavior: '主动参与话题讨论、生成UGC内容',
key_conversion: '浏览 → 点赞 → 评论 → 转发 → 创作',
},
releaseStatus: 'pending',
riskWarnings: [],
},
];
const confirmedFields = {
goal: '提升品牌在Z世代用户中的认知度',
audience: '18-25岁基层青年',
behavior: '主动参与互动并生成UGC内容',
driver: '待分析',
narrative: '待分析',
};
const currentPhaseName = phases.find(p => p.status === 'active')?.name || '未知';
return (
<div className="h-[calc(100vh-140px)] flex flex-col">
{/* 顶部:运行状态栏(精简版 - 无流程图) */}
<div className={`rounded-xl p-4 flex-shrink-0 border ${
isDark ? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30' : 'bg-white border-red-100 shadow-sm'
}`}>
<div className="flex items-center justify-between">
{/* 左侧:当前阶段和运行状态 */}
<div className="flex items-center gap-6">
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${
isDark ? 'bg-blue-500/20' : 'bg-red-100'
}`}>
<Target className={`w-5 h-5 ${isDark ? 'text-blue-400' : 'text-red-500'}`} />
</div>
<div>
<p className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>当前阶段</p>
<p className={`font-semibold ${isDark ? 'text-white' : 'text-gray-900'}`}>{currentPhaseName}</p>
</div>
</div>
<div className={`h-8 w-px ${isDark ? 'bg-blue-800/30' : 'bg-red-200'}`} />
<span className={`px-3 py-1.5 rounded-full text-sm font-medium flex items-center gap-1.5 ${
isDark
? 'bg-blue-500/20 text-blue-400'
: 'bg-red-100 text-red-600'
}`}>
<span className={`w-2 h-2 rounded-full animate-pulse ${
isDark ? 'bg-blue-400' : 'bg-red-500'
}`} />
运行中
</span>
</div>
{/* 右侧:全局操作按钮 */}
<div className="flex items-center gap-2">
<button className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm transition-colors border ${
isDark
? 'bg-yellow-500/20 hover:bg-yellow-500/30 border-yellow-500/30 text-yellow-400'
: 'bg-yellow-100 hover:bg-yellow-200 border-yellow-200 text-yellow-600'
}`}>
<Pause className="w-4 h-4" />
暂停
</button>
<button className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm transition-colors ${
isDark
? 'bg-green-600 hover:bg-green-500 text-white'
: 'bg-green-600 hover:bg-green-500 text-white shadow-md'
}`}>
<Play className="w-4 h-4" />
开始
</button>
<button
onClick={() => onNavigate('strategy-result')}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm transition-colors border ${
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-200 text-gray-700'
}`}
>
<Eye className="w-4 h-4" />
查看结果
</button>
<button className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm transition-colors border ${
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-200 text-gray-700'
}`}>
<Download className="w-4 h-4" />
导出
</button>
<button className={`p-2 rounded-lg transition-colors border ${
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-200 text-gray-700'
}`}>
<ChevronRight className="w-4 h-4" />
</button>
</div>
</div>
</div>
{/* 三栏式主工作区 */}
<div className="flex-1 grid grid-cols-12 gap-4 mt-4 min-h-0 overflow-hidden">
{/* 左栏 - 任务与流程区 (22%) */}
<div className="col-span-3 flex flex-col gap-4 min-h-0 overflow-y-auto">
{/* 任务信息卡 */}
<div className={`rounded-xl p-4 flex-shrink-0 border ${
isDark ? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30' : 'bg-white border-red-100 shadow-sm'
}`}>
<h3 className={`font-semibold text-sm mb-3 flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<FileText className={`w-4 h-4 ${isDark ? 'text-blue-400' : 'text-red-500'}`} />
任务信息
</h3>
<div className="space-y-2 text-sm">
<div className="flex items-center justify-between">
<span className={isDark ? 'text-slate-500' : 'text-gray-500'}>任务标题</span>
<span className={`text-right truncate max-w-[140px] ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>五四青年节宣传活动策略</span>
</div>
<div className="flex items-center justify-between">
<span className={isDark ? 'text-slate-500' : 'text-gray-500'}>任务编号</span>
<span className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-400'}`}>TASK-0319-001</span>
</div>
<div className="flex items-center justify-between">
<span className={isDark ? 'text-slate-500' : 'text-gray-500'}>所属场景</span>
<span className={`text-xs ${isDark ? 'text-blue-400' : 'text-red-600'}`}>宣传策略生成</span>
</div>
<div className={`pt-2 border-t ${isDark ? 'border-blue-800/30' : 'border-red-100'}`}>
<div className="flex items-center justify-between text-xs">
<span className={`flex items-center gap-1 ${isDark ? 'text-slate-500' : 'text-gray-500'}`}>
<User className="w-3 h-3" /> 张策略
</span>
<span className={`flex items-center gap-1 ${isDark ? 'text-slate-500' : 'text-gray-500'}`}>
<Clock className="w-3 h-3" /> 2026-03-19
</span>
</div>
</div>
</div>
</div>
{/* 流程节点区 */}
<div className={`rounded-xl p-4 flex-shrink-0 border ${
isDark ? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30' : 'bg-white border-red-100 shadow-sm'
}`}>
<h3 className={`font-semibold text-sm mb-3 flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<Bot className={`w-4 h-4 ${isDark ? 'text-cyan-400' : 'text-red-500'}`} />
流程节点
</h3>
<div className="space-y-1.5">
{phases.map((phase) => (
<button
key={phase.id}
onClick={() => setCurrentPhase(phase.id)}
className={`w-full flex items-center gap-2 p-2 rounded-lg transition-all text-left border ${
phase.status === 'active'
? isDark
? 'bg-blue-500/20 border-blue-500/30'
: 'bg-red-50 border-red-200'
: phase.status === 'completed'
? isDark
? 'bg-slate-900/50 hover:bg-slate-800/50'
: 'bg-gray-50 hover:bg-gray-100'
: isDark
? 'bg-slate-900/30 opacity-50'
: 'bg-gray-50 opacity-50'
}`}
>
<div className={`w-5 h-5 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0 ${
phase.status === 'completed'
? isDark ? 'bg-green-500 text-white' : 'bg-green-500 text-white'
: phase.status === 'active'
? isDark ? 'bg-blue-500 text-white' : 'bg-blue-500 text-white'
: isDark ? 'bg-slate-700 text-slate-400' : 'bg-gray-200 text-gray-500'
}`}>
{phase.id}
</div>
<div className="flex-1 min-w-0">
<p className={`text-xs font-medium truncate ${
phase.status === 'active' ? (isDark ? 'text-white' : 'text-gray-900') : (isDark ? 'text-slate-400' : 'text-gray-500')
}`}>
{phase.name}
</p>
<p className={`text-[10px] truncate ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>{phase.agent}</p>
</div>
<span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${
phase.status === 'completed' ? (isDark ? 'bg-green-400' : 'bg-green-500') :
phase.status === 'active' ? (isDark ? 'bg-blue-400' : 'bg-blue-500') :
(isDark ? 'bg-slate-600' : 'bg-gray-300')
}`} />
</button>
))}
</div>
</div>
{/* 快捷操作卡 */}
<div className={`rounded-xl p-4 flex-shrink-0 border ${
isDark ? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30' : 'bg-white border-red-100 shadow-sm'
}`}>
<h3 className={`font-semibold text-sm mb-3 flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<Zap className={`w-4 h-4 ${isDark ? 'text-yellow-400' : 'text-red-500'}`} />
快捷操作
</h3>
<div className="space-y-2">
<button className={`w-full flex items-center justify-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors border ${
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-200 text-gray-700'
}`}>
<RefreshCw className="w-4 h-4" />
重新运行当前节点
</button>
<button className={`w-full flex items-center justify-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors border ${
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-200 text-gray-700'
}`}>
<RotateCcw className="w-4 h-4" />
发起回退
</button>
<button className={`w-full flex items-center justify-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors border ${
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-200 text-gray-700'
}`}>
<FileInput className="w-4 h-4" />
查看日志
</button>
<button
onClick={() => setShowHumanIntervention(true)}
className={`w-full flex items-center justify-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors border ${
isDark
? 'bg-purple-500/20 hover:bg-purple-500/30 border-purple-500/30 text-purple-400'
: 'bg-purple-100 hover:bg-purple-200 border-purple-200 text-purple-600'
}`}
>
<UserCheck className="w-4 h-4" />
人工介入
</button>
</div>
</div>
</div>
{/* 中栏 - 讨论区 (47%) */}
<div className="col-span-6 flex flex-col gap-4 min-h-0">
{/* 当前议题卡 */}
<div className={`rounded-xl p-4 flex-shrink-0 border ${
isDark ? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30' : 'bg-white border-red-100 shadow-sm'
}`}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${
isDark ? 'bg-cyan-500/20' : 'bg-red-100'
}`}>
<MessageSquare className={`w-4 h-4 ${isDark ? 'text-cyan-400' : 'text-red-500'}`} />
</div>
<div>
<p className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>当前议题</p>
<p className={`text-sm font-medium ${isDark ? 'text-white' : 'text-gray-900'}`}>明确目标受众的行为模式和转化路径</p>
</div>
</div>
<div className="flex items-center gap-2">
<span className={`px-2.5 py-1 rounded-full text-xs font-medium ${
isDark ? 'bg-cyan-500/20 text-cyan-400' : 'bg-red-100 text-red-600'
}`}>
BehaviorAgent
</span>
<button className={`px-2 py-1 rounded text-xs transition-colors border ${
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-200 text-gray-700'
}`}>
指定发言
</button>
<button className={`px-2 py-1 rounded text-xs transition-colors border ${
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-200 text-gray-700'
}`}>
结束本轮
</button>
</div>
</div>
<div className={`mt-3 p-2 rounded-lg text-xs ${isDark ? 'bg-slate-900/50' : 'bg-gray-50'}`}>
<span className={isDark ? 'text-slate-500' : 'text-gray-400'}>上游输入:</span>
<span className={isDark ? 'text-cyan-400' : 'text-red-600'}>AudienceAgent 已确认输出</span>
<span className={`mx-2 ${isDark ? 'text-slate-400' : 'text-gray-400'}`}>→</span>
<span className={isDark ? 'text-slate-300' : 'text-gray-700'}>核心受众已明确:18-25岁基层青年群体</span>
</div>
</div>
{/* 讨论消息流 */}
<div className={`flex-1 rounded-xl p-4 overflow-y-auto min-h-0 border ${
isDark ? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30' : 'bg-white border-red-100 shadow-sm'
}`}>
<h3 className={`font-semibold text-sm mb-4 flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<Bot className={`w-4 h-4 ${isDark ? 'text-purple-400' : 'text-red-500'}`} />
讨论消息流
</h3>
<div className="space-y-4">
{discussionCards.map((card) => (
<div
key={card.id}
className={`p-4 rounded-xl border ${
card.releaseStatus === 'passed'
? isDark
? 'bg-slate-900/50 border-green-500/30'
: 'bg-green-50 border-green-200'
: card.releaseStatus === 'pending'
? isDark
? 'bg-blue-500/10 border-blue-500/30'
: 'bg-blue-50 border-blue-200'
: isDark
? 'bg-red-500/10 border-red-500/30'
: 'bg-red-50 border-red-200'
}`}
>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-2">
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${
isDark ? 'bg-purple-500/20' : 'bg-red-100'
}`}>
<Bot className={`w-4 h-4 ${isDark ? 'text-purple-400' : 'text-red-500'}`} />
</div>
<div>
<div className="flex items-center gap-2">
<p className={`text-sm font-medium ${isDark ? 'text-white' : 'text-gray-900'}`}>{card.agent}</p>
<span className={`px-1.5 py-0.5 rounded text-[10px] ${isDark ? 'bg-slate-700 text-slate-400' : 'bg-gray-100 text-gray-500'}`}>
{card.type === 'judgment' ? '独立判断' : '回应'}
</span>
</div>
<p className={`text-xs ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>{card.phase} · {card.time}</p>
</div>
</div>
<span className={`px-2 py-1 rounded-full text-xs font-medium flex items-center gap-1 ${
card.releaseStatus === 'passed'
? isDark
? 'bg-green-500/20 text-green-400'
: 'bg-green-100 text-green-600'
: card.releaseStatus === 'pending'
? isDark
? 'bg-yellow-500/20 text-yellow-400'
: 'bg-yellow-100 text-yellow-600'
: isDark
? 'bg-red-500/20 text-red-400'
: 'bg-red-100 text-red-600'
}`}>
{card.releaseStatus === 'passed' ? (
<><CheckCircle className="w-3 h-3" /> 已放行</>
) : card.releaseStatus === 'pending' ? (
<><Clock className="w-3 h-3" /> 待放行</>
) : (
<><AlertTriangle className="w-3 h-3" /> 未通过</>
)}
</span>
</div>
<div className="mb-3">
<p className={`text-xs mb-1 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>核心观点</p>
<p className={`text-sm ${isDark ? 'text-white' : 'text-gray-900'}`}>{card.coreJudgment}</p>
</div>
<div className={`mb-3 p-3 rounded-lg ${isDark ? 'bg-slate-800/50' : 'bg-gray-50'}`}>
<p className={`text-xs mb-2 ${isDark ? 'text-slate-500' : 'text-gray-400'}`}>输出字段摘要</p>
<div className="space-y-1 text-xs">
{Object.entries(card.outputFields).map(([key, value]) => (
<div key={key} className="flex items-start gap-2">
<span className={`font-mono ${isDark ? 'text-cyan-400' : 'text-red-600'}`}>{key}:</span>
<span className={isDark ? 'text-slate-300' : 'text-gray-700'}>
{Array.isArray(value) ? value.join(', ') : value}
</span>
</div>
))}
</div>
</div>
{card.riskWarnings.length > 0 && (
<div className={`mb-3 p-2 rounded-lg border ${
isDark ? 'bg-amber-500/10 border-amber-500/30' : 'bg-yellow-50 border-yellow-200'
}`}>
<p className={`text-xs font-medium flex items-center gap-1 ${
isDark ? 'text-amber-400' : 'text-yellow-600'
}`}>
<AlertTriangle className="w-3 h-3" /> 风险提示
</p>
<p className={`text-xs mt-1 ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>{card.riskWarnings[0]}</p>
</div>
)}
<div className={`flex items-center gap-2 pt-2 border-t ${isDark ? 'border-blue-800/30' : 'border-gray-200'}`}>
<button className={`px-2.5 py-1.5 rounded text-xs transition-colors ${
isDark
? 'bg-green-600/50 hover:bg-green-600 text-white'
: 'bg-green-600 hover:bg-green-500 text-white'
}`}>
采纳
</button>
<button className={`px-2.5 py-1.5 rounded text-xs transition-colors border ${
isDark
? 'bg-yellow-500/20 hover:bg-yellow-500/30 border-yellow-500/30 text-yellow-400'
: 'bg-yellow-100 hover:bg-yellow-200 border-yellow-200 text-yellow-600'
}`}>
待确认
</button>
<button className={`px-2.5 py-1.5 rounded text-xs transition-colors border ${
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-200 text-gray-700'
}`}>
引用追问
</button>
<button className={`px-2.5 py-1.5 rounded text-xs transition-colors border ${
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-200 text-gray-700'
}`}>
写入策略链
</button>
</div>
</div>
))}
{/* 系统摘要卡 */}
<div className={`p-4 rounded-xl border ${
isDark ? 'bg-blue-500/10 border-blue-500/30' : 'bg-blue-50 border-blue-200'
}`}>
<div className="flex items-center gap-2 mb-3">
<div className={`w-6 h-6 rounded flex items-center justify-center ${
isDark ? 'bg-blue-500/20' : 'bg-blue-100'
}`}>
<Lightbulb className={`w-3 h-3 ${isDark ? 'text-blue-400' : 'text-blue-500'}`} />
</div>
<p className={`text-sm font-medium ${isDark ? 'text-blue-400' : 'text-blue-600'}`}>系统摘要</p>
</div>
<div className="space-y-2 text-sm">
<div>
<p className={`text-xs mb-1 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>已形成结论:</p>
<ul className={`text-xs space-y-0.5 list-disc list-inside ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
<li>核心受众明确为18-25岁基层青年</li>
<li>目标行为建议拆分为参与和传播两层</li>
</ul>
</div>
<div>
<p className={`text-xs mb-1 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>待确认:</p>
<ul className={`text-xs space-y-0.5 list-disc list-inside ${isDark ? 'text-yellow-400' : 'text-yellow-600'}`}>
<li>UGC创作是否纳入核心行为?</li>
</ul>
</div>
<div className={`pt-2 border-t ${isDark ? 'border-blue-800/30' : 'border-blue-200'}`}>
<p className={`text-xs ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>建议下一步:</p>
<p className={`text-xs ${isDark ? 'text-cyan-400' : 'text-red-600'}`}>邀请 DriverAgent 分析驱动因素</p>
</div>
</div>
<div className="flex items-center gap-2 mt-3">
<button className={`px-3 py-1.5 rounded text-xs transition-colors ${
isDark
? 'bg-blue-600 hover:bg-blue-500 text-white'
: 'bg-blue-600 hover:bg-blue-500 text-white shadow-md'
}`}>
推进下一步
</button>
<button className={`px-3 py-1.5 rounded text-xs transition-colors border ${
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-200 text-gray-700'
}`}>
写入待确认
</button>
</div>
</div>
</div>
</div>
</div>
{/* 右栏 - 策略收敛区 (28%) */}
<div className="col-span-3 flex flex-col gap-4 min-h-0 overflow-y-auto">
{/* 策略链卡 */}
<div className={`rounded-xl p-4 flex-shrink-0 border ${
isDark
? 'bg-gradient-to-br from-blue-600/20 to-cyan-600/20 backdrop-blur-xl border-blue-500/30'
: 'bg-gradient-to-br from-red-50 to-red-100 border-red-200'
}`}>
<div className="flex items-center justify-between mb-3">
<h3 className={`font-semibold text-sm flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<Lightbulb className={`w-4 h-4 ${isDark ? 'text-yellow-400' : 'text-red-500'}`} />
策略链
</h3>
<div className="flex items-center gap-1">
<button className={`p-1 rounded transition-colors ${isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'}`} title="复制">
<FileText className={`w-3.5 h-3.5 ${isDark ? 'text-slate-400' : 'text-gray-400'}`} />
</button>
<button className={`p-1 rounded transition-colors ${isDark ? 'hover:bg-slate-700/50' : 'hover:bg-red-50'}`} title="编辑">
<RefreshCw className={`w-3.5 h-3.5 ${isDark ? 'text-slate-400' : 'text-gray-400'}`} />
</button>
</div>
</div>
<div className={`p-3 rounded-lg border ${
isDark ? 'bg-slate-900/50 border-blue-500/20' : 'bg-white border-red-100'
}`}>
<p className={`text-xs leading-relaxed ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>
面向<span className={`font-medium ${isDark ? 'text-cyan-400' : 'text-red-600'}`}>【18-25岁基层青年】</span>,
围绕其<span className={`font-medium ${isDark ? 'text-yellow-400' : 'text-red-600'}`}>【社交认同与自我表达】</span>驱动,
传播<span className={`font-medium ${isDark ? 'text-purple-400' : 'text-red-600'}`}>【真实、有态度的青年故事】</span>叙事,
促使其产生<span className={`font-medium ${isDark ? 'text-green-400' : 'text-red-600'}`}>【主动参与互动并生成UGC内容】</span>行为,
从而实现<span className={`font-medium ${isDark ? 'text-blue-400' : 'text-red-600'}`}>【提升品牌在Z世代用户中的认知度】</span>目标。
</p>
</div>
</div>
{/* 逻辑链一致性校验 */}
<div className={`rounded-xl p-4 flex-shrink-0 border ${
isDark ? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30' : 'bg-white border-red-100 shadow-sm'
}`}>
<div className="flex items-center justify-between mb-3">
<h3 className={`font-semibold text-sm flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<Shield className={`w-4 h-4 ${isDark ? 'text-green-400' : 'text-red-500'}`} />
逻辑链一致性
</h3>
<span className={`px-2 py-0.5 rounded text-xs ${
isDark ? 'bg-green-500/20 text-green-400' : 'bg-green-100 text-green-600'
}`}>
3/4 通过
</span>
</div>
<div className="space-y-1.5">
{logicChainChecks.map((check) => (
<div
key={check.id}
className={`p-2 rounded-lg border text-xs ${
check.status === 'passed'
? isDark
? 'bg-green-500/10 border-green-500/30'
: 'bg-green-50 border-green-200'
: check.status === 'warning'
? isDark
? 'bg-yellow-500/10 border-yellow-500/30'
: 'bg-yellow-50 border-yellow-200'
: isDark
? 'bg-red-500/10 border-red-500/30'
: 'bg-red-50 border-red-200'
}`}
>
<div className="flex items-center justify-between">
<span className={`font-medium ${
check.status === 'passed'
? isDark ? 'text-green-400' : 'text-green-600'
: check.status === 'warning'
? isDark ? 'text-yellow-400' : 'text-yellow-600'
: isDark ? 'text-red-400' : 'text-red-600'
}`}>
{check.label}
</span>
{check.status === 'passed' ? (
<CheckCircle2 className={`w-3.5 h-3.5 ${isDark ? 'text-green-400' : 'text-green-500'}`} />
) : check.status === 'warning' ? (
<AlertCircle className={`w-3.5 h-3.5 ${isDark ? 'text-yellow-400' : 'text-yellow-500'}`} />
) : (
<XCircle className={`w-3.5 h-3.5 ${isDark ? 'text-red-400' : 'text-red-500'}`} />
)}
</div>
</div>
))}
</div>
</div>
{/* 已采纳观点 */}
<div className={`rounded-xl p-4 flex-shrink-0 border ${
isDark ? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30' : 'bg-white border-red-100 shadow-sm'
}`}>
<h3 className={`font-semibold text-sm mb-3 flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<CheckCircle className={`w-4 h-4 ${isDark ? 'text-green-400' : 'text-red-500'}`} />
已采纳观点
</h3>
<div className="space-y-2">
{[
{ agent: 'GoalAgent', opinion: '主目标是提升认知度与好感度' },
{ agent: 'AudienceAgent', opinion: '核心受众为18-25岁基层青年' },
{ agent: 'BehaviorAgent', opinion: '目标行为应拆为参与和传播' },
].map((item, index) => (
<div key={index} className={`p-2 rounded-lg border ${
isDark ? 'bg-slate-900/50 border-green-500/20' : 'bg-green-50 border-green-200'
}`}>
<p className={`text-xs font-medium mb-0.5 ${isDark ? 'text-green-400' : 'text-green-600'}`}>{item.agent}</p>
<p className={`text-xs ${isDark ? 'text-slate-300' : 'text-gray-700'}`}>{item.opinion}</p>
</div>
))}
</div>
</div>
{/* 待确认与分歧 */}
<div className={`rounded-xl p-4 flex-shrink-0 border ${
isDark ? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30' : 'bg-white border-red-100 shadow-sm'
}`}>
<div className="flex items-center justify-between mb-3">
<h3 className={`font-semibold text-sm flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<AlertTriangle className={`w-4 h-4 ${isDark ? 'text-amber-400' : 'text-red-500'}`} />
待确认与分歧
</h3>
<span className={`px-2 py-0.5 rounded text-xs ${
isDark ? 'bg-amber-500/20 text-amber-400' : 'bg-yellow-100 text-yellow-600'
}`}>
{pendingQuestions.length} 项
</span>
</div>
<div className="space-y-2">
{pendingQuestions.map((question) => (
<div
key={question.id}
className={`p-2 rounded-lg border ${
question.priority === 'high'
? isDark
? 'bg-red-500/10 border-red-500/30'
: 'bg-red-50 border-red-200'
: isDark
? 'bg-amber-500/10 border-amber-500/30'
: 'bg-yellow-50 border-yellow-200'
}`}
>
<p className={`text-xs font-medium ${
question.priority === 'high' ? (isDark ? 'text-red-400' : 'text-red-600') : (isDark ? 'text-amber-400' : 'text-yellow-600')
}`}>
{question.title}
</p>
<p className={`text-xs mt-0.5 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>{question.description}</p>
<button className={`mt-1 text-xs flex items-center gap-1 ${
isDark ? 'text-blue-400 hover:text-blue-300' : 'text-blue-500 hover:text-blue-600'
}`}>
<UserCheck className="w-3 h-3" />
点名分析
</button>
</div>
))}
</div>
</div>
</div>
</div>
{/* 底部:固定输入区 */}
<div className={`mt-4 rounded-xl p-4 flex-shrink-0 border ${
isDark ? 'bg-slate-800/30 backdrop-blur-xl border-blue-800/30' : 'bg-white border-red-100 shadow-sm'
}`}>
<div className="flex items-center gap-3">
{/* 发送对象选择 */}
<select
value={sendTarget}
onChange={(e) => setSendTarget(e.target.value)}
className={`px-3 py-2 rounded-lg text-sm focus:outline-none ${
isDark
? 'bg-slate-900/50 border border-blue-800/30 text-white'
: 'bg-white border border-gray-200 text-gray-700'
}`}
>
<option value="current">当前智能体</option>
<option value="all">全部智能体</option>
<option value="host">主持人</option>
</select>
{/* 输入类型选择 */}
<select
value={inputType}
onChange={(e) => setInputType(e.target.value)}
className={`px-3 py-2 rounded-lg text-sm focus:outline-none ${
isDark
? 'bg-slate-900/50 border border-blue-800/30 text-white'
: 'bg-white border border-gray-200 text-gray-700'
}`}
>
<option value="question">提问</option>
<option value="instruction">指令</option>
<option value="judgment">人工判断</option>
<option value="note">批注</option>
</select>
{/* 主输入框 */}
<input
type="text"
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
placeholder="输入问题、指令、人工判断或批注..."
className={`flex-1 px-4 py-2 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-500/20 ${
isDark
? 'bg-slate-900/50 border border-blue-800/30 text-white placeholder-slate-500'
: 'bg-white border border-gray-200 text-gray-900 placeholder-gray-400'
}`}
/>
{/* 发送按钮 */}
<button className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
isDark
? 'bg-blue-600 hover:bg-blue-500 text-white'
: 'bg-red-600 hover:bg-red-500 text-white shadow-md'
}`}>
<Send className="w-4 h-4" />
发送
</button>
</div>
{/* 第二行快捷操作 */}
<div className="flex items-center gap-4 mt-3">
<button className={`flex items-center gap-1.5 px-2 py-1 rounded text-xs transition-colors border ${
isDark
? 'bg-slate-700/50 hover:bg-slate-600/50 border-blue-800/30 text-slate-400'
: 'bg-gray-100 hover:bg-gray-200 border-gray-200 text-gray-500'
}`}>
<AtSign className="w-3 h-3" />
@智能体
</button>
<button className={`flex items-center gap-1.5 px-2 py-1 rounded text-xs transition-colors border ${
isDark
? 'bg-slate-700/50 hover:bg-slate-600/50 border-blue-800/30 text-slate-400'
: 'bg-gray-100 hover:bg-gray-200 border-gray-200 text-gray-500'
}`}>
<FileInput className="w-3 h-3" />
引用消息
</button>
<button className={`flex items-center gap-1.5 px-2 py-1 rounded text-xs transition-colors border ${
isDark
? 'bg-slate-700/50 hover:bg-slate-600/50 border-blue-800/30 text-slate-400'
: 'bg-gray-100 hover:bg-gray-200 border-gray-200 text-gray-500'
}`}>
<Lightbulb className="w-3 h-3" />
生成摘要
</button>
<button className={`flex items-center gap-1.5 px-2 py-1 rounded text-xs transition-colors border ${
isDark
? 'bg-slate-700/50 hover:bg-slate-600/50 border-blue-800/30 text-slate-400'
: 'bg-gray-100 hover:bg-gray-200 border-gray-200 text-gray-500'
}`}>
<Plus className="w-3 h-3" />
写入整合区
</button>
</div>
</div>
{/* 人工干预弹窗 */}
{showHumanIntervention && (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50">
<div className={`rounded-2xl p-6 w-full max-w-lg border ${
isDark ? 'bg-slate-800 border-blue-800/50' : 'bg-white border-red-200'
}`}>
<div className="flex items-center justify-between mb-6">
<h3 className={`font-semibold text-lg flex items-center gap-2 ${isDark ? 'text-white' : 'text-gray-900'}`}>
<UserCheck className={`w-5 h-5 ${isDark ? 'text-purple-400' : 'text-red-500'}`} />
人工干预
</h3>
<button
onClick={() => setShowHumanIntervention(false)}
className={`p-2 rounded-lg transition-colors ${isDark ? 'hover:bg-slate-700' : 'hover:bg-gray-100'}`}
>
<XCircle className={`w-5 h-5 ${isDark ? 'text-slate-400' : 'text-gray-400'}`} />
</button>
</div>
<div className="space-y-4">
<div>
<label className={`block text-sm mb-2 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>干预类型</label>
<div className="grid grid-cols-2 gap-3">
{[
{ value: 'correct', label: '人工修正', icon: RefreshCw },
{ value: 'approve', label: '提前放行', icon: CheckCircle2 },
{ value: 'override', label: '强制覆盖', icon: Zap },
{ value: 'reject', label: '终止本轮', icon: XCircle },
].map((type) => (
<button
key={type.value}
className={`flex items-center gap-2 p-3 rounded-lg transition-colors border ${
isDark
? 'bg-slate-900/50 hover:bg-slate-700/50 border-blue-800/30 text-slate-300'
: 'bg-gray-50 hover:bg-gray-100 border-gray-200 text-gray-700'
}`}
>
<type.icon className={`w-4 h-4 ${isDark ? 'text-blue-400' : 'text-red-500'}`} />
<span className="text-sm">{type.label}</span>
</button>
))}
</div>
</div>
<div>
<label className={`block text-sm mb-2 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>目标字段</label>
<select className={`w-full px-4 py-2.5 rounded-lg text-sm ${
isDark
? 'bg-slate-900/50 border border-blue-800/30 text-white'
: 'bg-white border border-gray-200 text-gray-700'
}`}>
<option value="">请选择要干预的字段</option>
<option value="audience">受众细分</option>
<option value="behavior">目标行为</option>
<option value="driver">核心驱动</option>
<option value="narrative">叙事策略</option>
</select>
</div>
<div>
<label className={`block text-sm mb-2 ${isDark ? 'text-slate-400' : 'text-gray-500'}`}>干预内容</label>
<textarea
rows={4}
className={`w-full px-4 py-2.5 rounded-lg text-sm resize-none ${
isDark
? 'bg-slate-900/50 border border-blue-800/30 text-white placeholder-slate-500'
: 'bg-white border border-gray-200 text-gray-900 placeholder-gray-400'
}`}
placeholder="请输入人工干预的具体内容或修正意见..."
/>
</div>
<div className="flex gap-3 pt-2">
<button
onClick={() => setShowHumanIntervention(false)}
className={`flex-1 px-4 py-2.5 rounded-lg text-sm transition-colors border ${
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-200 text-gray-700'
}`}
>
取消
</button>
<button className={`flex-1 px-4 py-2.5 rounded-lg text-sm transition-colors ${
isDark
? 'bg-purple-600 hover:bg-purple-500 text-white'
: 'bg-red-600 hover:bg-red-500 text-white shadow-md'
}`}>
确认干预
</button>
</div>
</div>
</div>
</div>
)}
</div>
);
};
export default TaskWorkspace;
@@ -0,0 +1,26 @@
import { Outlet, useNavigate } from 'react-router-dom';
import React, { useState, useEffect, useRef } from 'react';
import { useSearchParams } from 'react-router-dom';
import { requestCrudService } from '@/service/request-axios-service';
const service = requestCrudService;
const userInfo = localStorage.getItem('userInfo');
const xkLinkUrl = import.meta.env.VITE_AIPI_XKLINKTO_URL;
interface TaskPgFromProps {
gopath: string
}
const TaskXkFrom: React.FC<TaskPgFromProps> = ({gopath}) => {
const [xkUrl, setXkUrl] = useState(xkLinkUrl);
useEffect(() => {
if(userInfo) {
const userData: any = JSON.parse(userInfo);
setXkUrl(`${xkLinkUrl}${gopath}?accessToken=${userData.accessToken}`);
}
}, [gopath]);
return (
<div className="h-full flex flex-col">
<iframe className="h-[calc(100vh-92px)]" src={xkUrl}></iframe>
</div>
);
}
export default TaskXkFrom
@@ -0,0 +1,35 @@
import React, { useEffect, useState } from "react";
import "./css/iframe.css";
import { useTheme } from "@/contexts/ThemeContext";
import { PageType } from "@/components/Sidebar";
interface XXTyPageProps {
title: string;
category: string;
subCategory: string;
onNavigate: (page: PageType) => void;
}
const TYURL = import.meta.env.VITE_AIPI_B2ACTOR_URL || "";
const XXTyPage: React.FC<XXTyPageProps> = ({ onNavigate }) => {
const { theme } = useTheme();
const isDark = theme === "dark";
const [iframeUrl, setIframeUrl] = useState("");
useEffect(() => {
initIframeUrl();
}, []);
const initIframeUrl = () => {
const url = `${TYURL}/fromB/toPage?defaultPage=iframeSysystem`;
console.log({ iframeUrl: url });
setIframeUrl(url);
};
return (
<div className="iframe-container">
<iframe src={iframeUrl} className="full-size-iframe"></iframe>
</div>
);
};
export default XXTyPage;
@@ -0,0 +1,35 @@
import React, { useEffect, useState } from "react";
import "./css/iframe.css";
import { useTheme } from "@/contexts/ThemeContext";
import { PageType } from "@/components/Sidebar";
interface XingdongTyPageProps {
title: string;
category: string;
subCategory: string;
onNavigate: (page: PageType) => void;
}
const TYURL = import.meta.env.VITE_AIPI_B2ACTOR_URL || "";
const XingdongTyPage: React.FC<XingdongTyPageProps> = ({ onNavigate }) => {
const { theme } = useTheme();
const isDark = theme === "dark";
const [iframeUrl, setIframeUrl] = useState("");
useEffect(() => {
initIframeUrl();
}, []);
const initIframeUrl = () => {
const url = `${TYURL}/fromB/toPage?defaultPage=iframeXdty`;
console.log({ iframeUrl: url });
setIframeUrl(url);
};
return (
<div className="iframe-container">
<iframe src={iframeUrl} className="full-size-iframe"></iframe>
</div>
);
};
export default XingdongTyPage;
@@ -0,0 +1,10 @@
.iframe-container {
width: 100%;
height: calc(100vh - 100px); /* 可根据需求调整 */
}
.full-size-iframe {
width: 100%;
height: 100%;
border: none;
}