feat(t52): per-folder 庫章——DirectConfig.libraries 對映+libraryFor()(資料夾名 slug/中文退後備/明列優先,go test 綠);兩支 ingest workflow 的 post_block metadata 補 library(真兇:blocks 沒蓋章=兩資料夾全歸 general);托盤連線時回報庫自動登記
This commit is contained in:
@@ -51,6 +51,7 @@ type directConfig struct {
|
||||
InstanceName string `json:"instance_name,omitempty"` // 暱稱(t26 選配;不取就顯示 email)
|
||||
Library string `json:"library,omitempty"`
|
||||
Extractor string `json:"extractor,omitempty"` // t54:連線精靈帶入(claude/gemma)
|
||||
Libraries map[string]string `json:"libraries,omitempty"` // t52:資料夾→庫對映(key=絕對路徑)
|
||||
IngestWF string `json:"ingest_workflow,omitempty"`
|
||||
RemovedWF string `json:"removed_workflow,omitempty"`
|
||||
PollSec int `json:"poll_interval_sec,omitempty"`
|
||||
@@ -289,6 +290,82 @@ func applyRemoteConfig(cfg *directConfig, r *daemonConfigResp) {
|
||||
}
|
||||
}
|
||||
|
||||
// registerLibraries 把「這台機器看守的資料夾各自對應的庫」報上雲端自動登記(t52)。
|
||||
// leo 2026-07-26:「用戶可以看到我有 2 個庫,地端雲端都是 2 個,如果只有一個一定被罵。」
|
||||
// 只在連線精靈那一刻做(那時手上才有帳密;daemon 平時不存密碼)。失敗不擋連線。
|
||||
func registerLibraries(portalURL, email, password string, cfg *directConfig) error {
|
||||
base, err := normalizePortalURL(portalURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
type libItem struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
}
|
||||
var libs []libItem
|
||||
seen := map[string]bool{}
|
||||
for _, folder := range cfg.Folders() {
|
||||
name := libraryNameFor(cfg, folder)
|
||||
if name == "" || seen[name] {
|
||||
continue
|
||||
}
|
||||
seen[name] = true
|
||||
libs = append(libs, libItem{Name: name, DisplayName: filepath.Base(folder)})
|
||||
}
|
||||
if len(libs) == 0 {
|
||||
return nil
|
||||
}
|
||||
body, _ := json.Marshal(map[string]any{"email": strings.TrimSpace(email), "password": password, "libraries": libs})
|
||||
req, err := http.NewRequest(http.MethodPost, base+"/portal/daemon/libraries", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
res, err := (&http.Client{Timeout: 20 * time.Second}).Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode >= 400 {
|
||||
return fmt.Errorf("庫登記回 HTTP %d", res.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// libraryNameFor 與 collector/direct.go 的 libraryFor 同規則(資料夾名 slug;空則退回 library)。
|
||||
func libraryNameFor(cfg *directConfig, folder string) string {
|
||||
if cfg.Libraries != nil {
|
||||
if v, ok := cfg.Libraries[folder]; ok && strings.TrimSpace(v) != "" {
|
||||
return strings.TrimSpace(v)
|
||||
}
|
||||
}
|
||||
base := filepath.Base(strings.TrimRight(folder, string(filepath.Separator)))
|
||||
var b strings.Builder
|
||||
lastUnderscore := false
|
||||
for _, r := range base {
|
||||
switch {
|
||||
case (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_':
|
||||
b.WriteRune(r)
|
||||
lastUnderscore = false
|
||||
case r >= 'A' && r <= 'Z':
|
||||
b.WriteRune(r + 32)
|
||||
lastUnderscore = false
|
||||
default:
|
||||
if !lastUnderscore && b.Len() > 0 {
|
||||
b.WriteRune('_')
|
||||
lastUnderscore = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if slug := strings.Trim(b.String(), "_"); slug != "" {
|
||||
return slug
|
||||
}
|
||||
if cfg.Library != "" {
|
||||
return cfg.Library
|
||||
}
|
||||
return "kb"
|
||||
}
|
||||
|
||||
// isConnected 判斷「這台機器已經連上某個知識庫」——沒有就該跳連線精靈。
|
||||
func isConnected(cfg *directConfig) bool {
|
||||
return strings.TrimSpace(cfg.CypherURL) != "" && strings.TrimSpace(cfg.Namespace) != ""
|
||||
@@ -364,6 +441,10 @@ func main() {
|
||||
dialog.ShowError(err, win)
|
||||
return
|
||||
}
|
||||
// t52:把本機資料夾對應的庫報上去自動登記(地端幾個資料夾=雲端幾個庫)
|
||||
if lerr := registerLibraries(urlEntry.Text, emailEntry.Text, pwEntry.Text, cfg); lerr != nil {
|
||||
fmt.Println("庫登記略過:", lerr) // 不擋連線(可能是還沒選資料夾)
|
||||
}
|
||||
restartWatch()
|
||||
rebuildTray()
|
||||
dialog.ShowInformation("連上了", "已連上「"+connectionStatusLabel(cfg.InstanceName, cfg.Email)+"」。\n接下來用選單「+ 新增知識資料夾…」挑要同步的資料夾就好。", win)
|
||||
|
||||
@@ -43,7 +43,11 @@ type DirectConfig struct {
|
||||
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;per-folder 未指定時的後備)
|
||||
// t52(leo 2026-07-25 裁決:「資料夾=庫」——企業有 10 個資料夾,財務/人事不准任何人看,
|
||||
// 全塞一個庫=找死):每個看守資料夾對應自己的庫。key=資料夾絕對路徑,value=庫名。
|
||||
// 未列出的資料夾=用資料夾名 slug 當庫名(librarySlug);再不行才退回 Library。
|
||||
Libraries map[string]string `json:"libraries,omitempty"`
|
||||
IngestWF string `json:"ingest_workflow"` // 直送萃取 workflow 名(空=rag_ingest_direct;extractor 模式不用)
|
||||
RemovedWF string `json:"removed_workflow"` // 下架 workflow 名(空=rag_takedown_direct;吃 {page_name,path})
|
||||
// —— 四步定稿(daemon-beta t3/t4/t6):本地萃卡模式 ——
|
||||
@@ -56,6 +60,47 @@ type DirectConfig struct {
|
||||
MaxRemoved float64 `json:"max_removed_ratio"` // 大量刪除防呆門檻(空/0=0.4)
|
||||
}
|
||||
|
||||
// librarySlug 把資料夾名轉成合法庫名(A-Za-z0-9_-;中文等非 ASCII 轉為底線分段)。
|
||||
// 空結果(如純中文名)回 "",交由 libraryFor 退回後備值——不硬造出無意義的庫名。
|
||||
func librarySlug(folder string) string {
|
||||
base := filepath.Base(strings.TrimRight(folder, string(filepath.Separator)))
|
||||
var b strings.Builder
|
||||
lastUnderscore := false
|
||||
for _, r := range base {
|
||||
switch {
|
||||
case (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_':
|
||||
b.WriteRune(r)
|
||||
lastUnderscore = false
|
||||
case r >= 'A' && r <= 'Z':
|
||||
b.WriteRune(r + 32) // 統一小寫(庫名大小寫不敏感比對較不易出錯)
|
||||
lastUnderscore = false
|
||||
default:
|
||||
if !lastUnderscore && b.Len() > 0 {
|
||||
b.WriteRune('_')
|
||||
lastUnderscore = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Trim(b.String(), "_")
|
||||
}
|
||||
|
||||
// libraryFor 決定某個看守資料夾的資料該蓋哪個庫章(t52)。
|
||||
// 優先序:config.Libraries 明列 > 資料夾名 slug > config.Library(後備,預設 kb)。
|
||||
func (c *DirectConfig) libraryFor(absRoot string) string {
|
||||
if c.Libraries != nil {
|
||||
if lib, ok := c.Libraries[absRoot]; ok && strings.TrimSpace(lib) != "" {
|
||||
return strings.TrimSpace(lib)
|
||||
}
|
||||
}
|
||||
if slug := librarySlug(absRoot); slug != "" {
|
||||
return slug
|
||||
}
|
||||
if c.Library != "" {
|
||||
return c.Library
|
||||
}
|
||||
return "kb"
|
||||
}
|
||||
|
||||
// expandHome 把開頭的 `~/`(或單獨的 `~`)展開成使用者家目錄的絕對路徑。
|
||||
//
|
||||
// t39(07-24 安裝器實案):成功頁下載的 config.json 把 manifest 寫成 `~/.arcrun-rag/manifest.json`——
|
||||
@@ -327,7 +372,7 @@ func runDirectOnceRoot(cfg *DirectConfig, root string, dryRun bool) ([]DirectRes
|
||||
"page_name": pageNameOf(cardRel),
|
||||
"path": ev.Path,
|
||||
"card_content": string(cardData),
|
||||
"library": cfg.Library,
|
||||
"library": cfg.libraryFor(absRoot),
|
||||
})
|
||||
res.HTTPStatus = status
|
||||
if perr != nil {
|
||||
@@ -349,7 +394,7 @@ func runDirectOnceRoot(cfg *DirectConfig, root string, dryRun bool) ([]DirectRes
|
||||
"page_name": pageNameOf(ev.Path),
|
||||
"path": ev.Path,
|
||||
"content": string(content),
|
||||
"library": cfg.Library,
|
||||
"library": cfg.libraryFor(absRoot),
|
||||
})
|
||||
res.HTTPStatus = status
|
||||
if perr != nil {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLibraryPerFolder(t *testing.T) {
|
||||
cfg := &DirectConfig{Library: "kb"}
|
||||
cases := map[string]string{
|
||||
"/Users/x/Desktop/finance": "finance",
|
||||
"/Users/x/Desktop/HR Docs": "hr_docs",
|
||||
"/Users/x/Desktop/官方總圖": "kb",
|
||||
"/Users/x/Desktop/test 01": "test_01",
|
||||
}
|
||||
for in, want := range cases {
|
||||
if got := cfg.libraryFor(in); got != want {
|
||||
t.Errorf("libraryFor(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
cfg.Libraries = map[string]string{"/Users/x/Desktop/官方總圖": "official"}
|
||||
if got := cfg.libraryFor("/Users/x/Desktop/官方總圖"); got != "official" {
|
||||
t.Errorf("明列對映應優先,got %q", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user