diff --git a/common/tokenizer.go b/common/tokenizer.go index a598210..24e124b 100644 --- a/common/tokenizer.go +++ b/common/tokenizer.go @@ -29,7 +29,8 @@ func Tokenize(text string) string { return strings.TrimSpace(sb.String()) } -// TokenizeQuery FTS5 查询串:每个词用双引号包裹(AND 语义),过滤 FTS5 特殊字符 +// TokenizeQuery FTS5 查询串:过滤 FTS5 特殊字符与单字虚词,OR 语义。 +// 中文问题分词后通常只有少数词与文档重合,AND 全命中会空召回,OR + BM25 排序更稳健。 func TokenizeQuery(text string) string { tokens := seg.Cut(text, true) var parts []string @@ -41,7 +42,10 @@ func TokenizeQuery(text string) string { if strings.ContainsAny(t, "\"*:()") { continue } + if len([]rune(t)) < 2 { + continue + } parts = append(parts, "\""+t+"\"") } - return strings.Join(parts, " ") + return strings.Join(parts, " OR ") } diff --git a/kb/dao/model_config_dao.go b/kb/dao/model_config_dao.go index 758eb23..1e52847 100644 --- a/kb/dao/model_config_dao.go +++ b/kb/dao/model_config_dao.go @@ -11,7 +11,6 @@ import ( "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" - "github.com/gogf/gf/v2/os/gcache" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gconv" ) @@ -53,6 +52,27 @@ func (d *modelConfigDao) GetOne(ctx context.Context, id int64) (*entity.ModelCon return &m, nil } +// GetByName 按 model_name 查找已有配置(排除指定 id),供新配置继承端点与维度 +func (d *modelConfigDao) GetByName(ctx context.Context, modelName string, excludeId int64) (*entity.ModelConfig, error) { + var m entity.ModelConfig + err := g.DB(consts.DbGroupSystem).Model(consts.TableNameModelConfig).Ctx(ctx). + Where("model_name", modelName). + Where("id <> ?", excludeId). + OrderAsc("id"). + Limit(1). + Scan(&m) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + return nil, err + } + if m.Id == 0 { + return nil, nil + } + return &m, nil +} + func (d *modelConfigDao) List(ctx context.Context, modelType string) ([]*entity.ModelConfig, error) { var list []*entity.ModelConfig m := g.DB(consts.DbGroupSystem).Model(consts.TableNameModelConfig).Ctx(ctx).OrderAsc("id") @@ -96,7 +116,8 @@ func (d *modelConfigDao) Update(ctx context.Context, data *entity.ModelConfig) e if err != nil { return err } - _, _ = gcache.Remove(ctx, "model_config_GetOne_"+gconv.String(data.Id)) + // 查询缓存挂在 DB 内部缓存实例上且键带 SelectCache: 前缀,全局 gcache.Remove 无效 + _, _ = g.DB(consts.DbGroupSystem).GetCache().Remove(ctx, "SelectCache:model_config_GetOne_"+gconv.String(data.Id)) return nil } @@ -105,6 +126,6 @@ func (d *modelConfigDao) Delete(ctx context.Context, id int64) error { if err != nil { return err } - _, _ = gcache.Remove(ctx, "model_config_GetOne_"+gconv.String(id)) + _, _ = g.DB(consts.DbGroupSystem).GetCache().Remove(ctx, "SelectCache:model_config_GetOne_"+gconv.String(id)) return nil } diff --git a/kb/dao/system_config_dao.go b/kb/dao/system_config_dao.go index eb13ba1..b24ebf6 100644 --- a/kb/dao/system_config_dao.go +++ b/kb/dao/system_config_dao.go @@ -8,7 +8,6 @@ import ( "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" - "github.com/gogf/gf/v2/os/gcache" ) var SystemConfig = &systemConfigDao{} @@ -52,6 +51,7 @@ func (d *systemConfigDao) Set(ctx context.Context, key, value string) error { if err != nil { return err } - _, _ = gcache.Remove(ctx, "system_config_Get_"+key) + // 查询缓存挂在 DB 内部缓存实例上且键带 SelectCache: 前缀,必须用 GetCache() 按完整键移除 + _, _ = g.DB(consts.DbGroupSystem).GetCache().Remove(ctx, "SelectCache:system_config_Get_"+key) return nil } diff --git a/kb/service/model_config_service.go b/kb/service/model_config_service.go index a5b63bc..d859b69 100644 --- a/kb/service/model_config_service.go +++ b/kb/service/model_config_service.go @@ -21,6 +21,15 @@ func (s *modelConfigService) List(ctx context.Context, modelType string) ([]*ent } func (s *modelConfigService) Save(ctx context.Context, m *entity.ModelConfig) (int64, error) { + // 留空的接口地址/维度按表中已有同名模型记录继承(用户显式填写的值优先,不覆盖) + if other, err := dao.ModelConfig.GetByName(ctx, m.ModelName, m.Id); err == nil && other != nil { + if m.EndpointUrl == "" && other.EndpointUrl != "" { + m.EndpointUrl = other.EndpointUrl + } + if m.ModelType == consts.ModelTypeEmbedding && m.Dimension <= 0 && other.Dimension > 0 { + m.Dimension = other.Dimension + } + } if m.Dimension <= 0 { m.Dimension = consts.DefaultEmbeddingDim } diff --git a/ui-src/src/api/request.js b/ui-src/src/api/request.js index 3cb301c..c9f3e0e 100644 --- a/ui-src/src/api/request.js +++ b/ui-src/src/api/request.js @@ -1,5 +1,6 @@ import axios from 'axios' import { ElMessage } from 'element-plus' +import { useAuthStore } from '../stores/auth.js' const request = axios.create({ timeout: 300000 @@ -14,6 +15,10 @@ request.interceptors.request.use(config => { }) function redirectLogin() { + // 同时清空 Pinia store,否则路由守卫仍视为已登录,会拦回首页形成死循环 + try { + useAuthStore().logout() + } catch { /* store 未初始化时忽略 */ } localStorage.removeItem('token') window.location.href = '/#/login' } diff --git a/ui-src/src/views/Chat.vue b/ui-src/src/views/Chat.vue index 5a0e381..d02fad6 100644 --- a/ui-src/src/views/Chat.vue +++ b/ui-src/src/views/Chat.vue @@ -73,7 +73,8 @@ const msgBox = ref(null) onMounted(async () => { try { - datasets.value = await listDatasets() + const d = await listDatasets() + if (d && d.list) datasets.value = d.list if (datasets.value.length && !datasetId.value) { datasetId.value = datasets.value[0].id } diff --git a/ui-src/src/views/DatasetDetail.vue b/ui-src/src/views/DatasetDetail.vue index 68a62b3..5c965a7 100644 --- a/ui-src/src/views/DatasetDetail.vue +++ b/ui-src/src/views/DatasetDetail.vue @@ -72,7 +72,7 @@