fix(semantic): 故障照實說是故障——不再把壞掉說成「沒開通」(leo 2026-08-09 直令)

一、文案(portal/console/kbdb hint):語意搜尋是一安裝就提供的功能,
   降級=故障。橫幅改「語意搜尋目前故障/我們的問題/你不用做任何事」,
   拿掉「還沒開通、想開通請匯出診斷檔」這種要使用者申請開通的假框架。
   kbdb 降級回應加 degraded_reason(module_off / embed_query_failed)。

二、查詢向量化失敗不再偽裝成空結果(leo 點名的謊):
   semanticSearch 舊行為「AI 額度用完 → 回 []」會讓使用者以為
   自己的知識庫裡沒有這筆資料。改丟 EmbedQueryFailedError,
   route 誠實降級 keyword+照實告知是暫時故障。

三、源頭機制(裝好的實例為什麼會失去語意搜尋):
   - acr update:kbdb_embed 判斷 ===true → !==false。config 缺欄位時
     redeploy 會把 [[vectorize]]+[ai] binding 靜默剝掉(wrangler deploy
     整份覆蓋),一台正常實例就此壞掉。init 預設同步翻成 [Y/n]。
   -(另 repo)deploy-all.mjs ensureVectorizeIndex 失敗改致命中止。

四、順手自癒:孤兒向量/下架殘影搜尋時背景清除;空結果且 pending>0
   背景 backfill;no_index 拆「故障」vs「還沒有資料」兩態。

測試:kbdb 146/146(新增 degraded 6 案+selftest 1 案);cli 10/10;
瀏覽器端到端兩種故障畫面實測(local wrangler dev+portal 真登入)。
無 SDD 對應:leo 直令修故障(同 08-07 檢修孔前例的人閘直接授權路徑)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-08-09 02:05:12 +08:00
parent 8eb10049b8
commit 6846d6ddae
10 changed files with 425 additions and 49 deletions
+16 -6
View File
@@ -1158,16 +1158,26 @@ function taipeiMonthDay(ms) { var d = new Date(ms + TAIPEI_OFFSET_MS); return {
if (!x.ok) { $('se-count').innerHTML = '<span class="err">' + esc(x.d.error || ('查詢失敗(HTTP ' + x.status + '')) + '</span>'; return; }
var d = x.d;
if (S.mode === 'semantic' && d.mode === 'keyword') {
// 2026-08-07:不直接透傳後端 capability_hint——那句話是寫給工程師看的
// (會出現「叫 CC」「vectorize」「redeploy」這類我們內部的修法指令,不是講給
// 一般用戶聽的)。前端固定顯示「發生了什麼+現在怎麼辦」,不假裝有語意結果,
// 也不留用戶不知道下一步的空白。
$('se-banner').innerHTML = '<div class="honest" style="margin-top:18px"><div class="h">語意搜尋還沒開通</div><div class="b">語意搜尋能理解你問題的「意思」找資料,不只是比對關鍵字。<br>這個知識庫目前還沒開通這個功能,以下顯示的是關鍵字搜尋結果(不會假裝有語意結果)。<br>想開通的話,請到你電腦上的 Arcrun(同步小幫手)「版本與更新」頁,用「疑難排解」匯出診斷檔給我們,我們會幫你打開。</div></div>';
// 🔴 2026-08-09 leo:「語義搜尋已經確定是一安裝就提供的功能⋯⋯我沒有不開通這個
// 功能,是壞了,沒有人會把 bug 美化成沒提供沒開通。」
// 走到這裡=這台實例的語意搜尋壞了(缺 binding 或向量化失敗),照實說是故障、
// 是我們的問題,不要求使用者做任何事。文案優先用後端 capability_hint(已是人話,
// 能分「暫時故障/部署故障」),舊版後端沒有就用底下的通用故障文案。
var bhint = (d.capability_hint && !/開通|尚未啟用/.test(d.capability_hint))
? d.capability_hint
: '語意搜尋目前故障,先用關鍵字幫你找了下面的結果。這是我們系統的問題,不是你的操作問題,你不需要做任何事,我們會修好它。';
$('se-banner').innerHTML = '<div class="honest" style="margin-top:18px"><div class="h">語意搜尋目前故障</div><div class="b">' + esc(bhint) + '</div></div>';
}
var entries = d.entries || [];
$('se-count').textContent = '命中 ' + entries.length + ' 筆・模式 ' + searchModeLabel(d.mode || 'keyword') + (d.note ? '・' + d.note : '');
if (!entries.length) {
$('se-results').innerHTML = '<div class="muted" style="padding:30px 10px;text-align:center;grid-column:1/-1">找不到「' + esc(q) + '」——換個關鍵字試試。</div>';
// 空結果不一律怪查詢字:語意模式的空結果,後端 capability_hint 會分
// 「真的沒命中(換字)」與「索引故障/還沒有資料(不是用戶的問題)」,照實顯示。
// 降級(mode=keyword)時故障說明已在上方橫幅,這裡不重複。
var emptyMsg = (d.capability_hint && d.mode === 'semantic')
? esc(d.capability_hint)
: '找不到「' + esc(q) + '」——換個關鍵字試試。';
$('se-results').innerHTML = '<div class="muted" style="padding:30px 10px;text-align:center;grid-column:1/-1">' + emptyMsg + '</div>';
return;
}
$('se-results').innerHTML = entries.map(function (e) {