From 125377b899d6c7202c377bdbbcd94d41adb58d85 Mon Sep 17 00:00:00 2001 From: richblack Date: Fri, 24 Jul 2026 17:59:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(daemon-beta=20t26):=20=E6=89=98=E7=9B=A4?= =?UTF-8?q?=E9=81=B8=E5=96=AE=E9=A1=AF=E9=80=A3=E7=B7=9A=E8=BA=AB=E5=88=86?= =?UTF-8?q?=EF=BC=88=E6=9A=B1=E7=A8=B1>email>=E6=9C=AA=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=EF=BC=89=EF=BC=8CCF=20=E5=85=A8=E7=A8=8B=E9=9A=B1=E5=BD=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - directConfig/DirectConfig 加 Email、InstanceName(選配)欄,兩結構同形 - rebuildTray 選單頂加 disabled「連線中:<暱稱||email||未設定>」列; 有 cypher_url 再加一行縮短 host(fyne MenuItem 無 tooltip,取捨見註解) - connectionStatusLabel/shortCypherHost 抽純函式,3+3 案 table test - go vet + go test 全綠(collector 43 通、tray 10 通);tray go build 過 --- cmd/arcrun-tray/main.go | 40 ++++++++++++++- cmd/arcrun-tray/main_test.go | 94 ++++++++++++++++++++++++++++++++++++ direct.go | 2 + 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 cmd/arcrun-tray/main_test.go diff --git a/cmd/arcrun-tray/main.go b/cmd/arcrun-tray/main.go index d02f63f..5703d17 100644 --- a/cmd/arcrun-tray/main.go +++ b/cmd/arcrun-tray/main.go @@ -44,6 +44,8 @@ type directConfig struct { CypherURL string `json:"cypher_url"` Namespace string `json:"namespace"` APIKey string `json:"api_key,omitempty"` + Email string `json:"email,omitempty"` // 實例主身分(t26;人人記得自己的 email,CF 全程隱形) + InstanceName string `json:"instance_name,omitempty"` // 暱稱(t26 選配;不取就顯示 email) Library string `json:"library,omitempty"` IngestWF string `json:"ingest_workflow,omitempty"` RemovedWF string `json:"removed_workflow,omitempty"` @@ -163,6 +165,32 @@ func collectorBinPath() string { func isWindows() bool { return os.PathSeparator == '\\' } +// connectionStatusLabel 決定托盤選單頂列「連線中:…」顯示什麼身分(t26 leo 拍板): +// 暱稱(InstanceName)優先於 email,兩者都空才顯示「未設定」——CF/cypher 全程不入此字串。 +func connectionStatusLabel(instanceName, email string) string { + name := strings.TrimSpace(instanceName) + if name == "" { + name = strings.TrimSpace(email) + } + if name == "" { + name = "未設定" + } + return "連線中:" + name +} + +// shortCypherHost 把 cypher_url 縮成純 host 給第二行 disabled 選單項看(fyne MenuItem 無 tooltip, +// 取捨=直接多一行而非塞進同一行——選單寬度有限,host 通常已夠長)。解析失敗就原樣回傳(不隱藏錯誤設定)。 +func shortCypherHost(cypherURL string) string { + u := strings.TrimSpace(cypherURL) + if u == "" { + return "" + } + if parsed, err := neturl.Parse(u); err == nil && parsed.Host != "" { + return parsed.Host + } + return u +} + func main() { a := app.NewWithID("dev.arcrun.rag.tray") cfg := loadConfig() @@ -270,7 +298,17 @@ func main() { if !hasTray { return } - items := []*fyne.MenuItem{statusItem, fyne.NewMenuItemSeparator()} + // t26:選單最頂顯示「連線中:<暱稱||email>」(CF/cypher_url 全程不現身這一行); + // 有 cypher_url 時再加一行縮短的 host(fyne MenuItem 無 tooltip,取捨見 shortCypherHost 注解)。 + connItem := fyne.NewMenuItem(connectionStatusLabel(cfg.InstanceName, cfg.Email), nil) + connItem.Disabled = true + items := []*fyne.MenuItem{connItem} + if host := shortCypherHost(cfg.CypherURL); host != "" { + hostItem := fyne.NewMenuItem("→ "+host, nil) + hostItem.Disabled = true + items = append(items, hostItem) + } + items = append(items, statusItem, fyne.NewMenuItemSeparator()) for _, f := range cfg.Folders() { folder := f // capture it := fyne.NewMenuItem("📁 "+filepath.Base(folder), func() { diff --git a/cmd/arcrun-tray/main_test.go b/cmd/arcrun-tray/main_test.go new file mode 100644 index 0000000..c8ff877 --- /dev/null +++ b/cmd/arcrun-tray/main_test.go @@ -0,0 +1,94 @@ +package main + +import ( + "encoding/json" + "strings" + "testing" +) + +// t26:暱稱 > email > 未設定(leo 07-24 拍板:CF 全程隱形,這條邏輯不該提到 cypher/CF)。 +func TestConnectionStatusLabel(t *testing.T) { + cases := []struct { + name string + instanceName string + email string + want string + }{ + {"暱稱優先於email", "我的書房", "leo21c@gmail.com", "連線中:我的書房"}, + {"無暱稱退回email", "", "leo21c@gmail.com", "連線中:leo21c@gmail.com"}, + {"兩者皆空顯示未設定", "", "", "連線中:未設定"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := connectionStatusLabel(c.instanceName, c.email) + if got != c.want { + t.Errorf("connectionStatusLabel(%q, %q) = %q, want %q", c.instanceName, c.email, got, c.want) + } + }) + } +} + +func TestShortCypherHost(t *testing.T) { + cases := []struct { + name string + in string + want string + }{ + {"完整URL取host", "https://arcrun-cypher-executor.someacct.workers.dev/path", "arcrun-cypher-executor.someacct.workers.dev"}, + {"空字串回空字串", "", ""}, + {"無法解析出host則原樣回傳", "not-a-url", "not-a-url"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := shortCypherHost(c.in) + if got != c.want { + t.Errorf("shortCypherHost(%q) = %q, want %q", c.in, got, c.want) + } + }) + } +} + +// config 序列化含新欄(email/instance_name)——t26 施工面一驗收項。 +func TestDirectConfigJSONRoundTrip(t *testing.T) { + c := &directConfig{ + CypherURL: "https://example.workers.dev", + Namespace: "demo", + Email: "leo21c@gmail.com", + InstanceName: "我的書房", + } + data, err := json.Marshal(c) + if err != nil { + t.Fatalf("marshal 失敗:%v", err) + } + s := string(data) + if !strings.Contains(s, `"email":"leo21c@gmail.com"`) { + t.Errorf("序列化結果缺 email 欄:%s", s) + } + if !strings.Contains(s, `"instance_name":"我的書房"`) { + t.Errorf("序列化結果缺 instance_name 欄:%s", s) + } + + var back directConfig + if err := json.Unmarshal(data, &back); err != nil { + t.Fatalf("unmarshal 失敗:%v", err) + } + if back.Email != c.Email || back.InstanceName != c.InstanceName { + t.Errorf("round-trip 不一致:got email=%q instance_name=%q", back.Email, back.InstanceName) + } +} + +// 暱稱空時 config JSON 不應寫出 instance_name 欄(omitempty;對齊面二 installer 端「空就不寫欄位」的約定)。 +func TestDirectConfigJSONOmitsEmptyInstanceName(t *testing.T) { + c := &directConfig{ + CypherURL: "https://example.workers.dev", + Namespace: "demo", + Email: "leo21c@gmail.com", + } + data, err := json.Marshal(c) + if err != nil { + t.Fatalf("marshal 失敗:%v", err) + } + if strings.Contains(string(data), "instance_name") { + t.Errorf("暱稱空時不該序列化出 instance_name 欄:%s", data) + } +} diff --git a/direct.go b/direct.go index 0482379..624c423 100644 --- a/direct.go +++ b/direct.go @@ -41,6 +41,8 @@ type DirectConfig struct { CypherURL string `json:"cypher_url"` // 實例 cypher base(必填),如 https://arcrun-cypher-executor..workers.dev Namespace string `json:"namespace"` // 租戶 namespace(必填),如 demo APIKey string `json:"api_key"` // X-Arcrun-API-Key(空=沿用 namespace,demo 慣例) + Email string `json:"email,omitempty"` // 實例主身分(t26;人人記得自己的 email,CF 全程隱形) + InstanceName string `json:"instance_name,omitempty"` // 暱稱(t26 選配;不取就顯示 email) Library string `json:"library"` // 藏書地圖歸庫鍵(空=kb) IngestWF string `json:"ingest_workflow"` // 直送萃取 workflow 名(空=rag_ingest_direct;extractor 模式不用) RemovedWF string `json:"removed_workflow"` // 下架 workflow 名(空=rag_takedown_direct;吃 {page_name,path})