thread-master/apps/web/src/lib/jobLabels.ts

109 lines
3.2 KiB
TypeScript
Raw Normal View History

2026-07-13 01:15:30 +00:00
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);
// 若缺 keyi18n 常回傳 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(" · ");
}