feat(daemon/t54): 連線精靈——首次啟動輸入知識庫網址+帳密,自動換取設定並開始看守(不再需要下載 config.json 丟隱藏資料夾);portal→cypher 子域自動換算、三種輸入格式實測可用;選單加「連上知識庫…」供換帳號
This commit is contained in:
+163
-5
@@ -16,13 +16,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
neturl "net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"arcrun-rag/collector/supervisor"
|
||||
|
||||
@@ -47,6 +50,7 @@ type directConfig struct {
|
||||
Email string `json:"email,omitempty"` // 實例主身分(t26;人人記得自己的 email,CF 全程隱形)
|
||||
InstanceName string `json:"instance_name,omitempty"` // 暱稱(t26 選配;不取就顯示 email)
|
||||
Library string `json:"library,omitempty"`
|
||||
Extractor string `json:"extractor,omitempty"` // t54:連線精靈帶入(claude/gemma)
|
||||
IngestWF string `json:"ingest_workflow,omitempty"`
|
||||
RemovedWF string `json:"removed_workflow,omitempty"`
|
||||
PollSec int `json:"poll_interval_sec,omitempty"`
|
||||
@@ -191,6 +195,105 @@ func shortCypherHost(cypherURL string) string {
|
||||
return u
|
||||
}
|
||||
|
||||
// ── t54(leo 2026-07-25:「最好的就是把它的帳密直接輸入」)─────────────────────
|
||||
// 首次啟動不再需要用戶去網站下載 config.json 丟進隱藏資料夾:
|
||||
// 輸入「知識庫網址 + 帳號密碼」→ 打 /portal/daemon/config → 設定自動寫好。
|
||||
// 用戶只需要記得他剛在網站設的那組帳密(他本來就記得)。
|
||||
|
||||
// daemonConfigResp 是 /portal/daemon/config 的回應(只含連線設定,不含知識內容)。
|
||||
type daemonConfigResp struct {
|
||||
Success bool `json:"success"`
|
||||
Config struct {
|
||||
CypherURL string `json:"cypher_url"`
|
||||
Namespace string `json:"namespace"`
|
||||
Library string `json:"library"`
|
||||
Extractor string `json:"extractor"`
|
||||
Email string `json:"email"`
|
||||
InstanceName string `json:"instance_name"`
|
||||
} `json:"config"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// normalizePortalURL 把用戶貼的東西變成可打的 origin:
|
||||
// 允許貼完整 portal 網址(.../portal/)、只貼主機名、或大小寫/尾斜線不一致。
|
||||
func normalizePortalURL(raw string) (string, error) {
|
||||
s := strings.TrimSpace(raw)
|
||||
if s == "" {
|
||||
return "", errors.New("請貼上你的知識庫網址")
|
||||
}
|
||||
if !strings.Contains(s, "://") {
|
||||
s = "https://" + s
|
||||
}
|
||||
u, err := neturl.Parse(s)
|
||||
if err != nil || u.Host == "" {
|
||||
return "", errors.New("網址看起來不太對,請從信裡或瀏覽器網址列複製整段")
|
||||
}
|
||||
// portal(GUI)與 cypher(API)是不同子域:用戶手上的是 portal,這裡換算成 API 位址。
|
||||
host := u.Host
|
||||
if strings.HasPrefix(host, "arcrun-rag-ui.") {
|
||||
host = "arcrun-cypher-executor." + strings.TrimPrefix(host, "arcrun-rag-ui.")
|
||||
}
|
||||
return "https://" + host, nil
|
||||
}
|
||||
|
||||
// fetchConfigByLogin 用帳密向實例換取這台機器該用的設定。
|
||||
func fetchConfigByLogin(portalURL, email, password string) (*daemonConfigResp, error) {
|
||||
base, err := normalizePortalURL(portalURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, _ := json.Marshal(map[string]string{"email": strings.TrimSpace(email), "password": password})
|
||||
req, err := http.NewRequest(http.MethodPost, base+"/portal/daemon/config", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
client := &http.Client{Timeout: 20 * time.Second}
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.New("連不上這個網址——請確認網址正確、網路正常")
|
||||
}
|
||||
defer res.Body.Close()
|
||||
var out daemonConfigResp
|
||||
if err := json.NewDecoder(res.Body).Decode(&out); err != nil {
|
||||
return nil, errors.New("這個網址不像是 Arcrun RAG 知識庫,請再確認一次")
|
||||
}
|
||||
if res.StatusCode == http.StatusUnauthorized || res.StatusCode == http.StatusForbidden {
|
||||
return nil, errors.New("帳號或密碼不對——用你在知識庫網站設定的那組")
|
||||
}
|
||||
if !out.Success {
|
||||
if out.Error != "" {
|
||||
return nil, errors.New(out.Error)
|
||||
}
|
||||
return nil, errors.New("連線失敗,請稍後再試一次")
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// applyRemoteConfig 把換到的設定寫進本地 config(保留既有看守資料夾)。
|
||||
func applyRemoteConfig(cfg *directConfig, r *daemonConfigResp) {
|
||||
cfg.CypherURL = r.Config.CypherURL
|
||||
cfg.Namespace = r.Config.Namespace
|
||||
if r.Config.Library != "" {
|
||||
cfg.Library = r.Config.Library
|
||||
}
|
||||
if r.Config.Extractor != "" {
|
||||
cfg.Extractor = r.Config.Extractor
|
||||
}
|
||||
cfg.Email = r.Config.Email
|
||||
if r.Config.InstanceName != "" {
|
||||
cfg.InstanceName = r.Config.InstanceName
|
||||
}
|
||||
if cfg.Manifest == "" {
|
||||
cfg.Manifest = filepath.Join(appDir(), "manifest.json")
|
||||
}
|
||||
}
|
||||
|
||||
// isConnected 判斷「這台機器已經連上某個知識庫」——沒有就該跳連線精靈。
|
||||
func isConnected(cfg *directConfig) bool {
|
||||
return strings.TrimSpace(cfg.CypherURL) != "" && strings.TrimSpace(cfg.Namespace) != ""
|
||||
}
|
||||
|
||||
func main() {
|
||||
a := app.NewWithID("dev.arcrun.rag.tray")
|
||||
cfg := loadConfig()
|
||||
@@ -213,11 +316,65 @@ func main() {
|
||||
// 重設選單=刷新(比 (*Menu).Refresh() 跨版本更穩)。
|
||||
var rebuildTray func()
|
||||
|
||||
|
||||
restartWatch := func() {
|
||||
sup.Stop()
|
||||
sup.Start()
|
||||
}
|
||||
|
||||
// t54 連線精靈:輸入知識庫網址+帳密 → 換設定 → 寫檔 → 開始看守。
|
||||
// 沒連線過的機器一開 app 就自動跳;之後也能從選單「重新連線…」再開。
|
||||
var showConnectWizard func()
|
||||
showConnectWizard = func() {
|
||||
urlEntry := widget.NewEntry()
|
||||
urlEntry.SetPlaceHolder("https://…workers.dev/portal/")
|
||||
if cfg.CypherURL != "" {
|
||||
urlEntry.SetText(cfg.CypherURL)
|
||||
}
|
||||
emailEntry := widget.NewEntry()
|
||||
emailEntry.SetPlaceHolder("你的 Email")
|
||||
if cfg.Email != "" {
|
||||
emailEntry.SetText(cfg.Email)
|
||||
}
|
||||
pwEntry := widget.NewPasswordEntry()
|
||||
pwEntry.SetPlaceHolder("你在知識庫設定的密碼")
|
||||
hint := widget.NewLabel("")
|
||||
hint.Wrapping = fyne.TextWrapWord
|
||||
|
||||
form := container.NewVBox(
|
||||
widget.NewLabel("連上你的知識庫"),
|
||||
widget.NewLabel("貼上網址,再輸入你在網站上設定的帳號密碼:"),
|
||||
urlEntry, emailEntry, pwEntry, hint,
|
||||
)
|
||||
d := dialog.NewCustomConfirm("Arcrun RAG", "連線", "取消", form, func(ok bool) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
hint.SetText("連線中…")
|
||||
go func() {
|
||||
r, err := fetchConfigByLogin(urlEntry.Text, emailEntry.Text, pwEntry.Text)
|
||||
if err != nil {
|
||||
// fyne 的 UI 更新須回主執行緒;用 dialog 顯示即可(它內部處理)
|
||||
dialog.ShowError(err, win)
|
||||
showConnectWizard() // 讓用戶改完再試,不要把他丟回空白畫面
|
||||
return
|
||||
}
|
||||
applyRemoteConfig(cfg, r)
|
||||
if err := saveConfig(cfg); err != nil {
|
||||
dialog.ShowError(err, win)
|
||||
return
|
||||
}
|
||||
restartWatch()
|
||||
rebuildTray()
|
||||
dialog.ShowInformation("連上了", "已連上「"+connectionStatusLabel(cfg.InstanceName, cfg.Email)+"」。\n接下來用選單「+ 新增知識資料夾…」挑要同步的資料夾就好。", win)
|
||||
}()
|
||||
}, win)
|
||||
d.Resize(fyne.NewSize(460, 320))
|
||||
win.Show()
|
||||
win.RequestFocus()
|
||||
d.Show()
|
||||
}
|
||||
|
||||
addAction := func() {
|
||||
win.Show()
|
||||
win.RequestFocus()
|
||||
@@ -332,6 +489,7 @@ func main() {
|
||||
items = append(items,
|
||||
fyne.NewMenuItem("+ 新增知識資料夾…", addAction),
|
||||
fyne.NewMenuItem("+ 建立新資料夾並看守…", newFolderAction), // t17
|
||||
fyne.NewMenuItem("🔗 連上知識庫…(換帳號/重新連線)", func() { showConnectWizard() }), // t54
|
||||
fyne.NewMenuItemSeparator(),
|
||||
pauseItem,
|
||||
)
|
||||
@@ -363,13 +521,13 @@ func main() {
|
||||
widget.NewButton("建立新資料夾並看守…", func() { newFolderAction() }), // t17
|
||||
))
|
||||
|
||||
// 開機即看守(若設定完整)
|
||||
if cfg.CypherURL != "" && cfg.Namespace != "" {
|
||||
// 開機即看守(若已連上知識庫)
|
||||
if isConnected(cfg) {
|
||||
sup.Start()
|
||||
} else {
|
||||
// 缺安裝器帶入的 cypher_url/namespace:提示用戶先完成安裝
|
||||
statusItem.Label = "狀態:尚未連結(請先完成安裝)"
|
||||
win.Show()
|
||||
// t54:沒連過就直接跳連線精靈(輸入網址+帳密),不再要用戶去下載 config.json。
|
||||
statusItem.Label = "狀態:尚未連線"
|
||||
showConnectWizard()
|
||||
}
|
||||
|
||||
a.Lifecycle().SetOnStopped(func() { sup.Stop() })
|
||||
|
||||
Reference in New Issue
Block a user