feat(console): automation panel edit and richer list

- 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.
This commit is contained in:
host committed 2026-10-30 15:31:21 +08:00
1 parent e2ce04ce4f
commit 2d644f901b
7 files changed
+117 -35

No files matched your search

@@ -144,7 +144,7 @@ export function useConsoleStore(): ConsoleCtx {
followups: detail.followups,
approvals: detail.approvals, approvalMsg: detail.approvalMsg, refreshApprovals: detail.refreshApprovals, decideApproval: detail.decideApproval,
peerMessages: detail.peerMessages, peerMsg: detail.peerMsg, postPeer: detail.postPeer,
automations: tools.automations, autoMsg: tools.autoMsg, refreshAutomations: () => void tools.refreshAutomations(), createAutomation: tools.createAutomation, cancelAutomation: tools.cancelAutomation,
automations: tools.automations, autoMsg: tools.autoMsg, refreshAutomations: () => void tools.refreshAutomations(), createAutomation: tools.createAutomation, updateAutomation: tools.updateAutomation, cancelAutomation: tools.cancelAutomation,
triage: tools.triage, triageError: tools.triageError, refreshTriage: () => void tools.refreshTriage(),
threadText: detail.threadText, threadGateNote: detail.threadGateNote, threadMsg: detail.threadMsg, threadList: detail.threadList, threadAction: detail.threadAction,
dispatch: async (body, atts) => {
@@ -35,6 +35,33 @@ export function useProjectTools(getProjectId: () => string) {
} 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;
@@ -53,5 +80,5 @@ export function useProjectTools(getProjectId: () => string) {
} catch (e: any) { setTriageError("分诊加载失败:" + e.message); }
}, [getProjectId]);
return { automations, autoMsg, triage, triageError, refreshAutomations, createAutomation, cancelAutomation, refreshTriage };
return { automations, autoMsg, triage, triageError, refreshAutomations, createAutomation, updateAutomation, cancelAutomation, refreshTriage };
}