t176:雲端不再管地端 LLM 設定(leo 08-03 緊急)

leo 回報四件事:①雲端 Gemini 無法用 ②搞不清楚設定 AI 是設雲端還是地端
③地端不會萃、每個人都死掉因為都去抓 Claude ④雲端還要設置明明就有 Workers AI。
目標:「雲端裝好就能用,地端輸一個 Gemini API Key,然後就開始用。」

根因:extractor_config 的 KV key 由 portalTenant() 組出,而 portalTenant 是
**worker 層級**環境變數(portal.ts:43)⇒ **全租戶共用一把**。任一處設了 claude,
所有人的 daemon 都收到 claude;沒裝 Claude Code 的機器萃取全滅,而 portal 的
Claude 勾選框又恆 disabled(daemon 從未實作 report-capabilities ⇒ daemon_caps 永遠空)
⇒ 用戶自己解不開(awindhon 08-03 實證:雲端同步成功、金鑰有效、零張卡)。

本次(雲端側):
- POST /portal/daemon/config **不再下發** extractor/gemini_api_key/llm_model,
  只回連線欄位。⚠️ route 本身與 daemon/libraries 資料夾管理完全不動(leo 明確劃界:
  「雲端拉地端檔案夾部分不要刪,刪掉從雲端設置地端 LLM 選項部分」)。
- 移除 POST|GET /portal/admin/extractor(t122)——這是「雲端指定地端引擎」的入口,
  且無任何伺服器端驗證,打一下就能把全租戶設成 claude。
