feat(mira/feed): WikiStatusBadge 加手動重試按鈕
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<number | null>(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 (
|
||||
<span
|
||||
className="wiki-status wiki-status-done"
|
||||
title="已合成 wiki — click 看詳細 entity"
|
||||
style={{ color: '#3a8a3a', fontSize: '0.85em' }}
|
||||
title="已合成 wiki — 若實際看不到 entity 可能是 fan-out 假處理,按 ↻ 重試"
|
||||
style={{ color: '#3a8a3a', fontSize: '0.85em', display: 'inline-flex', gap: 4, alignItems: 'center' }}
|
||||
>
|
||||
✅ wiki
|
||||
<button
|
||||
onClick={handleRetry}
|
||||
disabled={retrying || !!recentlyRetried}
|
||||
title="重跑(適用 tag 已標但實際沒 wiki-page 的情況)"
|
||||
style={{
|
||||
background: 'transparent', border: 'none', color: '#888',
|
||||
fontSize: '0.85em', padding: 0, cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
{recentlyRetried ? '已重試' : (retrying ? '…' : '↻')}
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const ageMs = Date.now() - toDate(createdAt).getTime();
|
||||
const minutes = ageMs / 60_000;
|
||||
if (recentlyRetried) {
|
||||
return <span style={{ color: '#888', fontSize: '0.85em' }}>⏳ 重試中</span>;
|
||||
}
|
||||
|
||||
if (minutes < 6) {
|
||||
return (
|
||||
<span
|
||||
className="wiki-status wiki-status-pending"
|
||||
title="處理中(mira_feed_watcher 每 5 分鐘掃一次)"
|
||||
style={{ color: '#888', fontSize: '0.85em' }}
|
||||
>
|
||||
<span title="處理中(mira_feed_watcher 每 5 分鐘掃一次)" style={{ color: '#888', fontSize: '0.85em' }}>
|
||||
⏳ 處理中
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (minutes < 30) {
|
||||
return (
|
||||
<span
|
||||
className="wiki-status wiki-status-queued"
|
||||
title="排隊中等下一個 cron tick"
|
||||
style={{ color: '#aaa', fontSize: '0.85em' }}
|
||||
>
|
||||
<span title="排隊中等下一個 cron tick (5 分鐘一次)" style={{ color: '#aaa', fontSize: '0.85em' }}>
|
||||
○ 排隊
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span
|
||||
className="wiki-status wiki-status-stuck"
|
||||
title={`已 ${Math.floor(minutes)} 分鐘未處理 — 可能失敗,看 wiki/ 確認或 trigger watcher`}
|
||||
style={{ color: '#c66', fontSize: '0.85em' }}
|
||||
<button
|
||||
onClick={handleRetry}
|
||||
disabled={retrying}
|
||||
title={`已 ${Math.floor(minutes)} 分鐘未處理 — cron 應該已試多次。點擊強制重試一次`}
|
||||
style={{
|
||||
background: 'transparent', border: '1px solid #c66', color: '#c66',
|
||||
fontSize: '0.85em', padding: '0 6px', borderRadius: 4, cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
⚠️ 漏了?
|
||||
</span>
|
||||
{retrying ? '…' : '⚠️ 漏了 ↻'}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user