mirror of https://github.com/chaitin/PandaWiki.git
Compare commits
12 Commits
a5c99fca95
...
ca323de9b2
| Author | SHA1 | Date |
|---|---|---|
|
|
ca323de9b2 | |
|
|
5c81d714b1 | |
|
|
2a123cf8b1 | |
|
|
b1074f0956 | |
|
|
b98bf6664c | |
|
|
2b02d85fb3 | |
|
|
21f5a776df | |
|
|
3999621981 | |
|
|
da17b21387 | |
|
|
e361644f01 | |
|
|
b8a1a130ac | |
|
|
385c21a36c |
|
|
@ -96,7 +96,9 @@ func createApp() (*App, error) {
|
|||
return nil, err
|
||||
}
|
||||
authRepo := pg2.NewAuthRepo(db, logger, cacheCache)
|
||||
nodeUsecase := usecase.NewNodeUsecase(nodeRepository, appRepository, ragRepository, userRepository, knowledgeBaseRepository, llmUsecase, ragService, logger, minioClient, modelRepository, authRepo)
|
||||
systemSettingRepo := pg2.NewSystemSettingRepo(db, logger)
|
||||
modelUsecase := usecase.NewModelUsecase(modelRepository, nodeRepository, ragRepository, ragService, logger, configConfig, knowledgeBaseRepository, systemSettingRepo)
|
||||
nodeUsecase := usecase.NewNodeUsecase(nodeRepository, appRepository, ragRepository, userRepository, knowledgeBaseRepository, llmUsecase, ragService, logger, minioClient, modelRepository, authRepo, modelUsecase)
|
||||
nodeHandler := v1.NewNodeHandler(baseHandler, echo, nodeUsecase, authMiddleware, logger)
|
||||
geoRepo := cache2.NewGeoCache(cacheCache, db, logger)
|
||||
ipdbIPDB, err := ipdb.NewIPDB(configConfig, logger)
|
||||
|
|
@ -105,7 +107,6 @@ func createApp() (*App, error) {
|
|||
}
|
||||
ipAddressRepo := ipdb2.NewIPAddressRepo(ipdbIPDB, logger)
|
||||
conversationUsecase := usecase.NewConversationUsecase(conversationRepository, nodeRepository, geoRepo, logger, ipAddressRepo, authRepo)
|
||||
modelUsecase := usecase.NewModelUsecase(modelRepository, nodeRepository, ragRepository, ragService, logger, configConfig, knowledgeBaseRepository)
|
||||
blockWordRepo := pg2.NewBlockWordRepo(db, logger)
|
||||
chatUsecase, err := usecase.NewChatUsecase(llmUsecase, knowledgeBaseRepository, conversationUsecase, modelUsecase, appRepository, blockWordRepo, authRepo, logger)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ package main
|
|||
|
||||
import (
|
||||
"github.com/chaitin/panda-wiki/config"
|
||||
mq2 "github.com/chaitin/panda-wiki/handler/mq"
|
||||
mq3 "github.com/chaitin/panda-wiki/handler/mq"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
"github.com/chaitin/panda-wiki/mq"
|
||||
cache2 "github.com/chaitin/panda-wiki/repo/cache"
|
||||
ipdb2 "github.com/chaitin/panda-wiki/repo/ipdb"
|
||||
mq3 "github.com/chaitin/panda-wiki/repo/mq"
|
||||
mq2 "github.com/chaitin/panda-wiki/repo/mq"
|
||||
pg2 "github.com/chaitin/panda-wiki/repo/pg"
|
||||
"github.com/chaitin/panda-wiki/store/cache"
|
||||
"github.com/chaitin/panda-wiki/store/ipdb"
|
||||
|
|
@ -49,11 +49,18 @@ func createApp() (*App, error) {
|
|||
modelRepository := pg2.NewModelRepository(db, logger)
|
||||
promptRepo := pg2.NewPromptRepo(db, logger)
|
||||
llmUsecase := usecase.NewLLMUsecase(configConfig, ragService, conversationRepository, knowledgeBaseRepository, nodeRepository, modelRepository, promptRepo, logger)
|
||||
ragmqHandler, err := mq2.NewRAGMQHandler(mqConsumer, logger, ragService, nodeRepository, knowledgeBaseRepository, llmUsecase, modelRepository)
|
||||
mqProducer, err := mq.NewMQProducer(configConfig, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ragDocUpdateHandler, err := mq2.NewRagDocUpdateHandler(mqConsumer, logger, nodeRepository)
|
||||
ragRepository := mq2.NewRAGRepository(mqProducer)
|
||||
systemSettingRepo := pg2.NewSystemSettingRepo(db, logger)
|
||||
modelUsecase := usecase.NewModelUsecase(modelRepository, nodeRepository, ragRepository, ragService, logger, configConfig, knowledgeBaseRepository, systemSettingRepo)
|
||||
ragmqHandler, err := mq3.NewRAGMQHandler(mqConsumer, logger, ragService, nodeRepository, knowledgeBaseRepository, llmUsecase, modelUsecase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ragDocUpdateHandler, err := mq3.NewRagDocUpdateHandler(mqConsumer, logger, nodeRepository)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -71,22 +78,17 @@ func createApp() (*App, error) {
|
|||
geoRepo := cache2.NewGeoCache(cacheCache, db, logger)
|
||||
authRepo := pg2.NewAuthRepo(db, logger, cacheCache)
|
||||
statUseCase := usecase.NewStatUseCase(statRepository, nodeRepository, conversationRepository, appRepository, ipAddressRepo, geoRepo, authRepo, knowledgeBaseRepository, logger)
|
||||
mqProducer, err := mq.NewMQProducer(configConfig, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ragRepository := mq3.NewRAGRepository(mqProducer)
|
||||
userRepository := pg2.NewUserRepository(db, logger)
|
||||
minioClient, err := s3.NewMinioClient(configConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodeUsecase := usecase.NewNodeUsecase(nodeRepository, appRepository, ragRepository, userRepository, knowledgeBaseRepository, llmUsecase, ragService, logger, minioClient, modelRepository, authRepo)
|
||||
cronHandler, err := mq2.NewStatCronHandler(logger, statRepository, statUseCase, nodeUsecase)
|
||||
nodeUsecase := usecase.NewNodeUsecase(nodeRepository, appRepository, ragRepository, userRepository, knowledgeBaseRepository, llmUsecase, ragService, logger, minioClient, modelRepository, authRepo, modelUsecase)
|
||||
cronHandler, err := mq3.NewStatCronHandler(logger, statRepository, statUseCase, nodeUsecase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mqHandlers := &mq2.MQHandlers{
|
||||
mqHandlers := &mq3.MQHandlers{
|
||||
RAGMQHandler: ragmqHandler,
|
||||
RagDocUpdateHandler: ragDocUpdateHandler,
|
||||
StatCronHandler: cronHandler,
|
||||
|
|
@ -105,6 +107,6 @@ func createApp() (*App, error) {
|
|||
type App struct {
|
||||
MQConsumer mq.MQConsumer
|
||||
Config *config.Config
|
||||
MQHandlers *mq2.MQHandlers
|
||||
StatCronHandler *mq2.CronHandler
|
||||
MQHandlers *mq3.MQHandlers
|
||||
StatCronHandler *mq3.CronHandler
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,9 @@ func createApp() (*App, error) {
|
|||
return nil, err
|
||||
}
|
||||
authRepo := pg2.NewAuthRepo(db, logger, cacheCache)
|
||||
nodeUsecase := usecase.NewNodeUsecase(nodeRepository, appRepository, ragRepository, userRepository, knowledgeBaseRepository, llmUsecase, ragService, logger, minioClient, modelRepository, authRepo)
|
||||
systemSettingRepo := pg2.NewSystemSettingRepo(db, logger)
|
||||
modelUsecase := usecase.NewModelUsecase(modelRepository, nodeRepository, ragRepository, ragService, logger, configConfig, knowledgeBaseRepository, systemSettingRepo)
|
||||
nodeUsecase := usecase.NewNodeUsecase(nodeRepository, appRepository, ragRepository, userRepository, knowledgeBaseRepository, llmUsecase, ragService, logger, minioClient, modelRepository, authRepo, modelUsecase)
|
||||
kbRepo := cache2.NewKBRepo(cacheCache)
|
||||
knowledgeBaseUsecase, err := usecase.NewKnowledgeBaseUsecase(knowledgeBaseRepository, nodeRepository, ragRepository, userRepository, ragService, kbRepo, logger, configConfig)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package consts
|
||||
|
||||
type AutoModeDefaultModel string
|
||||
|
||||
const (
|
||||
AutoModeDefaultChatModel AutoModeDefaultModel = "deepseek-chat"
|
||||
AutoModeDefaultEmbeddingModel AutoModeDefaultModel = "bge-m3"
|
||||
AutoModeDefaultRerankModel AutoModeDefaultModel = "bge-reranker-v2-m3"
|
||||
AutoModeDefaultAnalysisModel AutoModeDefaultModel = "qwen2.5-3b-instruct"
|
||||
AutoModeDefaultAnalysisVLModel AutoModeDefaultModel = "qwen3-vl-max"
|
||||
)
|
||||
|
||||
func GetAutoModeDefaultModel(modelType string) string {
|
||||
switch modelType {
|
||||
case "chat":
|
||||
return string(AutoModeDefaultChatModel)
|
||||
case "embedding":
|
||||
return string(AutoModeDefaultEmbeddingModel)
|
||||
case "rerank":
|
||||
return string(AutoModeDefaultRerankModel)
|
||||
case "analysis":
|
||||
return string(AutoModeDefaultAnalysisModel)
|
||||
case "analysis-vl":
|
||||
return string(AutoModeDefaultAnalysisVLModel)
|
||||
default:
|
||||
return string(AutoModeDefaultChatModel)
|
||||
}
|
||||
}
|
||||
|
||||
type ModelSettingMode string
|
||||
|
||||
const (
|
||||
ModelSettingModeManual ModelSettingMode = "manual"
|
||||
ModelSettingModeAuto ModelSettingMode = "auto"
|
||||
)
|
||||
|
||||
const (
|
||||
AutoModeBaseURL = "https://model-square.app.baizhi.cloud/v1"
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package consts
|
||||
|
||||
type SystemSettingKey string
|
||||
|
||||
const (
|
||||
SystemSettingModelMode SystemSettingKey = "model_setting_mode"
|
||||
)
|
||||
|
|
@ -1565,6 +1565,41 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/model/mode-setting": {
|
||||
"get": {
|
||||
"description": "get current model mode setting including mode, API key and chat model",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"model"
|
||||
],
|
||||
"summary": "get model mode setting",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.ModelModeSetting"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/model/provider/supported": {
|
||||
"post": {
|
||||
"description": "get provider supported model list",
|
||||
|
|
@ -1611,6 +1646,52 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/model/switch-mode": {
|
||||
"post": {
|
||||
"description": "switch model mode between manual and auto",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"model"
|
||||
],
|
||||
"summary": "switch mode",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "switch mode request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.SwitchModeReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.SwitchModeResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/node": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
|
@ -4004,6 +4085,17 @@ const docTemplate = `{
|
|||
"LicenseEditionEnterprise"
|
||||
]
|
||||
},
|
||||
"consts.ModelSettingMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"manual",
|
||||
"auto"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ModelSettingModeManual",
|
||||
"ModelSettingModeAuto"
|
||||
]
|
||||
},
|
||||
"consts.NodeAccessPerm": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
@ -6258,6 +6350,31 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ModelModeSetting": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"auto_mode_api_key": {
|
||||
"description": "百智云 API Key",
|
||||
"type": "string"
|
||||
},
|
||||
"chat_model": {
|
||||
"description": "自定义对话模型名称",
|
||||
"type": "string"
|
||||
},
|
||||
"is_manual_embedding_updated": {
|
||||
"description": "手动模式下嵌入模型是否更新",
|
||||
"type": "boolean"
|
||||
},
|
||||
"mode": {
|
||||
"description": "模式: manual 或 auto",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/consts.ModelSettingMode"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.ModelType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
@ -7097,6 +7214,37 @@ const docTemplate = `{
|
|||
"StatPageSceneLogin"
|
||||
]
|
||||
},
|
||||
"domain.SwitchModeReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mode"
|
||||
],
|
||||
"properties": {
|
||||
"auto_mode_api_key": {
|
||||
"description": "百智云 API Key",
|
||||
"type": "string"
|
||||
},
|
||||
"chat_model": {
|
||||
"description": "自定义对话模型名称",
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"manual",
|
||||
"auto"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.SwitchModeResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.TextConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -1558,6 +1558,41 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/model/mode-setting": {
|
||||
"get": {
|
||||
"description": "get current model mode setting including mode, API key and chat model",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"model"
|
||||
],
|
||||
"summary": "get model mode setting",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.ModelModeSetting"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/model/provider/supported": {
|
||||
"post": {
|
||||
"description": "get provider supported model list",
|
||||
|
|
@ -1604,6 +1639,52 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/model/switch-mode": {
|
||||
"post": {
|
||||
"description": "switch model mode between manual and auto",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"model"
|
||||
],
|
||||
"summary": "switch mode",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "switch mode request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.SwitchModeReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.SwitchModeResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/node": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
|
@ -3997,6 +4078,17 @@
|
|||
"LicenseEditionEnterprise"
|
||||
]
|
||||
},
|
||||
"consts.ModelSettingMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"manual",
|
||||
"auto"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ModelSettingModeManual",
|
||||
"ModelSettingModeAuto"
|
||||
]
|
||||
},
|
||||
"consts.NodeAccessPerm": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
@ -6251,6 +6343,31 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ModelModeSetting": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"auto_mode_api_key": {
|
||||
"description": "百智云 API Key",
|
||||
"type": "string"
|
||||
},
|
||||
"chat_model": {
|
||||
"description": "自定义对话模型名称",
|
||||
"type": "string"
|
||||
},
|
||||
"is_manual_embedding_updated": {
|
||||
"description": "手动模式下嵌入模型是否更新",
|
||||
"type": "boolean"
|
||||
},
|
||||
"mode": {
|
||||
"description": "模式: manual 或 auto",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/consts.ModelSettingMode"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.ModelType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
@ -7090,6 +7207,37 @@
|
|||
"StatPageSceneLogin"
|
||||
]
|
||||
},
|
||||
"domain.SwitchModeReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mode"
|
||||
],
|
||||
"properties": {
|
||||
"auto_mode_api_key": {
|
||||
"description": "百智云 API Key",
|
||||
"type": "string"
|
||||
},
|
||||
"chat_model": {
|
||||
"description": "自定义对话模型名称",
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"manual",
|
||||
"auto"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.SwitchModeResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.TextConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -131,6 +131,14 @@ definitions:
|
|||
- LicenseEditionFree
|
||||
- LicenseEditionContributor
|
||||
- LicenseEditionEnterprise
|
||||
consts.ModelSettingMode:
|
||||
enum:
|
||||
- manual
|
||||
- auto
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- ModelSettingModeManual
|
||||
- ModelSettingModeAuto
|
||||
consts.NodeAccessPerm:
|
||||
enum:
|
||||
- open
|
||||
|
|
@ -1628,6 +1636,22 @@ definitions:
|
|||
type:
|
||||
type: string
|
||||
type: object
|
||||
domain.ModelModeSetting:
|
||||
properties:
|
||||
auto_mode_api_key:
|
||||
description: 百智云 API Key
|
||||
type: string
|
||||
chat_model:
|
||||
description: 自定义对话模型名称
|
||||
type: string
|
||||
is_manual_embedding_updated:
|
||||
description: 手动模式下嵌入模型是否更新
|
||||
type: boolean
|
||||
mode:
|
||||
allOf:
|
||||
- $ref: '#/definitions/consts.ModelSettingMode'
|
||||
description: '模式: manual 或 auto'
|
||||
type: object
|
||||
domain.ModelType:
|
||||
enum:
|
||||
- chat
|
||||
|
|
@ -2178,6 +2202,27 @@ definitions:
|
|||
- StatPageSceneNodeDetail
|
||||
- StatPageSceneChat
|
||||
- StatPageSceneLogin
|
||||
domain.SwitchModeReq:
|
||||
properties:
|
||||
auto_mode_api_key:
|
||||
description: 百智云 API Key
|
||||
type: string
|
||||
chat_model:
|
||||
description: 自定义对话模型名称
|
||||
type: string
|
||||
mode:
|
||||
enum:
|
||||
- manual
|
||||
- auto
|
||||
type: string
|
||||
required:
|
||||
- mode
|
||||
type: object
|
||||
domain.SwitchModeResp:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
domain.TextConfig:
|
||||
properties:
|
||||
title:
|
||||
|
|
@ -4065,6 +4110,27 @@ paths:
|
|||
summary: get model list
|
||||
tags:
|
||||
- model
|
||||
/api/v1/model/mode-setting:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: get current model mode setting including mode, API key and chat
|
||||
model
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/domain.ModelModeSetting'
|
||||
type: object
|
||||
summary: get model mode setting
|
||||
tags:
|
||||
- model
|
||||
/api/v1/model/provider/supported:
|
||||
post:
|
||||
consumes:
|
||||
|
|
@ -4092,6 +4158,33 @@ paths:
|
|||
summary: get provider supported model list
|
||||
tags:
|
||||
- model
|
||||
/api/v1/model/switch-mode:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: switch model mode between manual and auto
|
||||
parameters:
|
||||
- description: switch mode request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.SwitchModeReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/domain.SwitchModeResp'
|
||||
type: object
|
||||
summary: switch mode
|
||||
tags:
|
||||
- model
|
||||
/api/v1/node:
|
||||
post:
|
||||
consumes:
|
||||
|
|
|
|||
|
|
@ -165,3 +165,13 @@ type ProviderModelListItem struct {
|
|||
type ActivateModelReq struct {
|
||||
ModelID string `json:"model_id" validate:"required"`
|
||||
}
|
||||
|
||||
type SwitchModeReq struct {
|
||||
Mode string `json:"mode" validate:"required,oneof=manual auto"`
|
||||
AutoModeAPIKey string `json:"auto_mode_api_key"` // 百智云 API Key
|
||||
ChatModel string `json:"chat_model"` // 自定义对话模型名称
|
||||
}
|
||||
|
||||
type SwitchModeResp struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/chaitin/panda-wiki/consts"
|
||||
)
|
||||
|
||||
// table: settings
|
||||
type SystemSetting struct {
|
||||
ID int `json:"id" gorm:"primary_key"`
|
||||
Key consts.SystemSettingKey `json:"key"`
|
||||
Value []byte `json:"value" gorm:"type:jsonb"` // JSON string
|
||||
Description string `json:"description"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (SystemSetting) TableName() string {
|
||||
return "system_settings"
|
||||
}
|
||||
|
||||
// ModelModeSetting 模型配置结构体
|
||||
type ModelModeSetting struct {
|
||||
Mode consts.ModelSettingMode `json:"mode"` // 模式: manual 或 auto
|
||||
AutoModeAPIKey string `json:"auto_mode_api_key"` // 百智云 API Key
|
||||
ChatModel string `json:"chat_model"` // 自定义对话模型名称
|
||||
IsManualEmbeddingUpdated bool `json:"is_manual_embedding_updated"` // 手动模式下嵌入模型是否更新
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ var ProviderSet = wire.NewSet(
|
|||
usecase.NewLLMUsecase,
|
||||
usecase.NewStatUseCase,
|
||||
usecase.NewNodeUsecase,
|
||||
usecase.NewModelUsecase,
|
||||
|
||||
NewRAGMQHandler,
|
||||
NewRagDocUpdateHandler,
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ type RAGMQHandler struct {
|
|||
rag rag.RAGService
|
||||
nodeRepo *pg.NodeRepository
|
||||
kbRepo *pg.KnowledgeBaseRepository
|
||||
modelRepo *pg.ModelRepository
|
||||
llmUsecase *usecase.LLMUsecase
|
||||
modelUsecase *usecase.ModelUsecase
|
||||
}
|
||||
|
||||
func NewRAGMQHandler(consumer mq.MQConsumer, logger *log.Logger, rag rag.RAGService, nodeRepo *pg.NodeRepository, kbRepo *pg.KnowledgeBaseRepository, llmUsecase *usecase.LLMUsecase, modelRepo *pg.ModelRepository) (*RAGMQHandler, error) {
|
||||
func NewRAGMQHandler(consumer mq.MQConsumer, logger *log.Logger, rag rag.RAGService, nodeRepo *pg.NodeRepository, kbRepo *pg.KnowledgeBaseRepository, llmUsecase *usecase.LLMUsecase, modelUsecase *usecase.ModelUsecase) (*RAGMQHandler, error) {
|
||||
h := &RAGMQHandler{
|
||||
consumer: consumer,
|
||||
logger: logger.WithModule("mq.rag"),
|
||||
|
|
@ -32,7 +32,7 @@ func NewRAGMQHandler(consumer mq.MQConsumer, logger *log.Logger, rag rag.RAGServ
|
|||
nodeRepo: nodeRepo,
|
||||
kbRepo: kbRepo,
|
||||
llmUsecase: llmUsecase,
|
||||
modelRepo: modelRepo,
|
||||
modelUsecase: modelUsecase,
|
||||
}
|
||||
if err := consumer.RegisterHandler(domain.VectorTaskTopic, h.HandleNodeContentVectorRequest); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -134,11 +134,13 @@ func (h *RAGMQHandler) HandleNodeContentVectorRequest(ctx context.Context, msg t
|
|||
h.logger.Info("node is folder, skip summary", log.Any("node_id", request.NodeID))
|
||||
return nil
|
||||
}
|
||||
model, err := h.modelRepo.GetChatModel(ctx)
|
||||
|
||||
model, err := h.modelUsecase.GetChatModel(ctx)
|
||||
if err != nil {
|
||||
h.logger.Error("get chat model failed", log.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
summary, err := h.llmUsecase.SummaryNode(ctx, model, node.Name, node.Content)
|
||||
if err != nil {
|
||||
h.logger.Error("summary node content failed", log.Error(err))
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ func NewModelHandler(echo *echo.Echo, baseHandler *handler.BaseHandler, logger *
|
|||
group.POST("/check", handler.CheckModel)
|
||||
group.POST("/provider/supported", handler.GetProviderSupportedModelList)
|
||||
group.PUT("", handler.UpdateModel)
|
||||
group.POST("/switch-mode", handler.SwitchMode)
|
||||
group.GET("/mode-setting", handler.GetModelModeSetting)
|
||||
|
||||
return handler
|
||||
}
|
||||
|
|
@ -211,3 +213,58 @@ func (h *ModelHandler) GetProviderSupportedModelList(c echo.Context) error {
|
|||
}
|
||||
return h.NewResponseWithData(c, models)
|
||||
}
|
||||
|
||||
// SwitchMode
|
||||
//
|
||||
// @Summary switch mode
|
||||
// @Description switch model mode between manual and auto
|
||||
// @Tags model
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body domain.SwitchModeReq true "switch mode request"
|
||||
// @Success 200 {object} domain.Response{data=domain.SwitchModeResp}
|
||||
// @Router /api/v1/model/switch-mode [post]
|
||||
func (h *ModelHandler) SwitchMode(c echo.Context) error {
|
||||
var req domain.SwitchModeReq
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return h.NewResponseWithError(c, "bind request failed", err)
|
||||
}
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return h.NewResponseWithError(c, "validate request failed", err)
|
||||
}
|
||||
ctx := c.Request().Context()
|
||||
|
||||
if err := h.usecase.SwitchMode(ctx, &req); err != nil {
|
||||
return h.NewResponseWithError(c, err.Error(), err)
|
||||
}
|
||||
|
||||
resp := &domain.SwitchModeResp{
|
||||
Message: "模式切换成功",
|
||||
}
|
||||
return h.NewResponseWithData(c, resp)
|
||||
}
|
||||
|
||||
// GetModelModeSetting
|
||||
//
|
||||
// @Summary get model mode setting
|
||||
// @Description get current model mode setting including mode, API key and chat model
|
||||
// @Tags model
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} domain.Response{data=domain.ModelModeSetting}
|
||||
// @Router /api/v1/model/mode-setting [get]
|
||||
func (h *ModelHandler) GetModelModeSetting(c echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
setting, err := h.usecase.GetModelModeSetting(ctx)
|
||||
if err != nil {
|
||||
// 如果获取失败,返回默认值(手动模式)
|
||||
h.logger.Warn("failed to get model mode setting, return default", log.Error(err))
|
||||
defaultSetting := domain.ModelModeSetting{
|
||||
Mode: consts.ModelSettingModeManual,
|
||||
AutoModeAPIKey: "",
|
||||
ChatModel: "",
|
||||
}
|
||||
return h.NewResponseWithData(c, defaultSetting)
|
||||
}
|
||||
return h.NewResponseWithData(c, setting)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 3f6d9b2aca901c53abfcad18f244496843eda3eb
|
||||
Subproject commit c4dc498df094cb617d31c95580db8239a445d652
|
||||
|
|
@ -23,4 +23,5 @@ var ProviderSet = wire.NewSet(
|
|||
NewAuthRepo,
|
||||
NewWechatRepository,
|
||||
NewAPITokenRepo,
|
||||
NewSystemSettingRepo,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package pg
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
"github.com/chaitin/panda-wiki/store/pg"
|
||||
)
|
||||
|
||||
type SystemSettingRepo struct {
|
||||
db *pg.DB
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewSystemSettingRepo(db *pg.DB, logger *log.Logger) *SystemSettingRepo {
|
||||
return &SystemSettingRepo{
|
||||
db: db,
|
||||
logger: logger.WithModule("repo.pg.system_setting"),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SystemSettingRepo) GetSystemSetting(ctx context.Context, key string) (*domain.SystemSetting, error) {
|
||||
var setting domain.SystemSetting
|
||||
result := r.db.WithContext(ctx).Where("key = ?", key).First(&setting)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return &setting, nil
|
||||
}
|
||||
|
||||
func (r *SystemSettingRepo) UpdateSystemSetting(ctx context.Context, key, value string) error {
|
||||
return r.db.WithContext(ctx).Model(&domain.SystemSetting{}).Where("key = ?", key).Update("value", value).Error
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
-- Drop settings table
|
||||
DROP TABLE IF EXISTS system_settings;
|
||||
-- drop index
|
||||
DROP INDEX IF EXISTS idx_system_settings_key;
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
-- Create settings table
|
||||
CREATE TABLE IF NOT EXISTS system_settings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
key TEXT NOT NULL,
|
||||
value JSONB NOT NULL,
|
||||
description TEXT,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_uniq_system_settings_key ON system_settings(key);
|
||||
|
||||
-- Insert model_setting_mode setting
|
||||
-- If there are existing knowledge bases, set mode to 'manual', otherwise set to 'auto'
|
||||
INSERT INTO system_settings (key, value, description)
|
||||
SELECT
|
||||
'model_setting_mode',
|
||||
jsonb_build_object(
|
||||
'mode', CASE
|
||||
WHEN EXISTS (SELECT 1 FROM knowledge_bases LIMIT 1) THEN 'manual'
|
||||
ELSE 'auto'
|
||||
END,
|
||||
'auto_mode_api_key', '',
|
||||
'chat_model', '',
|
||||
'is_manual_embedding_updated', false
|
||||
),
|
||||
'Model setting mode configuration'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM system_settings WHERE key = 'model_setting_mode'
|
||||
);
|
||||
|
|
@ -84,7 +84,7 @@ func (u *CreationUsecase) TextCreation(ctx context.Context, req *domain.TextReq,
|
|||
func (u *CreationUsecase) TabComplete(ctx context.Context, req *domain.CompleteReq) (string, error) {
|
||||
// For FIM (Fill in Middle) style completion, we need to handle prefix and suffix
|
||||
if req.Prefix != "" || req.Suffix != "" {
|
||||
model, err := u.model.GetModelByType(ctx, domain.ModelTypeChat)
|
||||
model, err := u.model.GetChatModel(ctx)
|
||||
if err != nil {
|
||||
u.logger.Error("get chat model failed", log.Error(err))
|
||||
return "", domain.ErrModelNotConfigured
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@ package usecase
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"github.com/google/uuid"
|
||||
"github.com/samber/lo"
|
||||
|
||||
modelkitDomain "github.com/chaitin/ModelKit/v2/domain"
|
||||
modelkit "github.com/chaitin/ModelKit/v2/usecase"
|
||||
"github.com/chaitin/panda-wiki/config"
|
||||
"github.com/chaitin/panda-wiki/consts"
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
"github.com/chaitin/panda-wiki/repo/mq"
|
||||
|
|
@ -25,9 +26,12 @@ type ModelUsecase struct {
|
|||
ragRepo *mq.RAGRepository
|
||||
ragStore rag.RAGService
|
||||
kbRepo *pg.KnowledgeBaseRepository
|
||||
systemSettingRepo *pg.SystemSettingRepo
|
||||
modelkit *modelkit.ModelKit
|
||||
}
|
||||
|
||||
func NewModelUsecase(modelRepo *pg.ModelRepository, nodeRepo *pg.NodeRepository, ragRepo *mq.RAGRepository, ragStore rag.RAGService, logger *log.Logger, config *config.Config, kbRepo *pg.KnowledgeBaseRepository) *ModelUsecase {
|
||||
func NewModelUsecase(modelRepo *pg.ModelRepository, nodeRepo *pg.NodeRepository, ragRepo *mq.RAGRepository, ragStore rag.RAGService, logger *log.Logger, config *config.Config, kbRepo *pg.KnowledgeBaseRepository, settingRepo *pg.SystemSettingRepo) *ModelUsecase {
|
||||
modelkit := modelkit.NewModelKit(logger.Logger)
|
||||
u := &ModelUsecase{
|
||||
modelRepo: modelRepo,
|
||||
logger: logger.WithModule("usecase.model"),
|
||||
|
|
@ -36,100 +40,26 @@ func NewModelUsecase(modelRepo *pg.ModelRepository, nodeRepo *pg.NodeRepository,
|
|||
ragRepo: ragRepo,
|
||||
ragStore: ragStore,
|
||||
kbRepo: kbRepo,
|
||||
}
|
||||
if err := u.initEmbeddingAndRerankModel(context.Background()); err != nil {
|
||||
logger.Error("init embedding & rerank & analysis model failed", log.Any("error", err))
|
||||
systemSettingRepo: settingRepo,
|
||||
modelkit: modelkit,
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *ModelUsecase) initEmbeddingAndRerankModel(ctx context.Context) error {
|
||||
isReady := false
|
||||
// wait for raglite to be ready
|
||||
for range 60 {
|
||||
models, err := u.ragStore.GetModelList(ctx)
|
||||
if err != nil {
|
||||
u.logger.Error("wait for raglite to be ready", log.Any("error", err))
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
isReady = true
|
||||
if len(models) > 0 {
|
||||
// init analysis model for old user
|
||||
hasAnalysis := false
|
||||
for _, m := range models {
|
||||
if m.Type == domain.ModelTypeAnalysis {
|
||||
hasAnalysis = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasAnalysis {
|
||||
if err := u.createAndSyncModelToRAGLite(ctx, "qwen2.5-3b-instruct", domain.ModelTypeAnalysis); err != nil {
|
||||
return fmt.Errorf("add analysis model err: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isReady {
|
||||
return fmt.Errorf("raglite is not ready")
|
||||
}
|
||||
|
||||
if err := u.createAndSyncModelToRAGLite(ctx, "bge-m3", domain.ModelTypeEmbedding); err != nil {
|
||||
return fmt.Errorf("create and sync model err: %v", err)
|
||||
}
|
||||
if err := u.createAndSyncModelToRAGLite(ctx, "bge-reranker-v2-m3", domain.ModelTypeRerank); err != nil {
|
||||
return fmt.Errorf("create and sync model err: %v", err)
|
||||
}
|
||||
if err := u.createAndSyncModelToRAGLite(ctx, "qwen2.5-3b-instruct", domain.ModelTypeAnalysis); err != nil {
|
||||
return fmt.Errorf("create and sync model err: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ModelUsecase) createAndSyncModelToRAGLite(ctx context.Context, modelName string, modelType domain.ModelType) error {
|
||||
// FIXME: just for test, remove it later
|
||||
// shared_key by BaiZhiCloud
|
||||
sharedKey := "sk-r8tmBtcU1JotPDPnlgZLOY4Z6Dbb7FufcSeTkFpRWA5v4Llr"
|
||||
baseURL := "https://model-square.app.baizhi.cloud/v1"
|
||||
model := &domain.Model{
|
||||
ID: uuid.New().String(),
|
||||
Provider: domain.ModelProviderBrandBaiZhiCloud,
|
||||
Model: modelName,
|
||||
APIKey: sharedKey,
|
||||
APIHeader: "",
|
||||
BaseURL: baseURL,
|
||||
IsActive: true,
|
||||
APIVersion: "",
|
||||
Type: modelType,
|
||||
}
|
||||
id, err := u.ragStore.AddModel(ctx, model)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init %s model failed: %w", modelName, err)
|
||||
}
|
||||
model.ID = id
|
||||
if err := u.modelRepo.Create(ctx, model); err != nil {
|
||||
return fmt.Errorf("create %s model failed: %w", modelName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ModelUsecase) Create(ctx context.Context, model *domain.Model) error {
|
||||
var updatedEmbeddingModel bool
|
||||
if model.Type == domain.ModelTypeEmbedding {
|
||||
updatedEmbeddingModel = true
|
||||
}
|
||||
if err := u.modelRepo.Create(ctx, model); err != nil {
|
||||
return err
|
||||
}
|
||||
if model.Type == domain.ModelTypeEmbedding || model.Type == domain.ModelTypeRerank || model.Type == domain.ModelTypeAnalysis || model.Type == domain.ModelTypeAnalysisVL {
|
||||
if id, err := u.ragStore.AddModel(ctx, model); err != nil {
|
||||
// 模型更新成功后,如果更新嵌入模型,则触发记录更新
|
||||
if updatedEmbeddingModel {
|
||||
if _, err := u.updateModeSettingConfig(ctx, "", "", "", true); err != nil {
|
||||
return err
|
||||
} else {
|
||||
model.ID = id
|
||||
}
|
||||
}
|
||||
if model.Type == domain.ModelTypeEmbedding {
|
||||
return u.TriggerUpsertRecords(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -178,44 +108,50 @@ func (u *ModelUsecase) TriggerUpsertRecords(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (u *ModelUsecase) Update(ctx context.Context, req *domain.UpdateModelReq) error {
|
||||
var updatedEmbeddingModel bool
|
||||
if req.Type == domain.ModelTypeEmbedding {
|
||||
updatedEmbeddingModel = true
|
||||
}
|
||||
if err := u.modelRepo.Update(ctx, req); err != nil {
|
||||
return err
|
||||
}
|
||||
ragModelTypes := []domain.ModelType{
|
||||
domain.ModelTypeEmbedding,
|
||||
domain.ModelTypeRerank,
|
||||
domain.ModelTypeAnalysis,
|
||||
domain.ModelTypeAnalysisVL,
|
||||
}
|
||||
if lo.Contains(ragModelTypes, req.Type) {
|
||||
updateModel := &domain.Model{
|
||||
ID: req.ID,
|
||||
Model: req.Model,
|
||||
Type: req.Type,
|
||||
BaseURL: req.BaseURL,
|
||||
APIKey: req.APIKey,
|
||||
IsActive: true,
|
||||
}
|
||||
if req.Parameters != nil {
|
||||
updateModel.Parameters = *req.Parameters
|
||||
}
|
||||
// update is active flag for analysis models
|
||||
if (req.Type == domain.ModelTypeAnalysis || req.Type == domain.ModelTypeAnalysisVL) && req.IsActive != nil {
|
||||
updateModel.IsActive = *req.IsActive
|
||||
}
|
||||
if err := u.ragStore.UpdateModel(ctx, updateModel); err != nil {
|
||||
// 模型更新成功后,如果更新嵌入模型,则触发记录更新
|
||||
if updatedEmbeddingModel {
|
||||
if _, err := u.updateModeSettingConfig(ctx, "", "", "", true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// update all records when embedding model is updated
|
||||
if req.Type == domain.ModelTypeEmbedding {
|
||||
return u.TriggerUpsertRecords(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ModelUsecase) GetChatModel(ctx context.Context) (*domain.Model, error) {
|
||||
return u.modelRepo.GetChatModel(ctx)
|
||||
var model *domain.Model
|
||||
modelModeSetting, err := u.GetModelModeSetting(ctx)
|
||||
// 获取不到模型模式时,使用手动模式, 不返回错误
|
||||
if err != nil {
|
||||
u.logger.Error("get model mode setting failed, use manual mode", log.Error(err))
|
||||
}
|
||||
if err == nil && modelModeSetting.Mode == consts.ModelSettingModeAuto && modelModeSetting.AutoModeAPIKey != "" {
|
||||
modelName := modelModeSetting.ChatModel
|
||||
if modelName == "" {
|
||||
modelName = string(consts.AutoModeDefaultChatModel)
|
||||
}
|
||||
model = &domain.Model{
|
||||
Model: modelName,
|
||||
Type: domain.ModelTypeChat,
|
||||
IsActive: true,
|
||||
BaseURL: consts.AutoModeBaseURL,
|
||||
APIKey: modelModeSetting.AutoModeAPIKey,
|
||||
Provider: domain.ModelProviderBrandBaiZhiCloud,
|
||||
}
|
||||
return model, nil
|
||||
}
|
||||
model, err = u.modelRepo.GetChatModel(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func (u *ModelUsecase) GetModelByType(ctx context.Context, modelType domain.ModelType) (*domain.Model, error) {
|
||||
|
|
@ -225,3 +161,175 @@ func (u *ModelUsecase) GetModelByType(ctx context.Context, modelType domain.Mode
|
|||
func (u *ModelUsecase) UpdateUsage(ctx context.Context, modelID string, usage *schema.TokenUsage) error {
|
||||
return u.modelRepo.UpdateUsage(ctx, modelID, usage)
|
||||
}
|
||||
|
||||
func (u *ModelUsecase) SwitchMode(ctx context.Context, req *domain.SwitchModeReq) error {
|
||||
// 只有配置正确才能切换模式
|
||||
if req.Mode == string(consts.ModelSettingModeAuto) {
|
||||
if req.AutoModeAPIKey == "" {
|
||||
return fmt.Errorf("auto mode api key is required")
|
||||
}
|
||||
modelName := req.ChatModel
|
||||
if modelName == "" {
|
||||
modelName = consts.GetAutoModeDefaultModel(string(domain.ModelTypeChat))
|
||||
}
|
||||
// 检查 API Key 是否有效
|
||||
check, err := u.modelkit.CheckModel(ctx, &modelkitDomain.CheckModelReq{
|
||||
Provider: string(domain.ModelProviderBrandBaiZhiCloud),
|
||||
Model: modelName,
|
||||
BaseURL: consts.AutoModeBaseURL,
|
||||
APIKey: req.AutoModeAPIKey,
|
||||
Type: string(domain.ModelTypeChat),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("百智云模型 API Key 检查失败: %w", err)
|
||||
}
|
||||
if check.Error != "" {
|
||||
return fmt.Errorf("百智云模型 API Key 检查失败: %s", check.Error)
|
||||
}
|
||||
} else {
|
||||
needModelTypes := []domain.ModelType{
|
||||
domain.ModelTypeChat,
|
||||
domain.ModelTypeEmbedding,
|
||||
domain.ModelTypeRerank,
|
||||
domain.ModelTypeAnalysis,
|
||||
}
|
||||
for _, modelType := range needModelTypes {
|
||||
if _, err := u.modelRepo.GetModelByType(ctx, modelType); err != nil {
|
||||
return fmt.Errorf("需要配置 %s 模型", modelType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oldModelModeSetting, err := u.GetModelModeSetting(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var isResetEmbeddingUpdateFlag = true
|
||||
// 只有切换手动模式时,重置isManualEmbeddingUpdated为false
|
||||
if req.Mode == string(consts.ModelSettingModeManual) {
|
||||
isResetEmbeddingUpdateFlag = false
|
||||
}
|
||||
|
||||
modelModeSetting, err := u.updateModeSettingConfig(ctx, req.Mode, req.AutoModeAPIKey, req.ChatModel, isResetEmbeddingUpdateFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return u.updateRAGModelsByMode(ctx, req.Mode, modelModeSetting.AutoModeAPIKey, oldModelModeSetting)
|
||||
}
|
||||
|
||||
// updateModeSettingConfig 读取当前设置并更新,然后持久化
|
||||
func (u *ModelUsecase) updateModeSettingConfig(ctx context.Context, mode, apiKey, chatModel string, isManualEmbeddingUpdated bool) (*domain.ModelModeSetting, error) {
|
||||
// 读取当前设置
|
||||
setting, err := u.systemSettingRepo.GetSystemSetting(ctx, string(consts.SystemSettingModelMode))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get current model setting: %w", err)
|
||||
}
|
||||
|
||||
var config domain.ModelModeSetting
|
||||
if err := json.Unmarshal(setting.Value, &config); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse current model setting: %w", err)
|
||||
}
|
||||
|
||||
// 更新设置
|
||||
if apiKey != "" {
|
||||
config.AutoModeAPIKey = apiKey
|
||||
}
|
||||
if chatModel != "" {
|
||||
config.ChatModel = chatModel
|
||||
}
|
||||
if mode != "" {
|
||||
config.Mode = consts.ModelSettingMode(mode)
|
||||
}
|
||||
|
||||
config.IsManualEmbeddingUpdated = isManualEmbeddingUpdated
|
||||
|
||||
// 持久化设置
|
||||
updatedValue, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal updated model setting: %w", err)
|
||||
}
|
||||
if err := u.systemSettingRepo.UpdateSystemSetting(ctx, string(consts.SystemSettingModelMode), string(updatedValue)); err != nil {
|
||||
return nil, fmt.Errorf("failed to update model setting: %w", err)
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func (u *ModelUsecase) GetModelModeSetting(ctx context.Context) (domain.ModelModeSetting, error) {
|
||||
setting, err := u.systemSettingRepo.GetSystemSetting(ctx, string(consts.SystemSettingModelMode))
|
||||
if err != nil {
|
||||
return domain.ModelModeSetting{}, fmt.Errorf("failed to get model mode setting: %w", err)
|
||||
}
|
||||
var config domain.ModelModeSetting
|
||||
if err := json.Unmarshal(setting.Value, &config); err != nil {
|
||||
return domain.ModelModeSetting{}, fmt.Errorf("failed to parse model mode setting: %w", err)
|
||||
}
|
||||
// 无效设置检查
|
||||
if config == (domain.ModelModeSetting{}) || config.Mode == "" {
|
||||
return domain.ModelModeSetting{}, fmt.Errorf("model mode setting is invalid")
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// updateRAGModelsByMode 根据模式更新 RAG 模型(embedding、rerank、analysis、analysisVL)
|
||||
func (u *ModelUsecase) updateRAGModelsByMode(ctx context.Context, mode, autoModeAPIKey string, oldModelModeSetting domain.ModelModeSetting) error {
|
||||
var isTriggerUpsertRecords = true
|
||||
|
||||
// 手动切换到手动模式, 根据IsManualEmbeddingUpdated字段决定
|
||||
if oldModelModeSetting.Mode == consts.ModelSettingModeManual && mode == string(consts.ModelSettingModeManual) {
|
||||
isTriggerUpsertRecords = oldModelModeSetting.IsManualEmbeddingUpdated
|
||||
}
|
||||
|
||||
ragModelTypes := []domain.ModelType{
|
||||
domain.ModelTypeEmbedding,
|
||||
domain.ModelTypeRerank,
|
||||
domain.ModelTypeAnalysis,
|
||||
domain.ModelTypeAnalysisVL,
|
||||
}
|
||||
|
||||
for _, modelType := range ragModelTypes {
|
||||
var model *domain.Model
|
||||
|
||||
if mode == string(consts.ModelSettingModeManual) {
|
||||
// 获取该类型的活跃模型
|
||||
m, err := u.modelRepo.GetModelByType(ctx, modelType)
|
||||
if err != nil {
|
||||
u.logger.Warn("failed to get model by type", log.String("type", string(modelType)), log.Any("error", err))
|
||||
continue
|
||||
}
|
||||
if m == nil || !m.IsActive {
|
||||
u.logger.Warn("no active model found for type", log.String("type", string(modelType)))
|
||||
continue
|
||||
}
|
||||
model = m
|
||||
} else {
|
||||
modelName := consts.GetAutoModeDefaultModel(string(modelType))
|
||||
model = &domain.Model{
|
||||
Model: modelName,
|
||||
Type: modelType,
|
||||
IsActive: true,
|
||||
BaseURL: consts.AutoModeBaseURL,
|
||||
APIKey: autoModeAPIKey,
|
||||
Provider: domain.ModelProviderBrandBaiZhiCloud,
|
||||
}
|
||||
}
|
||||
|
||||
// 更新RAG存储中的模型
|
||||
if model != nil {
|
||||
// rag store中更新失败不影响其他模型更新
|
||||
if err := u.ragStore.UpdateModel(ctx, model); err != nil {
|
||||
u.logger.Error("failed to update model in RAG store", log.String("model_id", model.ID), log.String("type", string(modelType)), log.Any("error", err))
|
||||
continue
|
||||
}
|
||||
u.logger.Info("successfully updated RAG model", log.String("model name: ", string(model.Model)))
|
||||
}
|
||||
}
|
||||
|
||||
// 触发记录更新
|
||||
if isTriggerUpsertRecords {
|
||||
u.logger.Info("embedding model updated, triggering upsert records")
|
||||
return u.TriggerUpsertRecords(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ type NodeUsecase struct {
|
|||
logger *log.Logger
|
||||
s3Client *s3.MinioClient
|
||||
rAGService rag.RAGService
|
||||
modelUsecase *ModelUsecase
|
||||
}
|
||||
|
||||
func NewNodeUsecase(
|
||||
|
|
@ -52,6 +53,7 @@ func NewNodeUsecase(
|
|||
s3Client *s3.MinioClient,
|
||||
modelRepo *pg.ModelRepository,
|
||||
authRepo *pg.AuthRepo,
|
||||
modelUsecase *ModelUsecase,
|
||||
) *NodeUsecase {
|
||||
return &NodeUsecase{
|
||||
nodeRepo: nodeRepo,
|
||||
|
|
@ -65,6 +67,7 @@ func NewNodeUsecase(
|
|||
modelRepo: modelRepo,
|
||||
logger: logger.WithModule("usecase.node"),
|
||||
s3Client: s3Client,
|
||||
modelUsecase: modelUsecase,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +223,7 @@ func (u *NodeUsecase) MoveNode(ctx context.Context, req *domain.MoveNodeReq) err
|
|||
}
|
||||
|
||||
func (u *NodeUsecase) SummaryNode(ctx context.Context, req *domain.NodeSummaryReq) (string, error) {
|
||||
model, err := u.modelRepo.GetChatModel(ctx)
|
||||
model, err := u.modelUsecase.GetChatModel(ctx)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return "", domain.ErrModelNotConfigured
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useState, useRef, useEffect } from 'react';
|
||||
import { Box, Stepper, Step, StepLabel } from '@mui/material';
|
||||
import { Modal } from '@ctzhian/ui';
|
||||
import { Modal, message } from '@ctzhian/ui';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import {
|
||||
setKbC,
|
||||
|
|
@ -10,18 +10,20 @@ import {
|
|||
import { useAppSelector, useAppDispatch } from '@/store';
|
||||
import { postApiV1KnowledgeBaseRelease } from '@/request/KnowledgeBase';
|
||||
import {
|
||||
Step1Config,
|
||||
Step2Import,
|
||||
Step3Publish,
|
||||
Step4Test,
|
||||
Step5Decorate,
|
||||
Step6Complete,
|
||||
Step1Model,
|
||||
Step2Config,
|
||||
Step3Import,
|
||||
Step4Publish,
|
||||
Step5Test,
|
||||
Step6Decorate,
|
||||
Step7Complete,
|
||||
} from './steps';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// Remove interface as we're using Redux state
|
||||
|
||||
const steps = [
|
||||
'模型配置',
|
||||
'配置监听',
|
||||
'录入文档',
|
||||
'发布内容',
|
||||
|
|
@ -31,20 +33,19 @@ const steps = [
|
|||
];
|
||||
|
||||
const CreateWikiModal = () => {
|
||||
const { kb_c, kb_id, kbList, modelStatus } = useAppSelector(
|
||||
state => state.config,
|
||||
);
|
||||
const { kb_c, kb_id, kbList } = useAppSelector(state => state.config);
|
||||
const dispatch = useAppDispatch();
|
||||
const location = useLocation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const [nodeIds, setNodeIds] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const step1ConfigRef = useRef<{ onSubmit: () => Promise<void> }>(null);
|
||||
const step2ImportRef = useRef<{
|
||||
const Step1ModelRef = useRef<{ onSubmit: () => Promise<void> }>(null);
|
||||
const step2ConfigRef = useRef<{ onSubmit: () => Promise<void> }>(null);
|
||||
const step3ImportRef = useRef<{
|
||||
onSubmit: () => Promise<Record<'id', string>[]>;
|
||||
}>(null);
|
||||
const step5DecorateRef = useRef<{ onSubmit: () => Promise<void> }>(null);
|
||||
const step6DecorateRef = useRef<{ onSubmit: () => Promise<void> }>(null);
|
||||
|
||||
const onCancel = () => {
|
||||
dispatch(setKbC(false));
|
||||
|
|
@ -66,7 +67,20 @@ const CreateWikiModal = () => {
|
|||
const handleNext = () => {
|
||||
if (activeStep === 0) {
|
||||
setLoading(true);
|
||||
step1ConfigRef.current
|
||||
Step1ModelRef.current
|
||||
?.onSubmit?.()
|
||||
.then(() => {
|
||||
setActiveStep(prev => prev + 1);
|
||||
})
|
||||
.catch(error => {
|
||||
message.error(error.message || '模型配置验证失败');
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else if (activeStep === 1) {
|
||||
setLoading(true);
|
||||
step2ConfigRef.current
|
||||
?.onSubmit?.()
|
||||
.then(() => {
|
||||
setActiveStep(prev => prev + 1);
|
||||
|
|
@ -74,9 +88,9 @@ const CreateWikiModal = () => {
|
|||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else if (activeStep === 1) {
|
||||
} else if (activeStep === 2) {
|
||||
setLoading(true);
|
||||
step2ImportRef.current
|
||||
step3ImportRef.current
|
||||
?.onSubmit?.()
|
||||
.then(res => {
|
||||
setNodeIds(res.map(item => item.id));
|
||||
|
|
@ -85,17 +99,17 @@ const CreateWikiModal = () => {
|
|||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else if (activeStep === 2) {
|
||||
} else if (activeStep === 3) {
|
||||
setLoading(true);
|
||||
onPublish().finally(() => {
|
||||
setActiveStep(prev => prev + 1);
|
||||
setLoading(false);
|
||||
});
|
||||
} else if (activeStep === 3) {
|
||||
setActiveStep(prev => prev + 1);
|
||||
} else if (activeStep === 4) {
|
||||
setActiveStep(prev => prev + 1);
|
||||
} else if (activeStep === 5) {
|
||||
setLoading(true);
|
||||
step5DecorateRef.current
|
||||
step6DecorateRef.current
|
||||
?.onSubmit?.()
|
||||
.then(() => {
|
||||
setActiveStep(prev => prev + 1);
|
||||
|
|
@ -103,7 +117,7 @@ const CreateWikiModal = () => {
|
|||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else if (activeStep === 5) {
|
||||
} else if (activeStep === 6) {
|
||||
onCancel();
|
||||
}
|
||||
};
|
||||
|
|
@ -117,17 +131,19 @@ const CreateWikiModal = () => {
|
|||
const renderStepContent = () => {
|
||||
switch (activeStep) {
|
||||
case 0:
|
||||
return <Step1Config ref={step1ConfigRef} />;
|
||||
return <Step1Model ref={Step1ModelRef} />;
|
||||
case 1:
|
||||
return <Step2Import ref={step2ImportRef} />;
|
||||
return <Step2Config ref={step2ConfigRef} />;
|
||||
case 2:
|
||||
return <Step3Publish />;
|
||||
return <Step3Import ref={step3ImportRef} />;
|
||||
case 3:
|
||||
return <Step4Test />;
|
||||
return <Step4Publish />;
|
||||
case 4:
|
||||
return <Step5Decorate ref={step5DecorateRef} nodeIds={nodeIds} />;
|
||||
return <Step5Test />;
|
||||
case 5:
|
||||
return <Step6Complete />;
|
||||
return <Step6Decorate ref={step6DecorateRef} nodeIds={nodeIds} />;
|
||||
case 6:
|
||||
return <Step7Complete />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
@ -148,8 +164,8 @@ const CreateWikiModal = () => {
|
|||
}, [kb_c]);
|
||||
|
||||
useEffect(() => {
|
||||
if (kbList?.length === 0 && modelStatus) setOpen(true);
|
||||
}, [kbList, modelStatus]);
|
||||
if (kbList?.length === 0) setOpen(true);
|
||||
}, [kbList]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
import React, {
|
||||
useState,
|
||||
useImperativeHandle,
|
||||
Ref,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useAppSelector, useAppDispatch } from '@/store';
|
||||
import { setModelList } from '@/store/slices/config';
|
||||
import { getApiV1ModelList, getApiV1ModelModeSetting } from '@/request/Model';
|
||||
import { GithubComChaitinPandaWikiDomainModelListItem } from '@/request/types';
|
||||
import ModelConfig, {
|
||||
ModelConfigRef,
|
||||
} from '@/components/System/component/ModelConfig';
|
||||
|
||||
interface Step1ModelProps {
|
||||
ref: Ref<{ onSubmit: () => Promise<void> }>;
|
||||
}
|
||||
|
||||
const Step1Model: React.FC<Step1ModelProps> = ({ ref }) => {
|
||||
const { modelList } = useAppSelector(state => state.config);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const modelConfigRef = useRef<ModelConfigRef>(null);
|
||||
|
||||
const [chatModelData, setChatModelData] =
|
||||
useState<GithubComChaitinPandaWikiDomainModelListItem | null>(null);
|
||||
const [embeddingModelData, setEmbeddingModelData] =
|
||||
useState<GithubComChaitinPandaWikiDomainModelListItem | null>(null);
|
||||
const [rerankModelData, setRerankModelData] =
|
||||
useState<GithubComChaitinPandaWikiDomainModelListItem | null>(null);
|
||||
const [analysisModelData, setAnalysisModelData] =
|
||||
useState<GithubComChaitinPandaWikiDomainModelListItem | null>(null);
|
||||
const [analysisVLModelData, setAnalysisVLModelData] =
|
||||
useState<GithubComChaitinPandaWikiDomainModelListItem | null>(null);
|
||||
|
||||
const getModelList = () => {
|
||||
getApiV1ModelList().then(res => {
|
||||
dispatch(
|
||||
setModelList(res as GithubComChaitinPandaWikiDomainModelListItem[]),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleModelList = (
|
||||
list: GithubComChaitinPandaWikiDomainModelListItem[],
|
||||
) => {
|
||||
const chat = list.find(it => it.type === 'chat') || null;
|
||||
const embedding = list.find(it => it.type === 'embedding') || null;
|
||||
const rerank = list.find(it => it.type === 'rerank') || null;
|
||||
const analysis = list.find(it => it.type === 'analysis') || null;
|
||||
const analysisVL = list.find(it => it.type === 'analysis-vl') || null;
|
||||
setChatModelData(chat);
|
||||
setEmbeddingModelData(embedding);
|
||||
setRerankModelData(rerank);
|
||||
setAnalysisModelData(analysis);
|
||||
setAnalysisVLModelData(analysisVL);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (modelList) {
|
||||
handleModelList(modelList);
|
||||
}
|
||||
}, [modelList]);
|
||||
|
||||
const onSubmit = async () => {
|
||||
// 检查模型模式设置
|
||||
try {
|
||||
const modeSetting = await getApiV1ModelModeSetting();
|
||||
|
||||
// 如果是 auto 模式,检查是否配置了 API key
|
||||
if (modeSetting?.mode === 'auto') {
|
||||
if (!modeSetting.auto_mode_api_key) {
|
||||
return Promise.reject(new Error('请点击应用完成模型配置'));
|
||||
}
|
||||
} else {
|
||||
// 手动模式检查
|
||||
if (
|
||||
!chatModelData ||
|
||||
!embeddingModelData ||
|
||||
!rerankModelData ||
|
||||
!analysisModelData
|
||||
) {
|
||||
return Promise.reject(new Error('请配置必要的模型后点击应用'));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
return Promise.reject(new Error('配置模型失败'));
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
onSubmit,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<ModelConfig
|
||||
ref={modelConfigRef}
|
||||
onCloseModal={() => {}}
|
||||
chatModelData={chatModelData}
|
||||
embeddingModelData={embeddingModelData}
|
||||
rerankModelData={rerankModelData}
|
||||
analysisModelData={analysisModelData}
|
||||
analysisVLModelData={analysisVLModelData}
|
||||
getModelList={getModelList}
|
||||
hideDocumentationHint={true}
|
||||
showTip={true}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Step1Model;
|
||||
|
|
@ -73,11 +73,11 @@ const VALIDATION_RULES = {
|
|||
},
|
||||
};
|
||||
|
||||
interface Step1ConfigProps {
|
||||
interface Step2ConfigProps {
|
||||
ref: Ref<{ onSubmit: () => Promise<unknown> }>;
|
||||
}
|
||||
|
||||
const Step1Config: React.FC<Step1ConfigProps> = ({ ref }) => {
|
||||
const Step2Config: React.FC<Step2ConfigProps> = ({ ref }) => {
|
||||
const {
|
||||
control,
|
||||
formState: { errors },
|
||||
|
|
@ -358,4 +358,4 @@ const Step1Config: React.FC<Step1ConfigProps> = ({ ref }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default Step1Config;
|
||||
export default Step2Config;
|
||||
|
|
@ -5,11 +5,11 @@ import { postApiV1Node } from '@/request/Node';
|
|||
import { INIT_DOC_DATA } from './initData';
|
||||
import { useAppSelector } from '@/store';
|
||||
|
||||
interface Step2ImportProps {
|
||||
interface Step3ImportProps {
|
||||
ref: Ref<{ onSubmit: () => Promise<Record<'id', string>[]> }>;
|
||||
}
|
||||
|
||||
const Step2Import: React.FC<Step2ImportProps> = ({ ref }) => {
|
||||
const Step3Import: React.FC<Step3ImportProps> = ({ ref }) => {
|
||||
const { kb_id } = useAppSelector(state => state.config);
|
||||
const onSubmit = () => {
|
||||
return Promise.all(
|
||||
|
|
@ -42,4 +42,4 @@ const Step2Import: React.FC<Step2ImportProps> = ({ ref }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default Step2Import;
|
||||
export default Step3Import;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Box, Stack, FormControlLabel, Checkbox } from '@mui/material';
|
||||
import publish from '@/assets/images/init/publish.png';
|
||||
|
||||
const Step3Publish = () => {
|
||||
const Step4Publish = () => {
|
||||
return (
|
||||
<Stack gap={2} sx={{ textAlign: 'center', py: 4 }}>
|
||||
<Box component='img' src={publish} sx={{ width: '100%' }}></Box>
|
||||
|
|
@ -18,4 +18,4 @@ const Step3Publish = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default Step3Publish;
|
||||
export default Step4Publish;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Box, Stack } from '@mui/material';
|
||||
import test from '@/assets/images/init/test.png';
|
||||
|
||||
const Step4Test = () => {
|
||||
const Step5Test = () => {
|
||||
return (
|
||||
<Stack gap={2} sx={{ textAlign: 'center', py: 4 }}>
|
||||
<Box component='img' src={test} sx={{ width: '100%' }}></Box>
|
||||
|
|
@ -9,4 +9,4 @@ const Step4Test = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default Step4Test;
|
||||
export default Step5Test;
|
||||
|
|
@ -5,12 +5,12 @@ import { INIT_LADING_DATA } from './initData';
|
|||
import { getApiV1AppDetail, putApiV1App } from '@/request/App';
|
||||
import { useAppSelector } from '@/store';
|
||||
|
||||
interface Step5DecorateProps {
|
||||
interface Step6DecorateProps {
|
||||
ref: Ref<{ onSubmit: () => void }>;
|
||||
nodeIds: string[];
|
||||
}
|
||||
|
||||
const Step5Decorate: React.FC<Step5DecorateProps> = ({ ref, nodeIds }) => {
|
||||
const Step6Decorate: React.FC<Step6DecorateProps> = ({ ref, nodeIds }) => {
|
||||
const { kb_id } = useAppSelector(state => state.config);
|
||||
const onSubmit = () => {
|
||||
return getApiV1AppDetail({
|
||||
|
|
@ -60,4 +60,4 @@ const Step5Decorate: React.FC<Step5DecorateProps> = ({ ref, nodeIds }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default Step5Decorate;
|
||||
export default Step6Decorate;
|
||||
|
|
@ -3,7 +3,7 @@ import { Box, Stack, Button } from '@mui/material';
|
|||
import complete from '@/assets/images/init/complete.png';
|
||||
import { useAppSelector } from '@/store';
|
||||
|
||||
const Step6Complete = () => {
|
||||
const Step7Complete = () => {
|
||||
const { kbDetail } = useAppSelector(state => state.config);
|
||||
|
||||
const wikiUrl = useMemo(() => {
|
||||
|
|
@ -56,4 +56,4 @@ const Step6Complete = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default Step6Complete;
|
||||
export default Step7Complete;
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
export { default as Step1Config } from './Step1Config';
|
||||
export { default as Step2Import } from './Step2Import';
|
||||
export { default as Step3Publish } from './Step3Publish';
|
||||
export { default as Step4Test } from './Step4Test';
|
||||
export { default as Step5Decorate } from './Step5Decorate';
|
||||
export { default as Step6Complete } from './Step6Complete';
|
||||
export { default as Step1Model } from './Step1Model';
|
||||
export { default as Step2Config } from './Step2Config';
|
||||
export { default as Step3Import } from './Step3Import';
|
||||
export { default as Step4Publish } from './Step4Publish';
|
||||
export { default as Step5Test } from './Step5Test';
|
||||
export { default as Step6Decorate } from './Step6Decorate';
|
||||
export { default as Step7Complete } from './Step7Complete';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,279 @@
|
|||
import Card from '@/components/Card';
|
||||
import { Icon, message } from '@ctzhian/ui';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Stack,
|
||||
TextField,
|
||||
Select,
|
||||
MenuItem,
|
||||
InputAdornment,
|
||||
IconButton,
|
||||
} from '@mui/material';
|
||||
import Visibility from '@mui/icons-material/Visibility';
|
||||
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
|
||||
export interface AutoModelConfigRef {
|
||||
getFormData: () => {
|
||||
apiKey: string;
|
||||
selectedModel: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface AutoModelConfigProps {
|
||||
showTip?: boolean;
|
||||
initialApiKey?: string;
|
||||
initialChatModel?: string;
|
||||
onDataChange?: () => void;
|
||||
}
|
||||
|
||||
const AutoModelConfig = forwardRef<AutoModelConfigRef, AutoModelConfigProps>(
|
||||
(props, ref) => {
|
||||
const {
|
||||
showTip = false,
|
||||
initialApiKey = '',
|
||||
initialChatModel = '',
|
||||
onDataChange,
|
||||
} = props;
|
||||
const [autoConfigApiKey, setAutoConfigApiKey] = useState(initialApiKey);
|
||||
const [selectedAutoChatModel, setSelectedAutoChatModel] =
|
||||
useState(initialChatModel);
|
||||
const [showApiKey, setShowApiKey] = useState(false);
|
||||
|
||||
// 默认百智云 Chat 模型列表
|
||||
const DEFAULT_BAIZHI_CLOUD_CHAT_MODELS: string[] = [
|
||||
'deepseek-chat',
|
||||
'deepseek-r1',
|
||||
'kimi-k2-0711-preview',
|
||||
'qwen-vl-max-latest',
|
||||
'glm-4.5',
|
||||
];
|
||||
|
||||
const modelList = DEFAULT_BAIZHI_CLOUD_CHAT_MODELS;
|
||||
|
||||
// 当从父组件接收到新的初始值时,更新状态
|
||||
useEffect(() => {
|
||||
if (initialApiKey) {
|
||||
setAutoConfigApiKey(initialApiKey);
|
||||
}
|
||||
}, [initialApiKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialChatModel) {
|
||||
setSelectedAutoChatModel(initialChatModel);
|
||||
}
|
||||
}, [initialChatModel]);
|
||||
|
||||
// 如果没有选中模型且有可用模型,默认选择第一个
|
||||
useEffect(() => {
|
||||
if (modelList.length && !selectedAutoChatModel) {
|
||||
setSelectedAutoChatModel(modelList[0]);
|
||||
}
|
||||
}, [modelList, selectedAutoChatModel]);
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
getFormData: () => ({
|
||||
apiKey: autoConfigApiKey,
|
||||
selectedModel: selectedAutoChatModel,
|
||||
}),
|
||||
}));
|
||||
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
flex: 1,
|
||||
p: 2,
|
||||
pl: 2,
|
||||
pr: 0,
|
||||
pt: 0,
|
||||
overflow: 'hidden',
|
||||
overflowY: 'auto',
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
{/* 提示信息 */}
|
||||
{showTip && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
gap: 1,
|
||||
p: 1.5,
|
||||
mb: 2,
|
||||
bgcolor: 'rgba(25, 118, 210, 0.08)',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid rgba(25, 118, 210, 0.2)',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
type='icon-info-circle'
|
||||
sx={{
|
||||
fontSize: 16,
|
||||
color: 'primary.main',
|
||||
flexShrink: 0,
|
||||
mt: 0.2,
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: 12,
|
||||
lineHeight: 1.6,
|
||||
color: 'text.secondary',
|
||||
}}
|
||||
>
|
||||
通过 API Key 连接百智云提供平台后,PandaWiki
|
||||
会自动配置好系统所需的问答模型、向量模型、重排序模型、文档分析模型。充分利用平台配置,无需逐个手动配置。
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
mb: '16px',
|
||||
pt: '32px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
color: 'text.primary',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 4,
|
||||
height: 10,
|
||||
bgcolor: 'primary.main',
|
||||
borderRadius: '30%',
|
||||
mr: 1,
|
||||
}}
|
||||
/>
|
||||
API Key
|
||||
</Box>
|
||||
<Box
|
||||
component='a'
|
||||
href='https://model-square.app.baizhi.cloud/token'
|
||||
target='_blank'
|
||||
sx={{
|
||||
color: 'primary.main',
|
||||
fontSize: 12,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.5,
|
||||
}}
|
||||
>
|
||||
<Icon type='icon-key' sx={{ fontSize: 14 }} />
|
||||
获取百智云 API Key
|
||||
</Box>
|
||||
</Box>
|
||||
<TextField
|
||||
fullWidth
|
||||
size='medium'
|
||||
type={showApiKey ? 'text' : 'password'}
|
||||
value={autoConfigApiKey}
|
||||
onChange={e => {
|
||||
setAutoConfigApiKey(e.target.value);
|
||||
onDataChange?.();
|
||||
}}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position='end'>
|
||||
<IconButton
|
||||
size='small'
|
||||
onClick={() => setShowApiKey(s => !s)}
|
||||
>
|
||||
{showApiKey ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
sx={{
|
||||
'& .MuiInputBase-root': {
|
||||
borderRadius: '10px',
|
||||
height: '52px',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{!showTip && (
|
||||
<Box sx={{ mt: 0 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
color: 'text.primary',
|
||||
mb: '16px',
|
||||
pt: '32px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 4,
|
||||
height: 10,
|
||||
bgcolor: 'primary.main',
|
||||
borderRadius: '30%',
|
||||
mr: 1,
|
||||
}}
|
||||
/>
|
||||
模型选择
|
||||
</Box>
|
||||
<Stack direction='row' alignItems='center' gap={2}>
|
||||
<Box sx={{ fontSize: 12, color: 'text.secondary', minWidth: 80 }}>
|
||||
对话模型
|
||||
</Box>
|
||||
<Select
|
||||
size='medium'
|
||||
displayEmpty
|
||||
value={selectedAutoChatModel}
|
||||
onChange={e => {
|
||||
setSelectedAutoChatModel(e.target.value as string);
|
||||
onDataChange?.();
|
||||
}}
|
||||
sx={{
|
||||
width: 300,
|
||||
height: '52px',
|
||||
'& .MuiInputBase-root': {
|
||||
borderRadius: '10px',
|
||||
bgcolor: '#F8F8FA',
|
||||
height: '52px',
|
||||
},
|
||||
'& .MuiSelect-select': {
|
||||
height: '52px',
|
||||
lineHeight: '52px',
|
||||
padding: '0 14px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}}
|
||||
renderValue={sel =>
|
||||
sel && (sel as string).length
|
||||
? (sel as string)
|
||||
: '请选择聊天模型'
|
||||
}
|
||||
>
|
||||
{modelList.map((model: string) => (
|
||||
<MenuItem key={model} value={model}>
|
||||
{model}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default AutoModelConfig;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -37,10 +37,12 @@ const RagErrorReStart = ({
|
|||
const ragErrorData =
|
||||
res?.filter(
|
||||
item =>
|
||||
item.type === 2 &&
|
||||
item.rag_info?.status &&
|
||||
[
|
||||
ConstsNodeRagInfoStatus.NodeRagStatusBasicFailed,
|
||||
ConstsNodeRagInfoStatus.NodeRagStatusEnhanceFailed,
|
||||
ConstsNodeRagInfoStatus.NodeRagStatusBasicPending,
|
||||
].includes(item.rag_info.status),
|
||||
) || [];
|
||||
setList(ragErrorData);
|
||||
|
|
@ -60,14 +62,14 @@ const RagErrorReStart = ({
|
|||
kb_id,
|
||||
node_ids: [...selected],
|
||||
}).then(() => {
|
||||
message.success('正在重新学习');
|
||||
message.success('正在学习');
|
||||
setSelected([]);
|
||||
onClose();
|
||||
refresh();
|
||||
});
|
||||
} else {
|
||||
message.error(
|
||||
list.length > 0 ? '请选择要重新学习的文档' : '暂无学习失败的文档',
|
||||
list.length > 0 ? '请选择需要学习的文档' : '暂无需要学习的文档',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -83,7 +85,7 @@ const RagErrorReStart = ({
|
|||
}, [selected, list]);
|
||||
|
||||
return (
|
||||
<Modal title='重新学习' open={open} onCancel={onClose} onOk={onSubmit}>
|
||||
<Modal title='学习文档' open={open} onCancel={onClose} onOk={onSubmit}>
|
||||
<Stack
|
||||
direction='row'
|
||||
component='label'
|
||||
|
|
@ -97,7 +99,7 @@ const RagErrorReStart = ({
|
|||
}}
|
||||
>
|
||||
<Box>
|
||||
学习失败文档
|
||||
未学习/学习失败文档
|
||||
<Box
|
||||
component='span'
|
||||
sx={{ color: 'text.tertiary', fontSize: 12, pl: 1 }}
|
||||
|
|
|
|||
|
|
@ -25,12 +25,6 @@ import DocDelete from '../../component/DocDelete';
|
|||
|
||||
interface HeaderProps {
|
||||
edit: boolean;
|
||||
collaborativeUsers?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
}>;
|
||||
isSyncing?: boolean;
|
||||
detail: V1NodeDetailResp;
|
||||
updateDetail: (detail: V1NodeDetailResp) => void;
|
||||
handleSave: () => void;
|
||||
|
|
@ -39,8 +33,6 @@ interface HeaderProps {
|
|||
|
||||
const Header = ({
|
||||
edit,
|
||||
collaborativeUsers = [],
|
||||
isSyncing = false,
|
||||
detail,
|
||||
updateDetail,
|
||||
handleSave,
|
||||
|
|
@ -54,10 +46,6 @@ const Header = ({
|
|||
const { catalogOpen, nodeDetail, setCatalogOpen } =
|
||||
useOutletContext<WrapContext>();
|
||||
|
||||
// const docWidth = useMemo(() => {
|
||||
// return nodeDetail?.meta?.doc_width || 'full';
|
||||
// }, [nodeDetail]);
|
||||
|
||||
const [renameOpen, setRenameOpen] = useState(false);
|
||||
const [delOpen, setDelOpen] = useState(false);
|
||||
const [publishOpen, setPublishOpen] = useState(false);
|
||||
|
|
@ -68,22 +56,6 @@ const Header = ({
|
|||
return license.edition === 2;
|
||||
}, [license]);
|
||||
|
||||
// const updateDocWidth = (doc_width: string) => {
|
||||
// if (!nodeDetail) return;
|
||||
// putApiV1NodeDetail({
|
||||
// id: nodeDetail.id!,
|
||||
// kb_id,
|
||||
// doc_width,
|
||||
// }).then(() => {
|
||||
// updateDetail({
|
||||
// meta: {
|
||||
// ...nodeDetail.meta,
|
||||
// doc_width,
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
// };
|
||||
|
||||
const handlePublish = useCallback(() => {
|
||||
if (nodeDetail?.status === 2 && !edit) {
|
||||
message.info('当前已是最新版本!');
|
||||
|
|
|
|||
|
|
@ -39,8 +39,6 @@ const LoadingEditorWrap = () => {
|
|||
>
|
||||
<Header
|
||||
edit={false}
|
||||
isSyncing={isSyncing}
|
||||
collaborativeUsers={collaborativeUsers}
|
||||
detail={{}}
|
||||
updateDetail={() => {}}
|
||||
handleSave={() => {}}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
const { license } = useAppSelector(state => state.config);
|
||||
|
||||
const state = useLocation().state as { node?: V1NodeDetailResp };
|
||||
const { catalogOpen, nodeDetail, setNodeDetail, onSave, docWidth } =
|
||||
const { catalogOpen, setCatalogOpen, nodeDetail, setNodeDetail, onSave } =
|
||||
useOutletContext<WrapContext>();
|
||||
|
||||
const storageTocOpen = localStorage.getItem('toc-open');
|
||||
|
|
@ -197,9 +197,10 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
|
||||
const handleExport = useCallback(
|
||||
async (type: string) => {
|
||||
let value = editorRef?.getContent() || '';
|
||||
if (isMarkdown) {
|
||||
value = nodeDetail?.content || '';
|
||||
if (editorRef) {
|
||||
let value = nodeDetail?.content || '';
|
||||
if (!isMarkdown) {
|
||||
value = editorRef.getContent() || '';
|
||||
}
|
||||
if (!value) return;
|
||||
const content = completeIncompleteLinks(value);
|
||||
|
|
@ -211,24 +212,26 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
message.success('导出成功');
|
||||
}
|
||||
},
|
||||
[editorRef, nodeDetail?.content, nodeDetail?.name, isMarkdown],
|
||||
);
|
||||
|
||||
const checkIfEdited = useCallback(() => {
|
||||
let currentContent = editorRef?.getContent() || '';
|
||||
if (isMarkdown) {
|
||||
currentContent = nodeDetail?.content || '';
|
||||
if (editorRef) {
|
||||
let value = nodeDetail?.content || '';
|
||||
if (!isMarkdown) {
|
||||
value = editorRef.getContent() || '';
|
||||
}
|
||||
const currentSummary = summary;
|
||||
const currentEmoji = nodeDetail?.meta?.emoji || '';
|
||||
|
||||
const hasChanges =
|
||||
currentContent !== initialStateRef.current.content ||
|
||||
value !== initialStateRef.current.content ||
|
||||
currentSummary !== initialStateRef.current.summary ||
|
||||
currentEmoji !== initialStateRef.current.emoji;
|
||||
|
||||
setIsEditing(hasChanges);
|
||||
}
|
||||
}, [
|
||||
editorRef,
|
||||
summary,
|
||||
|
|
@ -277,14 +280,18 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
isMarkdown,
|
||||
]);
|
||||
|
||||
const handleGlobalSave = useCallback(
|
||||
const handleGlobalKeydown = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 's') {
|
||||
event.preventDefault();
|
||||
changeCatalogItem();
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 'b') {
|
||||
event.preventDefault();
|
||||
setCatalogOpen(!catalogOpen);
|
||||
}
|
||||
},
|
||||
[changeCatalogItem],
|
||||
[changeCatalogItem, catalogOpen, setCatalogOpen],
|
||||
);
|
||||
|
||||
const renderEditorTitleEmojiSummary = () => {
|
||||
|
|
@ -522,11 +529,11 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
}, [defaultDetail]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', handleGlobalSave);
|
||||
document.addEventListener('keydown', handleGlobalKeydown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleGlobalSave);
|
||||
document.removeEventListener('keydown', handleGlobalKeydown);
|
||||
};
|
||||
}, [handleGlobalSave]);
|
||||
}, [handleGlobalKeydown]);
|
||||
|
||||
useEffect(() => {
|
||||
if (state && state.node && editorRef.editor) {
|
||||
|
|
@ -633,10 +640,14 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
detail={nodeDetail!}
|
||||
updateDetail={updateDetail}
|
||||
handleSave={async () => {
|
||||
const content = editorRef?.getContent() || '';
|
||||
if (editorRef) {
|
||||
let content = nodeDetail?.content || '';
|
||||
if (!isMarkdown) {
|
||||
content = editorRef.getContent();
|
||||
updateDetail({
|
||||
content: content,
|
||||
});
|
||||
}
|
||||
await onSave(content);
|
||||
initialStateRef.current = {
|
||||
content: content,
|
||||
|
|
@ -644,6 +655,7 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
emoji: nodeDetail?.meta?.emoji || '',
|
||||
};
|
||||
setIsEditing(false);
|
||||
}
|
||||
}}
|
||||
handleExport={handleExport}
|
||||
/>
|
||||
|
|
@ -651,7 +663,10 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
<Toolbar editorRef={editorRef} handleAiGenerate={handleAiGenerate} />
|
||||
)}
|
||||
</Box>
|
||||
<Box sx={{ ...(fixedToc && { display: 'flex' }) }}>
|
||||
<Box
|
||||
sx={{ ...(fixedToc && { display: 'flex' }) }}
|
||||
onKeyDown={event => event.stopPropagation()}
|
||||
>
|
||||
{isMarkdown ? (
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -667,12 +682,13 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
editor={editorRef.editor}
|
||||
value={nodeDetail?.content || ''}
|
||||
onUpload={handleUpload}
|
||||
placeholder='请输入文档内容'
|
||||
onAceChange={value => {
|
||||
updateDetail({
|
||||
content: value,
|
||||
});
|
||||
}}
|
||||
height='calc(100vh - 360px)'
|
||||
height='calc(100vh - 103px)'
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ const Content = () => {
|
|||
const search = searchParams.get('search') || '';
|
||||
const [supportSelect, setBatchOpen] = useState(false);
|
||||
|
||||
const [ragErrorCount, setRagErrorCount] = useState(0);
|
||||
const [ragErrorIds, setRagErrorIds] = useState<string[]>([]);
|
||||
const [ragErrorOpen, setRagErrorOpen] = useState(false);
|
||||
const [ragReStartCount, setRagStartCount] = useState(0);
|
||||
const [ragIds, setRagIds] = useState<string[]>([]);
|
||||
const [ragOpen, setRagOpen] = useState(false);
|
||||
const [publish, setPublish] = useState({
|
||||
// published: 0,
|
||||
unpublished: 0,
|
||||
|
|
@ -128,8 +128,8 @@ const Content = () => {
|
|||
};
|
||||
|
||||
const handleRestudy = (item: ITreeItem) => {
|
||||
setRagErrorOpen(true);
|
||||
setRagErrorIds([item.id]);
|
||||
setRagOpen(true);
|
||||
setRagIds([item.id]);
|
||||
};
|
||||
|
||||
const handleProperties = (item: ITreeItem) => {
|
||||
|
|
@ -265,23 +265,25 @@ const Content = () => {
|
|||
// },
|
||||
]
|
||||
: []),
|
||||
...(item?.rag_status &&
|
||||
...(item.type === 2 &&
|
||||
item.rag_status &&
|
||||
[
|
||||
ConstsNodeRagInfoStatus.NodeRagStatusBasicFailed,
|
||||
ConstsNodeRagInfoStatus.NodeRagStatusEnhanceFailed,
|
||||
ConstsNodeRagInfoStatus.NodeRagStatusBasicPending,
|
||||
].includes(item.rag_status)
|
||||
? [
|
||||
{
|
||||
label: '重新学习',
|
||||
label:
|
||||
item.rag_status ===
|
||||
ConstsNodeRagInfoStatus.NodeRagStatusBasicPending
|
||||
? '学习文档'
|
||||
: '重新学习',
|
||||
key: 'restudy',
|
||||
onClick: () => handleRestudy(item),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(!isEditing
|
||||
? [{ label: '重命名', key: 'rename', onClick: renameItem }]
|
||||
: []),
|
||||
{ label: '删除', key: 'delete', onClick: () => handleDelete(item) },
|
||||
...(item.type === 2
|
||||
? [
|
||||
{
|
||||
|
|
@ -291,6 +293,10 @@ const Content = () => {
|
|||
},
|
||||
]
|
||||
: []),
|
||||
...(!isEditing
|
||||
? [{ label: '重命名', key: 'rename', onClick: renameItem }]
|
||||
: []),
|
||||
{ label: '删除', key: 'delete', onClick: () => handleDelete(item) },
|
||||
];
|
||||
};
|
||||
|
||||
|
|
@ -335,13 +341,15 @@ const Content = () => {
|
|||
setPublish({
|
||||
unpublished: res.filter(it => it.status === 1).length,
|
||||
});
|
||||
setRagErrorCount(
|
||||
setRagStartCount(
|
||||
res.filter(
|
||||
it =>
|
||||
it.type === 2 &&
|
||||
it.rag_info?.status &&
|
||||
[
|
||||
ConstsNodeRagInfoStatus.NodeRagStatusBasicFailed,
|
||||
ConstsNodeRagInfoStatus.NodeRagStatusEnhanceFailed,
|
||||
ConstsNodeRagInfoStatus.NodeRagStatusBasicPending,
|
||||
].includes(it.rag_info.status),
|
||||
).length,
|
||||
);
|
||||
|
|
@ -421,7 +429,7 @@ const Content = () => {
|
|||
</Button>
|
||||
</>
|
||||
)}
|
||||
{ragErrorCount > 0 && (
|
||||
{ragReStartCount > 0 && (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -431,16 +439,16 @@ const Content = () => {
|
|||
ml: 2,
|
||||
}}
|
||||
>
|
||||
{ragErrorCount} 个文档学习失败,
|
||||
{ragReStartCount} 个文档未学习,
|
||||
</Box>
|
||||
<Button
|
||||
size='small'
|
||||
sx={{ minWidth: 0, p: 0, fontSize: 12 }}
|
||||
onClick={() => {
|
||||
setRagErrorOpen(true);
|
||||
setRagOpen(true);
|
||||
}}
|
||||
>
|
||||
重新学习
|
||||
去学习
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -729,11 +737,11 @@ const Content = () => {
|
|||
refresh={getData}
|
||||
/>
|
||||
<RagErrorReStart
|
||||
open={ragErrorOpen}
|
||||
defaultSelected={ragErrorIds}
|
||||
open={ragOpen}
|
||||
defaultSelected={ragIds}
|
||||
onClose={() => {
|
||||
setRagErrorOpen(false);
|
||||
setRagErrorIds([]);
|
||||
setRagOpen(false);
|
||||
setRagIds([]);
|
||||
}}
|
||||
refresh={getData}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -15,8 +15,11 @@ import {
|
|||
DomainCreateModelReq,
|
||||
DomainGetProviderModelListReq,
|
||||
DomainGetProviderModelListResp,
|
||||
DomainModelModeSetting,
|
||||
DomainPWResponse,
|
||||
DomainResponse,
|
||||
DomainSwitchModeReq,
|
||||
DomainSwitchModeResp,
|
||||
DomainUpdateModelReq,
|
||||
GithubComChaitinPandaWikiDomainCheckModelReq,
|
||||
GithubComChaitinPandaWikiDomainCheckModelResp,
|
||||
|
|
@ -124,6 +127,32 @@ export const getApiV1ModelList = (params: RequestParams = {}) =>
|
|||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description get current model mode setting including mode, API key and chat model
|
||||
*
|
||||
* @tags model
|
||||
* @name GetApiV1ModelModeSetting
|
||||
* @summary get model mode setting
|
||||
* @request GET:/api/v1/model/mode-setting
|
||||
* @response `200` `(DomainResponse & {
|
||||
data?: DomainModelModeSetting,
|
||||
|
||||
})` OK
|
||||
*/
|
||||
|
||||
export const getApiV1ModelModeSetting = (params: RequestParams = {}) =>
|
||||
httpRequest<
|
||||
DomainResponse & {
|
||||
data?: DomainModelModeSetting;
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/model/mode-setting`,
|
||||
method: "GET",
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description get provider supported model list
|
||||
*
|
||||
|
|
@ -153,3 +182,33 @@ export const postApiV1ModelProviderSupported = (
|
|||
format: "json",
|
||||
...requestParams,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description switch model mode between manual and auto
|
||||
*
|
||||
* @tags model
|
||||
* @name PostApiV1ModelSwitchMode
|
||||
* @summary switch mode
|
||||
* @request POST:/api/v1/model/switch-mode
|
||||
* @response `200` `(DomainResponse & {
|
||||
data?: DomainSwitchModeResp,
|
||||
|
||||
})` OK
|
||||
*/
|
||||
|
||||
export const postApiV1ModelSwitchMode = (
|
||||
request: DomainSwitchModeReq,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
httpRequest<
|
||||
DomainResponse & {
|
||||
data?: DomainSwitchModeResp;
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/model/switch-mode`,
|
||||
method: "POST",
|
||||
body: request,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -922,6 +922,15 @@ export interface DomainMetricsConfig {
|
|||
type?: string;
|
||||
}
|
||||
|
||||
export interface DomainModelModeSetting {
|
||||
/** 百智云 API Key */
|
||||
auto_mode_api_key?: string;
|
||||
/** 自定义对话模型名称 */
|
||||
chat_model?: string;
|
||||
/** 模式: manual 或 auto */
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
export interface DomainMoveNodeReq {
|
||||
id: string;
|
||||
kb_id: string;
|
||||
|
|
@ -1195,6 +1204,18 @@ export interface DomainStatPageReq {
|
|||
scene: 1 | 2 | 3 | 4;
|
||||
}
|
||||
|
||||
export interface DomainSwitchModeReq {
|
||||
/** 百智云 API Key */
|
||||
auto_mode_api_key?: string;
|
||||
/** 自定义对话模型名称 */
|
||||
chat_model?: string;
|
||||
mode: "manual" | "auto";
|
||||
}
|
||||
|
||||
export interface DomainSwitchModeResp {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface DomainTextConfig {
|
||||
title?: string;
|
||||
type?: string;
|
||||
|
|
|
|||
|
|
@ -8,19 +8,11 @@ import { useEffect, useRef } from 'react';
|
|||
import { useWrapContext } from '..';
|
||||
|
||||
interface HeaderProps {
|
||||
edit: boolean;
|
||||
collaborativeUsers?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
}>;
|
||||
isSyncing?: boolean;
|
||||
detail: V1NodeDetailResp;
|
||||
updateDetail: (detail: V1NodeDetailResp) => void;
|
||||
handleSave: () => void;
|
||||
}
|
||||
|
||||
const Header = ({ edit, detail, handleSave }: HeaderProps) => {
|
||||
const Header = ({ detail, handleSave }: HeaderProps) => {
|
||||
const firstLoad = useRef(true);
|
||||
|
||||
const { catalogOpen, nodeDetail, setCatalogOpen, saveLoading } =
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
'use client';
|
||||
import { V1NodeDetailResp } from '@/request/types';
|
||||
import { useTiptap } from '@ctzhian/tiptap';
|
||||
import { Icon } from '@ctzhian/ui';
|
||||
import { Box, Skeleton, Stack } from '@mui/material';
|
||||
|
|
@ -35,14 +36,7 @@ const LoadingEditorWrap = () => {
|
|||
transition: 'left 0.3s ease-in-out',
|
||||
}}
|
||||
>
|
||||
<Header
|
||||
edit={false}
|
||||
isSyncing={isSyncing}
|
||||
collaborativeUsers={collaborativeUsers}
|
||||
detail={{}}
|
||||
updateDetail={() => {}}
|
||||
handleSave={() => {}}
|
||||
/>
|
||||
<Header detail={{} as V1NodeDetailResp} handleSave={() => {}} />
|
||||
{editorRef.editor && <Toolbar editorRef={editorRef} />}
|
||||
</Box>
|
||||
<Box>
|
||||
|
|
|
|||
|
|
@ -179,10 +179,14 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
|
|||
}}
|
||||
>
|
||||
<Header
|
||||
edit={isEditing}
|
||||
detail={nodeDetail!}
|
||||
updateDetail={updateDetail}
|
||||
handleSave={async () => {
|
||||
if (!isMarkdown) {
|
||||
const value = editorRef.getContent();
|
||||
updateDetail({
|
||||
content: value,
|
||||
});
|
||||
}
|
||||
if (checkRequiredFields()) {
|
||||
setConfirmModalOpen(true);
|
||||
}
|
||||
|
|
@ -299,6 +303,7 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
|
|||
ref={markdownEditorRef}
|
||||
editor={editorRef.editor}
|
||||
value={nodeDetail?.content || defaultDetail?.content || ''}
|
||||
placeholder='请输入文档内容'
|
||||
onAceChange={value => {
|
||||
updateDetail({
|
||||
content: value,
|
||||
|
|
@ -351,12 +356,17 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
|
|||
open={confirmModalOpen}
|
||||
onCancel={() => setConfirmModalOpen(false)}
|
||||
onOk={async (reason: string, token: string) => {
|
||||
const value = editorRef.getContent();
|
||||
if (editorRef) {
|
||||
let value = nodeDetail?.content || '';
|
||||
if (!isMarkdown) {
|
||||
value = editorRef.getContent();
|
||||
updateDetail({
|
||||
content: value,
|
||||
});
|
||||
}
|
||||
await onSave(value, reason, token, isMarkdown ? 'md' : 'html');
|
||||
setConfirmModalOpen(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.12.1",
|
||||
"dependencies": {
|
||||
"@ctzhian/tiptap": "^1.12.20",
|
||||
"@ctzhian/tiptap": "^1.12.21",
|
||||
"@ctzhian/ui": "^7.0.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ importers:
|
|||
.:
|
||||
dependencies:
|
||||
'@ctzhian/tiptap':
|
||||
specifier: ^1.12.20
|
||||
version: 1.12.20(3f8aa6e4b731b59772b9acd58d22fc94)
|
||||
specifier: ^1.12.21
|
||||
version: 1.12.21(3f8aa6e4b731b59772b9acd58d22fc94)
|
||||
'@ctzhian/ui':
|
||||
specifier: ^7.0.5
|
||||
version: 7.0.5(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/icons-material@7.3.4(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/utils@7.3.3(@types/react@19.2.2)(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
|
|
@ -514,8 +514,8 @@ packages:
|
|||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@ctzhian/tiptap@1.12.20':
|
||||
resolution: {integrity: sha512-FLGgzZcvNpf1ncgPdagFaHEfqnzkWjiRw3s9tT1loyhaX+KrxQRjY86MW3qPh7qB6WIhZsfb8T1sgWLwRNN0/Q==}
|
||||
'@ctzhian/tiptap@1.12.21':
|
||||
resolution: {integrity: sha512-BU6ZfMbt1UzX6XeTpGvbdp2fAF/wnBz3HcjcXm8Tf4HODPrO7alIF6fPaTPOrWVnWmSCYqBEE4msYiDpAS6TXA==}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11
|
||||
'@emotion/styled': ^11
|
||||
|
|
@ -5995,7 +5995,7 @@ snapshots:
|
|||
- react-native
|
||||
- typescript
|
||||
|
||||
'@ctzhian/tiptap@1.12.20(3f8aa6e4b731b59772b9acd58d22fc94)':
|
||||
'@ctzhian/tiptap@1.12.21(3f8aa6e4b731b59772b9acd58d22fc94)':
|
||||
dependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0)
|
||||
|
|
|
|||
Loading…
Reference in New Issue