// Package handler 将 service 层的能力暴露为 Wails 绑定方法。 // // Handler 层的职责非常单一: // 1. 调用 Wails runtime API(文件对话框、事件等); // 2. 将 service 返回值转换为前端友好的格式(string 错误消息等); // 3. 不包含任何业务逻辑,所有逻辑都在 service 层。 // // 此文件专门处理"知识库 CRUD + 文件导入"的 Wails 绑定。 package handler import ( "context" "AI-Expert-Sidebar/internal/database" "AI-Expert-Sidebar/internal/service" "github.com/wailsapp/wails/v2/pkg/runtime" ) // LibraryHandler 是知识库管理功能的 Wails 绑定集合。 // ctx 在 startup 时由 Wails 注入,用于调用 runtime API(如文件对话框)。 type LibraryHandler struct{ ctx context.Context } func NewLibraryHandler() *LibraryHandler { return &LibraryHandler{} } func (h *LibraryHandler) SetContext(ctx context.Context) { h.ctx = ctx } // ListLibraries 返回所有已注册知识库的列表,含实时条目数和"是否活跃"标志。 // is_active 由当前活跃库名称与各库名称对比判断(在 handler 层计算,service 层不感知"活跃"概念)。 func (h *LibraryHandler) ListLibraries() []LibraryInfo { libs, err := service.ListLibraries() if err != nil { return nil } out := make([]LibraryInfo, len(libs)) for i, l := range libs { out[i] = LibraryInfo{ ID: l.ID, Name: l.Name, Description: l.Description, EntryCount: l.EntryCount, IsActive: l.Name == database.GetActiveLibName(), } } return out } // GetActiveLibrary 返回当前活跃知识库的名称,用于前端 LibraryBar 标题显示。 func (h *LibraryHandler) GetActiveLibrary() string { return database.GetActiveLibName() } // CreateLibrary 创建新知识库,并自动切换到它。 // // 创建后立即切换的原因:用户刚创建的库通常就是下一步要操作的目标, // 省去一次额外的"切换"操作。 // 返回空字符串表示成功,否则返回中文错误信息供前端 Toast 显示。 func (h *LibraryHandler) CreateLibrary(name, description string) string { if name == "" { return "知识库名称不能为空" } lib, err := service.CreateLibrary(name, description) if err != nil { return err.Error() } // 忽略切换错误(文件刚创建,极少失败),用户可手动重新切换 service.SwitchLibrary(lib.Name) //nolint return "" } // SwitchLibrary 将指定名称的知识库激活为当前工作库。 // 返回空字符串表示成功,否则返回错误信息。 func (h *LibraryHandler) SwitchLibrary(name string) string { if err := service.SwitchLibrary(name); err != nil { return err.Error() } return "" } // DeleteLibrary 从注册表中移除知识库(不删除 .db 文件)。 // // 在删除前强制检查:不能删除当前正在使用的库, // 因为删除后活跃连接会变成悬空引用,后续写入会 panic。 func (h *LibraryHandler) DeleteLibrary(name string) string { if name == database.GetActiveLibName() { return "不能删除当前使用中的知识库,请先切换到其他库" } if err := service.DeleteLibrary(name, false); err != nil { return err.Error() } return "" } // ImportCSV 调起系统原生文件选择对话框,让用户选取 CSV 文件后导入。 // // 使用 Wails runtime.OpenFileDialog 而非让前端传入路径的原因: // 1. 安全性:前端(WebView)无法直接访问本地文件系统, // 必须通过 Wails 桥接调用原生对话框; // 2. 体验:原生对话框支持文件类型过滤(*.csv), // 比任何 HTML 都更流畅。 func (h *LibraryHandler) ImportCSV() service.ImportResult { filePath, err := runtime.OpenFileDialog(h.ctx, runtime.OpenDialogOptions{ Title: "选择 CSV 文件", Filters: []runtime.FileFilter{ {DisplayName: "CSV 文件", Pattern: "*.csv"}, {DisplayName: "所有文件", Pattern: "*"}, }, }) if err != nil || filePath == "" { return service.ImportResult{Error: "已取消"} } return service.ImportCSV(filePath) } // ImportExcel 调起原生文件对话框,让用户选取 .xlsx 文件后导入。 // 逻辑与 ImportCSV 完全对称,仅文件过滤器和 service 调用不同。 func (h *LibraryHandler) ImportExcel() service.ImportResult { filePath, err := runtime.OpenFileDialog(h.ctx, runtime.OpenDialogOptions{ Title: "选择 Excel 文件", Filters: []runtime.FileFilter{ {DisplayName: "Excel 文件", Pattern: "*.xlsx"}, {DisplayName: "所有文件", Pattern: "*"}, }, }) if err != nil || filePath == "" { return service.ImportResult{Error: "已取消"} } return service.ImportExcel(filePath) } // LibraryInfo 是 LibraryHandler 向前端暴露的知识库信息 DTO。 // 相比 models.Library,额外计算了 IsActive 字段,并去掉了 FilePath(不暴露内部路径)。 type LibraryInfo struct { ID uint `json:"id"` Name string `json:"name"` Description string `json:"description"` EntryCount int `json:"entry_count"` IsActive bool `json:"is_active"` }