fix(portal): 讀不到就說讀不到——總圖不再把「讀不到」畫成「你沒有」(Arcrun#100)
leo 2026-08-12 打開總圖看到:「0 個實體 · 0 條關聯/知識庫還沒有任何關聯—— 上傳文件後 AI 會自動織網」。**而他庫裡有 1854 條三元組。** 那句話會叫他去做一件不需要做的事。 三個獨立的洞疊起來才變成那句謊: ① console-dashboard.ts 打 kbdb-graph-plugin 沒帶認證(同段落打 kbdb 的兩支都有帶) ② 那顆 worker 不在更新的部署清單裡 ⇒ token 一換它就落單 ③ **畫面把 null 畫成 0**——後端已經誠實回 null 了,是前端把它變成謊話 為什麼一直沒被發現:graph plugin 原本身上沒有 token ⇒ 門開著 ⇒ 沒帶也進得去。 2026-08-12 輪替後它有了 token,門關上,401 才浮出來。 📍 repo:matrix/arcrun(cypher-executor/src/routes/、console-ui/public/) 📍 票:Leo/Arcrun#100
This commit is contained in:
@@ -48,7 +48,7 @@
|
||||
*/
|
||||
import { Hono } from 'hono';
|
||||
import type { Bindings } from '../types';
|
||||
import { kbdbBase, graphBase } from './kbdb-proxy';
|
||||
import { kbdbBase, graphBase, graphHeaders } from './kbdb-proxy';
|
||||
import { validateConsoleSession } from './console-auth';
|
||||
import {
|
||||
type KbdbEntry,
|
||||
@@ -104,6 +104,31 @@ async function fetchJson<T>(url: string, headers?: Record<string, string>): Prom
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 本租戶三元組的**真實總數**(null = 讀不到,畫面要顯示「讀不到」而非 0)。
|
||||
*
|
||||
* 🔴 Arcrun#100:不可以拿 graph-plugin `/triplets/stats` 的 `total` 當數量。
|
||||
* 那支的 `total` 是**分頁長度**不是 COUNT——它走 `/records/by-template/triplet`(KBDB 端
|
||||
* `searchByTemplate` 預設 limit=100、硬上限 500)且不帶 owner 過濾,所以 1854 條的庫
|
||||
* 只會回 100。修好 401 之後若還讀它,畫面會從「0」變成「100」——一樣是假的。
|
||||
* 真相源=KBDB `/records/triplet-stats`(真 SQL COUNT(*)、依 owner_id 過濾、無上限),
|
||||
* 回 `{ success, stats: [{ library, triplet_count }] }`,加總即全庫條數。
|
||||
*/
|
||||
async function fetchTripletTotal(env: Bindings, tenant: string): Promise<number | null> {
|
||||
const { base, headers } = kbdbBase(env);
|
||||
const data = await fetchJson<{ stats?: { triplet_count?: unknown }[] }>(
|
||||
`${base}/records/triplet-stats?owner_id=${encodeURIComponent(tenant)}`,
|
||||
headers,
|
||||
);
|
||||
if (!data || !Array.isArray(data.stats)) return null;
|
||||
let total = 0;
|
||||
for (const row of data.stats) {
|
||||
if (typeof row?.triplet_count !== 'number') return null; // 形狀不對 → 誠實回讀不到,不半信半疑加總
|
||||
total += row.triplet_count;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/** KBDB entries 符合條件的總數(limit=1 只拿 total 欄,不搬資料)。null = 讀不到。 */
|
||||
async function fetchEntryTotal(env: Bindings, filters: Record<string, string>): Promise<number | null> {
|
||||
const { base, headers } = kbdbBase(env);
|
||||
@@ -254,6 +279,7 @@ consoleDashboardRouter.get('/console/dashboard-data', async (c) => {
|
||||
kbdbHealth,
|
||||
embedStatus,
|
||||
graphStats,
|
||||
tripletTotal,
|
||||
entriesTotal,
|
||||
wikiCardTotal,
|
||||
workflowTotal,
|
||||
@@ -265,7 +291,13 @@ consoleDashboardRouter.get('/console/dashboard-data', async (c) => {
|
||||
cachedGiteaSprint(c.env, now, (p) => c.executionCtx.waitUntil(p)),
|
||||
fetchJson<{ ok?: boolean }>(`${kbdbUrl}/health`, kbdbHeaders),
|
||||
fetchJson<{ enabled?: boolean; pending?: number; embedded?: number }>(`${kbdbUrl}/embed/backfill/status`, kbdbHeaders),
|
||||
fetchJson<{ total?: number; recent?: { today?: number; this_week?: number } }>(`${graphUrl}/triplets/stats`),
|
||||
// graph-plugin 只拿來判「圖服務活著沒」(燈號)——數字不從這裡拿,見 fetchTripletTotal。
|
||||
// headers 一定要帶:plugin 的 /triplets 前綴掛 Bearer 閘,漏帶=永遠 401=永遠假紅燈(#100)。
|
||||
fetchJson<{ total?: number; recent?: { today?: number; this_week?: number } }>(
|
||||
`${graphUrl}/triplets/stats`,
|
||||
graphHeaders(c.env),
|
||||
),
|
||||
fetchTripletTotal(c.env, tenant),
|
||||
// owner_id 一律鎖本租戶:原本不帶 owner 會混到別租戶(實測 459,137 vs leo 的 458,732)
|
||||
fetchEntryTotal(c.env, { owner_id: tenant }),
|
||||
fetchEntryTotal(c.env, { entry_type: 'wiki_card', owner_id: tenant }),
|
||||
@@ -400,13 +432,14 @@ consoleDashboardRouter.get('/console/dashboard-data', async (c) => {
|
||||
embed: embedStatus
|
||||
? { enabled: embedStatus.enabled === true, embedded: embedStatus.embedded ?? null, pending: embedStatus.pending ?? null }
|
||||
: null,
|
||||
graph: graphStats ? { ok: true, triplets: graphStats.total ?? null } : { ok: false, triplets: null },
|
||||
// ok = plugin 通不通(graphStats 讀得到就是通);triplets = KBDB 真 COUNT(與 plugin 分頁長度無關)
|
||||
graph: { ok: graphStats !== null, triplets: tripletTotal },
|
||||
workflow_total: workflowTotal,
|
||||
},
|
||||
kb: {
|
||||
entries_total: entriesTotal,
|
||||
wiki_card_total: wikiCardTotal,
|
||||
triplets_total: graphStats?.total ?? null,
|
||||
triplets_total: tripletTotal,
|
||||
},
|
||||
generated_at: new Date(now).toISOString(),
|
||||
});
|
||||
@@ -420,15 +453,15 @@ consoleDashboardRouter.get('/console/dashboard-data', async (c) => {
|
||||
consoleDashboardRouter.get('/console/kb-scale-data', async (c) => {
|
||||
const tenant = c.env.CONSOLE_TENANT || 'leo';
|
||||
const { base, headers } = kbdbBase(c.env);
|
||||
const graphUrl = graphBase(c.env);
|
||||
const now = Date.now();
|
||||
const [wikiCards, graphStats, embedStatus] = await Promise.all([
|
||||
const [wikiCards, tripletTotal, embedStatus] = await Promise.all([
|
||||
// limit=1 順手拿最新一筆 created_at(list 為 created_at DESC)=「最近寫入時間」
|
||||
fetchJson<{ total?: number; entries?: { created_at?: string | number }[] }>(
|
||||
`${base}/entries?${new URLSearchParams({ owner_id: tenant, entry_type: 'wiki_card', limit: '1' }).toString()}`,
|
||||
headers,
|
||||
),
|
||||
fetchJson<{ total?: number }>(`${graphUrl}/triplets/stats`),
|
||||
// #100:三元組數改讀 KBDB 真 COUNT,不再讀 graph-plugin 的分頁長度(見 fetchTripletTotal 註)
|
||||
fetchTripletTotal(c.env, tenant),
|
||||
fetchJson<{ enabled?: boolean; embedded?: number; pending?: number }>(`${base}/embed/backfill/status`, headers),
|
||||
]);
|
||||
const latestMs = parseCreatedAtMs(wikiCards?.entries?.[0]?.created_at ?? null);
|
||||
@@ -436,7 +469,7 @@ consoleDashboardRouter.get('/console/kb-scale-data', async (c) => {
|
||||
return c.json({
|
||||
wiki_card_total: typeof wikiCards?.total === 'number' ? wikiCards.total : null,
|
||||
wiki_card_latest_ago_minutes: latestMs === null ? -1 : agoMinutes(now, latestMs),
|
||||
triplets_total: typeof graphStats?.total === 'number' ? graphStats.total : null,
|
||||
triplets_total: tripletTotal,
|
||||
embedded: embedStatus?.embedded ?? null,
|
||||
embed_enabled: embedStatus ? embedStatus.enabled === true : null,
|
||||
generated_at: new Date(now).toISOString(),
|
||||
|
||||
Reference in New Issue
Block a user