// Embed module admin route — backfill existing entries (issue #7 / mira-dissolve T2.4 缺口). // // 背景:embed 原本只有「寫入即嵌」(embedOnWrite),對「開 Vectorize binding 之前就寫入」或 // 「embed-on-write 當時漏掉」的既有 entry 沒有回填路徑 → is_embedded=0 且永遠補不回。 // 本 route = 把 embed.ts 既有 embedText/VECTORIZE.upsert 邏輯包成可重複呼叫的批次補嵌端點。 // // 鐵律對齊:embedding 屬 base optional 模組;模組未開(無 VECTORIZE+AI)→ 誠實回 409,不假裝(mindset §7)。 // base 對內容語意無知:只認通用 metadata.embed===true 旗標,不知 triplet/wiki(解耦)。 import { Hono } from 'hono'; import type { Bindings } from '../types'; import { embedEnabled, backfillEmbeddings, backfillStatus } from '../embed'; export const embedRoutes = new Hono<{ Bindings: Bindings }>(); const OFF_HINT = '語義補嵌需先開 embed 模組(Vectorize+AI binding)。叫 CC「幫我開語義查詢」(設 kbdb_embed:true + redeploy 注入 binding)後再呼叫本端點。'; // POST /embed/backfill — batch-embed existing embeddable entries with is_embedded=0. // body(皆選填):{ limit?:1-100(預設25), owner_id?, source?, reindex?, offset? }。 // 冪等:重跑不會重複嵌(已 is_embedded=1 的不再入選;upsert 同 id 冪等)。 // 分批:單次最多 limit 筆;回傳 remaining>0 表示還有 → 重複呼叫直到 remaining=0。 // reindex:true(Arcrun#11):改重推「所有 embeddable」既有向量(含 is_embedded=1), // 讓事後建立的 Vectorize metadata index 收錄它們(否則帶過濾語意查詢回 0);配 offset 分頁。 // 模組未開 → 409 + capability_hint(不假綠)。 embedRoutes.post('/backfill', async (c) => { if (!embedEnabled(c.env)) { return c.json( { success: false, error: 'embed module not enabled (need VECTORIZE + AI bindings)', capability_hint: OFF_HINT }, 409, ); } const body = (await c.req.json().catch(() => ({}))) as { limit?: number | string; owner_id?: string; source?: string; reindex?: boolean; offset?: number | string; }; const result = await backfillEmbeddings(c.env, { limit: body.limit !== undefined ? Number(body.limit) : undefined, owner_id: body.owner_id || undefined, source: body.source || undefined, // reindex(Arcrun#11):重推既有向量讓事後建立的 Vectorize metadata index 收錄(見 embed.ts)。 reindex: body.reindex === true, offset: body.offset !== undefined ? Number(body.offset) : undefined, }); return c.json({ success: true, ...result }); }); // GET /embed/backfill/status?owner_id=&source= — 待補嵌 / 已補嵌計數(回報 + 判斷是否清零用)。 embedRoutes.get('/backfill/status', async (c) => { const status = await backfillStatus(c.env, { owner_id: c.req.query('owner_id') || undefined, source: c.req.query('source') || undefined, }); return c.json({ success: true, ...status }); }); export default embedRoutes;