feat(daemon-beta t26): 托盤選單顯連線身分(暱稱>email>未設定),CF 全程隱形
- 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 過
This commit is contained in:
+39
-1
@@ -44,6 +44,8 @@ type directConfig struct {
|
|||||||
CypherURL string `json:"cypher_url"`
|
CypherURL string `json:"cypher_url"`
|
||||||
Namespace string `json:"namespace"`
|
Namespace string `json:"namespace"`
|
||||||
APIKey string `json:"api_key,omitempty"`
|
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"`
|
Library string `json:"library,omitempty"`
|
||||||
IngestWF string `json:"ingest_workflow,omitempty"`
|
IngestWF string `json:"ingest_workflow,omitempty"`
|
||||||
RemovedWF string `json:"removed_workflow,omitempty"`
|
RemovedWF string `json:"removed_workflow,omitempty"`
|
||||||
@@ -163,6 +165,32 @@ func collectorBinPath() string {
|
|||||||
|
|
||||||
func isWindows() bool { return os.PathSeparator == '\\' }
|
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() {
|
func main() {
|
||||||
a := app.NewWithID("dev.arcrun.rag.tray")
|
a := app.NewWithID("dev.arcrun.rag.tray")
|
||||||
cfg := loadConfig()
|
cfg := loadConfig()
|
||||||
@@ -270,7 +298,17 @@ func main() {
|
|||||||
if !hasTray {
|
if !hasTray {
|
||||||
return
|
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() {
|
for _, f := range cfg.Folders() {
|
||||||
folder := f // capture
|
folder := f // capture
|
||||||
it := fyne.NewMenuItem("📁 "+filepath.Base(folder), func() {
|
it := fyne.NewMenuItem("📁 "+filepath.Base(folder), func() {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,8 @@ type DirectConfig struct {
|
|||||||
CypherURL string `json:"cypher_url"` // 實例 cypher base(必填),如 https://arcrun-cypher-executor.<acct>.workers.dev
|
CypherURL string `json:"cypher_url"` // 實例 cypher base(必填),如 https://arcrun-cypher-executor.<acct>.workers.dev
|
||||||
Namespace string `json:"namespace"` // 租戶 namespace(必填),如 demo
|
Namespace string `json:"namespace"` // 租戶 namespace(必填),如 demo
|
||||||
APIKey string `json:"api_key"` // X-Arcrun-API-Key(空=沿用 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)
|
Library string `json:"library"` // 藏書地圖歸庫鍵(空=kb)
|
||||||
IngestWF string `json:"ingest_workflow"` // 直送萃取 workflow 名(空=rag_ingest_direct;extractor 模式不用)
|
IngestWF string `json:"ingest_workflow"` // 直送萃取 workflow 名(空=rag_ingest_direct;extractor 模式不用)
|
||||||
RemovedWF string `json:"removed_workflow"` // 下架 workflow 名(空=rag_takedown_direct;吃 {page_name,path})
|
RemovedWF string `json:"removed_workflow"` // 下架 workflow 名(空=rag_takedown_direct;吃 {page_name,path})
|
||||||
|
|||||||
Reference in New Issue
Block a user