import type { JobProgressDetail, JobTaskProgress } from "./types"; /** 海巡/分析任務的階段說明(給 UI 顯示用) */ export const SCAN_PIPELINE_STEPS = [ { phase: "web", title: "網路搜尋", hint: "Brave Search 補充 Threads 貼文連結" }, { phase: "tasks", title: "關鍵字搜尋", hint: "Threads API 或瀏覽器爬蟲(含真人延遲,可能較慢)" }, { phase: "save", title: "寫入資料", hint: "把找到的貼文存進資料庫" }, { phase: "replies", title: "抓取留言", hint: "瀏覽器讀取熱門貼文留言(Dev 模式,可略過)" }, { phase: "quality", title: "整理結果", hint: "依互動數排序,供你自行挑選" }, ] as const; export const ANALYZE_PIPELINE_STEPS = [ { phase: "ai", title: "AI 研究地圖", hint: "產出受眾問題、內容支柱、排除項" }, { phase: "accounts", title: "相似帳號", hint: "網路搜尋同領域創作者(僅爆款模仿)" }, ] as const; export const STYLE_8D_PIPELINE_STEPS = [ { phase: "session", title: "確認連線", hint: "確認 Threads Extension 同步狀態" }, { phase: "samples", title: "抓取樣本", hint: "讀取對標帳號近期貼文與公開互動" }, { phase: "style", title: "AI 8D", hint: "分析語氣、結構、互動、主題、節奏、視覺、轉換與風險" }, { phase: "store", title: "儲存策略", hint: "寫入目前帳號,供產文與回覆自動使用" }, ] as const; export const AI_TASK_PIPELINE_STEPS = [ { phase: "ai-work", title: "AI 處理", hint: "模型正在生成或分析;完成時間依模型與內容量而異" }, ] as const; export function parseJobProgressDetail( raw: string | null | undefined ): JobProgressDetail | null { if (!raw) return null; try { return JSON.parse(raw) as JobProgressDetail; } catch { return null; } } export function initTaskProgress( tasks: Array<{ id: string; label: string }> ): JobProgressDetail["tasks"] { return tasks.map((t) => ({ id: t.id, label: t.label, status: "pending" as const, })); } export function setTaskStatus( detail: JobProgressDetail, taskId: string, patch: Partial[number]> ) { if (!detail.tasks) return; const task = detail.tasks.find((t) => t.id === taskId); if (!task) return; Object.assign(task, patch); } export function formatTaskMetric(task: { id: string; found?: number }): string { if (task.found == null) return ""; if (task.id === "replies") return ` · ${task.found} 則留言`; if (task.id === "quality") return ` · ${task.found} 篇`; return ` · ${task.found} 篇`; } const PIPELINE_TASK_IDS = new Set(["web", "save", "replies", "quality", "ai", "accounts", "session", "samples", "style", "store", "ai-work"]); export function isPipelineTask(task: JobTaskProgress): boolean { return PIPELINE_TASK_IDS.has(task.id); } /** 比「進行中」更具體的狀態說明 */ export function describeTaskStatus( task: JobTaskProgress, phase?: JobProgressDetail["phase"] ): string { switch (task.status) { case "pending": return "等待"; case "done": return "完成"; case "failed": return "失敗"; case "cancelled": return "已取消"; case "running": if (task.id === "web") return "Brave 網搜中…"; if (task.id === "quality") return "整理結果中…"; if (task.id === "replies") return "瀏覽器抓留言中…"; if (task.id === "save") return "寫入中…"; if (task.id === "ai") return "AI 產出研究地圖中…"; if (task.id === "accounts") return "網路搜尋帳號中…"; if (task.id === "session") return "確認 Threads 連線中…"; if (task.id === "samples") return "抓取近期貼文中…"; if (task.id === "style") return "AI 8D 分析中…"; if (task.id === "store") return "儲存帳號策略中…"; if (task.id === "ai-work") return "AI 處理中…"; if (phase === "tasks") { if (task.step) return task.step; return "瀏覽器爬蟲中…"; } return "進行中…"; default: return "進行中…"; } } export function formatTaskElapsed(startedAt?: number): string { if (!startedAt) return ""; const sec = Math.max(0, Math.floor((Date.now() - startedAt) / 1000)); if (sec < 60) return ` · ${sec}s`; const min = Math.floor(sec / 60); const rem = sec % 60; return ` · ${min}m${rem}s`; } export function getActivePhaseHint( phase: JobProgressDetail["phase"] | undefined, jobType?: "scan" | "analyze-topic" | "style-8d" | "ai-task" ): string | null { if (!phase) return null; if (jobType === "analyze-topic") { return ANALYZE_PIPELINE_STEPS.find((s) => s.phase === phase)?.hint ?? null; } if (jobType === "style-8d") { return STYLE_8D_PIPELINE_STEPS.find((s) => s.phase === phase)?.hint ?? null; } if (jobType === "ai-task") { return AI_TASK_PIPELINE_STEPS.find((s) => s.phase === phase)?.hint ?? null; } return SCAN_PIPELINE_STEPS.find((s) => s.phase === phase)?.hint ?? null; }