ship 1.4.57:Arcrun@9a9891a0dde7

This commit is contained in:
ship
2026-08-27 12:49:58 +08:00
parent 45cf0865f0
commit 9dee6dcf5b
8 changed files with 340 additions and 44 deletions
+74
View File
@@ -2590,6 +2590,67 @@ function topPages(entries, limit) {
return [...best.values()].sort((a, b) => b.score - a.score).slice(0, limit);
}
// kbdb/src/actions/library-index.ts
var DEFAULT_CARD_LIMIT = 200;
var MAX_CARD_LIMIT = 500;
var GLOSS_HEADINGS = ["## \u6458\u8981", "## \u4E00\u53E5\u8A71\u5B9A\u7FA9"];
var INDEX_CARD_NAME = "00-INDEX";
function glossOf(block) {
if (!block) return null;
const lines = String(block).split("\n");
const body = lines.filter((l) => !GLOSS_HEADINGS.some((h) => l.trim().startsWith(h))).map((l) => l.trim()).filter(Boolean);
if (body.length === 0) return null;
const text = body.join(" ").replace(/\s+/g, " ").trim();
return text || null;
}
async function listLibraryCards(db, f) {
const library = f.library;
const owner = f.owner_id ?? "";
const limit = Math.min(Math.max(f.limit ?? DEFAULT_CARD_LIMIT, 1), MAX_CARD_LIMIT);
const glossLike = GLOSS_HEADINGS.map(() => "content LIKE ?").join(" OR ");
const glossParams = GLOSS_HEADINGS.map((h) => `${h}%`);
const where = `WHERE entry_type = 'block'
AND page_name IS NOT NULL AND page_name != ''
AND (? = '' OR owner_id = ?)
AND COALESCE(json_extract(metadata_json, '$.status'), '') != 'deprecated'
AND ${ENTRY_LIBRARY} = ?`;
const whereParams = [owner, owner, library];
const [rowsRes, countRow, indexRow] = await Promise.all([
db.prepare(
`SELECT
page_name,
COUNT(*) AS block_count,
MAX(CASE WHEN ${glossLike} THEN content END) AS gloss_block,
MAX(json_extract(metadata_json, '$.source_path')) AS source_path,
MAX(updated_at) AS updated_at
FROM entries
${where}
GROUP BY page_name
ORDER BY MAX(updated_at) DESC, page_name ASC
LIMIT ?`
).bind(...glossParams, ...whereParams, limit).all(),
db.prepare(`SELECT COUNT(DISTINCT page_name) AS total FROM entries ${where}`).bind(...whereParams).first(),
// 🔴 目錄卡在不在,**不能靠掃上面那一頁**——它照 updated_at 排序,
// 目錄卡很可能不在前 limit 名內 ⇒ 那樣會回報「沒有目錄」而其實有,
// 而這個欄位正是呼叫端用來決定「要不要退回導出清單」的開關。
// 它只有一張、名字固定,所以用卡名直接問一次(EXISTS,成本可忽略)。
db.prepare(`SELECT 1 AS hit FROM entries ${where} AND page_name = ? LIMIT 1`).bind(...whereParams, INDEX_CARD_NAME).first()
]);
const cards = (rowsRes.results ?? []).map((r) => ({
page_name: r.page_name,
block_count: Number(r.block_count) || 0,
gloss: glossOf(r.gloss_block),
source_path: r.source_path ?? null,
updated_at: Number(r.updated_at) || 0
}));
return {
library,
cards,
total: countRow?.total ?? cards.length,
has_index_card: !!indexRow
};
}
// kbdb/src/actions/maintenance-quota.ts
var DEFAULT_MAINTENANCE_DAILY_WRITE_LIMIT = 2e4;
function maintenanceDailyLimit(env) {
@@ -3162,6 +3223,19 @@ entryRoutes.get("/libraries", async (c) => {
const libraries = (rows.results ?? []).map((r) => r.library).filter(Boolean);
return c.json({ success: true, libraries, count: libraries.length });
});
entryRoutes.get("/library-cards", async (c) => {
const library = c.req.query("library") || "";
if (!library.trim()) return c.json({ success: false, error: "library required" }, 400);
const limitRaw = Number(c.req.query("limit"));
const result = await listLibraryCards(c.env.DB, {
library,
owner_id: c.req.query("owner_id") || void 0,
// 壞值當沒帶(回預設),不 clamp 成 1——clamp 會回一筆長得像「這個庫只有一張卡」
// 的結果(#100 教訓:把「讀不到」演成「你沒有」)。
limit: Number.isFinite(limitRaw) && limitRaw > 0 ? Math.floor(limitRaw) : void 0
});
return c.json({ success: true, ...result, count: result.cards.length });
});
entryRoutes.get("/library-stats", async (c) => {
const owner = c.req.query("owner_id") || "";
const rows = await c.env.DB.prepare(