From 66aec78960797c91aee252a65a9aadef40f5a25d Mon Sep 17 00:00:00 2001 From: wizardchen Date: Tue, 16 Sep 2025 20:18:47 +0800 Subject: [PATCH 1/2] chore(ui): Update Setting page --- frontend/src/api/initialization/index.ts | 1 + frontend/src/assets/img/user-green.svg | 4 + frontend/src/assets/img/user.svg | 4 + frontend/src/components/menu.vue | 9 +- frontend/src/router/index.ts | 6 +- .../initialization/InitializationConfig.vue | 306 +- .../initialization/InitializationContent.vue | 2712 +++++++++++++++++ .../src/views/settings/SystemSettings.vue | 125 + frontend/src/views/tenant/TenantInfo.vue | 8 +- internal/handler/initialization.go | 21 +- 10 files changed, 2944 insertions(+), 252 deletions(-) create mode 100644 frontend/src/assets/img/user-green.svg create mode 100644 frontend/src/assets/img/user.svg create mode 100644 frontend/src/views/initialization/InitializationContent.vue create mode 100644 frontend/src/views/settings/SystemSettings.vue diff --git a/frontend/src/api/initialization/index.ts b/frontend/src/api/initialization/index.ts index 66591c797..c42f1483b 100644 --- a/frontend/src/api/initialization/index.ts +++ b/frontend/src/api/initialization/index.ts @@ -19,6 +19,7 @@ export interface InitializationConfig { modelName: string; baseUrl: string; apiKey?: string; + enabled: boolean; }; multimodal: { enabled: boolean; diff --git a/frontend/src/assets/img/user-green.svg b/frontend/src/assets/img/user-green.svg new file mode 100644 index 000000000..6685b1b68 --- /dev/null +++ b/frontend/src/assets/img/user-green.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/img/user.svg b/frontend/src/assets/img/user.svg new file mode 100644 index 000000000..1e26d1ed4 --- /dev/null +++ b/frontend/src/assets/img/user.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/components/menu.vue b/frontend/src/components/menu.vue index ede1d29ba..afb55ca59 100644 --- a/frontend/src/components/menu.vue +++ b/frontend/src/components/menu.vue @@ -201,24 +201,19 @@ let knowledgeIcon = ref('zhishiku-green.svg'); let prefixIcon = ref('prefixIcon.svg'); let settingIcon = ref('setting.svg'); let logoutIcon = ref('logout.svg'); -let tenantIcon = ref('setting.svg'); // 暂时使用setting图标 +let tenantIcon = ref('user.svg'); // 使用专门的用户图标 let pathPrefix = ref(route.name) const getIcon = (path) => { fileAddIcon.value = path == 'knowledgeBase' ? 'file-add-green.svg' : 'file-add.svg'; knowledgeIcon.value = path == 'knowledgeBase' ? 'zhishiku-green.svg' : 'zhishiku.svg'; prefixIcon.value = path == 'creatChat' ? 'prefixIcon-green.svg' : path == 'knowledgeBase' ? 'prefixIcon-grey.svg' : 'prefixIcon.svg'; settingIcon.value = path == 'settings' ? 'setting-green.svg' : 'setting.svg'; - tenantIcon.value = path == 'tenant' ? 'setting-green.svg' : 'setting.svg'; // 暂时使用setting图标 + tenantIcon.value = path == 'tenant' ? 'user-green.svg' : 'user.svg'; // 使用专门的用户图标 logoutIcon.value = 'logout.svg'; } getIcon(route.name) const gotopage = (path) => { pathPrefix.value = path; - // 如果是系统设置,跳转到初始化配置页面 - if (path === 'settings') { - router.push('/initialization'); - return; - } // 处理退出登录 if (path === 'logout') { authStore.logout(); diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index f0a4b4a6c..f1fee0765 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -62,9 +62,9 @@ const router = createRouter({ { path: "settings", name: "settings", - component: () => import("../views/settings/Settings.vue"), - meta: { requiresInit: true } - }, + component: () => import("../views/settings/SystemSettings.vue"), + meta: { requiresInit: true } + }, ], }, ], diff --git a/frontend/src/views/initialization/InitializationConfig.vue b/frontend/src/views/initialization/InitializationConfig.vue index 8a82a71df..fa26650bc 100644 --- a/frontend/src/views/initialization/InitializationConfig.vue +++ b/frontend/src/views/initialization/InitializationConfig.vue @@ -2,29 +2,20 @@
-

WeKnora 系统初始化配置

-

首次使用需要配置模型和服务信息,完成后即可开始使用系统

+
+

WeKnora 系统初始化配置

+

首次使用需要配置模型和服务信息,完成后即可开始使用系统

+
+
+ + 退出登录 + +
- -
- - -
- + +
+
Ollama 服务状态 @@ -758,8 +749,7 @@
-
-
+
@@ -941,26 +931,6 @@ const imageUpload = ref(null); // Embedding 维度检测状态 const embeddingDimDetecting = ref(false); -// 左侧导航区段 -type Section = { id: string; label: string }; -const sections: Section[] = [ - { id: 'ollama', label: 'Ollama 服务' }, - { id: 'llm', label: 'LLM 模型' }, - { id: 'embedding', label: 'Embedding 模型' }, - { id: 'rerank', label: 'Rerank 配置' }, - { id: 'multimodal', label: '多模态配置' }, - { id: 'docsplit', label: '文档分割' }, - { id: 'submit', label: '完成配置' }, -]; - -const activeSectionId = ref('ollama'); -const goToSection = (id: string) => { - const el = document.getElementById(`section-${id}`); - if (el) { - el.scrollIntoView({ behavior: 'smooth', block: 'start' }); - activeSectionId.value = id; - } -}; // 退出登录 const handleLogout = () => { @@ -968,19 +938,6 @@ const handleLogout = () => { router.replace('/login'); }; -// 监听滚动,高亮当前区块 -const onScroll = () => { - const order = ['ollama','llm','embedding','rerank','multimodal','docsplit','submit']; - for (const id of order) { - const el = document.getElementById(`section-${id}`); - if (!el) continue; - const rect = el.getBoundingClientRect(); - if (rect.top <= 120 && rect.bottom >= 120) { - activeSectionId.value = id; - break; - } - } -}; // 配置回填 const loadCurrentConfig = async () => { @@ -1004,8 +961,8 @@ const loadCurrentConfig = async () => { if (config.rerank) { Object.assign(formData.rerank, config.rerank); } - formData.storageType = config.multimodal.storageType; if (config.multimodal) { + formData.storageType = config.multimodal.storageType || 'minio'; formData.multimodal.enabled = config.multimodal.enabled || false; if (config.multimodal.vlm) { Object.assign(formData.multimodal.vlm, config.multimodal.vlm); @@ -1014,11 +971,14 @@ const loadCurrentConfig = async () => { formData.multimodal.vlm.interfaceType = 'ollama'; } } - if (config.multimodal.storageType === 'cos') { + if (config.multimodal.storageType === 'cos' && config.multimodal.cos) { Object.assign(formData.multimodal.cos, config.multimodal.cos); - } else if (config.multimodal.storageType === 'minio') { + } else if (config.multimodal.storageType === 'minio' && config.multimodal.minio) { Object.assign(formData.multimodal.minio, config.multimodal.minio); } + } else { + // 如果没有多模态配置,设置默认存储类型 + formData.storageType = 'minio'; } if (config.documentSplitting) { Object.assign(formData.documentSplitting, config.documentSplitting); @@ -1351,8 +1311,6 @@ onUnmounted(() => { clearTimeout(submitDebounceTimer.value); submitDebounceTimer.value = null; } - - window.removeEventListener('scroll', onScroll); }); // 事件处理 @@ -1740,6 +1698,23 @@ const handleSubmit = async () => { // 调用初始化API const payload: any = JSON.parse(JSON.stringify(formData)); + + // 确保存储类型在正确的位置,并只保留选中的存储配置 + if (payload.multimodal && payload.storageType) { + payload.multimodal.storageType = payload.storageType; + + // 根据选择的存储类型,只保留对应的配置,删除其他存储配置 + if (payload.storageType === 'cos') { + // 保留COS配置,删除MinIO配置 + delete payload.multimodal.minio; + } else if (payload.storageType === 'minio') { + // 保留MinIO配置,删除COS配置 + delete payload.multimodal.cos; + } + + delete payload.storageType; // 删除顶级的storageType字段 + } + await initializeSystem(payload); const successMessage = isUpdateMode.value ? '配置更新成功!' : '系统初始化成功!'; @@ -1777,9 +1752,6 @@ onMounted(async () => { // 检查已配置模型状态 await checkAllConfiguredModels(); - - // 绑定滚动监听,用于左侧导航高亮 - window.addEventListener('scroll', onScroll, { passive: true }); }); const onRerankChange = () => { @@ -2347,144 +2319,75 @@ const detectEmbeddingDimension = async () => { diff --git a/frontend/src/views/settings/SystemSettings.vue b/frontend/src/views/settings/SystemSettings.vue new file mode 100644 index 000000000..730b3d04f --- /dev/null +++ b/frontend/src/views/settings/SystemSettings.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/frontend/src/views/tenant/TenantInfo.vue b/frontend/src/views/tenant/TenantInfo.vue index 6379b154d..1890cb26c 100644 --- a/frontend/src/views/tenant/TenantInfo.vue +++ b/frontend/src/views/tenant/TenantInfo.vue @@ -311,11 +311,11 @@ onMounted(() => { .tenant-info-container { padding: 20px; background-color: #fff; - border-radius: 8px; - margin: 0 20px 20px 20px; - height: calc(100vh - 40px); + margin: 0 20px 0 20px; + height: calc(100vh); overflow-y: auto; box-sizing: border-box; + flex: 1; } .tenant-header { @@ -363,7 +363,7 @@ onMounted(() => { .api-key-content, .storage-content, .doc-content { -// padding: 16px 0; + margin-top: 0; } .api-key-input { diff --git a/internal/handler/initialization.go b/internal/handler/initialization.go index 07078a401..b74fd5580 100644 --- a/internal/handler/initialization.go +++ b/internal/handler/initialization.go @@ -83,9 +83,7 @@ func NewInitializationHandler( // InitializationRequest 初始化请求结构 type InitializationRequest struct { - // 前端传入的存储类型:cos 或 minio - StorageType string `json:"storageType"` - LLM struct { + LLM struct { Source string `json:"source" binding:"required"` ModelName string `json:"modelName" binding:"required"` BaseURL string `json:"baseUrl"` @@ -115,7 +113,8 @@ type InitializationRequest struct { APIKey string `json:"apiKey"` InterfaceType string `json:"interfaceType"` // "ollama" or "openai" } `json:"vlm,omitempty"` - COS *struct { + StorageType string `json:"storageType"` + COS *struct { SecretID string `json:"secretId"` SecretKey string `json:"secretKey"` Region string `json:"region"` @@ -210,7 +209,7 @@ func (h *InitializationHandler) Initialize(c *gin.Context) { // 验证多模态配置 if req.Multimodal.Enabled { - storageType := strings.ToLower(req.StorageType) + storageType := strings.ToLower(req.Multimodal.StorageType) if req.Multimodal.VLM == nil { logger.Error(ctx, "Multimodal enabled but missing VLM configuration") c.Error(errors.NewBadRequestError("启用多模态时需要配置VLM信息")) @@ -502,11 +501,11 @@ func (h *InitializationHandler) Initialize(c *gin.Context) { InterfaceType: req.Multimodal.VLM.InterfaceType, }, } - switch req.StorageType { + switch req.Multimodal.StorageType { case "cos": if req.Multimodal.COS != nil { kb.StorageConfig = types.StorageConfig{ - Provider: req.StorageType, + Provider: req.Multimodal.StorageType, BucketName: req.Multimodal.COS.BucketName, AppID: req.Multimodal.COS.AppID, PathPrefix: req.Multimodal.COS.PathPrefix, @@ -518,7 +517,7 @@ func (h *InitializationHandler) Initialize(c *gin.Context) { case "minio": if req.Multimodal.Minio != nil { kb.StorageConfig = types.StorageConfig{ - Provider: req.StorageType, + Provider: req.Multimodal.StorageType, BucketName: req.Multimodal.Minio.BucketName, PathPrefix: req.Multimodal.Minio.PathPrefix, SecretID: os.Getenv("MINIO_ACCESS_KEY_ID"), @@ -560,11 +559,11 @@ func (h *InitializationHandler) Initialize(c *gin.Context) { APIKey: req.Multimodal.VLM.APIKey, InterfaceType: req.Multimodal.VLM.InterfaceType, } - switch req.StorageType { + switch req.Multimodal.StorageType { case "cos": if req.Multimodal.COS != nil { kb.StorageConfig = types.StorageConfig{ - Provider: req.StorageType, + Provider: req.Multimodal.StorageType, SecretID: req.Multimodal.COS.SecretID, SecretKey: req.Multimodal.COS.SecretKey, Region: req.Multimodal.COS.Region, @@ -576,7 +575,7 @@ func (h *InitializationHandler) Initialize(c *gin.Context) { case "minio": if req.Multimodal.Minio != nil { kb.StorageConfig = types.StorageConfig{ - Provider: req.StorageType, + Provider: req.Multimodal.StorageType, BucketName: req.Multimodal.Minio.BucketName, PathPrefix: req.Multimodal.Minio.PathPrefix, SecretID: os.Getenv("MINIO_ACCESS_KEY_ID"), From c589a911dcb1a1af40ba6dfd9ef6f7a5672386a1 Mon Sep 17 00:00:00 2001 From: begoniezhao Date: Tue, 16 Sep 2025 20:53:39 +0800 Subject: [PATCH 2/2] feat: Added multi-data source search engine configuration and optimization logic --- internal/application/service/user.go | 71 ++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 15 deletions(-) diff --git a/internal/application/service/user.go b/internal/application/service/user.go index a0475b6f6..671d12e82 100644 --- a/internal/application/service/user.go +++ b/internal/application/service/user.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "os" + "strings" "time" "github.com/golang-jwt/jwt/v5" @@ -34,6 +36,35 @@ func NewUserService(userRepo interfaces.UserRepository, tokenRepo interfaces.Aut } } +var engine = map[string][]types.RetrieverEngineParams{ + "postgres": { + { + RetrieverType: types.KeywordsRetrieverType, + RetrieverEngineType: types.PostgresRetrieverEngineType, + }, + { + RetrieverType: types.VectorRetrieverType, + RetrieverEngineType: types.PostgresRetrieverEngineType, + }, + }, + "elasticsearch_v7": { + { + RetrieverType: types.KeywordsRetrieverType, + RetrieverEngineType: types.ElasticsearchRetrieverEngineType, + }, + }, + "elasticsearch_v8": { + { + RetrieverType: types.KeywordsRetrieverType, + RetrieverEngineType: types.ElasticsearchRetrieverEngineType, + }, + { + RetrieverType: types.VectorRetrieverType, + RetrieverEngineType: types.ElasticsearchRetrieverEngineType, + }, + }, +} + // Register creates a new user account func (s *userService) Register(ctx context.Context, req *types.RegisterRequest) (*types.User, error) { logger.Info(ctx, "Start user registration") @@ -61,23 +92,21 @@ func (s *userService) Register(ctx context.Context, req *types.RegisterRequest) return nil, errors.New("failed to process password") } + egs := []types.RetrieverEngineParams{} + for _, driver := range strings.Split(os.Getenv("RETRIEVE_DRIVER"), ",") { + if val, ok := engine[driver]; ok { + egs = append(egs, val...) + } + } + egs = uniqueRetrieverEngine(egs) + logger.Debugf(ctx, "user register retriever engines: %v", egs) + // Create default tenant for the user tenant := &types.Tenant{ - Name: fmt.Sprintf("%s's Workspace", req.Username), - Description: "Default workspace", - Status: "active", - RetrieverEngines: types.RetrieverEngines{ - Engines: []types.RetrieverEngineParams{ - { - RetrieverType: types.KeywordsRetrieverType, - RetrieverEngineType: types.PostgresRetrieverEngineType, - }, - { - RetrieverType: types.VectorRetrieverType, - RetrieverEngineType: types.PostgresRetrieverEngineType, - }, - }, - }, + Name: fmt.Sprintf("%s's Workspace", req.Username), + Description: "Default workspace", + Status: "active", + RetrieverEngines: types.RetrieverEngines{Engines: egs}, } createdTenant, err := s.tenantService.CreateTenant(ctx, tenant) @@ -406,3 +435,15 @@ func (s *userService) GetCurrentUser(ctx context.Context) (*types.User, error) { return user, nil } + +func uniqueRetrieverEngine(engine []types.RetrieverEngineParams) []types.RetrieverEngineParams { + seen := make(map[types.RetrieverEngineParams]bool) + var result []types.RetrieverEngineParams + for _, v := range engine { + if !seen[v] { + seen[v] = true + result = append(result, v) + } + } + return result +}