From 7dae958dbeb86bd4b35393bffe565913dd57ed22 Mon Sep 17 00:00:00 2001 From: richblack Date: Sun, 17 May 2026 10:40:40 +0800 Subject: [PATCH] =?UTF-8?q?feat(mira/wiki):=20backlinks=20section=20?= =?UTF-8?q?=E2=80=94=20=E9=A1=AF=E7=A4=BA=E6=8F=90=E5=88=B0=E6=AD=A4=20ent?= =?UTF-8?q?ity=20=E7=9A=84=20raw=20notes=20(#2=20leo=20=E5=8F=8D=E9=A5=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit leo 2026-05-17 反饋:「從這本書的條目應該反向連到那篇筆記去, 如果在 Logseq 會放在下方列表提到這個條目的其他內容」 實作: - 撈所有 content === entity 的 wiki-page (V3 一次寫入會建多個 wiki-page per raw mention) - 從每個 wiki-page tags_json 取 raw:XXX tag → unique raw_ids - fetch 對應 raw blocks → render 「📎 提到此 entity 的筆記」section 每條 link 跳 /mira/feed#page=... - 顯示前 100 字 preview,全文 hover title - 樣式:左 border + 暗色背景 (區分於主內容) 對應 wiki_synthesis V3 (commit 63ac4c9 mira) 的 wiki-page tags raw:XXX 標記設計:每篇 raw 提到某 entity 時,create_wiki_page 都會寫一個新的 wiki-page (page_name 同名),tags 含 raw:{raw_id}。反查 wiki 對應 raw 不靠 KBDB graph 反向 index,純走客戶端 wiki-page list filter。 Co-Authored-By: Claude Opus 4.7 --- landing/app/mira/wiki/[pageName]/page.tsx | 73 +++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/landing/app/mira/wiki/[pageName]/page.tsx b/landing/app/mira/wiki/[pageName]/page.tsx index 6df40fa..d824508 100644 --- a/landing/app/mira/wiki/[pageName]/page.tsx +++ b/landing/app/mira/wiki/[pageName]/page.tsx @@ -49,6 +49,9 @@ export default function WikiPagePage({ const [paragraphs, setParagraphs] = useState([]); const [triplets, setTriplets] = useState([]); const [entitySet, setEntitySet] = useState>(new Set()); + // Backlinks:所有提到此 entity 的 raw note(V3 wiki_synthesis 在 wiki-page tags 寫 raw:XXX) + // 對應 leo 2026-05-17 #2 反饋:「從這本書的條目應該反向連到那篇筆記去」 + const [backlinkRaws, setBacklinkRaws] = useState([]); const [collapsed, setCollapsed] = useState>({}); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -117,6 +120,42 @@ export default function WikiPagePage({ } } setEntitySet(eset); + + // Backlinks:找此 entity 的所有 wiki-page (可能多次寫入),提取 raw:XXX tag → fetch raw blocks + if (wikiPage.type === 'wiki-page' && wikiPage.content) { + const sameEntity = allPages.filter((p) => p.content?.trim() === wikiPage.content?.trim()); + const rawIds = new Set(); + for (const wp of sameEntity) { + try { + const tags = JSON.parse(wp.tags_json || '[]') as string[]; + for (const t of tags) { + if (typeof t === 'string' && t.startsWith('raw:')) { + rawIds.add(t.slice(4)); + } + } + } catch { /* skip */ } + } + if (rawIds.size > 0) { + // 一次撈 raw blocks,page_name 是 unique 一次 query 一個 + const rawBlocks: Block[] = []; + await Promise.all( + Array.from(rawIds).map(async (rawId) => { + try { + // KBDB GET /blocks/:id 直接 by id (走 list with block_id filter) + const r = await fetch(`${KBDB_BASE}/blocks/${rawId}`, { headers }); + if (r.ok) { + const data = await r.json(); + const b = data.blocks?.[0] ?? data; + if (b?.id) rawBlocks.push(b as Block); + } + } catch { /* skip */ } + }), + ); + if (!cancelled) { + setBacklinkRaws(rawBlocks.sort((a, b) => b.updated_at - a.updated_at)); + } + } + } } catch (e: any) { if (!cancelled) setError(e?.message ?? 'load failed'); } finally { @@ -207,6 +246,40 @@ export default function WikiPagePage({ )} + {/* Backlinks:提到此 entity 的 raw notes */} + {isWikiPage && backlinkRaws.length > 0 && ( +
+

+ 📎 提到此 entity 的筆記 ({backlinkRaws.length}) +

+ +
+ )} +