diff --git a/pkg/nodes/registry_server.go b/pkg/nodes/registry_server.go index d989e41..6b2dae1 100644 --- a/pkg/nodes/registry_server.go +++ b/pkg/nodes/registry_server.go @@ -1350,7 +1350,74 @@ func (s *RegistryServer) handleWebUITaskAudit(w http.ResponseWriter, r *http.Req http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } - path := filepath.Join(strings.TrimSpace(s.workspacePath), "memory", "task-audit.jsonl") + if r.Method == http.MethodPost { + var body map[string]interface{} + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, "invalid json", http.StatusBadRequest) + return + } + action := fmt.Sprintf("%v", body["action"]) + taskID := fmt.Sprintf("%v", body["task_id"]) + if taskID == "" { + http.Error(w, "task_id required", http.StatusBadRequest) + return + } + tasksPath := filepath.Join(strings.TrimSpace(s.workspacePath), "memory", "tasks.json") + tb, err := os.ReadFile(tasksPath) + if err != nil { + http.Error(w, "tasks not found", http.StatusNotFound) + return + } + var tasks []map[string]interface{} + if err := json.Unmarshal(tb, &tasks); err != nil { + http.Error(w, "invalid tasks file", http.StatusInternalServerError) + return + } + now := time.Now().UTC().Format(time.RFC3339) + updated := false + for _, t := range tasks { + if fmt.Sprintf("%v", t["id"]) != taskID { + continue + } + switch action { + case "pause": + t["status"] = "waiting" + t["block_reason"] = "manual_pause" + t["last_pause_reason"] = "manual_pause" + t["last_pause_at"] = now + case "retry": + t["status"] = "todo" + t["block_reason"] = "" + t["retry_after"] = "" + case "complete": + t["status"] = "done" + t["block_reason"] = "manual_complete" + case "ignore": + t["status"] = "blocked" + t["block_reason"] = "manual_ignore" + t["retry_after"] = "2099-01-01T00:00:00Z" + default: + http.Error(w, "unsupported action", http.StatusBadRequest) + return + } + t["updated_at"] = now + updated = true + break + } + if !updated { + http.Error(w, "task not found", http.StatusNotFound) + return + } + out, _ := json.MarshalIndent(tasks, "", " ") + if err := os.WriteFile(tasksPath, out, 0644); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + _ = json.NewEncoder(w).Encode(map[string]interface{}{"ok": true}) + return + } + +path := filepath.Join(strings.TrimSpace(s.workspacePath), "memory", "task-audit.jsonl") limit := 100 if v := r.URL.Query().Get("limit"); v != "" { if n, err := strconv.Atoi(v); err == nil && n > 0 { diff --git a/webui/src/i18n/index.ts b/webui/src/i18n/index.ts index e70c55d..92d0713 100644 --- a/webui/src/i18n/index.ts +++ b/webui/src/i18n/index.ts @@ -23,6 +23,10 @@ const resources = { lastPauseAt: 'Last Pause Time', allSources: 'All Sources', allStatus: 'All Status', + pauseTask: 'Pause', + retryTask: 'Retry', + completeTask: 'Complete', + ignoreTask: 'Ignore', error: 'Error', noTaskAudit: 'No task audit records', selectTask: 'Select a task from the left list', @@ -180,6 +184,10 @@ const resources = { lastPauseAt: '最近暂停时间', allSources: '全部来源', allStatus: '全部状态', + pauseTask: '暂停', + retryTask: '重试', + completeTask: '完成', + ignoreTask: '忽略', error: '错误', noTaskAudit: '暂无任务审计记录', selectTask: '请从左侧选择任务', diff --git a/webui/src/pages/TaskAudit.tsx b/webui/src/pages/TaskAudit.tsx index f6b1520..7b197e1 100644 --- a/webui/src/pages/TaskAudit.tsx +++ b/webui/src/pages/TaskAudit.tsx @@ -61,6 +61,18 @@ const TaskAudit: React.FC = () => { if (statusFilter !== 'all' && String(it.status || '-') !== statusFilter) return false; return true; }), [items, sourceFilter, statusFilter]); + + const taskAction = async (action: 'pause'|'retry'|'complete'|'ignore') => { + if (!selected?.task_id) return; + try { + const url = `/webui/api/task_queue${q}`; + const r = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action, task_id: selected.task_id }) }); + if (!r.ok) throw new Error(await r.text()); + await fetchData(); + } catch (e) { + console.error(e); + } + }; const selectedPretty = useMemo(() => selected ? JSON.stringify(selected, null, 2) : '', [selected]); return ( @@ -114,6 +126,14 @@ const TaskAudit: React.FC = () => {