- 移除 POST /portal/daemon/report-capabilities(t131)——daemon 端從未實作該呼叫。
- 移除 t131 那組重複註冊的 /portal/admin/ai。**它是重複 route**:後段(arcrun-rag#10)
  另有同路徑一組,Hono 先到先比 ⇒ 舊的一直贏,後段修好的「金鑰真的寫進 credentials」
  形同死碼。保留後段那組(只管 Gemini 金鑰、走 storeCredential),並拿掉 Claude 偏好欄。
- portal 設定頁「AI 設定」整塊移除,改成一句話說明:雲端不需設定(Workers AI 免金鑰),
  地端請在同步小幫手的「AI 設定…」填 Gemini Key。順手清掉 main 上既有的 conflict 標記。
- 清掉隨之孤兒化的 helper(ExtractorConfig/AiConfig/DaemonCapabilities/
  syncExtractorFromAiConfig/readAiPref 等)。

測試:**相對 merge 前 main 基準線,新增失敗 = 0**(基準線本就 9 紅:console 藏書地圖 6/
portal HTML 殼 2/POST execute 1,皆與本次無關)。t122/t131 兩組測試改寫成
**回歸守衛**(斷言那些端點/欄位確實 404、確實不存在),不是刪掉充綠。
順手修 health bundle_version 測試與實作對齊(實作刻意省略該欄,測試卻期待空字串)。

未送達:本 commit 只到 code,尚未部署上線。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-08-03 22:11:45 +08:00
parent eee3f93815
commit 66f1b59f7f
5 changed files with 124 additions and 633 deletions
+15 -149
View File
@@ -363,44 +363,18 @@ if (!window.ARCRUN_API_BASE) {
<div style="margin-top:8px;font-size:13px;line-height:1.7;color:rgba(var(--ink-rgb),.5)">封測版未簽章,第一次請右鍵→打開。裝好第一次開啟時,貼上這個網址+你的帳號密碼就連上了。</div>
</div>
</div>
<!-- t131 合併 AI 設定(Gemini API Key 必填;Claude 加強版視 daemon 回報 enable -->
<div class="panel" id="st-ai-panel">
<!-- t176leo 08-03):AI 設定整塊移除。
雲端聊天問答走 Workers AI(用戶自己 CF 帳號內建,**免金鑰、裝好就能用**);
地端萃取用哪把金鑰改由「同步小幫手」自己設定(托盤選單「AI 設定…」)。
為什麼移除:雲端的 extractor 設定是全租戶共用一把 KV,任一處設了 claude
會讓所有沒裝 Claude Code 的機器萃取全滅,而這裡的 Claude 勾選框又恆為
disableddaemon 從未回報 has_claude)⇒ 用戶自己解不開(08-03 封測實證)。
leo:「搞不清楚設定 AI 是設雲端還是地端」——設定只留一處,就不會再搞混。 -->
<div class="panel">
<div style="font-size:17px;font-weight:600">AI 設定</div>
<div style="margin-top:14px;display:flex;flex-direction:column;gap:16px">
<!-- 第一格:Gemini API Key(必填,聊天+萃取共用) -->
<div>
<div style="font-size:14px;font-weight:500;margin-bottom:4px">Gemini API Key</div>
<div style="font-size:13px;line-height:1.6;color:rgba(var(--ink-rgb),.55);margin-bottom:8px">聊天問答與文件萃取都用這一把。<a href="https://aistudio.google.com/apikey" target="_blank" rel="noopener">免費申請</a>,金鑰只存在你自己的知識庫裡。</div>
<<<<<<< HEAD
<!-- 已存狀態leo 2026-08-01 規格唯讀顯示已輸入修改**不顯示 key 本身**
舊版只把 placeholder 改成藍色小字已設定 讀起來像臨時提示不像已保存的記錄
這正是它看起來像 UI bug 的原因arcrun-rag#10 附帶修正 -->
<div id="st-ai-key-saved" style="display:none;align-items:center;gap:10px">
<span style="flex:1;padding:9px 12px;border:1px solid rgba(var(--ink-rgb),.14);border-radius:8px;background:rgba(var(--ink-rgb),.03);font-size:14px">
<b>已輸入</b><span style="color:rgba(var(--ink-rgb),.45)">(金鑰已安全保存,不顯示內容)</span>
</span>
<button type="button" class="btn" id="st-ai-key-edit" style="flex:none">修改</button>
</div>
=======
>>>>>>> main
<input type="password" id="st-ai-key" class="txt" placeholder="貼上 Gemini API Key" autocomplete="off">
</div>
<!-- 第二格:Claude 加強版(選填,依 daemon 回報 enable -->
<div style="padding-top:12px;border-top:1px solid rgba(var(--ink-rgb),.08)">
<div style="font-size:14px;font-weight:500;margin-bottom:6px">讓知識卡整理得更好(選填)</div>
<label style="display:flex;gap:8px;align-items:flex-start;cursor:pointer">
<input type="checkbox" id="st-ai-use-claude" style="margin-top:3px;flex:none" disabled>
<span style="font-size:14px">本地萃取改用 Claude Code</span>
</label>
<div id="st-ai-claude-desc" style="margin-top:8px;font-size:13px;line-height:1.65;color:rgba(var(--ink-rgb),.55)">
讀你文件、整理成知識卡的那個 AI,換成更強的模型。<b style="color:var(--ink)">卡片會更抓得到重點、關聯也連得更準</b>,之後搜尋和問答的品質跟著提升。需要你的電腦已安裝 Claude Code。不填就用上面那把 Gemini,一樣能用。
</div>
<div id="st-ai-claude-hint" style="margin-top:6px;font-size:13px;color:rgba(var(--ink-rgb),.4);display:none"></div>
</div>
<div>
<button class="btn" id="st-ai-save">儲存 AI 設定</button>
<div id="st-ai-status" style="font-size:14px;min-height:1.2em;margin-top:8px"></div>
</div>
<div style="margin-top:12px;font-size:13.5px;line-height:1.7;color:rgba(var(--ink-rgb),.55);background:rgba(var(--ink-rgb),.03);border-radius:9px;padding:11px 13px">
<b style="color:var(--ink)">這裡不需要任何設定。</b>聊天問答用的是你自己 Cloudflare 帳號內建的 AI,裝好就能直接問。<br>
文件整理成知識卡的部分,請在<b style="color:var(--ink)">同步小幫手</b>(電腦上的托盤圖示)的「AI 設定…」填一把 Gemini API Key。
</div>
</div>
<button class="btn3" id="st-logout" style="padding:14px;font-size:16px;border-radius:11px">登出</button>
@@ -433,11 +407,7 @@ if (!window.ARCRUN_API_BASE) {
<button class="btn3" id="ad-copy-url" style="padding:5px 12px;font-size:13px;white-space:nowrap;flex:none">複製</button>
</div>
<div style="margin-bottom:12px;padding:12px 15px;border-radius:11px;background:rgba(var(--amber-rgb),.06);border:1px dashed rgba(var(--amber-rgb),.35);font-size:13.5px;line-height:1.7;color:rgba(var(--ink-rgb),.65)">
<<<<<<< HEAD
<!-- 07-27 leo拿掉人工建庫表單庫由同步小幫手看守資料夾時自動蓋章產生
=======
<!-- 07-27 leo:拿掉「登記新庫」——庫由「同步小幫手」看守資料夾時自動蓋章產生,
>>>>>>> main
人工登記只會製造對不上的空庫。這裡只做「已存在的庫」的管理。 -->
裝好<b style="color:var(--ink)">同步小幫手</b>並選好要看守的資料夾之後,每個資料夾會自動成為一個「庫」出現在下面——<b style="color:var(--ink)">不需要人工新增</b>。下面還是空的,代表小幫手還沒裝好或還沒選資料夾。
</div>
@@ -826,114 +796,10 @@ function taipeiMonthDay(ms) { var d = new Date(ms + TAIPEI_OFFSET_MS); return {
}
})();
// t131 AI 設定(合併 Gemini API KeyClaude 加強版)
(function () {
<<<<<<< HEAD
/**
* 切換金鑰欄的「已存 / 可輸入」兩態(leo 2026-08-01 規格,arcrun-rag#10):
* 已存 → 唯讀顯示「已輸入」+「修改」鈕(**不顯示 key 本身**,D36
* 未存 / 按了修改 → 一般輸入框
* 為什麼要這樣:舊版只把 placeholder 改成「已設定」=看起來像臨時提示,
* 用戶讀不出「這是已保存的記錄」⇒ 以為沒存到、反覆重填。
*/
function setAiKeySaved(saved) {
var box = $('st-ai-key-saved');
var input = $('st-ai-key');
if (!box || !input) return;
if (saved) {
box.style.display = 'flex';
input.style.display = 'none';
input.value = '';
} else {
box.style.display = 'none';
input.style.display = '';
}
}
// 按「修改」→ 變回可輸入框並聚焦(留空送出=不變更,由後端 body 判斷)
var editBtn = $('st-ai-key-edit');
if (editBtn) editBtn.addEventListener('click', function () {
setAiKeySaved(false);
var input = $('st-ai-key');
if (input) { input.placeholder = '貼上新的 Gemini API Key(留空取消變更)'; input.focus(); }
});
=======
>>>>>>> main
// 進入設定頁時讀取現有設定(GET /portal/admin/ai
function loadAiConfig() {
if (!(S.profile && S.profile.role === 'admin')) return;
fetch(API_BASE + '/portal/admin/ai', { headers: authHeaders() })
.then(function (r) { return r.ok ? safeJson(r) : null; })
.then(function (d) {
if (!d) return;
<<<<<<< HEAD
// leo 2026-08-01 規格:已存 → 唯讀「已輸入」+「修改」鈕;未存 → 一般輸入框
setAiKeySaved(!!d.has_key);
=======
var ki = $('st-ai-key');
if (ki && d.has_key) ki.placeholder = '已設定(留空=不變更)';
>>>>>>> main
var cb = $('st-ai-use-claude');
if (cb) {
// 有 claude 才能勾;沒有則停用並顯示提示
var hint = $('st-ai-claude-hint');
if (d.claude_available) {
cb.disabled = false;
cb.checked = !!d.use_claude_for_extract;
if (hint) hint.style.display = 'none';
} else {
cb.disabled = true;
cb.checked = false;
if (hint) {
hint.style.display = '';
// 區分:從未連上小幫手 vs 有連上但沒裝 Claude
hint.textContent = d.has_key
? '你的電腦沒有偵測到 Claude Code;裝好並讓小幫手重連一次後,這個選項就會開啟。'
: '連上小幫手後才知道你的電腦有沒有 Claude Code。';
}
}
}
})
.catch(function () { /* 讀不到不擋頁面 */ });
}
window._loadAiConfig = loadAiConfig;
// 儲存
var sb = $('st-ai-save');
if (sb) sb.addEventListener('click', function () {
var m = $('st-ai-status');
var k = (($('st-ai-key') && $('st-ai-key').value) || '').trim();
var cb = $('st-ai-use-claude');
var useClaud = cb && !cb.disabled ? cb.checked : undefined;
var body = {};
if (k) body.gemini_api_key = k;
if (useClaud !== undefined) body.use_claude_for_extract = useClaud;
sb.disabled = true; m.textContent = '儲存中…'; m.style.color = '';
fetch(API_BASE + '/portal/admin/ai', {
method: 'POST',
headers: Object.assign({ 'Content-Type': 'application/json' }, authHeaders()),
body: JSON.stringify(body)
}).then(function (r) { return safeJson(r).then(function (d) { return { ok: r.ok, status: r.status, d: d }; }); })
.then(function (x) {
sb.disabled = false;
if (guard401(x.status)) return;
if (!x.ok) { m.textContent = (x.d && x.d.error) || '儲存失敗'; m.style.color = '#b4462f'; return; }
<<<<<<< HEAD
// 存好了才切回「已輸入」唯讀態。只有真的送了新 key 才切;
// 單純改 Claude 勾選(k 為空)時維持現狀,不誤報「已輸入」。
if (k) setAiKeySaved(true);
=======
if ($('st-ai-key')) { $('st-ai-key').value = ''; $('st-ai-key').placeholder = '已設定(留空=不變更)'; }
>>>>>>> main
var claudeOn = x.d && x.d.use_claude_for_extract;
m.textContent = claudeOn
? '已儲存,萃取改用 Claude Code。小幫手請重連一次生效。'
: '已儲存,AI 問答與萃取皆可使用。';
m.style.color = '#3f7a4f';
})
.catch(function (e) { sb.disabled = false; m.textContent = friendlyErr(e); m.style.color = '#b4462f'; });
});
})();
// t176leo 08-03):AI 設定的前端邏輯整段移除。
// 雲端聊天走 Workers AI(免金鑰);地端萃取金鑰改由同步小幫手托盤「AI 設定…」自己設。
// 原本這裡有 Claude 勾選框的 enable/disable 邏輯——它依賴 daemon 回報 has_claude
// 而 daemon 從未實作該回報 ⇒ 勾選框恆為 disabled(leo 實測「hover 變可點但點擊沒反應」)。
$('st-logout').addEventListener('click', function () {
fetch(API_BASE + '/portal/logout', { method: 'POST', headers: authHeaders() }).catch(function () { /* 盡力而為 */ });