Files
AI-Expert-Sidebar/internal/handler/library.go
T
2026-04-01 15:29:35 +08:00

137 lines
5.0 KiB
Go

// 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 <input type="file"> 都更流畅。
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"`
}