diff --git a/landing/app/mira/feed/page.tsx b/landing/app/mira/feed/page.tsx index 9e0a7b2..91e4e29 100644 --- a/landing/app/mira/feed/page.tsx +++ b/landing/app/mira/feed/page.tsx @@ -1145,6 +1145,7 @@ function DocCard({ mainBlock={mainBlocksList[0]} createdAt={doc.created_at} showMira={showMira} + apiKey={me.api_key} /> ); return !showMira && mainBlocksList[0] @@ -2058,22 +2059,25 @@ function SourceBadge({ source }: { source: string }) { ); } -// WikiStatusBadge:顯示這篇 raw 是否已合成 wiki -// - ✅ 已合成(tags 含 wiki-processed) -// - ⏳ 處理中(< 6 分鐘前貼,可能還沒被 cron 撈到 / wiki_synthesis 正在跑) -// - ○ 排隊中(> 6 分鐘但 < 30 分鐘,等下一個 cron tick) -// - ⚠️ 可能漏了(> 30 分鐘還沒處理) -// 對應 leo 2026-05-17 反饋:「沒有符號顯示是否已建立 wiki」 +// WikiStatusBadge:顯示這篇 raw 是否已合成 wiki + 「漏了」手動重試按鈕 +// 對應 leo 2026-05-17 反饋:「漏了的要怎麼做?會自己慢慢完成還是要手動下令?」 +// +// 自動行為:mira_feed_watcher cron 每 5 min 掃 tags=[] 的 raw,會無限重試 +// 手動入口:⚠️ 漏了 變按鈕,點擊 = 清 tag + 立即 trigger wiki_synthesis(不等 cron) function WikiStatusBadge({ mainBlock, createdAt, showMira, + apiKey, }: { mainBlock: KBDBBlock | undefined; createdAt: number | string; showMira: boolean; + apiKey: string; }) { - // Mira 自己貼的(type=wiki-page)就是 wiki,不需要狀態 + const [retrying, setRetrying] = useState(false); + const [retriedAt, setRetriedAt] = useState(null); + if (showMira) return null; if (!mainBlock) return null; @@ -2085,51 +2089,83 @@ function WikiStatusBadge({ // ignore } + const ageMs = Date.now() - toDate(createdAt).getTime(); + const minutes = ageMs / 60_000; + const recentlyRetried = retriedAt && Date.now() - retriedAt < 60_000; + + // 手動重試:清 wiki-processed tag(保險:watcher 才會重撈)+ 立即 trigger wiki_synthesis + const handleRetry = useCallback(async () => { + if (retrying) return; + setRetrying(true); + try { + await fetch(`${KBDB_BASE}/blocks/${mainBlock.id}`, { + method: 'PATCH', + headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ tags: [] }), + }).catch(() => null); + await fetch(`${API_BASE}/webhooks/named/wiki_synthesis/trigger`, { + method: 'POST', + headers: { 'X-Arcrun-API-Key': apiKey, 'Content-Type': 'application/json' }, + body: JSON.stringify({ api_key: apiKey, raw_block_id: mainBlock.id }), + }).catch(() => null); + setRetriedAt(Date.now()); + } finally { + setRetrying(false); + } + }, [retrying, mainBlock?.id, apiKey]); + if (processed) { return ( ✅ wiki + ); } - const ageMs = Date.now() - toDate(createdAt).getTime(); - const minutes = ageMs / 60_000; + if (recentlyRetried) { + return ⏳ 重試中; + } if (minutes < 6) { return ( - + ⏳ 處理中 ); } if (minutes < 30) { return ( - + ○ 排隊 ); } return ( - - ⚠️ 漏了? - + {retrying ? '…' : '⚠️ 漏了 ↻'} + ); }