代码修改
This commit is contained in:
1 parent
ef1481be63
commit
a4b7ab5e9d
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;
|
||||
}
|
||||
Reference in new issue
Block a user