diff --git a/webui/src/pages/TasksPage.svelte b/webui/src/pages/TasksPage.svelte index 55d58d3..bf6e4f6 100644 --- a/webui/src/pages/TasksPage.svelte +++ b/webui/src/pages/TasksPage.svelte @@ -10,6 +10,7 @@ let runs = $state({}); let loading = $state(true); let error = $state(""); + let tick = $state(0); async function load() { loading = true; @@ -21,7 +22,7 @@ jobs = (await api("/api/jobs")).jobs; runs = Object.fromEntries(await Promise.all(jobs.map(async (job) => [ job.id, - await api(`/api/jobs/${encodeURIComponent(job.id)}/runs?limit=5`).then((value) => value.runs).catch(() => []) + await api(`/api/jobs/${encodeURIComponent(job.id)}/runs?limit=10`).then((value) => value.runs).catch(() => []) ]))); } } catch (caught) { @@ -36,7 +37,43 @@ load(); } - onMount(load); + function countdown(ts) { + void tick; + if (!ts) return "—"; + const ms = ts < 1e12 ? ts * 1000 : ts; + const diff = ms - Date.now(); + if (diff <= 0) return "即将运行"; + const mins = Math.floor(diff / 60000); + if (mins < 1) return "即将运行"; + if (mins < 60) return `${mins} 分钟后`; + const hours = Math.floor(mins / 60); + if (hours < 24) return `${hours} 小时后`; + return `${Math.floor(hours / 24)} 天后`; + } + + function elapsed(createdAt) { + void tick; + if (!createdAt) return ""; + const ms = createdAt < 1e12 ? createdAt * 1000 : createdAt; + const secs = Math.floor((Date.now() - ms) / 1000); + if (secs < 0) return ""; + if (secs < 60) return `${secs}s`; + if (secs < 3600) return `${Math.floor(secs / 60)}m ${secs % 60}s`; + return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`; + } + + function dotColor(status) { + if (status === "completed" || status === "success" || status === "ok") return "var(--signal)"; + if (status === "timeout") return "var(--accent)"; + if (status === "running") return "var(--info)"; + return "var(--danger)"; + } + + onMount(() => { + load(); + const timer = setInterval(() => { tick += 1; }, 30000); + return () => clearInterval(timer); + });
@@ -54,15 +91,60 @@ {:else if error}
{error}
{:else if tab === "background"} {#each tasks as task (task.id)} -

{task.prompt.slice(0, 100)}

{task.session_id}{formatTime(task.created_at)}{task.tool_calls_count} 次工具调用 · {task.iterations} 轮
{#if task.result}

{task.result}

{/if}{#if task.error}

{task.error}

{/if}
+
+
+
+

+ {#if task.status === "running"}{/if} + {task.prompt.slice(0, 100)} +

+
+ {task.session_id} + {formatTime(task.created_at)} + {#if task.status === "running"}{elapsed(task.created_at)}{/if} + {task.tool_calls_count} 次工具调用 · {task.iterations} 轮 +
+
+ +
+ {#if task.result}

{task.result}

{/if} + {#if task.error}

{task.error}

{/if} +
{:else}
暂无后台任务
{/each} {:else} {#each jobs as job (job.id)}
-

{job.name}

{job.prompt}

{job.job_kind === "monitor" ? "巡检" : "任务"} · {job.delivery_policy}{job.channel} · {job.chat_id}下次 {formatTime(job.next_run_at)}上次 {formatTime(job.last_run_at)}
+
+
+

{job.name}

+

{job.prompt}

+
+ {job.cron} + {job.job_kind === "monitor" ? "巡检" : "任务"} · {job.delivery_policy} + {job.channel} · {job.chat_id} + 下次 {countdown(job.next_run_at)} + 上次 {formatTime(job.last_run_at)} +
+ {#if runs[job.id]?.length} +
+ {#each runs[job.id] as run} + + {/each} +
+ {/if} +
+ +
{#if runs[job.id]?.length}
{#each runs[job.id] as run}
{formatTime(run.finished_at)} · {run.duration_ms}ms{run.result_kind ? ` · ${run.result_kind}` : ""}{run.delivery_status ? ` · ${run.delivery_status}` : ""}
{/each}
{/if}
{:else}
暂无定时任务
{/each} {/if}
+ +