feat(t122 🔴🔴): 萃取引擎雲端設定+隨連線下發——封測者終於萃得出東西
真兇(總管查證):daemon/config 寫死 extractor='claude' 且不下發金鑰 ⇒ 封測者 100% 萃取失敗 (leo:「地端沒有 AI 根本不能萃,那它就不能玩」)。 - POST/GET /portal/admin/extractor(admin 閘;GET 只回 has_key 不回明文) - daemon/config 改讀設定:未設定→gemma 無金鑰;設定後→含 gemini_api_key - portal 設定頁加「萃取引擎」區(Gemini 推薦+aistudio 連結,存後提示「小幫手點連上知識庫重連即可」) 測試 3 條新綠(vitest 17 passed;1 紅=console HTML 搬遷陳舊測試,非本案)。 (實作=子 CC;驗證+commit=總管)
This commit is contained in:
@@ -356,6 +356,29 @@
|
||||
<div id="st-key-status" style="font-size:14px;min-height:1.2em"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- t122 萃取引擎(daemon 萃取知識卡用的 AI;admin-level,同 chat-key;server 再強制閘) -->
|
||||
<div class="panel" id="st-extractor-panel">
|
||||
<div style="font-size:17px;font-weight:600">萃取引擎</div>
|
||||
<div style="margin-top:4px;font-size:14px;line-height:1.65;color:rgba(var(--ink-rgb),.55)">同步小幫手把你的文件變成知識卡時用的 AI。選好後請讓小幫手重連一次。</div>
|
||||
<div style="margin-top:14px;display:flex;flex-direction:column;gap:10px">
|
||||
<div style="display:flex;gap:16px;align-items:center">
|
||||
<label style="display:flex;gap:6px;align-items:center;cursor:pointer;font-size:14px">
|
||||
<input type="radio" name="st-extractor-engine" id="st-engine-gemma" value="gemma" checked>
|
||||
<span>Gemini(推薦,免費)</span>
|
||||
</label>
|
||||
<label style="display:flex;gap:6px;align-items:center;cursor:pointer;font-size:14px">
|
||||
<input type="radio" name="st-extractor-engine" id="st-engine-claude" value="claude">
|
||||
<span>Claude Code(本機已裝才選)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="st-extractor-key-row" style="display:flex;flex-direction:column;gap:8px">
|
||||
<div style="font-size:13px;color:rgba(var(--ink-rgb),.55)">到 <a href="https://aistudio.google.com/apikey" target="_blank" rel="noopener">aistudio.google.com/apikey</a> 免費取得金鑰</div>
|
||||
<input type="password" id="st-extractor-key" class="txt" placeholder="貼上 Gemini API 金鑰(留空=不變更)" autocomplete="off">
|
||||
</div>
|
||||
<button class="btn" id="st-extractor-save">儲存萃取引擎設定</button>
|
||||
<div id="st-extractor-status" style="font-size:14px;min-height:1.2em"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn3" id="st-logout" style="padding:14px;font-size:16px;border-radius:11px">登出</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -793,6 +816,70 @@ function taipeiMonthDay(ms) { var d = new Date(ms + TAIPEI_OFFSET_MS); return {
|
||||
});
|
||||
})();
|
||||
|
||||
// t122 萃取引擎設定(engine 切換顯示隱藏金鑰輸入框 + 存檔)
|
||||
(function () {
|
||||
// 引擎切換:Gemini 顯示金鑰欄;Claude Code 隱藏
|
||||
function updateExtractorKeyRowVisibility() {
|
||||
var row = $('st-extractor-key-row');
|
||||
if (!row) return;
|
||||
var el = document.getElementById('st-engine-gemma');
|
||||
row.style.display = (el && el.checked) ? '' : 'none';
|
||||
}
|
||||
['st-engine-gemma', 'st-engine-claude'].forEach(function (id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) el.addEventListener('change', updateExtractorKeyRowVisibility);
|
||||
});
|
||||
updateExtractorKeyRowVisibility();
|
||||
|
||||
// 進入設定頁時讀取現有設定(GET /portal/admin/extractor)
|
||||
function loadExtractorConfig() {
|
||||
if (!(S.profile && S.profile.role === 'admin')) return;
|
||||
fetch(API_BASE + '/portal/admin/extractor', { headers: authHeaders() })
|
||||
.then(function (r) { return r.ok ? r.json() : null; })
|
||||
.then(function (d) {
|
||||
if (!d) return;
|
||||
var gemmaEl = document.getElementById('st-engine-gemma');
|
||||
var claudeEl = document.getElementById('st-engine-claude');
|
||||
if (d.engine === 'claude' && claudeEl) claudeEl.checked = true;
|
||||
else if (gemmaEl) gemmaEl.checked = true;
|
||||
updateExtractorKeyRowVisibility();
|
||||
if (d.has_key) {
|
||||
var ki = $('st-extractor-key');
|
||||
if (ki) ki.placeholder = '已設定(留空=不變更)';
|
||||
}
|
||||
})
|
||||
.catch(function () { /* 讀不到不擋頁面 */ });
|
||||
}
|
||||
|
||||
// 存檔
|
||||
var sb = $('st-extractor-save');
|
||||
if (sb) sb.addEventListener('click', function () {
|
||||
var m = $('st-extractor-status');
|
||||
var engineEl = document.querySelector('input[name="st-extractor-engine"]:checked');
|
||||
var engine = engineEl ? engineEl.value : 'gemma';
|
||||
var key = (($('st-extractor-key') && $('st-extractor-key').value) || '').trim();
|
||||
var body = { engine: engine };
|
||||
if (engine === 'gemma' && key) body.gemini_api_key = key;
|
||||
sb.disabled = true; m.textContent = '儲存中…'; m.style.color = '';
|
||||
fetch(API_BASE + '/portal/admin/extractor', {
|
||||
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; }
|
||||
if ($('st-extractor-key')) { $('st-extractor-key').value = ''; $('st-extractor-key').placeholder = '已設定(留空=不變更)'; }
|
||||
m.textContent = '已儲存。小幫手請點「連上知識庫」重連一次即可生效'; m.style.color = '#3f7a4f';
|
||||
})
|
||||
.catch(function (e) { sb.disabled = false; m.textContent = friendlyErr(e); m.style.color = '#b4462f'; });
|
||||
});
|
||||
|
||||
// loadExtractorConfig 掛到全域(供 loadSettings 呼叫)
|
||||
window._loadExtractorConfig = loadExtractorConfig;
|
||||
})();
|
||||
|
||||
$('st-logout').addEventListener('click', function () {
|
||||
fetch(API_BASE + '/portal/logout', { method: 'POST', headers: authHeaders() }).catch(function () { /* 盡力而為 */ });
|
||||
dropSession();
|
||||
@@ -1605,6 +1692,7 @@ function taipeiMonthDay(ms) { var d = new Date(ms + TAIPEI_OFFSET_MS); return {
|
||||
function loadSettings() {
|
||||
var p = S.profile;
|
||||
if (!p) { $('st-me').innerHTML = '<div class="muted">載入中…</div>'; return; }
|
||||
if (window._loadExtractorConfig) window._loadExtractorConfig();
|
||||
var libs = p.libraries || [];
|
||||
var libHtml = libs.indexOf('*') >= 0
|
||||
? '<span class="tag green">全部知識庫</span>'
|
||||
|
||||
Reference in New Issue
Block a user