- AutomationPanel rows now show title, prompt content (160-char preview with expand/collapse), repo_url, last/next run, run_once badge and cancelled time. - Active automations get an inline edit form (name/prompt/interval/repo) that PATCHes only changed fields via the existing automation_update endpoint; backend semantics unchanged (no last_run_at reset, cancelled stays terminal). - Rebuilt cloud/console.html with tsc + vite (build green); automation python tests 18/18 OK.
85 lines
4.1 KiB
TypeScript
85 lines
4.1 KiB
TypeScript
import { useCallback, useState } from "react";
|
|
import { api } from "../api/client";
|
|
import { paths } from "../api/paths";
|
|
import type { Automation, Triage } from "../api/types";
|
|
|
|
// Project-scoped tools shown in the centre for their own views:
|
|
// automations (create/list/cancel) and the triage inbox (4 groups).
|
|
export function useProjectTools(getProjectId: () => string) {
|
|
const [automations, setAutomations] = useState<Automation[]>([]);
|
|
const [autoMsg, setAutoMsg] = useState("");
|
|
const [triage, setTriage] = useState<Triage | null>(null);
|
|
const [triageError, setTriageError] = useState("");
|
|
|
|
const refreshAutomations = useCallback(async (pid?: string) => {
|
|
const id = pid !== undefined ? pid : getProjectId();
|
|
if (!id) { setAutomations([]); return; }
|
|
try {
|
|
const d = await api<{ automations: Automation[] }>(paths.projectAutomations(id));
|
|
setAutomations(d.automations || []);
|
|
} catch (e: any) { setAutoMsg("自动化加载失败:" + e.message); }
|
|
}, [getProjectId]);
|
|
|
|
const createAutomation = useCallback(async (name: string, prompt: string, interval: string, repo: string) => {
|
|
const pid = getProjectId();
|
|
if (!pid) { setAutoMsg("先选项目"); return; }
|
|
if (!name.trim()) { setAutoMsg("自动化名称必填"); return; }
|
|
if (!prompt.trim()) { setAutoMsg("定时任务内容必填"); return; }
|
|
const body: { name: string; prompt: string; interval_seconds?: number; repo_url?: string } = { name: name.trim(), prompt: prompt.trim() };
|
|
if (interval.trim()) body.interval_seconds = parseInt(interval.trim(), 10);
|
|
if (repo.trim()) body.repo_url = repo.trim();
|
|
try {
|
|
await api(paths.projectAutomations(pid), "POST", body);
|
|
setAutoMsg("自动化已创建(登记定义,到期后由执行器认领)");
|
|
await refreshAutomations(pid);
|
|
} catch (e: any) { setAutoMsg("创建失败:" + e.message); }
|
|
}, [getProjectId, refreshAutomations]);
|
|
|
|
const updateAutomation = useCallback(async (a: Automation, name: string, prompt: string, interval: string, repo: string) => {
|
|
const pid = getProjectId();
|
|
if (!pid) { setAutoMsg("先选项目"); return false; }
|
|
const n = name.trim();
|
|
const p = prompt.trim();
|
|
if (!n) { setAutoMsg("自动化名称必填"); return false; }
|
|
if (!p) { setAutoMsg("定时任务内容必填"); return false; }
|
|
const body: { name?: string; prompt?: string; interval_seconds?: number; repo_url?: string | null } = {};
|
|
if (n !== (a.name || "")) body.name = n;
|
|
if (p !== (a.prompt || "")) body.prompt = p;
|
|
if (interval.trim()) {
|
|
const v = parseInt(interval.trim(), 10);
|
|
if (Number.isNaN(v)) { setAutoMsg("间隔秒数必须是数字"); return false; }
|
|
if (v < 60) { setAutoMsg("间隔秒数最小 60"); return false; }
|
|
if (v !== a.interval_seconds) body.interval_seconds = v;
|
|
}
|
|
const r = repo.trim();
|
|
if (r !== (a.repo_url || "")) body.repo_url = r === "" ? null : r;
|
|
if (Object.keys(body).length === 0) { setAutoMsg("没有改动"); return false; }
|
|
try {
|
|
await api(paths.projectAutomation(pid, a.id), "PATCH", body);
|
|
setAutoMsg("自动化已更新");
|
|
await refreshAutomations(pid);
|
|
return true;
|
|
} catch (e: any) { setAutoMsg("更新失败:" + e.message); return false; }
|
|
}, [getProjectId, refreshAutomations]);
|
|
|
|
const cancelAutomation = useCallback((id: string) => {
|
|
const pid = getProjectId();
|
|
if (!pid) return;
|
|
void api(paths.projectAutomationCancel(pid, id), "POST", {})
|
|
.then(() => { setAutoMsg("自动化已取消"); void refreshAutomations(pid); })
|
|
.catch((e: any) => setAutoMsg("取消失败:" + e.message));
|
|
}, [getProjectId, refreshAutomations]);
|
|
|
|
const refreshTriage = useCallback(async (pid?: string) => {
|
|
const id = pid !== undefined ? pid : getProjectId();
|
|
if (!id) { setTriage(null); return; }
|
|
try {
|
|
const d = await api<Triage>(paths.projectTriage(id));
|
|
setTriage(d);
|
|
setTriageError("");
|
|
} catch (e: any) { setTriageError("分诊加载失败:" + e.message); }
|
|
}, [getProjectId]);
|
|
|
|
return { automations, autoMsg, triage, triageError, refreshAutomations, createAutomation, updateAutomation, cancelAutomation, refreshTriage };
|
|
}
|