109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
import type { Job, JobStatus } from "../domain/types";
|
||
|
||
/** 任務類型顯示名(中文) */
|
||
export function jobTemplateLabel(
|
||
templateType: string,
|
||
t: (key: string, params?: Record<string, string | number>) => string,
|
||
): string {
|
||
switch (templateType) {
|
||
case "demo":
|
||
return t("jobs.demoLabel");
|
||
case "threads_token_renew":
|
||
return t("jobs.template.tokenRenew");
|
||
case "persona_analyze_account":
|
||
return t("jobs.template.personaAnalyzeAccount");
|
||
case "persona_analyze_text":
|
||
return t("jobs.template.personaAnalyzeText");
|
||
case "compose_mimic":
|
||
return t("jobs.template.composeMimic");
|
||
case "play_generate_script":
|
||
return t("jobs.template.playGenerateScript");
|
||
default:
|
||
return templateType || t("jobs.template.unknown");
|
||
}
|
||
}
|
||
|
||
/** 是否為約每 30 天的定期 Token 延長任務 */
|
||
export function isRecurringTokenRenew(job: { template_type: string }): boolean {
|
||
return job.template_type === "threads_token_renew";
|
||
}
|
||
|
||
/**
|
||
* 是否應在頂欄/加速輪詢中視為「即時進行中」:
|
||
* - running 一律
|
||
* - pending/queued 且已到期(run_after 空或 ≤ now)— 不含遠期排程(如 30 天後 token 延長)
|
||
*/
|
||
export function isJobLiveActive(
|
||
job: { status: string; run_after?: number },
|
||
nowNs: number = Date.now() * 1_000_000,
|
||
): boolean {
|
||
if (job.status === "running") return true;
|
||
if (job.status !== "pending" && job.status !== "queued") return false;
|
||
if (!job.run_after || job.run_after <= 0) return true;
|
||
return job.run_after <= nowNs;
|
||
}
|
||
|
||
/** 執行中不可手動刪除;其餘(含已排程/終態)可刪 */
|
||
export function canDeleteJob(job: { status: string }): boolean {
|
||
return job.status !== "running";
|
||
}
|
||
|
||
/** 狀態中文 */
|
||
export function jobStatusLabel(
|
||
status: JobStatus | string,
|
||
t: (key: string, params?: Record<string, string | number>) => string,
|
||
): string {
|
||
const key = `jobs.status.${status}`;
|
||
const label = t(key);
|
||
// 若缺 key,i18n 常回傳 key 本身
|
||
if (label === key) return status;
|
||
return label;
|
||
}
|
||
|
||
export function jobStatusTone(
|
||
status: JobStatus | string,
|
||
): "success" | "danger" | "warning" | "brand" | "neutral" {
|
||
switch (status) {
|
||
case "succeeded":
|
||
return "success";
|
||
case "failed":
|
||
return "danger";
|
||
case "cancelled":
|
||
case "cancel_requested":
|
||
return "neutral";
|
||
case "running":
|
||
return "brand";
|
||
case "queued":
|
||
case "pending":
|
||
return "warning";
|
||
default:
|
||
return "neutral";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 副標:定期節奏(約每 30 天)+ 進度摘要 + 下次執行時間。
|
||
* 標題已含「約每 30 天」;此處再強調一次節奏,方便列表一眼辨認。
|
||
*/
|
||
export function jobSubtitle(
|
||
job: Job,
|
||
t: (key: string, params?: Record<string, string | number>) => string,
|
||
formatTime: (n: number | null | undefined) => string,
|
||
): string {
|
||
const parts: string[] = [];
|
||
if (isRecurringTokenRenew(job)) {
|
||
parts.push(t("jobs.template.tokenRenewCadence"));
|
||
}
|
||
if (job.progress_summary) {
|
||
parts.push(job.progress_summary);
|
||
}
|
||
if (
|
||
job.run_after &&
|
||
job.run_after > 0 &&
|
||
(job.status === "queued" || job.status === "pending")
|
||
) {
|
||
parts.push(t("jobs.nextRun", { time: formatTime(job.run_after) }));
|
||
}
|
||
return parts.join(" · ");
|
||
}
|