From 175a29073063a1c00bbfbefbd900002e8956f7dc Mon Sep 17 00:00:00 2001 From: richblack Date: Sun, 17 May 2026 11:26:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(mira/feed):=20WikiStatusBadge=20=E5=8A=A0?= =?UTF-8?q?=E6=89=8B=E5=8B=95=E9=87=8D=E8=A9=A6=E6=8C=89=E9=88=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit leo 2026-05-17 反饋:「漏了的要怎麼做?會自己慢慢完成還是要手動下令?」 自動行為解釋: mira_feed_watcher cron 每 5 min 掃 tags=[] 的 raw 自動重試。 但若已被 mark wiki-processed (假處理),watcher 永遠跳過 → 需手動。 互動加: - ⚠️ 漏了 變按鈕,click → 清 tag + 立即 trigger wiki_synthesis - ✅ wiki 旁加小 ↻ icon,給「tag 標完成但實際沒 wiki」的情況用 - 點擊後 1 分鐘內顯示「⏳ 重試中」防 spam UX 細節: - title hover 解釋每個狀態跟動作 - retry 同時清 tag + 直接 trigger(不等下個 cron tick) - 失敗 fail silently (catch all),1 min 後可再試 Co-Authored-By: Claude Opus 4.7 --- landing/app/mira/feed/page.tsx | 92 +++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 28 deletions(-) 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 ? '…' : '⚠️ 漏了 ↻'} + ); }