392 lines
16 KiB
TypeScript
392 lines
16 KiB
TypeScript
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';
|
|
// 导入请求服务,用于与后端 API 交互
|
|
import { requestCrudService } from '@/service/request-axios-service';
|
|
import { KanbanColumn } from './KanbanColumn';
|
|
import { TaskListItem } from './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;
|
|
}
|
|
|
|
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}) => {
|
|
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 === '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);
|
|
} 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} 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} 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} 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;
|