diff --git a/backend/db/crud.py b/backend/db/crud.py index 3874b43..7c1f996 100644 --- a/backend/db/crud.py +++ b/backend/db/crud.py @@ -66,10 +66,10 @@ class MultiAgentTaskCRUD: def get_recent( db: Session, limit: int = 20, offset: int = 0 ) -> List[MultiAgentTask]: - """获取最近的任务记录""" + """获取最近的任务记录,置顶的排在最前面""" return ( db.query(MultiAgentTask) - .order_by(MultiAgentTask.created_at.desc()) + .order_by(MultiAgentTask.is_pinned.desc(), MultiAgentTask.created_at.desc()) .offset(offset) .limit(limit) .all() @@ -184,6 +184,18 @@ class MultiAgentTaskCRUD: db.refresh(task) return task + @staticmethod + def update_is_pinned( + db: Session, task_id: str, is_pinned: bool + ) -> Optional[MultiAgentTask]: + """更新任务置顶状态""" + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + task.is_pinned = is_pinned + db.commit() + db.refresh(task) + return task + @staticmethod def append_rehearsal_log( db: Session, task_id: str, log_entry: dict diff --git a/backend/db/models.py b/backend/db/models.py index b65f87c..2d20d53 100644 --- a/backend/db/models.py +++ b/backend/db/models.py @@ -6,7 +6,7 @@ SQLAlchemy ORM 数据模型 import uuid from datetime import datetime, timezone from enum import Enum as PyEnum -from sqlalchemy import Column, String, Text, DateTime, Integer, Enum, Index, ForeignKey +from sqlalchemy import Column, String, Text, DateTime, Integer, Enum, Index, ForeignKey, Boolean from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import relationship @@ -48,6 +48,7 @@ class MultiAgentTask(Base): execution_id = Column(String(64)) rehearsal_log = Column(JSONB) branches = Column(JSONB) # 任务大纲探索分支数据 + is_pinned = Column(Boolean, default=False, nullable=False) # 置顶标志 created_at = Column(DateTime(timezone=True), default=utc_now) updated_at = Column(DateTime(timezone=True), default=utc_now, onupdate=utc_now) @@ -74,6 +75,7 @@ class MultiAgentTask(Base): "execution_id": self.execution_id, "rehearsal_log": self.rehearsal_log, "branches": self.branches, + "is_pinned": self.is_pinned, "created_at": self.created_at.isoformat() if self.created_at else None, "updated_at": self.updated_at.isoformat() if self.updated_at else None, } diff --git a/backend/server.py b/backend/server.py index 13ecb72..97cb618 100644 --- a/backend/server.py +++ b/backend/server.py @@ -331,10 +331,11 @@ def handle_execute_plan_optimized_ws(data): MultiAgentTaskCRUD.update_status(db, task_id, TaskStatus.STOPPED) print(f"[execute_plan_optimized] 用户停止执行,跳过保存执行数据,已完成 {completed_steps_count}/{plan_steps_count} 步骤,task_id={task_id}") - # 任务大纲(用户可能编辑了)仍然保存 - if plan: - MultiAgentTaskCRUD.update_task_outline(db, task_id, plan) - print(f"[execute_plan_optimized] 已保存 task_outline 到数据库,task_id={task_id}") + # # 任务大纲(用户可能编辑了)仍然保存 + # # 注释原因:执行任务时不保存 task_outline,避免覆盖导致步骤 ID 变化与 agent_scores 不匹配 + # if plan: + # MultiAgentTaskCRUD.update_task_outline(db, task_id, plan) + # print(f"[execute_plan_optimized] 已保存 task_outline 到数据库,task_id={task_id}") # # 保存 assigned_agents(每个步骤使用的 agent) # # 注释原因:assigned_agents 只在生成阶段由用户手动选择写入,执行时不覆盖 @@ -1849,6 +1850,7 @@ def handle_get_plans(data): "status": task.status.value if task.status else 'unknown', "execution_count": task.execution_count or 0, "created_at": task.created_at.isoformat() if task.created_at else None, + "is_pinned": task.is_pinned or False, # 置顶标志 # 完整数据用于恢复 "task_outline": task.task_outline, "assigned_agents": task.assigned_agents, @@ -2060,6 +2062,54 @@ def handle_delete_plan(data): }) +@socketio.on('pin_plan') +def handle_pin_plan(data): + """ + WebSocket版本:置顶/取消置顶历史任务 + """ + # socketio 包装: data = { id: 'pin_plan-xxx', action: 'pin_plan', data: { id: 'ws_req_xxx', data: {...} } } + request_id = data.get('id') # socketio 包装的 id + incoming_data = data.get('data', {}).get('data', {}) # 真正的请求数据 + plan_id = incoming_data.get('plan_id') + is_pinned = incoming_data.get('is_pinned', True) # 默认为置顶 + + if not plan_id: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': '缺少 plan_id(task_id)' + }) + return + + try: + with get_db_context() as db: + task = MultiAgentTaskCRUD.update_is_pinned(db, plan_id, is_pinned) + + if not task: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': f'任务不存在: {plan_id}' + }) + return + + # 通知所有客户端刷新历史列表 + socketio.emit('history_updated', {'task_id': plan_id}) + + emit('response', { + 'id': request_id, + 'status': 'success', + 'data': {"message": "置顶成功" if is_pinned else "取消置顶成功"} + }) + + except Exception as e: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': str(e) + }) + + @socketio.on('save_branches') def handle_save_branches(data): """ diff --git a/frontend/src/components/DeleteConfirmDialog/index.vue b/frontend/src/components/DeleteConfirmDialog/index.vue new file mode 100644 index 0000000..e06eec1 --- /dev/null +++ b/frontend/src/components/DeleteConfirmDialog/index.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/frontend/src/components/SvgIcon/index.vue b/frontend/src/components/SvgIcon/index.vue index fd89493..2c6089b 100644 --- a/frontend/src/components/SvgIcon/index.vue +++ b/frontend/src/components/SvgIcon/index.vue @@ -18,7 +18,7 @@ const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`) diff --git a/frontend/src/layout/components/Main/TaskTemplate/HistoryList/index.vue b/frontend/src/layout/components/Main/TaskTemplate/HistoryList/index.vue index 042bc9d..a0b6e4f 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/HistoryList/index.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/HistoryList/index.vue @@ -1,13 +1,5 @@