staging 1.4.22: t213 診斷檔雲端半(新端點 GET /portal/daemon/diagnostics)+ portal 按鈕文案改導流
source Arcrun@8447165(93b1140 新端點 + 8447165 portal 文案) InkStoneCo 總管交辦,走 stage-first 出貨鏈驗收
This commit is contained in:
@@ -13430,6 +13430,90 @@ portalRouter.delete(
|
||||
});
|
||||
})
|
||||
);
|
||||
async function buildDiagnostics(env, tenant2) {
|
||||
const notes = [];
|
||||
let embedding = { checked: false };
|
||||
try {
|
||||
const [statusRes, selftestRes] = await Promise.all([
|
||||
kbdbFetch(env, `/embed/backfill/status?${new URLSearchParams({ owner_id: tenant2 }).toString()}`),
|
||||
kbdbFetch(env, `/embed/selftest?${new URLSearchParams({ owner_id: tenant2 }).toString()}`)
|
||||
]);
|
||||
const statusBody = await statusRes.json().catch(() => null);
|
||||
const selftestBody = await selftestRes.json().catch(() => null);
|
||||
embedding = {
|
||||
checked: true,
|
||||
module_enabled: statusBody?.enabled ?? false,
|
||||
// Vectorize+AI binding 都在,才有「index」這回事
|
||||
cards_embedded: statusBody?.embedded ?? 0,
|
||||
cards_pending: statusBody?.pending ?? 0,
|
||||
self_test: {
|
||||
ran: selftestBody?.tested ?? false,
|
||||
// 三態:true=能搜到自己 / false=搜不到自己(index 收錄有缺)/ null=還沒東西可測或模組未開
|
||||
found_itself: selftestBody?.tested ? selftestBody?.passed ?? null : null,
|
||||
note: selftestBody?.note ?? ""
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
notes.push(`embed \u5065\u5EB7\u72C0\u614B\u67E5\u8A62\u5931\u6557\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
||||
}
|
||||
let library_count = 0;
|
||||
let triplet_count = 0;
|
||||
const ownerParam = new URLSearchParams({ owner_id: tenant2 }).toString();
|
||||
try {
|
||||
const [registeredLibs, autoRes, tripletRes] = await Promise.all([
|
||||
listRecordsByTemplate(env, LIBRARY_TEMPLATE).catch(() => []),
|
||||
kbdbFetch(env, `/entries/libraries?${ownerParam}`),
|
||||
kbdbFetch(env, `/records/triplet-stats?${ownerParam}`)
|
||||
]);
|
||||
const knownLibs = new Set(
|
||||
registeredLibs.map((r) => (r.values.name ?? "").trim()).filter((n) => !!n)
|
||||
);
|
||||
const autoBody = await autoRes.json().catch(() => null);
|
||||
for (const name of autoBody?.libraries ?? []) {
|
||||
const n = String(name ?? "").trim();
|
||||
if (n && n !== "general") knownLibs.add(n);
|
||||
}
|
||||
library_count = knownLibs.size;
|
||||
const tripletBody = await tripletRes.json().catch(() => null);
|
||||
triplet_count = (tripletBody?.stats ?? []).reduce((sum, s) => sum + (Number(s.triplet_count) || 0), 0);
|
||||
} catch (e) {
|
||||
notes.push(`\u77E5\u8B58\u5EAB\u898F\u6A21\u67E5\u8A62\u5931\u6557\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
||||
}
|
||||
let library_scope_check = { ran: false };
|
||||
if (library_count === 0 && triplet_count === 0) {
|
||||
try {
|
||||
const probeRes = await kbdbFetch(env, `/entries?${new URLSearchParams({ owner_id: tenant2, limit: "1" }).toString()}`);
|
||||
const probeBody = await probeRes.json().catch(() => null);
|
||||
const total = probeBody?.total ?? 0;
|
||||
library_scope_check = {
|
||||
ran: true,
|
||||
any_entries_found: total > 0,
|
||||
note: total > 0 ? `\u9019\u500B\u79DF\u6236\u5E95\u4E0B\u67E5\u5F97\u5230\u5176\u4ED6\u8CC7\u6599\uFF08entries \u5171 ${total} \u7B46\uFF09\uFF0C\u4F46\u5EAB\uFF0F\u4E09\u5143\u7D44\u7D71\u8A08\u4ECD\u56DE 0\u2014\u2014\u50CF\u662F\u67E5\u8A62\u65B9\u5F0F\u6216\u79DF\u6236\u5C0D\u4E0D\u4E0A\uFF0C\u4E0D\u50CF\u771F\u7684\u6C92\u8CC7\u6599\uFF0C\u9700\u8981\u4EBA\u518D\u67E5\u4E00\u6B21` : "\u9019\u500B\u79DF\u6236\u5E95\u4E0B\u5B8C\u5168\u67E5\u4E0D\u5230\u4EFB\u4F55\u8CC7\u6599\u2014\u2014\u6BD4\u8F03\u50CF\u662F\u771F\u7684\u9084\u6C92\u6709\u8CC7\u6599\uFF0C\u4E0D\u662F\u67E5\u8A62\u65B9\u5F0F\u932F\u4E86"
|
||||
};
|
||||
} catch (e) {
|
||||
library_scope_check = {
|
||||
ran: true,
|
||||
any_entries_found: null,
|
||||
note: `\u81EA\u6211\u63A2\u6E2C\u67E5\u8A62\u672C\u8EAB\u5931\u6557\uFF1A${e instanceof Error ? e.message : String(e)}`
|
||||
};
|
||||
}
|
||||
}
|
||||
return { library_count, triplet_count, library_scope_check, embedding, notes };
|
||||
}
|
||||
portalRouter.get(
|
||||
"/portal/daemon/diagnostics",
|
||||
(c) => run(c, async () => {
|
||||
const apiKey = (c.req.header("X-Arcrun-API-Key") ?? "").trim();
|
||||
if (!apiKey) return c.json({ error: "\u7F3A\u5C11 X-Arcrun-API-Key header" }, 401);
|
||||
const core = await buildDiagnostics(c.env, apiKey);
|
||||
return c.json({
|
||||
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
||||
instance_url: new URL(c.req.url).origin,
|
||||
bundle_version: c.env.ARCRUN_BUNDLE_VERSION ?? null,
|
||||
...core
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// ../../matrix/arcrun/cypher-executor/src/routes/init-seed.ts
|
||||
var initSeedRouter = new Hono2();
|
||||
@@ -14576,51 +14660,12 @@ portalDataRouter.get(
|
||||
const auth = await requirePortalUser(c);
|
||||
if (!auth.ok) return auth.res;
|
||||
const tenant2 = portalTenant(c.env);
|
||||
const notes = [];
|
||||
let embedding = { checked: false };
|
||||
try {
|
||||
const [statusRes, selftestRes] = await Promise.all([
|
||||
kbdbFetch(c.env, `/embed/backfill/status?${new URLSearchParams({ owner_id: tenant2 }).toString()}`),
|
||||
kbdbFetch(c.env, `/embed/selftest?${new URLSearchParams({ owner_id: tenant2 }).toString()}`)
|
||||
]);
|
||||
const statusBody = await statusRes.json().catch(() => null);
|
||||
const selftestBody = await selftestRes.json().catch(() => null);
|
||||
embedding = {
|
||||
checked: true,
|
||||
module_enabled: statusBody?.enabled ?? false,
|
||||
// Vectorize+AI binding 都在,才有「index」這回事
|
||||
cards_embedded: statusBody?.embedded ?? 0,
|
||||
cards_pending: statusBody?.pending ?? 0,
|
||||
self_test: {
|
||||
ran: selftestBody?.tested ?? false,
|
||||
// 三態:true=能搜到自己 / false=搜不到自己(index 收錄有缺)/ null=還沒東西可測或模組未開
|
||||
found_itself: selftestBody?.tested ? selftestBody?.passed ?? null : null,
|
||||
note: selftestBody?.note ?? ""
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
notes.push(`embed \u5065\u5EB7\u72C0\u614B\u67E5\u8A62\u5931\u6557\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
||||
}
|
||||
let library_count = 0;
|
||||
let triplet_count = 0;
|
||||
try {
|
||||
const mapRes = await kbdbFetch(c.env, `/map?${new URLSearchParams({ owner_id: tenant2 }).toString()}`);
|
||||
const mapBody = await mapRes.json().catch(() => null);
|
||||
const libs = Array.isArray(mapBody?.libraries) ? mapBody.libraries : [];
|
||||
library_count = libs.length;
|
||||
triplet_count = libs.reduce((sum, l) => sum + (Number(l.triplet_count) || 0), 0);
|
||||
} catch (e) {
|
||||
notes.push(`\u77E5\u8B58\u5EAB\u898F\u6A21\u67E5\u8A62\u5931\u6557\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
||||
}
|
||||
notes.push("\u76EE\u524D\u96F2\u7AEF\u6C92\u6709\u4FDD\u5B58\u300C\u8403\u53D6/\u4E0A\u50B3\u5931\u6557\u300D\u7684\u6B77\u53F2\u7D00\u9304\uFF0C\u53EA\u80FD\u770B\u5230\u76EE\u524D\u7684\u805A\u5408\u8A08\u6578\uFF08\u4E0A\u9762 cards_pending\uFF0Fcards_embedded\uFF09\uFF1B\u82E5\u8981\u67E5\u67D0\u4E00\u6B21\u5931\u6557\u7684\u7576\u4E0B\u539F\u56E0\uFF0C\u9700\u5728\u5931\u6557\u7576\u4E0B\u7531\u5C01\u6E2C\u8005\u622A\u5716\u540C\u6B65\u5C0F\u5E6B\u624B\u8996\u7A97\u3002");
|
||||
const core = await buildDiagnostics(c.env, tenant2);
|
||||
return c.json({
|
||||
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
||||
instance_url: new URL(c.req.url).origin,
|
||||
bundle_version: c.env.ARCRUN_BUNDLE_VERSION ?? null,
|
||||
library_count,
|
||||
triplet_count,
|
||||
embedding,
|
||||
notes
|
||||
...core
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
+76
-3
@@ -3115,7 +3115,11 @@ async function recomputeLibraryMap(db, input) {
|
||||
bridgeMap.set(r.entity, libs);
|
||||
}
|
||||
const bridges = [...bridgeMap.entries()].map(([entity, libraries]) => ({ entity, libraries }));
|
||||
const narrative = input.narrative?.trim() || "";
|
||||
let narrative = input.narrative?.trim();
|
||||
if (!narrative) {
|
||||
const prev = await getLibraryMapDetail(db, library, owner);
|
||||
narrative = prev?.narrative?.trim() || "";
|
||||
}
|
||||
const coreNames = topEntities.slice(0, 3).map((t) => t.name);
|
||||
const content = `${library}\uFF1A${narrative || "\uFF08narrative \u5F85 ingest \u88DC\u5BEB\uFF09"}\u3002\u6838\u5FC3\uFF1A${coreNames.length ? coreNames.join("\u3001") : "\uFF08\u5C1A\u7121 entities\uFF09"}`;
|
||||
const blockEntry = await createEntry(db, {
|
||||
@@ -3171,6 +3175,68 @@ async function recomputeLibraryMap(db, input) {
|
||||
triplet_library_slot_added: librarySlotAdded
|
||||
};
|
||||
}
|
||||
async function liveTripletCountsByLibrary(db, tripletTemplateId, owner_id) {
|
||||
const params = owner_id ? [tripletTemplateId, owner_id] : [tripletTemplateId];
|
||||
const res = await db.prepare(
|
||||
`SELECT COALESCE(NULLIF(lib_e.content, ''), 'general') AS library, COUNT(*) AS n
|
||||
FROM (
|
||||
SELECT DISTINCT ev.record_id
|
||||
FROM entry_values ev JOIN entries e ON ev.entry_id = e.id
|
||||
WHERE ev.template_id = ?${owner_id ? " AND e.owner_id = ?" : ""}
|
||||
) AS tr
|
||||
LEFT JOIN entry_values lev ON lev.record_id = tr.record_id AND lev.slot_name = 'library'
|
||||
LEFT JOIN entries lib_e ON lib_e.id = lev.entry_id
|
||||
GROUP BY COALESCE(NULLIF(lib_e.content, ''), 'general')`
|
||||
).bind(...params).all();
|
||||
const m = /* @__PURE__ */ new Map();
|
||||
for (const r of res.results ?? []) m.set(r.library, r.n);
|
||||
return m;
|
||||
}
|
||||
async function knownLibraryNames(db, owner_id) {
|
||||
const names = /* @__PURE__ */ new Set();
|
||||
const entryParams = owner_id ? [owner_id] : [];
|
||||
const entryRows = await db.prepare(
|
||||
`SELECT DISTINCT json_extract(metadata_json, '$.library') AS library FROM entries
|
||||
WHERE ${owner_id ? "owner_id = ?" : "1=1"} AND json_extract(metadata_json, '$.library') IS NOT NULL`
|
||||
).bind(...entryParams).all();
|
||||
for (const r of entryRows.results ?? []) if (r.library) names.add(r.library);
|
||||
const libTpl = await getTemplate(db, "portal_library");
|
||||
if (libTpl) {
|
||||
const libParams = owner_id ? [libTpl.id, owner_id] : [libTpl.id];
|
||||
const libRows = await db.prepare(
|
||||
`SELECT MAX(CASE WHEN ev.slot_name = 'name' THEN e.content END) AS name
|
||||
FROM entry_values ev JOIN entries e ON ev.entry_id = e.id
|
||||
WHERE ev.template_id = ?${owner_id ? " AND e.owner_id = ?" : ""}
|
||||
GROUP BY ev.record_id`
|
||||
).bind(...libParams).all();
|
||||
for (const r of libRows.results ?? []) if (r.name) names.add(r.name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
async function ensureFreshLibraryMaps(db, owner_id, tripletTemplateName = DEFAULT_TRIPLET_TEMPLATE) {
|
||||
const tripletTpl = await getTemplate(db, tripletTemplateName);
|
||||
if (!tripletTpl) return;
|
||||
const [liveCounts, cached, known] = await Promise.all([
|
||||
liveTripletCountsByLibrary(db, tripletTpl.id, owner_id),
|
||||
listLibraryMaps(db, owner_id),
|
||||
knownLibraryNames(db, owner_id)
|
||||
]);
|
||||
const cachedByLib = new Map(cached.map((m) => [m.library, m]));
|
||||
const stale = /* @__PURE__ */ new Set();
|
||||
for (const [library, count] of liveCounts) {
|
||||
const c = cachedByLib.get(library);
|
||||
if (!c || c.triplet_count !== count) stale.add(library);
|
||||
}
|
||||
for (const name of known) {
|
||||
if (!liveCounts.has(name) && !cachedByLib.has(name)) stale.add(name);
|
||||
}
|
||||
await Promise.all(
|
||||
[...stale].map(
|
||||
(library) => recomputeLibraryMap(db, { library, owner_id, triplet_template: tripletTemplateName }).catch(() => {
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
async function listLibraryMaps(db, owner_id) {
|
||||
const tpl = await getTemplate(db, LIBRARY_MAP_TEMPLATE_NAME);
|
||||
if (!tpl) return [];
|
||||
@@ -3241,11 +3307,18 @@ mapRoutes.post("/recompute", async (c) => {
|
||||
}
|
||||
});
|
||||
mapRoutes.get("/", async (c) => {
|
||||
const libraries = await listLibraryMaps(c.env.DB, c.req.query("owner_id") || void 0);
|
||||
const owner = c.req.query("owner_id") || void 0;
|
||||
await ensureFreshLibraryMaps(c.env.DB, owner).catch(() => {
|
||||
});
|
||||
const libraries = await listLibraryMaps(c.env.DB, owner);
|
||||
return c.json({ success: true, libraries, count: libraries.length });
|
||||
});
|
||||
mapRoutes.get("/:library", async (c) => {
|
||||
const map = await getLibraryMapDetail(c.env.DB, c.req.param("library"), c.req.query("owner_id") || void 0);
|
||||
const owner = c.req.query("owner_id") || void 0;
|
||||
const library = c.req.param("library");
|
||||
await ensureFreshLibraryMaps(c.env.DB, owner).catch(() => {
|
||||
});
|
||||
const map = await getLibraryMapDetail(c.env.DB, library, owner);
|
||||
if (!map) return c.json({ success: false, error: "not found" }, 404);
|
||||
return c.json({ success: true, map });
|
||||
});
|
||||
|
||||
+689
-689
File diff suppressed because it is too large
Load Diff
+2
-2
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user