feat(mira/feed): WikiStatusBadge — 顯示貼文 wiki 合成狀態 (leo 2026-05-17 反饋)
leo 反饋:「沒有符號顯示是否已建立 wiki,不知道是出錯了還是要等下一批」 新 component WikiStatusBadge 顯示在 PostCard header 來源/時間旁邊: - ✅ wiki — tags 含 wiki-processed - ⏳ 處理中 — 貼 < 6 分鐘前(cron 5 分鐘一輪,可能還沒撈到) - ○ 排隊 — 6-30 分鐘(等下一個 tick) - ⚠️ 漏了? — > 30 分鐘還沒處理(可能 wiki_synthesis 失敗) Mira 自己貼文(type=wiki-page from showMira)不顯示 — 它本身就是 wiki。 資料來源純 client-side:mainBlocksList[0].tags_json + doc.created_at。 未來可加 click → 跳對應 wiki page,或 hover 顯示 entity 預覽。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1139,6 +1139,18 @@ function DocCard({
|
|||||||
<SourceBadge source={postSource} />
|
<SourceBadge source={postSource} />
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
<RelTime when={doc.updated_at} />
|
<RelTime when={doc.updated_at} />
|
||||||
|
{(() => {
|
||||||
|
const badge = (
|
||||||
|
<WikiStatusBadge
|
||||||
|
mainBlock={mainBlocksList[0]}
|
||||||
|
createdAt={doc.created_at}
|
||||||
|
showMira={showMira}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return !showMira && mainBlocksList[0]
|
||||||
|
? (<><span>·</span>{badge}</>)
|
||||||
|
: null;
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MoreMenu items={cardMenu} />
|
<MoreMenu items={cardMenu} />
|
||||||
@@ -2046,6 +2058,81 @@ 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」
|
||||||
|
function WikiStatusBadge({
|
||||||
|
mainBlock,
|
||||||
|
createdAt,
|
||||||
|
showMira,
|
||||||
|
}: {
|
||||||
|
mainBlock: KBDBBlock | undefined;
|
||||||
|
createdAt: number | string;
|
||||||
|
showMira: boolean;
|
||||||
|
}) {
|
||||||
|
// Mira 自己貼的(type=wiki-page)就是 wiki,不需要狀態
|
||||||
|
if (showMira) return null;
|
||||||
|
if (!mainBlock) return null;
|
||||||
|
|
||||||
|
let processed = false;
|
||||||
|
try {
|
||||||
|
const tags = JSON.parse(mainBlock.tags_json || '[]') as string[];
|
||||||
|
processed = Array.isArray(tags) && tags.includes('wiki-processed');
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processed) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="wiki-status wiki-status-done"
|
||||||
|
title="已合成 wiki — click 看詳細 entity"
|
||||||
|
style={{ color: '#3a8a3a', fontSize: '0.85em' }}
|
||||||
|
>
|
||||||
|
✅ wiki
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ageMs = Date.now() - toDate(createdAt).getTime();
|
||||||
|
const minutes = ageMs / 60_000;
|
||||||
|
|
||||||
|
if (minutes < 6) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="wiki-status wiki-status-pending"
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="wiki-status wiki-status-stuck"
|
||||||
|
title={`已 ${Math.floor(minutes)} 分鐘未處理 — 可能失敗,看 wiki/ 確認或 trigger watcher`}
|
||||||
|
style={{ color: '#c66', fontSize: '0.85em' }}
|
||||||
|
>
|
||||||
|
⚠️ 漏了?
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function RelTime({ when }: { when: number | string }) {
|
function RelTime({ when }: { when: number | string }) {
|
||||||
const d = toDate(when);
|
const d = toDate(when);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|||||||
Reference in New Issue
Block a user