From 5017d0381e63be4e375b66f83fb7216fbfbac6ee Mon Sep 17 00:00:00 2001 From: richblack Date: Fri, 24 Jul 2026 12:32:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(daemon-beta=20t7):=20=E6=89=98=E7=9B=A4?= =?UTF-8?q?=E5=A4=9A=E8=B3=87=E6=96=99=E5=A4=BE=E9=81=B8=E5=96=AE=EF=BC=88?= =?UTF-8?q?=E6=AF=8F=E6=A0=B9=E4=B8=80=E5=88=97=EF=BC=9A=E9=BB=9E=E9=96=8B?= =?UTF-8?q?=20Finder/=E5=AD=90=E9=81=B8=E5=96=AE=E5=81=9C=E6=AD=A2?= =?UTF-8?q?=E7=9C=8B=E5=AE=88/=E5=8B=95=E6=85=8B=E9=87=8D=E5=BB=BA?= =?UTF-8?q?=EF=BC=89=EF=BC=8Bbuild-mac.sh=EF=BC=88fyne=20package=EF=BC=8BL?= =?UTF-8?q?SUIElement=3Dtrue=20=E9=9A=B1=20Dock=E2=80=94=E2=80=94leo=2007-?= =?UTF-8?q?24=20=E8=A3=81=E6=B1=BA=EF=BC=89=EF=BC=9B=E7=B7=A8=E8=AD=AF?= =?UTF-8?q?=E9=80=9A=E9=81=8E=EF=BC=8C=E7=9C=9F=E6=A9=9F=E9=A9=97=E6=AD=B8?= =?UTF-8?q?=20leo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/arcrun-tray/build-mac.sh | 30 +++++++++ cmd/arcrun-tray/main.go | 118 ++++++++++++++++++++++++++--------- 2 files changed, 120 insertions(+), 28 deletions(-) create mode 100755 cmd/arcrun-tray/build-mac.sh diff --git a/cmd/arcrun-tray/build-mac.sh b/cmd/arcrun-tray/build-mac.sh new file mode 100755 index 0000000..10f06e9 --- /dev/null +++ b/cmd/arcrun-tray/build-mac.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# build-mac.sh — 打包 Arcrun RAG 托盤 app(daemon-beta t7/t8,真機執行)。 +# +# leo 07-24 裁決:**選單列托盤 icon 是唯一指示器,不佔 Dock**—— +# fyne 預設 bundle 會出現 Dock icon,打包後把 LSUIElement=true 寫進 Info.plist 即隱。 +# +# 用法(真機、需 Xcode CLT): +# cd collector/cmd/arcrun-tray && bash build-mac.sh +# 產物:Arcrun RAG.app(含同綑 arcrun-collector)。簽章/公證另行(見 README)。 +set -euo pipefail +cd "$(dirname "$0")" + +echo "① 編 collector(同綑執行檔)" +( cd ../.. && go build -o "cmd/arcrun-tray/arcrun-collector" . ) + +echo "② fyne package" +go run fyne.io/fyne/v2/cmd/fyne@latest package -os darwin -icon icon.png -name "Arcrun RAG" + +APP="Arcrun RAG.app" +PLIST="$APP/Contents/Info.plist" + +echo "③ LSUIElement=true(托盤唯一指示器,隱 Dock icon——leo 07-24 裁決)" +/usr/libexec/PlistBuddy -c "Add :LSUIElement bool true" "$PLIST" 2>/dev/null \ + || /usr/libexec/PlistBuddy -c "Set :LSUIElement true" "$PLIST" + +echo "④ 同綑 collector 進 app bundle(托盤 collectorBinPath 找同層)" +cp arcrun-collector "$APP/Contents/MacOS/arcrun-collector" + +echo "✅ 完成:$APP" +echo "驗法:open '$APP' → 選單列出現 icon、Dock **不出現** icon → 選單列出資料夾清單" diff --git a/cmd/arcrun-tray/main.go b/cmd/arcrun-tray/main.go index 6d02bfd..e00cf03 100644 --- a/cmd/arcrun-tray/main.go +++ b/cmd/arcrun-tray/main.go @@ -100,6 +100,44 @@ func addWatchFolder(c *directConfig, p string) { c.WatchFolder = c.WatchFolders[0] } +// Folders 回傳監看根清單(正規化:單數舊制併入、去重、保序;與 collector DirectConfig 同語意)。 +func (c *directConfig) Folders() []string { + seen := map[string]bool{} + var out []string + add := func(p string) { + if p == "" || seen[p] { + return + } + seen[p] = true + out = append(out, p) + } + add(c.WatchFolder) + for _, f := range c.WatchFolders { + add(f) + } + return out +} + +// removeWatchFolder 把資料夾移出監看清單,並維持單數欄位=第一根鏡像(清單空=兩欄皆空)。 +func removeWatchFolder(c *directConfig, p string) { + if len(c.WatchFolders) == 0 && c.WatchFolder == p { + c.WatchFolder = "" + return + } + var out []string + for _, f := range c.WatchFolders { + if f != p { + out = append(out, f) + } + } + c.WatchFolders = out + if len(out) > 0 { + c.WatchFolder = out[0] + } else { + c.WatchFolder = "" + } +} + func saveConfig(c *directConfig) error { if err := os.MkdirAll(appDir(), 0o755); err != nil { return err @@ -138,7 +176,19 @@ func main() { statusItem := fyne.NewMenuItem("狀態:尚未開始", nil) statusItem.Disabled = true - folderItem := fyne.NewMenuItem("選擇知識資料夾…", func() { + desk, hasTray := a.(desktop.App) + + // rebuildTray 依現況重建整個 tray 選單(daemon-beta t7 多資料夾): + // 每個看守中的資料夾一列(點=在 Finder 打開)+各自的「停止看守」項; + // 重設選單=刷新(比 (*Menu).Refresh() 跨版本更穩)。 + var rebuildTray func() + + restartWatch := func() { + sup.Stop() + sup.Start() + } + + addAction := func() { win.Show() win.RequestFocus() dialog.ShowFolderOpen(func(uri fyne.ListableURI, err error) { @@ -150,19 +200,11 @@ func main() { dialog.ShowError(err, win) return } - // 換資料夾=重起看守 - sup.Stop() - sup.Start() + restartWatch() + rebuildTray() win.Hide() }, win) - }) - - openFolderItem := fyne.NewMenuItem("打開資料夾", func() { - // fyne 的 App.OpenURL 吃 *url.URL(非 fyne.URI)——2026-07-21 真機 build 實測修正 - if u, err := neturl.Parse("file://" + cfg.WatchFolder); err == nil { - _ = a.OpenURL(u) - } - }) + } var pauseItem *fyne.MenuItem pauseItem = fyne.NewMenuItem("暫停看守", func() { @@ -174,27 +216,47 @@ func main() { sup.Stop() pauseItem.Label = "繼續看守" } + rebuildTray() }) - tray := fyne.NewMenu("Arcrun RAG", - statusItem, - fyne.NewMenuItemSeparator(), - folderItem, - openFolderItem, - pauseItem, - ) + rebuildTray = func() { + if !hasTray { + return + } + items := []*fyne.MenuItem{statusItem, fyne.NewMenuItemSeparator()} + for _, f := range cfg.Folders() { + folder := f // capture + it := fyne.NewMenuItem("📁 "+filepath.Base(folder), func() { + if u, err := neturl.Parse("file://" + folder); err == nil { + _ = a.OpenURL(u) + } + }) + it.ChildMenu = fyne.NewMenu("", + fyne.NewMenuItem("停止看守這個資料夾", func() { + removeWatchFolder(cfg, folder) + if err := saveConfig(cfg); err != nil { + dialog.ShowError(err, win) + return + } + restartWatch() + rebuildTray() + }), + ) + items = append(items, it) + } + items = append(items, + fyne.NewMenuItem("+ 新增知識資料夾…", addAction), + fyne.NewMenuItemSeparator(), + pauseItem, + ) + desk.SetSystemTrayMenu(fyne.NewMenu("Arcrun RAG", items...)) + } - desk, hasTray := a.(desktop.App) if hasTray { - desk.SetSystemTrayMenu(tray) desk.SetSystemTrayIcon(theme.StorageIcon()) // TODO 換品牌 icon(leo logo 進行中);Mac 建議 template 單色 } - // 重設選單=刷新(比 (*Menu).Refresh() 跨版本更穩) - refreshTray := func() { - if hasTray { - desk.SetSystemTrayMenu(tray) - } - } + rebuildTray() // 開機即建初始選單(否則第一次狀態回呼前選單是空的) + refreshTray := rebuildTray // 狀態變更 → 刷新 tray 文案(回呼在 supervisor goroutine,切回 UI thread) sup.SetOnChange(func(s supervisor.Status) { @@ -208,7 +270,7 @@ func main() { win.SetContent(container.NewVBox( widget.NewLabelWithStyle("Arcrun RAG 知識同步", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), widget.NewLabel("把檔案丟進你選的資料夾,就會自動進你的知識庫。\n這個程式會待在選單列/系統匣,關掉視窗它仍在背景看守。"), - widget.NewButton("選擇知識資料夾…", func() { folderItem.Action() }), + widget.NewButton("新增知識資料夾…", func() { addAction() }), )) // 開機即看守(若設定完整)