105 Commits
Author SHA1 Message Date
huangzhuang_3rd 0b5ab072fa feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:27 +08:00
huangzhuang_3rd 1c8952e063 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:25 +08:00
huangzhuang_3rd 4385f9aa19 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:24 +08:00
huangzhuang_3rd 7609c35d7d feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:22 +08:00
huangzhuang_3rd 73b8e6ad47 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:21 +08:00
huangzhuang_3rd 6ad31778a2 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:19 +08:00
huangzhuang_3rd 6042967b33 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:18 +08:00
huangzhuang_3rd 20736f0fb1 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:16 +08:00
huangzhuang_3rd fca4c517e4 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:15 +08:00
huangzhuang_3rd 813f24751f feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:14 +08:00
huangzhuang_3rd 364cbfe6c8 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:12 +08:00
huangzhuang_3rd 58d41e5852 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:10 +08:00
huangzhuang_3rd 7c2b7fb4cf feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:09 +08:00
huangzhuang_3rd a99f049589 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:07 +08:00
huangzhuang_3rd 2512ef72b7 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:06 +08:00
huangzhuang_3rd 4409d6ed5b feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:04 +08:00
huangzhuang_3rd 4acfd06184 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:03 +08:00
huangzhuang_3rd 5450c0e248 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:13:00 +08:00
huangzhuang_3rd cc697ad399 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:59 +08:00
huangzhuang_3rd 7d5a1d0c6f feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:58 +08:00
huangzhuang_3rd dc2ed9b970 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:56 +08:00
huangzhuang_3rd d1a89cda51 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:55 +08:00
huangzhuang_3rd bd220050c7 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:53 +08:00
huangzhuang_3rd c4020f2725 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:52 +08:00
huangzhuang_3rd e36c82c123 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:51 +08:00
huangzhuang_3rd c8f8ffc3e8 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:49 +08:00
huangzhuang_3rd aaa4f9e202 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:48 +08:00
huangzhuang_3rd 89831aee21 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:46 +08:00
huangzhuang_3rd 8ff1902937 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:43 +08:00
huangzhuang_3rd b5fe33ce47 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:40 +08:00
huangzhuang_3rd d3586b4c77 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:39 +08:00
huangzhuang_3rd e127003ee9 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:37 +08:00
huangzhuang_3rd 9125cd7ae4 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:35 +08:00
huangzhuang_3rd d1c01ea847 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:34 +08:00
huangzhuang_3rd 1dea91f295 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:32 +08:00
huangzhuang_3rd ff795eb21e feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:31 +08:00
huangzhuang_3rd c0fa083328 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:29 +08:00
huangzhuang_3rd ad9887b102 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:28 +08:00
huangzhuang_3rd 4ea20216f1 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:26 +08:00
huangzhuang_3rd 676d1362d8 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:25 +08:00
huangzhuang_3rd 7b9db415b1 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:23 +08:00
huangzhuang_3rd 70deedfdb1 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:22 +08:00
huangzhuang_3rd 64afcc47d9 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:20 +08:00
huangzhuang_3rd 3df45e9767 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:19 +08:00
huangzhuang_3rd f4858ee73f feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:17 +08:00
huangzhuang_3rd 3eface01cd feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:16 +08:00
huangzhuang_3rd 725c0c6f79 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:14 +08:00
huangzhuang_3rd 76b9e76bf7 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:13 +08:00
huangzhuang_3rd c8f4060988 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:11 +08:00
huangzhuang_3rd 1dba371374 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:10 +08:00
huangzhuang_3rd 2ffa8724af feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:08 +08:00
huangzhuang_3rd 73cca10bbb feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:07 +08:00
huangzhuang_3rd f368fd590f feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:05 +08:00
huangzhuang_3rd b04e5f7fa6 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:04 +08:00
huangzhuang_3rd a610fb6e08 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:02 +08:00
huangzhuang_3rd 68ab70c7eb feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:01 +08:00
huangzhuang_3rd c7ad2ac2ca feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:12:00 +08:00
huangzhuang_3rd 2d79408644 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:58 +08:00
huangzhuang_3rd 86b8fb7485 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:57 +08:00
huangzhuang_3rd 0349b0c75c feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:55 +08:00
huangzhuang_3rd 559133bcb2 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:54 +08:00
huangzhuang_3rd 126e571ed1 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:52 +08:00
huangzhuang_3rd 5692db6621 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:51 +08:00
huangzhuang_3rd 7545ead561 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:49 +08:00
huangzhuang_3rd e3531e9e96 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:48 +08:00
huangzhuang_3rd 92d51710c1 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:46 +08:00
huangzhuang_3rd 45ec4b0321 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:45 +08:00
huangzhuang_3rd fa492b1528 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:43 +08:00
huangzhuang_3rd 361e00de1f feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:42 +08:00
huangzhuang_3rd abe175d87f feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:40 +08:00
huangzhuang_3rd 3e5f33851c feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:39 +08:00
huangzhuang_3rd d7b399ef76 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:37 +08:00
huangzhuang_3rd 44ea460b28 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:36 +08:00
huangzhuang_3rd 5cf474c7c2 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:34 +08:00
huangzhuang_3rd e957ae978c feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:33 +08:00
huangzhuang_3rd 6043ddf69e feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:32 +08:00
huangzhuang_3rd 5b9fd5a12d feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:30 +08:00
huangzhuang_3rd ffa7dee85a feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:29 +08:00
huangzhuang_3rd c6dc0832e2 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:27 +08:00
huangzhuang_3rd 62b96b3390 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:26 +08:00
huangzhuang_3rd 842ba96631 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:24 +08:00
huangzhuang_3rd fd5bfc653e feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:23 +08:00
huangzhuang_3rd 6f71760057 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:21 +08:00
huangzhuang_3rd 191c74b061 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:20 +08:00
huangzhuang_3rd 4ab6b24793 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:18 +08:00
huangzhuang_3rd bfdde2b1c2 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:16 +08:00
huangzhuang_3rd 7069ec3827 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:15 +08:00
huangzhuang_3rd 940428b683 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:13 +08:00
huangzhuang_3rd 3316ac45ff feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:12 +08:00
huangzhuang_3rd d883c982b3 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:10 +08:00
huangzhuang_3rd 6644050290 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:09 +08:00
huangzhuang_3rd cb4bcd67c5 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:07 +08:00
huangzhuang_3rd 9dfd6c94dc feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:06 +08:00
huangzhuang_3rd 5f7b64a68f feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:04 +08:00
huangzhuang_3rd c9d0241369 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:03 +08:00
huangzhuang_3rd 72de66f87b feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:01 +08:00
huangzhuang_3rd f3cb259965 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:11:00 +08:00
huangzhuang_3rd 30905c6e6a feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:10:58 +08:00
huangzhuang_3rd 273947b733 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:10:57 +08:00
huangzhuang_3rd 54683bf87b feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:10:55 +08:00
huangzhuang_3rd 1db7b10ae6 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:10:54 +08:00
huangzhuang_3rd b63bcaf495 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:10:52 +08:00
huangzhuang_3rd 6f9442f7fb feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:10:51 +08:00
huangzhuang_3rd 54a8646b94 feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:10:49 +08:00
huangzhuang_3rd def2f09c3d feat: DAG画布嵌入项目详情页,任务节点点击弹详情Modal 2026-08-13 23:10:48 +08:00
6 changed files with 311 additions and 121 deletions
-2
View File
@@ -12,7 +12,6 @@ import Workers from '@/pages/Workers';
import Reviews from '@/pages/Reviews';
import Alerts from '@/pages/Alerts';
import KnowledgeBase from '@/pages/KnowledgeBase';
import DagCanvas from '@/pages/DagCanvas';
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated } = useAuthStore();
@@ -62,7 +61,6 @@ export default function App() {
<Route path="reviews" element={<Reviews />} />
<Route path="alerts" element={<Alerts />} />
<Route path="knowledge" element={<KnowledgeBase />} />
<Route path="dag" element={<DagCanvas />} />
</Route>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
-6
View File
@@ -18,7 +18,6 @@ import {
AuditOutlined,
AlertOutlined,
BookOutlined,
ApartmentOutlined,
LogoutOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
@@ -63,11 +62,6 @@ const menuItems: MenuProps['items'] = [
icon: <CheckSquareOutlined />,
label: '任务管理',
},
{
key: '/dag',
icon: <ApartmentOutlined />,
label: 'DAG 编排',
},
{
key: '/workers',
icon: <RobotOutlined />,
+140
View File
@@ -0,0 +1,140 @@
import { useEffect, useState } from 'react';
import { Modal, Descriptions, Spin, Tag, Typography, Empty } from 'antd';
import { taskApi } from '@/api';
import type { Task } from '@/types';
import { TaskStatusTag, PriorityTag } from '@/components/constants';
import dayjs from 'dayjs';
const { Paragraph, Text } = Typography;
interface Props {
taskId: number | null;
open: boolean;
onClose: () => void;
}
/** 任务详情弹窗:在 DAG 画布/项目页内点击任务卡片时弹出,不跳转页面 */
export default function TaskDetailModal({ taskId, open, onClose }: Props) {
const [task, setTask] = useState<Task | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!open || taskId == null) return;
setLoading(true);
setTask(null);
taskApi
.get(taskId)
.then(setTask)
.catch(() => setTask(null))
.finally(() => setLoading(false));
}, [open, taskId]);
const renderJsonBlock = (raw?: string) => {
if (!raw) return <Text type="secondary"></Text>;
let pretty = raw;
try {
pretty = JSON.stringify(JSON.parse(raw), null, 2);
} catch {
/* keep raw */
}
return (
<pre
style={{
background: '#f6f8fa',
border: '1px solid #e8e8e8',
borderRadius: 6,
padding: 12,
maxHeight: 260,
overflow: 'auto',
fontSize: 12,
margin: 0,
whiteSpace: 'pre-wrap',
wordBreak: 'break-all',
}}
>
{pretty}
</pre>
);
};
return (
<Modal
title={task ? `任务 #${task.id} ${task.title}` : '任务详情'}
open={open}
onCancel={onClose}
footer={null}
width={720}
destroyOnClose
>
{loading ? (
<div style={{ textAlign: 'center', padding: 40 }}>
<Spin />
</div>
) : !task ? (
<Empty description="任务不存在或加载失败" />
) : (
<div>
<Descriptions column={2} bordered size="small">
<Descriptions.Item label="状态">
<TaskStatusTag status={task.status} />
</Descriptions.Item>
<Descriptions.Item label="优先级">
<PriorityTag priority={task.priority} />
</Descriptions.Item>
<Descriptions.Item label="任务类型">{task.task_type}</Descriptions.Item>
<Descriptions.Item label="需要审核">
{task.requires_review ? (
<Tag color="purple"></Tag>
) : (
<Tag></Tag>
)}
</Descriptions.Item>
{task.review_status && (
<Descriptions.Item label="审核状态">{task.review_status}</Descriptions.Item>
)}
{task.worker_id && (
<Descriptions.Item label="AI Worker">#{task.worker_id}</Descriptions.Item>
)}
{task.due_date && (
<Descriptions.Item label="截止时间">
{dayjs(task.due_date).format('YYYY-MM-DD HH:mm')}
</Descriptions.Item>
)}
{task.started_at && (
<Descriptions.Item label="开始时间">
{dayjs(task.started_at).format('YYYY-MM-DD HH:mm')}
</Descriptions.Item>
)}
{task.completed_at && (
<Descriptions.Item label="完成时间">
{dayjs(task.completed_at).format('YYYY-MM-DD HH:mm')}
</Descriptions.Item>
)}
<Descriptions.Item label="创建时间">
{dayjs(task.created_at).format('YYYY-MM-DD HH:mm')}
</Descriptions.Item>
{task.description && (
<Descriptions.Item label="描述" span={2}>
{task.description}
</Descriptions.Item>
)}
</Descriptions>
<div style={{ marginTop: 16 }}>
<Paragraph strong style={{ marginBottom: 8 }}>
Prompt /
</Paragraph>
{renderJsonBlock(task.input_data)}
</div>
<div style={{ marginTop: 16 }}>
<Paragraph strong style={{ marginBottom: 8 }}>
</Paragraph>
{renderJsonBlock(task.output_data)}
</div>
</div>
)}
</Modal>
);
}
+85 -80
View File
@@ -14,8 +14,6 @@ import {
import type { Node, Edge, Connection, NodeProps } from 'reactflow';
import 'reactflow/dist/style.css';
import {
Card,
Select,
Button,
Space,
message,
@@ -30,9 +28,10 @@ import {
ReloadOutlined,
DeleteOutlined,
} from '@ant-design/icons';
import { projectApi, dagApi } from '@/api';
import type { Project, TaskStatus, DagGraphNode, DagGraphEdge } from '@/types';
import { dagApi } from '@/api';
import type { TaskStatus, DagGraphNode, DagGraphEdge } from '@/types';
import { taskStatusConfig, priorityConfig } from '@/components/constants';
import TaskDetailModal from '@/components/TaskDetailModal';
const { Text } = Typography;
@@ -52,6 +51,7 @@ function TaskNode({ data }: NodeProps) {
borderRadius: 8,
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
overflow: 'hidden',
cursor: 'pointer',
}}
>
<Handle type="target" position={Position.Top} />
@@ -121,23 +121,19 @@ function layoutNodes(graphNodes: DagGraphNode[], graphEdges: DagGraphEdge[]) {
return positions;
}
export default function DagCanvas() {
const [projects, setProjects] = useState<Project[]>([]);
const [projectId, setProjectId] = useState<number | undefined>();
interface Props {
projectId: number;
}
/** DAG 编排画布(嵌入项目详情页使用) */
export default function DagCanvas({ projectId }: Props) {
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const [loading, setLoading] = useState(false);
const [executing, setExecuting] = useState(false);
const [selectedEdge, setSelectedEdge] = useState<Edge | null>(null);
// Load projects
useEffect(() => {
projectApi.list().then((ps) => {
setProjects(ps);
if (ps.length > 0 && !projectId) setProjectId(ps[0].id);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const [detailTaskId, setDetailTaskId] = useState<number | null>(null);
const [detailOpen, setDetailOpen] = useState(false);
const loadGraph = useCallback(async (pid: number) => {
setLoading(true);
@@ -182,7 +178,7 @@ export default function DagCanvas() {
// Connect: add dependency (drag from source(bottom) to target(top))
const onConnect = useCallback(
async (conn: Connection) => {
if (!conn.source || !conn.target || !projectId) return;
if (!conn.source || !conn.target) return;
try {
await dagApi.addDependency({
task_id: Number(conn.target),
@@ -205,9 +201,15 @@ export default function DagCanvas() {
message.error(e.response?.data?.detail || '添加依赖失败');
}
},
[projectId, setEdges]
[setEdges]
);
// Node click -> open task detail modal
const onNodeClick = useCallback((_: unknown, node: Node) => {
setDetailTaskId(Number(node.id));
setDetailOpen(true);
}, []);
// Delete selected edge
const removeSelectedEdge = useCallback(async () => {
if (!selectedEdge) return;
@@ -269,70 +271,73 @@ export default function DagCanvas() {
return (
<div>
<Card
title="DAG 编排画布"
extra={
<Space>
<Select
style={{ width: 220 }}
placeholder="选择项目"
value={projectId}
onChange={setProjectId}
options={projects.map((p) => ({ label: p.name, value: p.id }))}
/>
<Tooltip title="刷新">
<Button icon={<ReloadOutlined />} onClick={() => projectId && loadGraph(projectId)} loading={loading} />
</Tooltip>
<Tooltip title={selectedEdge ? '删除选中的依赖连线' : '先点击选中一条连线'}>
<Button
danger
icon={<DeleteOutlined />}
disabled={!selectedEdge}
onClick={removeSelectedEdge}
>
</Button>
</Tooltip>
<Button
type="primary"
icon={<ThunderboltOutlined />}
onClick={executeAll}
loading={executing}
disabled={nodes.length === 0}
>
DAG
</Button>
</Space>
}
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 8,
}}
>
<div style={{ marginBottom: 8 }}>
<Text type="secondary" style={{ fontSize: 12 }}>
线
</Text>
</div>
<div style={{ height: 'calc(100vh - 260px)', minHeight: 480, border: '1px solid #f0f0f0', borderRadius: 8 }}>
{nodes.length === 0 && !loading ? (
<Empty description="该项目暂无任务,请先在任务管理中创建任务" style={{ marginTop: 120 }} />
) : (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onEdgeClick={(_, edge) => setSelectedEdge(edge)}
onPaneClick={() => setSelectedEdge(null)}
nodeTypes={nodeTypes}
fitView
deleteKeyCode={[]}
<Text type="secondary" style={{ fontSize: 12 }}>
线
</Text>
<Space>
<Tooltip title="刷新">
<Button size="small" icon={<ReloadOutlined />} onClick={() => loadGraph(projectId)} loading={loading} />
</Tooltip>
<Tooltip title={selectedEdge ? '删除选中的依赖连线' : '先点击选中一条连线'}>
<Button
size="small"
danger
icon={<DeleteOutlined />}
disabled={!selectedEdge}
onClick={removeSelectedEdge}
>
<Background gap={16} />
<Controls />
{minimap}
</ReactFlow>
)}
</div>
</Card>
</Button>
</Tooltip>
<Button
size="small"
type="primary"
icon={<ThunderboltOutlined />}
onClick={executeAll}
loading={executing}
disabled={nodes.length === 0}
>
DAG
</Button>
</Space>
</div>
<div style={{ height: 560, border: '1px solid #f0f0f0', borderRadius: 8 }}>
{nodes.length === 0 && !loading ? (
<Empty description="该项目暂无任务,请先在任务管理中创建任务" style={{ marginTop: 160 }} />
) : (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onNodeClick={onNodeClick}
onEdgeClick={(_, edge) => setSelectedEdge(edge)}
onPaneClick={() => setSelectedEdge(null)}
nodeTypes={nodeTypes}
fitView
deleteKeyCode={[]}
>
<Background gap={16} />
<Controls />
{minimap}
</ReactFlow>
)}
</div>
<TaskDetailModal
taskId={detailTaskId}
open={detailOpen}
onClose={() => setDetailOpen(false)}
/>
</div>
);
}
+85 -32
View File
@@ -12,15 +12,20 @@ import {
Tag,
Empty,
List,
Tabs,
} from 'antd';
import {
ArrowLeftOutlined,
ReloadOutlined,
ApartmentOutlined,
ProfileOutlined,
} from '@ant-design/icons';
import { useNavigate, useParams } from 'react-router-dom';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { projectApi, taskApi, artifactApi } from '@/api';
import type { Project, ProjectStats, Task, Artifact } from '@/types';
import { ProjectStatusTag, TaskStatusTag } from '@/components/constants';
import DagCanvas from '@/pages/DagCanvas';
import TaskDetailModal from '@/components/TaskDetailModal';
import dayjs from 'dayjs';
const { Title, Text } = Typography;
@@ -28,11 +33,20 @@ const { Title, Text } = Typography;
export default function ProjectDetail() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
const activeTab = searchParams.get('tab') || 'overview';
const [loading, setLoading] = useState(true);
const [project, setProject] = useState<Project | null>(null);
const [stats, setStats] = useState<ProjectStats | null>(null);
const [tasks, setTasks] = useState<Task[]>([]);
const [artifacts, setArtifacts] = useState<Artifact[]>([]);
const [detailTaskId, setDetailTaskId] = useState<number | null>(null);
const [detailOpen, setDetailOpen] = useState(false);
const openTaskDetail = (taskId: number) => {
setDetailTaskId(taskId);
setDetailOpen(true);
};
useEffect(() => {
if (!id) return;
@@ -75,36 +89,8 @@ export default function ProjectDetail() {
].filter((item) => item.count > 0)
: [];
return (
<div>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16,
}}
>
<Space>
<Button
icon={<ArrowLeftOutlined />}
onClick={() => navigate('/projects')}
>
</Button>
<Title level={4} style={{ margin: 0 }}>
{project.name}
</Title>
<ProjectStatusTag status={project.status} />
</Space>
<Button
icon={<ReloadOutlined />}
onClick={() => window.location.reload()}
>
</Button>
</div>
const overviewTab = (
<>
<Card style={{ marginBottom: 16 }}>
<Descriptions column={2} bordered size="small">
<Descriptions.Item label="项目名称">{project.name}</Descriptions.Item>
@@ -198,7 +184,7 @@ export default function ProjectDetail() {
>
<List.Item.Meta
title={
<a onClick={() => navigate('/tasks')}>{task.title}</a>
<a onClick={() => openTaskDetail(task.id)}>{task.title}</a>
}
description={dayjs(task.created_at).format('MM-DD HH:mm')}
/>
@@ -231,6 +217,73 @@ export default function ProjectDetail() {
</Card>
</Col>
</Row>
</>
);
return (
<div>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16,
}}
>
<Space>
<Button
icon={<ArrowLeftOutlined />}
onClick={() => navigate('/projects')}
>
</Button>
<Title level={4} style={{ margin: 0 }}>
{project.name}
</Title>
<ProjectStatusTag status={project.status} />
</Space>
<Button
icon={<ReloadOutlined />}
onClick={() => window.location.reload()}
>
</Button>
</div>
<Tabs
activeKey={activeTab}
onChange={(key) => setSearchParams({ tab: key })}
items={[
{
key: 'overview',
label: (
<span>
<ProfileOutlined />
</span>
),
children: overviewTab,
},
{
key: 'dag',
label: (
<span>
<ApartmentOutlined /> DAG
</span>
),
children: (
<Card size="small" title="DAG 编排画布">
<DagCanvas projectId={project.id} />
</Card>
),
},
]}
/>
<TaskDetailModal
taskId={detailTaskId}
open={detailOpen}
onClose={() => setDetailOpen(false)}
/>
</div>
);
}
+1 -1
View File
@@ -1 +1 @@
{"root":["./src/App.tsx","./src/main.tsx","./src/api/client.ts","./src/api/index.ts","./src/components/AppLayout.tsx","./src/components/constants.tsx","./src/pages/Alerts.tsx","./src/pages/DagCanvas.tsx","./src/pages/Dashboard.tsx","./src/pages/KnowledgeBase.tsx","./src/pages/Login.tsx","./src/pages/ProjectDetail.tsx","./src/pages/Projects.tsx","./src/pages/Reviews.tsx","./src/pages/Tasks.tsx","./src/pages/Workers.tsx","./src/stores/auth.ts","./src/types/index.ts"],"version":"5.9.3"}
{"root":["./src/App.tsx","./src/main.tsx","./src/api/client.ts","./src/api/index.ts","./src/components/AppLayout.tsx","./src/components/TaskDetailModal.tsx","./src/components/constants.tsx","./src/pages/Alerts.tsx","./src/pages/DagCanvas.tsx","./src/pages/Dashboard.tsx","./src/pages/KnowledgeBase.tsx","./src/pages/Login.tsx","./src/pages/ProjectDetail.tsx","./src/pages/Projects.tsx","./src/pages/Reviews.tsx","./src/pages/Tasks.tsx","./src/pages/Workers.tsx","./src/stores/auth.ts","./src/types/index.ts"],"version":"5.9.3"}