t176 daemon:LLM 設定移回地端+托盤單一實例(leo 08-03 架構翻案)
leo 回報 Windows daemon 三症狀,查完 ①③ 同根,根在「雲端控制地端」這個設計。 【①③ 真兇】雲端 extractor_config 是**全租戶共用一把 KV** (arcrun:portal.ts:43 portalTenant = worker 層級變數,不分用戶), 任一處設了 claude → 所有人的 daemon 都收到 claude。沒裝 Claude Code 的機器 FindClaudeBin 失敗 → 每檔萃取 failed、一張卡都沒建;想去 portal 改回 gemini, checkbox 卻恆 disabled(claude_available 恆 false,因 daemon 從未實作 report-capabilities 回報 → daemon_caps KV 永遠空)⇒ 用戶自己解不開。 awindhon 實證:雲端同步成功、Gemini key 有效、零張卡,config.json extractor="claude"。 【leo 裁示】「地端要用什麼模型就在 daemon 上輸入 API Key 設置,而不是雲端設置後 控制地端」「地端先限制 Gemini API Key 配合客戶要求」「雲端就是 Workers AI」。 本次(daemon 端): - addOrUpdateAccount 不再接受雲端下發的 extractor/gemini_api_key/llm_model, 只收連線欄位。t126「每帳號一份萃取設定」照舊保留——t126 修的是「存在哪一層」, 本次改的是「值從哪來」,兩者正交。 - 托盤新增「AI 設定…」:使用者自己填 Gemini API Key,寫本地 config 後立即生效。 - 萃取一律走 gemini:殘留的 extractor:"claude" 正規化為 gemma;claude 路退役。 ⚠️ 這不是「自動偵測有無 claude」(leo 07-27 已否決的 B 案),是整條路先不支援。 - 清掉隨之死亡的 claude_bin 回寫(死代碼=錯誤的環境信號)。 - 托盤單一實例(症狀②):pidfile + 跨平台 processAlive。 mac 之前不多開是借 macOS Launch Services 的巧合,Windows 沒有該層 ⇒ 每點一次多一個 icon。 Unix 用 signal 0(EPERM 也算活著,測試抓到的實際 bug)/Windows 用 OpenProcess+ExitCode。 測試:collector 全綠、tray 全綠。5 個原本用 claude stub 的測試改走**真實 gemma 路** (httptest 替身注入 gemmaBaseURL),不是改斷言充綠;t126②③ 兩案翻轉成 「雲端下發一律被忽略」的回歸守衛;新增 4 案 single-instance。 未送達:本 commit 只到 code,尚未打包出貨;雲端側(刪 portal AI 設定區塊、 extractor 下發、admin/extractor)未動,待部署授權。CP rag-beta 步驟仍為 ◐。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+70
-14
@@ -602,28 +602,25 @@ func addOrUpdateAccount(cfg *directConfig, r *daemonConfigResp) bool {
|
||||
if r.Config.InstanceName != "" {
|
||||
cfg.Accounts[i].InstanceName = r.Config.InstanceName
|
||||
}
|
||||
// t126:萃取設定寫帳號層,不覆蓋機器層
|
||||
if r.Config.Extractor != "" {
|
||||
cfg.Accounts[i].Extractor = r.Config.Extractor
|
||||
}
|
||||
if r.Config.GeminiAPIKey != "" {
|
||||
cfg.Accounts[i].GeminiAPIKey = r.Config.GeminiAPIKey
|
||||
}
|
||||
if r.Config.LLMModel != "" {
|
||||
cfg.Accounts[i].LLMModel = r.Config.LLMModel
|
||||
}
|
||||
// t176(leo 08-03 翻案):**不再接受雲端下發的 LLM 設定**。
|
||||
// 地端用哪個模型/哪把金鑰,一律由使用者在托盤「AI 設定…」自己填。
|
||||
// t126 的「每帳號一份萃取設定」照舊保留(欄位還在、讀取仍帳號層優先),
|
||||
// 這裡拔掉的只是「值**從雲端來**」這條來源——t126 修的是存在哪一層,本次改的是值從哪來。
|
||||
// 為什麼拔(08-03 實證):雲端 extractor_config 是**全租戶共用一把 KV**
|
||||
// (arcrun:portal.ts:43 portalTenant 為 worker 層級變數,不分用戶),
|
||||
// 任一處設了 claude → 所有人的 daemon 都收到;沒裝 Claude Code 的機器萃取全滅,
|
||||
// 而 portal 的 claude 勾選框又恆 disabled ⇒ 用戶自己解不開(awindhon 實證:零張卡)。
|
||||
// leo:「地端要用什麼模型就在 daemon 上輸入 API Key 設置,而不是雲端設置後控制地端」
|
||||
return false
|
||||
}
|
||||
}
|
||||
// 新帳號:t126 萃取設定直接寫帳號層
|
||||
// 新帳號:只寫連線欄位。t176——LLM 設定不吃雲端下發(見上方同批註解),
|
||||
// 由使用者在托盤「AI 設定…」自己填;欄位留白時讀取端會繼承機器層。
|
||||
acc := accountCfg{
|
||||
InstanceName: r.Config.InstanceName,
|
||||
Email: r.Config.Email,
|
||||
CypherURL: strings.TrimSuffix(strings.TrimSpace(r.Config.CypherURL), "/"),
|
||||
Namespace: r.Config.Namespace,
|
||||
Extractor: r.Config.Extractor, // t126
|
||||
GeminiAPIKey: r.Config.GeminiAPIKey, // t126
|
||||
LLMModel: r.Config.LLMModel, // t126
|
||||
}
|
||||
if acc.APIKey == "" {
|
||||
acc.APIKey = acc.Namespace
|
||||
@@ -692,6 +689,15 @@ func accountDisplayName(acc accountCfg) string {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// t176:一台機器只跑一個托盤(leo 08-03「點選多次後托盤產生多個 icon」)。
|
||||
// 必須在建 fyne app 之前擋——一旦 app 起來就會掛上第二個托盤 icon。
|
||||
ok, releaseInstance := acquireSingleInstance()
|
||||
if !ok {
|
||||
fmt.Fprintln(os.Stderr, "Arcrun RAG 已經在執行中(系統匣裡找找看),這次不重複開啟。")
|
||||
return
|
||||
}
|
||||
defer releaseInstance()
|
||||
|
||||
a := app.NewWithID("dev.arcrun.rag.tray")
|
||||
cfg := loadConfig()
|
||||
|
||||
@@ -729,6 +735,7 @@ func main() {
|
||||
// leo 原話:「貼上網址後說這不是個網址然後清空內容,**很糟糕**」。
|
||||
// ⇒ 改成帶入上次輸入(prefill)。密碼不帶回(打錯密碼時重打比較安全,也避免把密碼留在記憶體更久)。
|
||||
var showConnectWizard func()
|
||||
var showAISettings func() // t176:地端 AI 設定(Gemini 金鑰)
|
||||
var showConnectWizardWith func(prevURL, prevEmail string)
|
||||
showConnectWizard = func() { showConnectWizardWith("", "") }
|
||||
showConnectWizardWith = func(prevURL, prevEmail string) {
|
||||
@@ -802,6 +809,53 @@ func main() {
|
||||
d.Show()
|
||||
}
|
||||
|
||||
// t176(leo 08-03 架構翻案):AI 設定住在地端,不再由雲端下發。
|
||||
// 現階段**只支援 Gemini**(leo:「地端先限制 Gemini API Key 配合客戶要求」);
|
||||
// 之後要擴充成「任何用戶想用的模型」時,這裡加 provider 選單即可(欄位已預留 LLMModel)。
|
||||
// ⚠️ 這是「緊急先加上輸入位置」的版本——leo 已指出托盤下拉選單塞不下未來的設定量
|
||||
//(30+ 資料夾根本認不出),後續走本地 HTML 設定頁(像 Google Drive)。
|
||||
showAISettings = func() {
|
||||
keyEntry := widget.NewPasswordEntry()
|
||||
keyEntry.SetPlaceHolder("貼上你的 Gemini API Key")
|
||||
keyEntry.SetText(cfg.GeminiAPIKey) // 帶入現值,讓他看得到「已經設過了」
|
||||
hint := widget.NewLabel("")
|
||||
hint.Wrapping = fyne.TextWrapWord
|
||||
|
||||
form := container.NewVBox(
|
||||
widget.NewLabel("萃取用的 AI 設定"),
|
||||
widget.NewLabel("目前使用 Google Gemini。到 aistudio.google.com 免費申請金鑰,貼在下面:"),
|
||||
keyEntry, hint,
|
||||
)
|
||||
d := dialog.NewCustomConfirm("AI 設定", "儲存", "取消", form, func(ok bool) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
key := strings.TrimSpace(keyEntry.Text)
|
||||
if key == "" {
|
||||
dialog.ShowError(errors.New("金鑰是空的——請貼上 Gemini API Key"), win)
|
||||
return
|
||||
}
|
||||
// 機器層寫一份當預設;各帳號層一併寫,避免舊 config 殘留的空欄位繼承到別的值。
|
||||
cfg.GeminiAPIKey = key
|
||||
cfg.Extractor = "gemma"
|
||||
for i := range cfg.Accounts {
|
||||
cfg.Accounts[i].GeminiAPIKey = key
|
||||
cfg.Accounts[i].Extractor = "gemma"
|
||||
}
|
||||
if err := saveConfig(cfg); err != nil {
|
||||
dialog.ShowError(err, win)
|
||||
return
|
||||
}
|
||||
restartWatch() // 讓新金鑰立刻生效,不必等下一輪
|
||||
rebuildTray()
|
||||
dialog.ShowInformation("已儲存", "AI 設定已更新,接下來的同步會用這把金鑰萃取。", win)
|
||||
}, win)
|
||||
d.Resize(fyne.NewSize(460, 260))
|
||||
win.Show()
|
||||
win.RequestFocus()
|
||||
d.Show()
|
||||
}
|
||||
|
||||
addAction := func() {
|
||||
win.Show()
|
||||
win.RequestFocus()
|
||||
@@ -996,6 +1050,8 @@ func main() {
|
||||
syncNowItem, // t98:立刻同步
|
||||
// t104:「連上知識庫」改為「新增帳號」——精靈成功後 append 到 accounts,不換掉舊帳號
|
||||
fyne.NewMenuItem("+ 新增帳號…", func() { showConnectWizard() }),
|
||||
// t176:AI 設定移到地端(leo 08-03:「地端要用什麼模型就在 daemon 上輸入 API Key 設置」)
|
||||
fyne.NewMenuItem("AI 設定…", func() { showAISettings() }),
|
||||
fyne.NewMenuItemSeparator(),
|
||||
versionItem,
|
||||
checkUpdateItem, // t150:手動檢查更新(leo:「他去按一下檢查更新然後更新就好」)
|
||||
|
||||
Reference in New Issue
Block a user