mirror of https://github.com/chaitin/PandaWiki.git
Compare commits
39 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
1c30e16fd6 | |
|
|
cd804dbddd | |
|
|
574fc999a4 | |
|
|
ab24f7b78b | |
|
|
5347726535 | |
|
|
7c03aa7f6d | |
|
|
a5e649a1bf | |
|
|
59afc05284 | |
|
|
1dd222f759 | |
|
|
409b69dda1 | |
|
|
a76dbc4d62 | |
|
|
94da5431ad | |
|
|
4e2db886e2 | |
|
|
5c901aa6b3 | |
|
|
f80d240944 | |
|
|
189aa2e286 | |
|
|
a8999c3dba | |
|
|
5ba454e084 | |
|
|
24a5a1574b | |
|
|
9f9e6388fe | |
|
|
5ba7c6b73a | |
|
|
9350e631a4 | |
|
|
039b6b3061 | |
|
|
9fa8ad5177 | |
|
|
2d7c20c799 | |
|
|
4a980b92c5 | |
|
|
fe2d70bfa9 | |
|
|
c9f3a61e86 | |
|
|
7b75f1bf55 | |
|
|
07646f0823 | |
|
|
63a8d8a743 | |
|
|
587c2842be | |
|
|
fd34bb4c55 | |
|
|
46afba778f | |
|
|
79234c2394 | |
|
|
67e796dbb7 | |
|
|
55c563cc48 | |
|
|
cfdc546d20 | |
|
|
da9039ff37 |
|
|
@ -30,6 +30,7 @@ type NodeDetailResp struct {
|
|||
CreatorAccount string `json:"creator_account"`
|
||||
EditorAccount string `json:"editor_account"`
|
||||
PublisherAccount string `json:"publisher_account" gorm:"-"`
|
||||
PV int64 `json:"pv" gorm:"-"`
|
||||
}
|
||||
|
||||
type NodePermissionReq struct {
|
||||
|
|
|
|||
|
|
@ -25,4 +25,5 @@ type ShareNodeDetailResp struct {
|
|||
EditorAccount string `json:"editor_account"`
|
||||
PublisherAccount string `json:"publisher_account"`
|
||||
List []*domain.ShareNodeDetailItem `json:"list" gorm:"-"`
|
||||
PV int64 `json:"pv" gorm:"-"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package v1
|
||||
|
||||
type WechatAppInfoResp struct {
|
||||
WeChatAppIsEnabled bool `json:"wechat_app_is_enabled"`
|
||||
FeedbackEnable bool `json:"feedback_enable"`
|
||||
FeedbackType []string `json:"feedback_type"`
|
||||
DisclaimerContent string `json:"disclaimer_content"`
|
||||
}
|
||||
|
|
@ -166,7 +166,7 @@ func createApp() (*App, error) {
|
|||
wechatRepository := pg2.NewWechatRepository(db, logger)
|
||||
wechatUsecase := usecase.NewWechatUsecase(logger, appUsecase, chatUsecase, wechatRepository, authRepo)
|
||||
wecomUsecase := usecase.NewWecomUsecase(logger, cacheCache, appUsecase, chatUsecase, authRepo)
|
||||
wechatAppUsecase := usecase.NewWechatAppUsecase(logger, appUsecase, chatUsecase, wechatRepository, authRepo)
|
||||
wechatAppUsecase := usecase.NewWechatAppUsecase(logger, appUsecase, chatUsecase, wechatRepository, authRepo, appRepository)
|
||||
shareWechatHandler := share.NewShareWechatHandler(echo, baseHandler, logger, appUsecase, conversationUsecase, wechatUsecase, wecomUsecase, wechatAppUsecase)
|
||||
shareCaptchaHandler := share.NewShareCaptchaHandler(baseHandler, echo, logger)
|
||||
openapiV1Handler := share.NewOpenapiV1Handler(echo, baseHandler, logger, authUsecase, appUsecase)
|
||||
|
|
@ -185,7 +185,8 @@ func createApp() (*App, error) {
|
|||
OpenapiV1Handler: openapiV1Handler,
|
||||
ShareCommonHandler: shareCommonHandler,
|
||||
}
|
||||
client, err := telemetry.NewClient(logger, knowledgeBaseRepository)
|
||||
mcpRepository := pg2.NewMCPRepository(db, logger)
|
||||
client, err := telemetry.NewClient(logger, knowledgeBaseRepository, modelUsecase, userUsecase, nodeRepository, conversationRepository, mcpRepository, configConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ const (
|
|||
SourceTypeDiscordBot SourceType = "discord_bot"
|
||||
SourceTypeWechatOfficialAccount SourceType = "wechat_official_account"
|
||||
SourceTypeOpenAIAPI SourceType = "openai_api"
|
||||
SourceTypeMcpServer SourceType = "mcp_server"
|
||||
)
|
||||
|
||||
func (s SourceType) Name() string {
|
||||
|
|
@ -46,6 +47,8 @@ func (s SourceType) Name() string {
|
|||
return "Discord 机器人"
|
||||
case SourceTypeWechatOfficialAccount:
|
||||
return "微信公众号"
|
||||
case SourceTypeMcpServer:
|
||||
return "MCP 服务器"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,7 +240,8 @@ const docTemplate = `{
|
|||
"wechat_service_bot",
|
||||
"discord_bot",
|
||||
"wechat_official_account",
|
||||
"openai_api"
|
||||
"openai_api",
|
||||
"mcp_server"
|
||||
],
|
||||
"type": "string",
|
||||
"x-enum-varnames": [
|
||||
|
|
@ -260,7 +261,8 @@ const docTemplate = `{
|
|||
"SourceTypeWechatServiceBot",
|
||||
"SourceTypeDiscordBot",
|
||||
"SourceTypeWechatOfficialAccount",
|
||||
"SourceTypeOpenAIAPI"
|
||||
"SourceTypeOpenAIAPI",
|
||||
"SourceTypeMcpServer"
|
||||
],
|
||||
"name": "source_type",
|
||||
"in": "query",
|
||||
|
|
@ -3022,6 +3024,50 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/share/v1/app/wechat/info": {
|
||||
"get": {
|
||||
"description": "WechatAppInfo",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"share_chat"
|
||||
],
|
||||
"summary": "WechatAppInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "kb id",
|
||||
"name": "X-KB-ID",
|
||||
"in": "header",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/v1.WechatAppInfoResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/share/v1/app/wechat/service/answer": {
|
||||
"get": {
|
||||
"description": "GetWechatAnswer",
|
||||
|
|
@ -4268,7 +4314,8 @@ const docTemplate = `{
|
|||
"wechat_service_bot",
|
||||
"discord_bot",
|
||||
"wechat_official_account",
|
||||
"openai_api"
|
||||
"openai_api",
|
||||
"mcp_server"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"SourceTypeDingTalk",
|
||||
|
|
@ -4287,7 +4334,8 @@ const docTemplate = `{
|
|||
"SourceTypeWechatServiceBot",
|
||||
"SourceTypeDiscordBot",
|
||||
"SourceTypeWechatOfficialAccount",
|
||||
"SourceTypeOpenAIAPI"
|
||||
"SourceTypeOpenAIAPI",
|
||||
"SourceTypeMcpServer"
|
||||
]
|
||||
},
|
||||
"consts.StatDay": {
|
||||
|
|
@ -4512,9 +4560,6 @@ const docTemplate = `{
|
|||
}
|
||||
]
|
||||
},
|
||||
"auto_sitemap": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"body_code": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4623,6 +4668,14 @@ const docTemplate = `{
|
|||
}
|
||||
]
|
||||
},
|
||||
"mcp_server_settings": {
|
||||
"description": "MCP Server Settings",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.MCPServerSettings"
|
||||
}
|
||||
]
|
||||
},
|
||||
"openai_api_bot_settings": {
|
||||
"description": "OpenAI API Bot settings",
|
||||
"allOf": [
|
||||
|
|
@ -4646,6 +4699,9 @@ const docTemplate = `{
|
|||
"search_placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"stats_setting": {
|
||||
"$ref": "#/definitions/domain.StatsSetting"
|
||||
},
|
||||
"theme_and_style": {
|
||||
"$ref": "#/definitions/domain.ThemeAndStyle"
|
||||
},
|
||||
|
|
@ -4698,6 +4754,9 @@ const docTemplate = `{
|
|||
"web_app_landing_theme": {
|
||||
"$ref": "#/definitions/domain.WebAppLandingTheme"
|
||||
},
|
||||
"wechat_app_advanced_setting": {
|
||||
"$ref": "#/definitions/domain.WeChatAppAdvancedSetting"
|
||||
},
|
||||
"wechat_app_agent_id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4794,9 +4853,6 @@ const docTemplate = `{
|
|||
}
|
||||
]
|
||||
},
|
||||
"auto_sitemap": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"body_code": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4896,6 +4952,14 @@ const docTemplate = `{
|
|||
}
|
||||
]
|
||||
},
|
||||
"mcp_server_settings": {
|
||||
"description": "MCP Server Settings",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.MCPServerSettings"
|
||||
}
|
||||
]
|
||||
},
|
||||
"openai_api_bot_settings": {
|
||||
"description": "OpenAI API settings",
|
||||
"allOf": [
|
||||
|
|
@ -4919,6 +4983,9 @@ const docTemplate = `{
|
|||
"search_placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"stats_setting": {
|
||||
"$ref": "#/definitions/domain.StatsSetting"
|
||||
},
|
||||
"theme_and_style": {
|
||||
"$ref": "#/definitions/domain.ThemeAndStyle"
|
||||
},
|
||||
|
|
@ -4962,6 +5029,9 @@ const docTemplate = `{
|
|||
"web_app_landing_theme": {
|
||||
"$ref": "#/definitions/domain.WebAppLandingTheme"
|
||||
},
|
||||
"wechat_app_advanced_setting": {
|
||||
"$ref": "#/definitions/domain.WeChatAppAdvancedSetting"
|
||||
},
|
||||
"wechat_app_agent_id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -5056,7 +5126,8 @@ const docTemplate = `{
|
|||
8,
|
||||
9,
|
||||
10,
|
||||
11
|
||||
11,
|
||||
12
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"AppTypeWeb",
|
||||
|
|
@ -5069,7 +5140,8 @@ const docTemplate = `{
|
|||
"AppTypeWechatOfficialAccount",
|
||||
"AppTypeOpenAIAPI",
|
||||
"AppTypeWecomAIBot",
|
||||
"AppTypeLarkBot"
|
||||
"AppTypeLarkBot",
|
||||
"AppTypeMcpServer"
|
||||
]
|
||||
},
|
||||
"domain.AuthUserInfo": {
|
||||
|
|
@ -6378,6 +6450,31 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.MCPServerSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"docs_tool_settings": {
|
||||
"$ref": "#/definitions/domain.MCPToolSettings"
|
||||
},
|
||||
"is_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sample_auth": {
|
||||
"$ref": "#/definitions/domain.SimpleAuth"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.MCPToolSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"desc": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.MessageContent": {
|
||||
"type": "object"
|
||||
},
|
||||
|
|
@ -7336,6 +7433,14 @@ const docTemplate = `{
|
|||
"StatPageSceneLogin"
|
||||
]
|
||||
},
|
||||
"domain.StatsSetting": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pv_enable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.SwitchModeReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -7574,6 +7679,29 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.WeChatAppAdvancedSetting": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"disclaimer_content": {
|
||||
"type": "string"
|
||||
},
|
||||
"feedback_enable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"feedback_type": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string"
|
||||
},
|
||||
"text_response_enable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.WebAppCommentSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -8532,6 +8660,9 @@ const docTemplate = `{
|
|||
"publisher_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"pv": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/domain.NodeStatus"
|
||||
},
|
||||
|
|
@ -8709,6 +8840,9 @@ const docTemplate = `{
|
|||
"publisher_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"pv": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/domain.NodeStatus"
|
||||
},
|
||||
|
|
@ -8790,6 +8924,26 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.WechatAppInfoResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"disclaimer_content": {
|
||||
"type": "string"
|
||||
},
|
||||
"feedback_enable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"feedback_type": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"wechat_app_is_enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
|
|
|
|||
|
|
@ -233,7 +233,8 @@
|
|||
"wechat_service_bot",
|
||||
"discord_bot",
|
||||
"wechat_official_account",
|
||||
"openai_api"
|
||||
"openai_api",
|
||||
"mcp_server"
|
||||
],
|
||||
"type": "string",
|
||||
"x-enum-varnames": [
|
||||
|
|
@ -253,7 +254,8 @@
|
|||
"SourceTypeWechatServiceBot",
|
||||
"SourceTypeDiscordBot",
|
||||
"SourceTypeWechatOfficialAccount",
|
||||
"SourceTypeOpenAIAPI"
|
||||
"SourceTypeOpenAIAPI",
|
||||
"SourceTypeMcpServer"
|
||||
],
|
||||
"name": "source_type",
|
||||
"in": "query",
|
||||
|
|
@ -3015,6 +3017,50 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/share/v1/app/wechat/info": {
|
||||
"get": {
|
||||
"description": "WechatAppInfo",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"share_chat"
|
||||
],
|
||||
"summary": "WechatAppInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "kb id",
|
||||
"name": "X-KB-ID",
|
||||
"in": "header",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/v1.WechatAppInfoResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/share/v1/app/wechat/service/answer": {
|
||||
"get": {
|
||||
"description": "GetWechatAnswer",
|
||||
|
|
@ -4261,7 +4307,8 @@
|
|||
"wechat_service_bot",
|
||||
"discord_bot",
|
||||
"wechat_official_account",
|
||||
"openai_api"
|
||||
"openai_api",
|
||||
"mcp_server"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"SourceTypeDingTalk",
|
||||
|
|
@ -4280,7 +4327,8 @@
|
|||
"SourceTypeWechatServiceBot",
|
||||
"SourceTypeDiscordBot",
|
||||
"SourceTypeWechatOfficialAccount",
|
||||
"SourceTypeOpenAIAPI"
|
||||
"SourceTypeOpenAIAPI",
|
||||
"SourceTypeMcpServer"
|
||||
]
|
||||
},
|
||||
"consts.StatDay": {
|
||||
|
|
@ -4505,9 +4553,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"auto_sitemap": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"body_code": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4616,6 +4661,14 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"mcp_server_settings": {
|
||||
"description": "MCP Server Settings",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.MCPServerSettings"
|
||||
}
|
||||
]
|
||||
},
|
||||
"openai_api_bot_settings": {
|
||||
"description": "OpenAI API Bot settings",
|
||||
"allOf": [
|
||||
|
|
@ -4639,6 +4692,9 @@
|
|||
"search_placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"stats_setting": {
|
||||
"$ref": "#/definitions/domain.StatsSetting"
|
||||
},
|
||||
"theme_and_style": {
|
||||
"$ref": "#/definitions/domain.ThemeAndStyle"
|
||||
},
|
||||
|
|
@ -4691,6 +4747,9 @@
|
|||
"web_app_landing_theme": {
|
||||
"$ref": "#/definitions/domain.WebAppLandingTheme"
|
||||
},
|
||||
"wechat_app_advanced_setting": {
|
||||
"$ref": "#/definitions/domain.WeChatAppAdvancedSetting"
|
||||
},
|
||||
"wechat_app_agent_id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4787,9 +4846,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"auto_sitemap": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"body_code": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4889,6 +4945,14 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"mcp_server_settings": {
|
||||
"description": "MCP Server Settings",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.MCPServerSettings"
|
||||
}
|
||||
]
|
||||
},
|
||||
"openai_api_bot_settings": {
|
||||
"description": "OpenAI API settings",
|
||||
"allOf": [
|
||||
|
|
@ -4912,6 +4976,9 @@
|
|||
"search_placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"stats_setting": {
|
||||
"$ref": "#/definitions/domain.StatsSetting"
|
||||
},
|
||||
"theme_and_style": {
|
||||
"$ref": "#/definitions/domain.ThemeAndStyle"
|
||||
},
|
||||
|
|
@ -4955,6 +5022,9 @@
|
|||
"web_app_landing_theme": {
|
||||
"$ref": "#/definitions/domain.WebAppLandingTheme"
|
||||
},
|
||||
"wechat_app_advanced_setting": {
|
||||
"$ref": "#/definitions/domain.WeChatAppAdvancedSetting"
|
||||
},
|
||||
"wechat_app_agent_id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -5049,7 +5119,8 @@
|
|||
8,
|
||||
9,
|
||||
10,
|
||||
11
|
||||
11,
|
||||
12
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"AppTypeWeb",
|
||||
|
|
@ -5062,7 +5133,8 @@
|
|||
"AppTypeWechatOfficialAccount",
|
||||
"AppTypeOpenAIAPI",
|
||||
"AppTypeWecomAIBot",
|
||||
"AppTypeLarkBot"
|
||||
"AppTypeLarkBot",
|
||||
"AppTypeMcpServer"
|
||||
]
|
||||
},
|
||||
"domain.AuthUserInfo": {
|
||||
|
|
@ -6371,6 +6443,31 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.MCPServerSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"docs_tool_settings": {
|
||||
"$ref": "#/definitions/domain.MCPToolSettings"
|
||||
},
|
||||
"is_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sample_auth": {
|
||||
"$ref": "#/definitions/domain.SimpleAuth"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.MCPToolSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"desc": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.MessageContent": {
|
||||
"type": "object"
|
||||
},
|
||||
|
|
@ -7329,6 +7426,14 @@
|
|||
"StatPageSceneLogin"
|
||||
]
|
||||
},
|
||||
"domain.StatsSetting": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pv_enable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.SwitchModeReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -7567,6 +7672,29 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.WeChatAppAdvancedSetting": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"disclaimer_content": {
|
||||
"type": "string"
|
||||
},
|
||||
"feedback_enable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"feedback_type": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string"
|
||||
},
|
||||
"text_response_enable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.WebAppCommentSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -8525,6 +8653,9 @@
|
|||
"publisher_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"pv": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/domain.NodeStatus"
|
||||
},
|
||||
|
|
@ -8702,6 +8833,9 @@
|
|||
"publisher_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"pv": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/domain.NodeStatus"
|
||||
},
|
||||
|
|
@ -8783,6 +8917,26 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.WechatAppInfoResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"disclaimer_content": {
|
||||
"type": "string"
|
||||
},
|
||||
"feedback_enable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"feedback_type": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"wechat_app_is_enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
|
|
|
|||
|
|
@ -245,6 +245,7 @@ definitions:
|
|||
- discord_bot
|
||||
- wechat_official_account
|
||||
- openai_api
|
||||
- mcp_server
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- SourceTypeDingTalk
|
||||
|
|
@ -264,6 +265,7 @@ definitions:
|
|||
- SourceTypeDiscordBot
|
||||
- SourceTypeWechatOfficialAccount
|
||||
- SourceTypeOpenAIAPI
|
||||
- SourceTypeMcpServer
|
||||
consts.StatDay:
|
||||
enum:
|
||||
- 1
|
||||
|
|
@ -418,8 +420,6 @@ definitions:
|
|||
allOf:
|
||||
- $ref: '#/definitions/domain.AIFeedbackSettings'
|
||||
description: AI feedback
|
||||
auto_sitemap:
|
||||
type: boolean
|
||||
body_code:
|
||||
type: string
|
||||
btns:
|
||||
|
|
@ -488,6 +488,10 @@ definitions:
|
|||
allOf:
|
||||
- $ref: '#/definitions/domain.LarkBotSettings'
|
||||
description: LarkBot
|
||||
mcp_server_settings:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.MCPServerSettings'
|
||||
description: MCP Server Settings
|
||||
openai_api_bot_settings:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.OpenAIAPIBotSettings'
|
||||
|
|
@ -502,6 +506,8 @@ definitions:
|
|||
type: array
|
||||
search_placeholder:
|
||||
type: string
|
||||
stats_setting:
|
||||
$ref: '#/definitions/domain.StatsSetting'
|
||||
theme_and_style:
|
||||
$ref: '#/definitions/domain.ThemeAndStyle'
|
||||
theme_mode:
|
||||
|
|
@ -534,6 +540,8 @@ definitions:
|
|||
type: array
|
||||
web_app_landing_theme:
|
||||
$ref: '#/definitions/domain.WebAppLandingTheme'
|
||||
wechat_app_advanced_setting:
|
||||
$ref: '#/definitions/domain.WeChatAppAdvancedSetting'
|
||||
wechat_app_agent_id:
|
||||
type: string
|
||||
wechat_app_corpid:
|
||||
|
|
@ -595,8 +603,6 @@ definitions:
|
|||
allOf:
|
||||
- $ref: '#/definitions/domain.AIFeedbackSettings'
|
||||
description: AI feedback
|
||||
auto_sitemap:
|
||||
type: boolean
|
||||
body_code:
|
||||
type: string
|
||||
btns:
|
||||
|
|
@ -660,6 +666,10 @@ definitions:
|
|||
allOf:
|
||||
- $ref: '#/definitions/domain.LarkBotSettings'
|
||||
description: LarkBot
|
||||
mcp_server_settings:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.MCPServerSettings'
|
||||
description: MCP Server Settings
|
||||
openai_api_bot_settings:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.OpenAIAPIBotSettings'
|
||||
|
|
@ -674,6 +684,8 @@ definitions:
|
|||
type: array
|
||||
search_placeholder:
|
||||
type: string
|
||||
stats_setting:
|
||||
$ref: '#/definitions/domain.StatsSetting'
|
||||
theme_and_style:
|
||||
$ref: '#/definitions/domain.ThemeAndStyle'
|
||||
theme_mode:
|
||||
|
|
@ -701,6 +713,8 @@ definitions:
|
|||
type: array
|
||||
web_app_landing_theme:
|
||||
$ref: '#/definitions/domain.WebAppLandingTheme'
|
||||
wechat_app_advanced_setting:
|
||||
$ref: '#/definitions/domain.WeChatAppAdvancedSetting'
|
||||
wechat_app_agent_id:
|
||||
type: string
|
||||
wechat_app_corpid:
|
||||
|
|
@ -767,6 +781,7 @@ definitions:
|
|||
- 9
|
||||
- 10
|
||||
- 11
|
||||
- 12
|
||||
format: int32
|
||||
type: integer
|
||||
x-enum-varnames:
|
||||
|
|
@ -781,6 +796,7 @@ definitions:
|
|||
- AppTypeOpenAIAPI
|
||||
- AppTypeWecomAIBot
|
||||
- AppTypeLarkBot
|
||||
- AppTypeMcpServer
|
||||
domain.AuthUserInfo:
|
||||
properties:
|
||||
avatar_url:
|
||||
|
|
@ -1625,6 +1641,22 @@ definitions:
|
|||
url:
|
||||
type: string
|
||||
type: object
|
||||
domain.MCPServerSettings:
|
||||
properties:
|
||||
docs_tool_settings:
|
||||
$ref: '#/definitions/domain.MCPToolSettings'
|
||||
is_enabled:
|
||||
type: boolean
|
||||
sample_auth:
|
||||
$ref: '#/definitions/domain.SimpleAuth'
|
||||
type: object
|
||||
domain.MCPToolSettings:
|
||||
properties:
|
||||
desc:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
domain.MessageContent:
|
||||
type: object
|
||||
domain.MessageFrom:
|
||||
|
|
@ -2253,6 +2285,11 @@ definitions:
|
|||
- StatPageSceneNodeDetail
|
||||
- StatPageSceneChat
|
||||
- StatPageSceneLogin
|
||||
domain.StatsSetting:
|
||||
properties:
|
||||
pv_enable:
|
||||
type: boolean
|
||||
type: object
|
||||
domain.SwitchModeReq:
|
||||
properties:
|
||||
auto_mode_api_key:
|
||||
|
|
@ -2411,6 +2448,21 @@ definitions:
|
|||
user_id:
|
||||
type: string
|
||||
type: object
|
||||
domain.WeChatAppAdvancedSetting:
|
||||
properties:
|
||||
disclaimer_content:
|
||||
type: string
|
||||
feedback_enable:
|
||||
type: boolean
|
||||
feedback_type:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
prompt:
|
||||
type: string
|
||||
text_response_enable:
|
||||
type: boolean
|
||||
type: object
|
||||
domain.WebAppCommentSettings:
|
||||
properties:
|
||||
is_enable:
|
||||
|
|
@ -3040,6 +3092,8 @@ definitions:
|
|||
type: string
|
||||
publisher_id:
|
||||
type: string
|
||||
pv:
|
||||
type: integer
|
||||
status:
|
||||
$ref: '#/definitions/domain.NodeStatus'
|
||||
type:
|
||||
|
|
@ -3160,6 +3214,8 @@ definitions:
|
|||
type: string
|
||||
publisher_id:
|
||||
type: string
|
||||
pv:
|
||||
type: integer
|
||||
status:
|
||||
$ref: '#/definitions/domain.NodeStatus'
|
||||
type:
|
||||
|
|
@ -3213,6 +3269,19 @@ definitions:
|
|||
$ref: '#/definitions/v1.UserListItemResp'
|
||||
type: array
|
||||
type: object
|
||||
v1.WechatAppInfoResp:
|
||||
properties:
|
||||
disclaimer_content:
|
||||
type: string
|
||||
feedback_enable:
|
||||
type: boolean
|
||||
feedback_type:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
wechat_app_is_enabled:
|
||||
type: boolean
|
||||
type: object
|
||||
info:
|
||||
contact: {}
|
||||
description: panda-wiki API documentation
|
||||
|
|
@ -3359,6 +3428,7 @@ paths:
|
|||
- discord_bot
|
||||
- wechat_official_account
|
||||
- openai_api
|
||||
- mcp_server
|
||||
in: query
|
||||
name: source_type
|
||||
required: true
|
||||
|
|
@ -3381,6 +3451,7 @@ paths:
|
|||
- SourceTypeDiscordBot
|
||||
- SourceTypeWechatOfficialAccount
|
||||
- SourceTypeOpenAIAPI
|
||||
- SourceTypeMcpServer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
|
@ -5058,6 +5129,32 @@ paths:
|
|||
summary: GetAppInfo
|
||||
tags:
|
||||
- share_app
|
||||
/share/v1/app/wechat/info:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: WechatAppInfo
|
||||
parameters:
|
||||
- description: kb id
|
||||
in: header
|
||||
name: X-KB-ID
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/v1.WechatAppInfoResp'
|
||||
type: object
|
||||
summary: WechatAppInfo
|
||||
tags:
|
||||
- share_chat
|
||||
/share/v1/app/wechat/service/answer:
|
||||
get:
|
||||
consumes:
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ const (
|
|||
AppTypeOpenAIAPI
|
||||
AppTypeWecomAIBot
|
||||
AppTypeLarkBot
|
||||
AppTypeMcpServer
|
||||
)
|
||||
|
||||
var AppTypes = []AppType{
|
||||
|
|
@ -38,6 +39,7 @@ var AppTypes = []AppType{
|
|||
AppTypeOpenAIAPI,
|
||||
AppTypeWecomAIBot,
|
||||
AppTypeLarkBot,
|
||||
AppTypeMcpServer,
|
||||
}
|
||||
|
||||
func (t AppType) ToSourceType() consts.SourceType {
|
||||
|
|
@ -92,9 +94,8 @@ type AppSettings struct {
|
|||
RecommendQuestions []string `json:"recommend_questions,omitempty"`
|
||||
RecommendNodeIDs []string `json:"recommend_node_ids,omitempty"`
|
||||
// seo
|
||||
Desc string `json:"desc,omitempty"`
|
||||
Keyword string `json:"keyword,omitempty"`
|
||||
AutoSitemap bool `json:"auto_sitemap,omitempty"`
|
||||
Desc string `json:"desc,omitempty"`
|
||||
Keyword string `json:"keyword,omitempty"`
|
||||
// inject code
|
||||
HeadCode string `json:"head_code,omitempty"`
|
||||
BodyCode string `json:"body_code,omitempty"`
|
||||
|
|
@ -110,12 +111,13 @@ type AppSettings struct {
|
|||
// LarkBot
|
||||
LarkBotSettings LarkBotSettings `json:"lark_bot_settings,omitempty"`
|
||||
// WechatAppBot 企业微信机器人
|
||||
WeChatAppIsEnabled *bool `json:"wechat_app_is_enabled,omitempty"`
|
||||
WeChatAppToken string `json:"wechat_app_token,omitempty"`
|
||||
WeChatAppEncodingAESKey string `json:"wechat_app_encodingaeskey,omitempty"`
|
||||
WeChatAppCorpID string `json:"wechat_app_corpid,omitempty"`
|
||||
WeChatAppSecret string `json:"wechat_app_secret,omitempty"`
|
||||
WeChatAppAgentID string `json:"wechat_app_agent_id,omitempty"`
|
||||
WeChatAppIsEnabled *bool `json:"wechat_app_is_enabled,omitempty"`
|
||||
WeChatAppToken string `json:"wechat_app_token,omitempty"`
|
||||
WeChatAppEncodingAESKey string `json:"wechat_app_encodingaeskey,omitempty"`
|
||||
WeChatAppCorpID string `json:"wechat_app_corpid,omitempty"`
|
||||
WeChatAppSecret string `json:"wechat_app_secret,omitempty"`
|
||||
WeChatAppAgentID string `json:"wechat_app_agent_id,omitempty"`
|
||||
WeChatAppAdvancedSetting WeChatAppAdvancedSetting `json:"wechat_app_advanced_setting"`
|
||||
// WecomAIBotSettings 企业微信智能机器人
|
||||
WecomAIBotSettings WecomAIBotSettings `json:"wecom_ai_bot_settings"`
|
||||
// WechatServiceBot
|
||||
|
|
@ -167,6 +169,21 @@ type AppSettings struct {
|
|||
ContributeSettings ContributeSettings `json:"contribute_settings"`
|
||||
HomePageSetting consts.HomePageSetting `json:"home_page_setting"`
|
||||
ConversationSetting ConversationSetting `json:"conversation_setting"`
|
||||
// MCP Server Settings
|
||||
MCPServerSettings MCPServerSettings `json:"mcp_server_settings,omitempty"`
|
||||
StatsSetting StatsSetting `json:"stats_setting"`
|
||||
}
|
||||
|
||||
type WeChatAppAdvancedSetting struct {
|
||||
TextResponseEnable bool `json:"text_response_enable,omitempty"`
|
||||
FeedbackEnable bool `json:"feedback_enable,omitempty"`
|
||||
FeedbackType []string `json:"feedback_type,omitempty"`
|
||||
DisclaimerContent string `json:"disclaimer_content,omitempty"`
|
||||
Prompt string `json:"prompt,omitempty"`
|
||||
}
|
||||
|
||||
type StatsSetting struct {
|
||||
PVEnable bool `json:"pv_enable"`
|
||||
}
|
||||
|
||||
type ConversationSetting struct {
|
||||
|
|
@ -178,6 +195,17 @@ type WebAppLandingTheme struct {
|
|||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type MCPServerSettings struct {
|
||||
IsEnabled bool `json:"is_enabled"`
|
||||
DocsToolSettings MCPToolSettings `json:"docs_tool_settings"`
|
||||
SampleAuth SimpleAuth `json:"sample_auth"`
|
||||
}
|
||||
|
||||
type MCPToolSettings struct {
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
type LarkBotSettings struct {
|
||||
IsEnabled *bool `json:"is_enabled"`
|
||||
AppID string `json:"app_id"`
|
||||
|
|
@ -467,9 +495,8 @@ type AppSettingsResp struct {
|
|||
RecommendQuestions []string `json:"recommend_questions,omitempty"`
|
||||
RecommendNodeIDs []string `json:"recommend_node_ids,omitempty"`
|
||||
// seo
|
||||
Desc string `json:"desc,omitempty"`
|
||||
Keyword string `json:"keyword,omitempty"`
|
||||
AutoSitemap bool `json:"auto_sitemap,omitempty"`
|
||||
Desc string `json:"desc,omitempty"`
|
||||
Keyword string `json:"keyword,omitempty"`
|
||||
// inject code
|
||||
HeadCode string `json:"head_code,omitempty"`
|
||||
BodyCode string `json:"body_code,omitempty"`
|
||||
|
|
@ -485,12 +512,13 @@ type AppSettingsResp struct {
|
|||
// LarkBot
|
||||
LarkBotSettings LarkBotSettings `json:"lark_bot_settings,omitempty"`
|
||||
// WechatAppBot
|
||||
WeChatAppIsEnabled *bool `json:"wechat_app_is_enabled,omitempty"`
|
||||
WeChatAppToken string `json:"wechat_app_token,omitempty"`
|
||||
WeChatAppEncodingAESKey string `json:"wechat_app_encodingaeskey,omitempty"`
|
||||
WeChatAppCorpID string `json:"wechat_app_corpid,omitempty"`
|
||||
WeChatAppSecret string `json:"wechat_app_secret,omitempty"`
|
||||
WeChatAppAgentID string `json:"wechat_app_agent_id,omitempty"`
|
||||
WeChatAppIsEnabled *bool `json:"wechat_app_is_enabled,omitempty"`
|
||||
WeChatAppToken string `json:"wechat_app_token,omitempty"`
|
||||
WeChatAppEncodingAESKey string `json:"wechat_app_encodingaeskey,omitempty"`
|
||||
WeChatAppCorpID string `json:"wechat_app_corpid,omitempty"`
|
||||
WeChatAppSecret string `json:"wechat_app_secret,omitempty"`
|
||||
WeChatAppAgentID string `json:"wechat_app_agent_id,omitempty"`
|
||||
WeChatAppAdvancedSetting WeChatAppAdvancedSetting `json:"wechat_app_advanced_setting"`
|
||||
// WechatServiceBot
|
||||
WeChatServiceIsEnabled *bool `json:"wechat_service_is_enabled,omitempty"`
|
||||
WeChatServiceToken string `json:"wechat_service_token,omitempty"`
|
||||
|
|
@ -544,6 +572,9 @@ type AppSettingsResp struct {
|
|||
WebAppLandingTheme WebAppLandingTheme `json:"web_app_landing_theme"`
|
||||
HomePageSetting consts.HomePageSetting `json:"home_page_setting"`
|
||||
ConversationSetting ConversationSetting `json:"conversation_setting"`
|
||||
// MCP Server Settings
|
||||
MCPServerSettings MCPServerSettings `json:"mcp_server_settings,omitempty"`
|
||||
StatsSetting StatsSetting `json:"stats_setting"`
|
||||
}
|
||||
|
||||
type WebAppLandingConfigResp struct {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,16 @@ type ChatRequest struct {
|
|||
|
||||
RemoteIP string `json:"-"`
|
||||
Info ConversationInfo `json:"-"`
|
||||
Prompt string `json:"-"`
|
||||
}
|
||||
|
||||
type ChatRagOnlyRequest struct {
|
||||
Message string `json:"message" validate:"required"`
|
||||
|
||||
KBID string `json:"-" validate:"required"`
|
||||
|
||||
UserInfo UserInfo `json:"user_info"`
|
||||
AppType AppType `json:"app_type" validate:"required,oneof=1 2"`
|
||||
}
|
||||
|
||||
type ConversationInfo struct {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ type BaseEditionLimitation struct {
|
|||
AllowWatermark bool `json:"allow_watermark"` // 支持水印
|
||||
AllowCopyProtection bool `json:"allow_copy_protection"` // 支持内容复制保护
|
||||
AllowOpenAIBotSettings bool `json:"allow_open_ai_bot_settings"` // 支持问答机器人
|
||||
AllowMCPServer bool `json:"allow_mcp_server"` // 支持创建MCP Server
|
||||
AllowNodeStats bool `json:"allow_node_stats"` // 支持文档统计
|
||||
}
|
||||
|
||||
var baseEditionLimitationDefault = BaseEditionLimitation{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
var SystemPrompt = `
|
||||
var SystemDefaultPrompt = `
|
||||
你是一个专业的AI知识库问答助手,要按照以下步骤回答用户问题。
|
||||
|
||||
请仔细阅读以下信息:
|
||||
|
|
|
|||
|
|
@ -105,3 +105,14 @@ type StatPageHour struct {
|
|||
func (StatPageHour) TableName() string {
|
||||
return "stat_page_hours"
|
||||
}
|
||||
|
||||
// NodeStats node表统计数据
|
||||
type NodeStats struct {
|
||||
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
NodeID string `json:"node_id" gorm:"uniqueIndex"`
|
||||
PV int64 `json:"pv"`
|
||||
}
|
||||
|
||||
func (NodeStats) TableName() string {
|
||||
return "node_stats"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ require (
|
|||
github.com/larksuite/oapi-sdk-go/v3 v3.4.20
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20250508043914-ed57fa5c5274
|
||||
github.com/mark3labs/mcp-go v0.43.0
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/mileusna/useragent v1.3.5
|
||||
github.com/minio/minio-go/v7 v7.0.91
|
||||
|
|
@ -137,6 +138,7 @@ require (
|
|||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
github.com/invopop/yaml v0.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
|
|
@ -186,6 +188,7 @@ require (
|
|||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/yargevad/filepathx v1.0.0 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
|
|
|
|||
|
|
@ -321,6 +321,8 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C
|
|||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
|
||||
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
|
|
@ -396,6 +398,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
|
|||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mark3labs/mcp-go v0.43.0 h1:lgiKcWMddh4sngbU+hoWOZ9iAe/qp/m851RQpj3Y7jA=
|
||||
github.com/mark3labs/mcp-go v0.43.0/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
|
|
@ -576,6 +580,8 @@ github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJ
|
|||
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||
github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc=
|
||||
github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package mq
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
|
|
@ -66,6 +67,16 @@ func NewStatCronHandler(logger *log.Logger, statRepo *pg.StatRepository, statUse
|
|||
|
||||
func (h *CronHandler) RemoveOldStatData() {
|
||||
h.logger.Info("remove old stat data start")
|
||||
|
||||
// 零点时同步数据至node_stats持久化
|
||||
if time.Now().Hour() == 0 {
|
||||
if err := h.statUseCase.MigrateYesterdayPVToNodeStats(context.Background()); err != nil {
|
||||
h.logger.Error("migrate yesterday PV data to node_stats failed", log.Error(err))
|
||||
} else {
|
||||
h.logger.Info("migrate yesterday PV data to node_stats successful")
|
||||
}
|
||||
}
|
||||
|
||||
err := h.statRepo.RemoveOldData(context.Background())
|
||||
if err != nil {
|
||||
h.logger.Error("remove old stat data failed", log.Error(err))
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ func NewShareAppHandler(
|
|||
})
|
||||
share.GET("/web/info", h.GetWebAppInfo)
|
||||
share.GET("/widget/info", h.GetWidgetAppInfo)
|
||||
share.GET("/wechat/info", h.WechatAppInfo)
|
||||
|
||||
// wechat official account
|
||||
share.GET("/wechat/official_account", h.VerifyUrlWechatOfficialAccount)
|
||||
|
|
@ -101,6 +102,28 @@ func (h *ShareAppHandler) GetWidgetAppInfo(c echo.Context) error {
|
|||
return h.NewResponseWithData(c, appInfo)
|
||||
}
|
||||
|
||||
// WechatAppInfo
|
||||
//
|
||||
// @Summary WechatAppInfo
|
||||
// @Description WechatAppInfo
|
||||
// @Tags share_chat
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param X-KB-ID header string true "kb id"
|
||||
// @Success 200 {object} domain.Response{data=v1.WechatAppInfoResp}
|
||||
// @Router /share/v1/app/wechat/info [get]
|
||||
func (h *ShareAppHandler) WechatAppInfo(c echo.Context) error {
|
||||
kbID := c.Request().Header.Get("X-KB-ID")
|
||||
if kbID == "" {
|
||||
return h.NewResponseWithError(c, "kb_id is required", nil)
|
||||
}
|
||||
appInfo, err := h.usecase.GetWechatAppInfo(c.Request().Context(), kbID)
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, err.Error(), err)
|
||||
}
|
||||
return h.NewResponseWithData(c, appInfo)
|
||||
}
|
||||
|
||||
func (h *ShareAppHandler) VerifyUrlWechatOfficialAccount(c echo.Context) error {
|
||||
kbID := c.Request().Header.Get("X-KB-ID")
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/handler"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
"github.com/chaitin/panda-wiki/usecase"
|
||||
|
|
@ -37,13 +36,6 @@ func (h *ShareSitemapHandler) GetSitemap(c echo.Context) error {
|
|||
if kbID == "" {
|
||||
return h.NewResponseWithError(c, "kb_id is required", nil)
|
||||
}
|
||||
appInfo, err := h.appUsecase.ShareGetWebAppInfo(c.Request().Context(), kbID, domain.GetAuthID(c))
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "web app not found", err)
|
||||
}
|
||||
if !appInfo.Settings.AutoSitemap {
|
||||
return h.NewResponseWithError(c, "未开启自动生成站点地图功能", nil)
|
||||
}
|
||||
|
||||
xml, err := h.sitemapUsecase.GetSitemap(c.Request().Context(), kbID)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/sbzhu/weworkapi_golang/wxbizmsgcrypt"
|
||||
|
|
@ -111,6 +112,12 @@ func (h *ShareWechatHandler) GetWechatAnswer(c echo.Context) error {
|
|||
return err
|
||||
}
|
||||
//2.answer
|
||||
if err := h.writeSSEEvent(c, domain.SSEEvent{Type: "feedback_score", Content: strconv.Itoa(int(conversation.Messages[1].Info.Score))}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := h.writeSSEEvent(c, domain.SSEEvent{Type: "message_id", Content: conversation.Messages[1].ID}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := h.writeSSEEvent(c, domain.SSEEvent{Type: "answer", Content: conversation.Messages[1].Content}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -331,6 +338,7 @@ func (h *ShareWechatHandler) VerifyUrlWechatApp(c echo.Context) error {
|
|||
return c.String(http.StatusOK, string(req))
|
||||
}
|
||||
|
||||
// WechatHandlerApp /share/v1/app/wechat/app
|
||||
func (h *ShareWechatHandler) WechatHandlerApp(c echo.Context) error {
|
||||
signature := c.QueryParam("msg_signature")
|
||||
timestamp := c.QueryParam("timestamp")
|
||||
|
|
@ -383,20 +391,22 @@ func (h *ShareWechatHandler) WechatHandlerApp(c echo.Context) error {
|
|||
return c.String(http.StatusOK, "")
|
||||
}
|
||||
|
||||
immediateResponse, err := wechatConfig.SendResponse(*msg, "正在思考您的问题,请稍候...")
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "Failed to send immediate response", err)
|
||||
var immediateResponse []byte
|
||||
if domain.GetBaseEditionLimitation(ctx).AllowAdvancedBot && appInfo.Settings.WeChatAppAdvancedSetting.TextResponseEnable {
|
||||
immediateResponse, err = wechatConfig.SendResponse(*msg, "正在思考您的问题,请稍候...")
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "Failed to send immediate response", err)
|
||||
}
|
||||
}
|
||||
|
||||
go func(msg *wechat.ReceivedMessage, wechatConfig *wechat.WechatConfig, kbId string) {
|
||||
ctx := context.Background()
|
||||
err := h.wechatAppUsecase.Wechat(ctx, msg, wechatConfig, kbId)
|
||||
go func(ctx context.Context, msg *wechat.ReceivedMessage, wechatConfig *wechat.WechatConfig, kbId string, appInfo *domain.AppDetailResp) {
|
||||
err := h.wechatAppUsecase.Wechat(ctx, msg, wechatConfig, kbId, &appInfo.Settings.WeChatAppAdvancedSetting)
|
||||
if err != nil {
|
||||
h.logger.Error("wechat async failed")
|
||||
}
|
||||
}(msg, wechatConfig, kbID)
|
||||
}(ctx, msg, wechatConfig, kbID, appInfo)
|
||||
|
||||
return c.XMLBlob(http.StatusOK, []byte(immediateResponse))
|
||||
return c.XMLBlob(http.StatusOK, immediateResponse)
|
||||
}
|
||||
|
||||
func (h *ShareWechatHandler) WecomAIBotVerify(c echo.Context) error {
|
||||
|
|
|
|||
|
|
@ -100,8 +100,8 @@ func (h *AppHandler) UpdateApp(c echo.Context) error {
|
|||
}
|
||||
|
||||
ctx := c.Request().Context()
|
||||
if err := h.usecase.ValidateUpdateApp(ctx, id, &appRequest, consts.GetLicenseEdition(c)); err != nil {
|
||||
h.logger.Error("UpdateApp", log.Any("req:", appRequest), log.Any("err:", err))
|
||||
if err := h.usecase.ValidateUpdateApp(ctx, id, &appRequest); err != nil {
|
||||
h.logger.Error("UpdateApp", log.Any("req:", appRequest.Settings), log.Any("err:", err))
|
||||
return h.NewResponseWithErrCode(c, domain.ErrCodePermissionDenied)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ func (h *NodeHandler) GetNodeDetail(c echo.Context) error {
|
|||
|
||||
node, err := h.usecase.GetNodeByKBID(c.Request().Context(), req.ID, req.KbId, req.Format)
|
||||
if err != nil {
|
||||
h.logger.Error("get node by kb id failed", log.Error(err))
|
||||
return h.NewResponseWithError(c, "get node detail failed", err)
|
||||
}
|
||||
return h.NewResponseWithData(c, node)
|
||||
|
|
|
|||
|
|
@ -14,11 +14,14 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/sbzhu/weworkapi_golang/wxbizmsgcrypt"
|
||||
|
||||
"github.com/chaitin/panda-wiki/consts"
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
"github.com/chaitin/panda-wiki/pkg/bot"
|
||||
)
|
||||
|
||||
const wechatMessageMaxBytes = 2000
|
||||
|
||||
func NewWechatConfig(ctx context.Context, CorpID, Token, EncodingAESKey string, kbid string, secret string, agentID string, logger *log.Logger) (*WechatConfig, error) {
|
||||
return &WechatConfig{
|
||||
Ctx: ctx,
|
||||
|
|
@ -49,22 +52,30 @@ func (cfg *WechatConfig) VerifyUrlWechatAPP(signature, timestamp, nonce, echostr
|
|||
return decryptEchoStr, nil
|
||||
}
|
||||
|
||||
func (cfg *WechatConfig) Wechat(msg ReceivedMessage, getQA bot.GetQAFun, userinfo *UserInfo) error {
|
||||
func (cfg *WechatConfig) Wechat(msg ReceivedMessage, getQA bot.GetQAFun, userinfo *UserInfo, useTextResponse bool, weChatAppAdvancedSetting *domain.WeChatAppAdvancedSetting) error {
|
||||
|
||||
token, err := cfg.GetAccessToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = cfg.ProcessMessage(msg, getQA, token, userinfo)
|
||||
if err != nil {
|
||||
cfg.logger.Error("send to ai failed!", log.Error(err))
|
||||
return err
|
||||
if useTextResponse {
|
||||
err = cfg.ProcessTextMessage(msg, getQA, token, userinfo, weChatAppAdvancedSetting.DisclaimerContent)
|
||||
if err != nil {
|
||||
cfg.logger.Error("send to ai failed!", log.Error(err))
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := cfg.ProcessUrlMessage(msg, getQA, token, userinfo); err != nil {
|
||||
cfg.logger.Error("send to ai failed!", log.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// forwardToBackend
|
||||
func (cfg *WechatConfig) ProcessMessage(msg ReceivedMessage, GetQA bot.GetQAFun, token string, userinfo *UserInfo) error {
|
||||
func (cfg *WechatConfig) ProcessUrlMessage(msg ReceivedMessage, GetQA bot.GetQAFun, token string, userinfo *UserInfo) error {
|
||||
// 1. get ai channel
|
||||
id, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
|
|
@ -73,7 +84,7 @@ func (cfg *WechatConfig) ProcessMessage(msg ReceivedMessage, GetQA bot.GetQAFun,
|
|||
}
|
||||
conversationID := id.String()
|
||||
|
||||
wccontent, err := GetQA(cfg.Ctx, msg.Content, domain.ConversationInfo{
|
||||
contentChan, err := GetQA(cfg.Ctx, msg.Content, domain.ConversationInfo{
|
||||
UserInfo: domain.UserInfo{
|
||||
UserID: userinfo.UserID,
|
||||
NickName: userinfo.Name,
|
||||
|
|
@ -93,7 +104,7 @@ func (cfg *WechatConfig) ProcessMessage(msg ReceivedMessage, GetQA bot.GetQAFun,
|
|||
}
|
||||
domain.ConversationManager.Store(conversationID, state)
|
||||
|
||||
go cfg.SendQuestionToAI(conversationID, wccontent)
|
||||
go cfg.SendQuestionToAI(conversationID, contentChan)
|
||||
}
|
||||
|
||||
baseUrl, err := cfg.WeRepo.GetWechatBaseURL(cfg.Ctx, cfg.kbID)
|
||||
|
|
@ -112,6 +123,54 @@ func (cfg *WechatConfig) ProcessMessage(msg ReceivedMessage, GetQA bot.GetQAFun,
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cfg *WechatConfig) ProcessTextMessage(msg ReceivedMessage, GetQA bot.GetQAFun, token string, userinfo *UserInfo, disclaimerContent string) error {
|
||||
// 1. get ai channel
|
||||
id, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
cfg.logger.Error("failed to generate conversation uuid", log.Error(err))
|
||||
id = uuid.New()
|
||||
}
|
||||
conversationID := id.String()
|
||||
|
||||
contentChan, err := GetQA(cfg.Ctx, msg.Content, domain.ConversationInfo{
|
||||
UserInfo: domain.UserInfo{
|
||||
UserID: userinfo.UserID,
|
||||
NickName: userinfo.Name,
|
||||
From: domain.MessageFromPrivate,
|
||||
}}, conversationID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var fullResponse string
|
||||
for content := range contentChan {
|
||||
fullResponse += content
|
||||
if len([]byte(fullResponse)) > wechatMessageMaxBytes { // wechat limit 2048 byte
|
||||
if _, _, err := cfg.SendResponseToUser(fullResponse, msg.FromUserName, token); err != nil {
|
||||
return err
|
||||
}
|
||||
fullResponse = ""
|
||||
}
|
||||
}
|
||||
if len([]byte(fullResponse+disclaimerContent)) > wechatMessageMaxBytes {
|
||||
if _, _, err := cfg.SendResponseToUser(fullResponse, msg.FromUserName, token); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, _, err := cfg.SendResponseToUser(disclaimerContent, msg.FromUserName, token); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if disclaimerContent != "" {
|
||||
fullResponse += fmt.Sprintf("\n%s", disclaimerContent)
|
||||
}
|
||||
if _, _, err := cfg.SendResponseToUser(fullResponse, msg.FromUserName, token); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendResponseToUser
|
||||
func (cfg *WechatConfig) SendURLToUser(touser, question, token, conversationID, baseUrl string) (int, string, error) {
|
||||
msgData := map[string]interface{}{
|
||||
|
|
@ -121,7 +180,7 @@ func (cfg *WechatConfig) SendURLToUser(touser, question, token, conversationID,
|
|||
"textcard": map[string]interface{}{
|
||||
"title": question,
|
||||
"description": "<div class = \"highlight\">本回答由 PandaWiki 基于 AI 生成,仅供参考。</div>",
|
||||
"url": fmt.Sprintf("%s/h5-chat?id=%s", baseUrl, conversationID),
|
||||
"url": fmt.Sprintf("%s/h5-chat?id=%s&source_type=%s", baseUrl, conversationID, consts.SourceTypeWechatBot),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +209,6 @@ func (cfg *WechatConfig) SendURLToUser(touser, question, token, conversationID,
|
|||
return result.Errcode, result.Errmsg, nil
|
||||
}
|
||||
|
||||
// SendResponseToUser
|
||||
func (cfg *WechatConfig) SendResponseToUser(response string, touser string, token string) (int, string, error) {
|
||||
|
||||
msgData := map[string]interface{}{
|
||||
|
|
@ -184,6 +242,9 @@ func (cfg *WechatConfig) SendResponseToUser(response string, touser string, toke
|
|||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
if result.Errcode != 0 {
|
||||
return result.Errcode, result.Errmsg, fmt.Errorf("wechat Api failed : %s (code: %d)", result.Errmsg, result.Errcode)
|
||||
}
|
||||
return result.Errcode, result.Errmsg, nil
|
||||
}
|
||||
|
||||
|
|
@ -235,7 +296,7 @@ func (cfg *WechatConfig) GetAccessToken() (string, error) {
|
|||
defer tokenCache.Mutex.Unlock()
|
||||
|
||||
if tokenCache.AccessToken != "" && time.Now().Before(tokenCache.TokenExpire) {
|
||||
cfg.logger.Info("access token has existed and is valid")
|
||||
cfg.logger.Debug("access token has existed and is valid")
|
||||
return tokenCache.AccessToken, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ func (cfg *WechatServiceConfig) GetAccessToken() (string, error) {
|
|||
defer tokenCache.Mutex.Unlock()
|
||||
|
||||
if tokenCache.AccessToken != "" && time.Now().Before(tokenCache.TokenExpire) {
|
||||
cfg.logger.Info("access token has existed and is valid")
|
||||
cfg.logger.Debug("access token has existed and is valid")
|
||||
return tokenCache.AccessToken, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit f6497a225d4b6ad76fe2dbedb427bd0e464a7662
|
||||
Subproject commit 530a059db3c8b1ef86c3a43eaf70d75c46c75df9
|
||||
|
|
@ -2,5 +2,7 @@ package backend
|
|||
|
||||
import (
|
||||
_ "github.com/jinzhu/copier"
|
||||
_ "github.com/mark3labs/mcp-go/mcp"
|
||||
_ "github.com/mark3labs/mcp-go/server"
|
||||
_ "google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -263,3 +263,27 @@ func (r *ConversationRepository) GetConversationDistributionByHour(ctx context.C
|
|||
|
||||
return counts, nil
|
||||
}
|
||||
|
||||
func (r *ConversationRepository) GetConversationCountByAppType(ctx context.Context) (map[domain.AppType]int64, error) {
|
||||
type row struct {
|
||||
AppType int `gorm:"column:app_type"`
|
||||
Count int64 `gorm:"column:count"`
|
||||
}
|
||||
var rows []row
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&domain.Conversation{}).
|
||||
Joins("JOIN apps ON conversations.app_id = apps.id").
|
||||
Select("apps.type as app_type, COUNT(*) as count").
|
||||
Group("apps.type").
|
||||
Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make(map[domain.AppType]int64)
|
||||
for _, t := range domain.AppTypes {
|
||||
result[t] = 0
|
||||
}
|
||||
for _, rrow := range rows {
|
||||
result[domain.AppType(rrow.AppType)] = rrow.Count
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ func (r *KnowledgeBaseRepository) SyncKBAccessSettingsToCaddy(ctx context.Contex
|
|||
{
|
||||
"match": []map[string]any{
|
||||
{
|
||||
"path": []string{"/share/v1/chat/completions", "/share/v1/app/wechat/app", "/share/v1/app/wechat/service", "/sitemap.xml", "/share/v1/app/wechat/official_account", "/share/v1/app/wechat/service/answer"},
|
||||
"path": []string{"/share/v1/chat/completions", "/share/v1/app/wechat/app", "/share/v1/app/wechat/service", "/sitemap.xml", "/share/v1/app/wechat/official_account", "/share/v1/app/wechat/service/answer", "/mcp"},
|
||||
},
|
||||
},
|
||||
"handle": []map[string]any{
|
||||
|
|
@ -341,12 +341,11 @@ func (r *KnowledgeBaseRepository) CreateKnowledgeBase(ctx context.Context, maxKB
|
|||
Name: kb.Name,
|
||||
Type: domain.AppTypeWeb,
|
||||
Settings: domain.AppSettings{
|
||||
Title: kb.Name,
|
||||
Desc: kb.Name,
|
||||
Keyword: kb.Name,
|
||||
AutoSitemap: true,
|
||||
Icon: domain.DefaultPandaWikiIconB64,
|
||||
WelcomeStr: fmt.Sprintf("欢迎使用%s", kb.Name),
|
||||
Title: kb.Name,
|
||||
Desc: kb.Name,
|
||||
Keyword: kb.Name,
|
||||
Icon: domain.DefaultPandaWikiIconB64,
|
||||
WelcomeStr: fmt.Sprintf("欢迎使用%s", kb.Name),
|
||||
Btns: []any{
|
||||
AppBtn{
|
||||
ID: uuid.New().String(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package pg
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
"github.com/chaitin/panda-wiki/store/pg"
|
||||
)
|
||||
|
||||
type MCPRepository struct {
|
||||
db *pg.DB
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewMCPRepository(db *pg.DB, logger *log.Logger) *MCPRepository {
|
||||
return &MCPRepository{db: db, logger: logger}
|
||||
}
|
||||
|
||||
func (r *MCPRepository) GetMCPCallCount(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).Table("mcp_calls").Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
|
@ -1178,3 +1178,14 @@ func (r *NodeRepository) GetNodeIdsByDocIds(ctx context.Context, docIds []string
|
|||
|
||||
return docToNodeMap, nil
|
||||
}
|
||||
|
||||
func (r *NodeRepository) GetNodeCount(ctx context.Context) (int, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&domain.Node{}).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(count), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package pg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/utils"
|
||||
)
|
||||
|
||||
func (r *NodeRepository) GetNodeStatsByNodeId(ctx context.Context, nodeId string) (*domain.NodeStats, error) {
|
||||
var nodeStats *domain.NodeStats
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&domain.NodeStats{}).
|
||||
Where("node_id = ?", nodeId).
|
||||
First(&nodeStats).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
nodeStats = &domain.NodeStats{
|
||||
ID: 0,
|
||||
NodeID: nodeId,
|
||||
PV: 0,
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var todayStats int64
|
||||
if err := r.db.WithContext(ctx).Model(&domain.StatPage{}).
|
||||
Where("created_at >= ?", utils.GetTimeHourOffset(-24)).
|
||||
Where("node_id = ?", nodeId).Count(&todayStats).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodeStats.PV += todayStats
|
||||
|
||||
return nodeStats, nil
|
||||
}
|
||||
|
|
@ -24,4 +24,5 @@ var ProviderSet = wire.NewSet(
|
|||
NewWechatRepository,
|
||||
NewAPITokenRepo,
|
||||
NewSystemSettingRepo,
|
||||
NewMCPRepository,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package pg
|
|||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
v1 "github.com/chaitin/panda-wiki/api/stat/v1"
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/store/cache"
|
||||
|
|
@ -156,3 +159,46 @@ func (r *StatRepository) RemoveOldData(ctx context.Context) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetYesterdayPVByNode 获取昨天的PV数据,按node_id分组
|
||||
func (r *StatRepository) GetYesterdayPVByNode(ctx context.Context) (map[string]int64, error) {
|
||||
type PVResult struct {
|
||||
NodeID string
|
||||
Count int64
|
||||
}
|
||||
|
||||
var results []PVResult
|
||||
if err := r.db.WithContext(ctx).Model(&domain.StatPage{}).
|
||||
Where("created_at < ?", utils.GetTimeHourOffset(0)).
|
||||
Where("created_at >= ?", utils.GetTimeHourOffset(-24)).
|
||||
Where("node_id != ?", "").
|
||||
Group("node_id").
|
||||
Select("node_id, COUNT(*) as count").
|
||||
Find(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pvMap := make(map[string]int64)
|
||||
for _, result := range results {
|
||||
pvMap[result.NodeID] = result.Count
|
||||
}
|
||||
return pvMap, nil
|
||||
}
|
||||
|
||||
// UpsertNodeStats 插入或更新node_stats表
|
||||
func (r *StatRepository) UpsertNodeStats(ctx context.Context, nodeID string, pvCount int64) error {
|
||||
nodeStats := &domain.NodeStats{
|
||||
NodeID: nodeID,
|
||||
PV: pvCount,
|
||||
}
|
||||
|
||||
// 使用GORM的Clauses进行upsert操作
|
||||
return r.db.WithContext(ctx).
|
||||
Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "node_id"}},
|
||||
DoUpdates: clause.Assignments(map[string]interface{}{
|
||||
"pv": gorm.Expr("node_stats.pv + ?", pvCount),
|
||||
}),
|
||||
}).
|
||||
Create(nodeStats).Error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS mcp_calls;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
CREATE TABLE IF NOT EXISTS mcp_calls (
|
||||
id SERIAL PRIMARY KEY,
|
||||
mcp_session_id TEXT NOT NULL,
|
||||
kb_id TEXT NOT NULL,
|
||||
remote_ip TEXT,
|
||||
initialize_req JSONB,
|
||||
initialize_resp JSONB,
|
||||
tool_call_req JSONB,
|
||||
tool_call_resp TEXT,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS node_stats;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CREATE TABLE IF NOT EXISTS node_stats (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
node_id TEXT NOT NULL UNIQUE,
|
||||
pv BIGINT NOT NULL DEFAULT 0,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -13,8 +14,12 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"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/pg"
|
||||
"github.com/chaitin/panda-wiki/usecase"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -24,27 +29,43 @@ const (
|
|||
|
||||
// Client is the telemetry client
|
||||
type Client struct {
|
||||
baseURL string
|
||||
httpClient *http.Client
|
||||
machineID string
|
||||
firstReport bool
|
||||
stopChan chan struct{}
|
||||
logger *log.Logger
|
||||
repo *pg.KnowledgeBaseRepository
|
||||
baseURL string
|
||||
httpClient *http.Client
|
||||
machineID string
|
||||
firstReport bool
|
||||
stopChan chan struct{}
|
||||
logger *log.Logger
|
||||
repo *pg.KnowledgeBaseRepository
|
||||
modelUsecase *usecase.ModelUsecase
|
||||
userUsecase *usecase.UserUsecase
|
||||
nodeRepo *pg.NodeRepository
|
||||
conversationRepo *pg.ConversationRepository
|
||||
mcpRepo *pg.MCPRepository
|
||||
cfg *config.Config
|
||||
aesKey string
|
||||
}
|
||||
|
||||
// NewClient creates a new telemetry client
|
||||
func NewClient(logger *log.Logger, repo *pg.KnowledgeBaseRepository) (*Client, error) {
|
||||
func NewClient(logger *log.Logger, repo *pg.KnowledgeBaseRepository, modelUsecase *usecase.ModelUsecase, userUsecase *usecase.UserUsecase, nodeRepo *pg.NodeRepository, conversationRepo *pg.ConversationRepository, mcpRepo *pg.MCPRepository, cfg *config.Config) (*Client, error) {
|
||||
baseURL := "https://baizhi.cloud/api/public/data/report"
|
||||
aesKey := "SZ3SDP38y9Gg2c6yHdLPgDeX"
|
||||
|
||||
client := &Client{
|
||||
baseURL: baseURL,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
firstReport: true,
|
||||
stopChan: make(chan struct{}),
|
||||
logger: logger.WithModule("telemetry"),
|
||||
repo: repo,
|
||||
firstReport: true,
|
||||
stopChan: make(chan struct{}),
|
||||
logger: logger.WithModule("telemetry"),
|
||||
repo: repo,
|
||||
modelUsecase: modelUsecase,
|
||||
userUsecase: userUsecase,
|
||||
nodeRepo: nodeRepo,
|
||||
conversationRepo: conversationRepo,
|
||||
mcpRepo: mcpRepo,
|
||||
cfg: cfg,
|
||||
aesKey: aesKey,
|
||||
}
|
||||
|
||||
// get or create machine ID
|
||||
|
|
@ -139,18 +160,50 @@ func (c *Client) startPeriodicReport() {
|
|||
ticker := time.NewTicker(reportInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
dataTimer := time.NewTimer(c.nextReportDataDelay())
|
||||
defer dataTimer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := c.reportInstallation(); err != nil {
|
||||
c.logger.Error("periodic report installation", log.Error(err))
|
||||
}
|
||||
case <-dataTimer.C:
|
||||
if err := c.reportData(); err != nil {
|
||||
c.logger.Error("periodic report data", log.Error(err))
|
||||
}
|
||||
dataTimer.Reset(c.nextReportDataDelay())
|
||||
case <-c.stopChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 计算下一次数据上报的延迟,使其在每天 0:00:00–0:29:59 窗口内随机触发。
|
||||
// 若当前时间位于当日窗口内,返回窗口剩余时间内的随机秒数;否则返回到次日窗口的随机偏移。
|
||||
func (c *Client) nextReportDataDelay() time.Duration {
|
||||
now := time.Now()
|
||||
loc := now.Location()
|
||||
|
||||
if now.Hour() == 0 && now.Minute() < 30 {
|
||||
end := time.Date(now.Year(), now.Month(), now.Day(), 0, 29, 59, 0, loc)
|
||||
remaining := end.Sub(now)
|
||||
sec := int(remaining / time.Second)
|
||||
if sec <= 0 {
|
||||
nextMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc).Add(24 * time.Hour)
|
||||
offset := time.Duration(rand.Intn(30*60)) * time.Second
|
||||
return time.Until(nextMidnight.Add(offset))
|
||||
}
|
||||
offset := rand.Intn(sec) + 1
|
||||
return time.Duration(offset) * time.Second
|
||||
}
|
||||
|
||||
nextMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc).Add(24 * time.Hour)
|
||||
offset := time.Duration(rand.Intn(30*60)) * time.Second
|
||||
return time.Until(nextMidnight.Add(offset))
|
||||
}
|
||||
|
||||
// reportInstallation reports installation information
|
||||
func (c *Client) reportInstallation() error {
|
||||
event := InstallationEvent{
|
||||
|
|
@ -172,7 +225,7 @@ func (c *Client) reportInstallation() error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("marshal installation event: %w", err)
|
||||
}
|
||||
eventEncrypted, err := Encrypt([]byte("SZ3SDP38y9Gg2c6yHdLPgDeX"), eventRaw)
|
||||
eventEncrypted, err := Encrypt([]byte(c.aesKey), eventRaw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt installation event: %w", err)
|
||||
}
|
||||
|
|
@ -206,6 +259,120 @@ func (c *Client) reportInstallation() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) reportData() error {
|
||||
event := DailyReportEvent{
|
||||
InstallationEvent: InstallationEvent{
|
||||
Version: Version,
|
||||
Timestamp: time.Now().Format(time.RFC3339),
|
||||
MachineID: c.machineID,
|
||||
Type: "data_report",
|
||||
},
|
||||
}
|
||||
|
||||
if repoList, err := c.repo.GetKnowledgeBaseList(context.Background()); err == nil {
|
||||
event.KBCount = len(repoList)
|
||||
} else {
|
||||
c.logger.Error("get knowledge base list failed in telemetry", log.Error(err))
|
||||
}
|
||||
|
||||
if modelModeSetting, err := c.modelUsecase.GetModelModeSetting(context.Background()); err == nil {
|
||||
event.ModelConfigMode = string(modelModeSetting.Mode)
|
||||
} else {
|
||||
c.logger.Error("get model config mode failed in telemetry", log.Error(err))
|
||||
}
|
||||
|
||||
if ok, err := c.isAdminLoggedInYesterday(); err == nil {
|
||||
event.AdminLoggedInToday = ok
|
||||
} else {
|
||||
c.logger.Error("get admin login today failed in telemetry", log.Error(err))
|
||||
}
|
||||
|
||||
if count, err := c.nodeRepo.GetNodeCount(context.Background()); err == nil {
|
||||
event.DocsCount = count
|
||||
} else {
|
||||
c.logger.Error("get docs count failed in telemetry", log.Error(err))
|
||||
}
|
||||
|
||||
// conversation counts by app type across all KBs
|
||||
if totals, err := c.conversationRepo.GetConversationCountByAppType(context.Background()); err == nil {
|
||||
event.WebConversationCount = int(totals[domain.AppTypeWeb])
|
||||
event.WidgetConversationCount = int(totals[domain.AppTypeWidget])
|
||||
event.DingTalkBotConversationCount = int(totals[domain.AppTypeDingTalkBot])
|
||||
event.FeishuBotConversationCount = int(totals[domain.AppTypeFeishuBot])
|
||||
event.WechatBotConversationCount = int(totals[domain.AppTypeWechatBot])
|
||||
event.WeChatServerBotConversationCount = int(totals[domain.AppTypeWechatServiceBot])
|
||||
event.DiscordBotConversationCount = int(totals[domain.AppTypeDisCordBot])
|
||||
event.WechatOfficialAccountConversationCount = int(totals[domain.AppTypeWechatOfficialAccount])
|
||||
event.OpenAIAPIConversationCount = int(totals[domain.AppTypeOpenAIAPI])
|
||||
event.WecomAIBotConversationCount = int(totals[domain.AppTypeWecomAIBot])
|
||||
event.LarkBotConversationCount = int(totals[domain.AppTypeLarkBot])
|
||||
} else {
|
||||
c.logger.Error("get conversation count by app type failed", log.Error(err))
|
||||
}
|
||||
|
||||
if count, err := c.mcpRepo.GetMCPCallCount(context.Background()); err == nil {
|
||||
event.McpServerConversationCount = int(count)
|
||||
} else {
|
||||
c.logger.Error("get mcp call count failed", log.Error(err))
|
||||
}
|
||||
|
||||
eventRaw, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal installation event: %w", err)
|
||||
}
|
||||
c.logger.Info("report data event", log.String("event", string(eventRaw)))
|
||||
eventEncrypted, err := Encrypt([]byte(c.aesKey), eventRaw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encrypt installation event: %w", err)
|
||||
}
|
||||
data := map[string]string{
|
||||
"index": "panda-wiki-installation",
|
||||
"data": eventEncrypted,
|
||||
"id": uuid.New().String(),
|
||||
}
|
||||
eventEncryptedRaw, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal installation event: %w", err)
|
||||
}
|
||||
req, err := http.NewRequest("POST", c.baseURL, bytes.NewBuffer(eventEncryptedRaw))
|
||||
if err != nil {
|
||||
return fmt.Errorf("create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("send request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 判断“昨日是否有管理员访问”。
|
||||
// 因为数据在每天 0–1 点上报,这里采用昨日 0:00 至今日 0:00 的时间窗口。
|
||||
func (c *Client) isAdminLoggedInYesterday() (bool, error) {
|
||||
resp, err := c.userUsecase.ListUsers(context.Background())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
now := time.Now()
|
||||
loc := now.Location()
|
||||
todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
|
||||
yesterdayMidnight := todayMidnight.Add(-24 * time.Hour)
|
||||
for _, u := range resp.Users {
|
||||
if u.Role == consts.UserRoleAdmin && u.LastAccess != nil && !u.LastAccess.Before(yesterdayMidnight) && u.LastAccess.Before(todayMidnight) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Stop stops periodic report
|
||||
func (c *Client) Stop() {
|
||||
close(c.stopChan)
|
||||
|
|
@ -219,3 +386,22 @@ type InstallationEvent struct {
|
|||
Type string `json:"type"`
|
||||
KBCount int `json:"kb_count"`
|
||||
}
|
||||
|
||||
type DailyReportEvent struct {
|
||||
InstallationEvent
|
||||
ModelConfigMode string `json:"model_config_mode"` // 模型配置模式
|
||||
AdminLoggedInToday bool `json:"admin_logged_in_today"` // 是否今日登录管理端
|
||||
DocsCount int `json:"docs_count"` // 文件数量
|
||||
WebConversationCount int `json:"web_conversation_count"` // 网页对话次数
|
||||
WidgetConversationCount int `json:"widget_conversation_count"` // 插件对话次数
|
||||
DingTalkBotConversationCount int `json:"dingtalk_bot_conversation_count"` // 钉钉机器人对话次数
|
||||
FeishuBotConversationCount int `json:"feishu_bot_conversation_count"` // 飞书机器人对话次数
|
||||
WechatBotConversationCount int `json:"wechat_bot_conversation_count"` // 企业微信机器人对话次数
|
||||
WeChatServerBotConversationCount int `json:"wechat_server_bot_conversation_count"` // 企业微信客服对话次数
|
||||
DiscordBotConversationCount int `json:"discord_bot_conversation_count"` // Discord 机器人对话次数
|
||||
WechatOfficialAccountConversationCount int `json:"wechat_official_account_conversation_count"` // 微信公众号对话次数
|
||||
OpenAIAPIConversationCount int `json:"openai_api_conversation_count"` // OpenAI API 调用次数
|
||||
WecomAIBotConversationCount int `json:"wecom_ai_bot_conversation_count"` // 企业微信智能机器人对话次数
|
||||
LarkBotConversationCount int `json:"lark_bot_conversation_count"` // 飞书机器人对话次数
|
||||
McpServerConversationCount int `json:"mcp_server_conversation_count"` // MCP 对话次数
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
v1 "github.com/chaitin/panda-wiki/api/share/v1"
|
||||
"github.com/chaitin/panda-wiki/config"
|
||||
"github.com/chaitin/panda-wiki/consts"
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
|
|
@ -87,7 +88,7 @@ func NewAppUsecase(
|
|||
return u
|
||||
}
|
||||
|
||||
func (u *AppUsecase) ValidateUpdateApp(ctx context.Context, id string, req *domain.UpdateAppReq, edition consts.LicenseEdition) error {
|
||||
func (u *AppUsecase) ValidateUpdateApp(ctx context.Context, id string, req *domain.UpdateAppReq) error {
|
||||
app, err := u.repo.GetAppDetail(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -109,6 +110,18 @@ func (u *AppUsecase) ValidateUpdateApp(ctx context.Context, id string, req *doma
|
|||
!slices.Equal(app.Settings.WechatServiceEqualKeywords, req.Settings.WechatServiceEqualKeywords) {
|
||||
return domain.ErrPermissionDenied
|
||||
}
|
||||
|
||||
if app.Settings.WeChatAppAdvancedSetting.FeedbackEnable != req.Settings.WeChatAppAdvancedSetting.FeedbackEnable ||
|
||||
app.Settings.WeChatAppAdvancedSetting.TextResponseEnable != req.Settings.WeChatAppAdvancedSetting.TextResponseEnable ||
|
||||
app.Settings.WeChatAppAdvancedSetting.Prompt != req.Settings.WeChatAppAdvancedSetting.Prompt ||
|
||||
!slices.Equal(app.Settings.WeChatAppAdvancedSetting.FeedbackType, req.Settings.WeChatAppAdvancedSetting.FeedbackType) ||
|
||||
app.Settings.WeChatAppAdvancedSetting.DisclaimerContent != req.Settings.WeChatAppAdvancedSetting.DisclaimerContent {
|
||||
return domain.ErrPermissionDenied
|
||||
}
|
||||
} else {
|
||||
if req.Settings.WeChatAppAdvancedSetting.Prompt == "" {
|
||||
req.Settings.WeChatAppAdvancedSetting.Prompt = domain.SystemDefaultPrompt
|
||||
}
|
||||
}
|
||||
|
||||
if !limitation.AllowCommentAudit && app.Settings.WebAppCommentSettings.ModerationEnable != req.Settings.WebAppCommentSettings.ModerationEnable {
|
||||
|
|
@ -129,6 +142,12 @@ func (u *AppUsecase) ValidateUpdateApp(ctx context.Context, id string, req *doma
|
|||
return domain.ErrPermissionDenied
|
||||
}
|
||||
if req.Settings.ConversationSetting.CopyrightInfo != domain.SettingCopyrightInfo && app.Settings.ConversationSetting.CopyrightInfo != req.Settings.ConversationSetting.CopyrightInfo {
|
||||
req.Settings.ConversationSetting.CopyrightInfo = domain.SettingCopyrightInfo
|
||||
}
|
||||
}
|
||||
|
||||
if !limitation.AllowMCPServer {
|
||||
if app.Settings.MCPServerSettings.IsEnabled != req.Settings.MCPServerSettings.IsEnabled {
|
||||
return domain.ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
|
|
@ -456,7 +475,6 @@ func (u *AppUsecase) GetAppDetailByKBIDAndAppType(ctx context.Context, kbID stri
|
|||
RecommendNodeIDs: app.Settings.RecommendNodeIDs,
|
||||
Desc: app.Settings.Desc,
|
||||
Keyword: app.Settings.Keyword,
|
||||
AutoSitemap: app.Settings.AutoSitemap,
|
||||
HeadCode: app.Settings.HeadCode,
|
||||
BodyCode: app.Settings.BodyCode,
|
||||
// DingTalkBot
|
||||
|
|
@ -471,12 +489,13 @@ func (u *AppUsecase) GetAppDetailByKBIDAndAppType(ctx context.Context, kbID stri
|
|||
// LarkBot
|
||||
LarkBotSettings: app.Settings.LarkBotSettings,
|
||||
// WechatBot
|
||||
WeChatAppIsEnabled: app.Settings.WeChatAppIsEnabled,
|
||||
WeChatAppToken: app.Settings.WeChatAppToken,
|
||||
WeChatAppCorpID: app.Settings.WeChatAppCorpID,
|
||||
WeChatAppEncodingAESKey: app.Settings.WeChatAppEncodingAESKey,
|
||||
WeChatAppSecret: app.Settings.WeChatAppSecret,
|
||||
WeChatAppAgentID: app.Settings.WeChatAppAgentID,
|
||||
WeChatAppIsEnabled: app.Settings.WeChatAppIsEnabled,
|
||||
WeChatAppToken: app.Settings.WeChatAppToken,
|
||||
WeChatAppCorpID: app.Settings.WeChatAppCorpID,
|
||||
WeChatAppEncodingAESKey: app.Settings.WeChatAppEncodingAESKey,
|
||||
WeChatAppSecret: app.Settings.WeChatAppSecret,
|
||||
WeChatAppAgentID: app.Settings.WeChatAppAgentID,
|
||||
WeChatAppAdvancedSetting: app.Settings.WeChatAppAdvancedSetting,
|
||||
// WechatServiceBot
|
||||
WeChatServiceIsEnabled: app.Settings.WeChatServiceIsEnabled,
|
||||
WeChatServiceToken: app.Settings.WeChatServiceToken,
|
||||
|
|
@ -527,6 +546,9 @@ func (u *AppUsecase) GetAppDetailByKBIDAndAppType(ctx context.Context, kbID stri
|
|||
ConversationSetting: app.Settings.ConversationSetting,
|
||||
|
||||
WecomAIBotSettings: app.Settings.WecomAIBotSettings,
|
||||
|
||||
MCPServerSettings: app.Settings.MCPServerSettings,
|
||||
StatsSetting: app.Settings.StatsSetting,
|
||||
}
|
||||
|
||||
if !domain.GetBaseEditionLimitation(ctx).AllowCustomCopyright {
|
||||
|
|
@ -556,6 +578,19 @@ func (u *AppUsecase) GetAppDetailByKBIDAndAppType(ctx context.Context, kbID stri
|
|||
return appDetailResp, nil
|
||||
}
|
||||
|
||||
func (u *AppUsecase) GetMCPServerAppInfo(ctx context.Context, kbID string) (*domain.AppInfoResp, error) {
|
||||
apiApp, err := u.repo.GetOrCreateAppByKBIDAndType(ctx, kbID, domain.AppTypeMcpServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appInfo := &domain.AppInfoResp{
|
||||
Settings: domain.AppSettingsResp{
|
||||
MCPServerSettings: apiApp.Settings.MCPServerSettings,
|
||||
},
|
||||
}
|
||||
return appInfo, nil
|
||||
}
|
||||
|
||||
func (u *AppUsecase) ShareGetWebAppInfo(ctx context.Context, kbID string, authId uint) (*domain.AppInfoResp, error) {
|
||||
app, err := u.repo.GetOrCreateAppByKBIDAndType(ctx, kbID, domain.AppTypeWeb)
|
||||
if err != nil {
|
||||
|
|
@ -602,7 +637,6 @@ func (u *AppUsecase) ShareGetWebAppInfo(ctx context.Context, kbID string, authId
|
|||
RecommendNodeIDs: app.Settings.RecommendNodeIDs,
|
||||
Desc: app.Settings.Desc,
|
||||
Keyword: app.Settings.Keyword,
|
||||
AutoSitemap: app.Settings.AutoSitemap,
|
||||
HeadCode: app.Settings.HeadCode,
|
||||
BodyCode: app.Settings.BodyCode,
|
||||
// theme
|
||||
|
|
@ -632,6 +666,7 @@ func (u *AppUsecase) ShareGetWebAppInfo(ctx context.Context, kbID string, authId
|
|||
ContributeSettings: app.Settings.ContributeSettings,
|
||||
HomePageSetting: app.Settings.HomePageSetting,
|
||||
ConversationSetting: app.Settings.ConversationSetting,
|
||||
StatsSetting: app.Settings.StatsSetting,
|
||||
},
|
||||
}
|
||||
// init ai feedback string
|
||||
|
|
@ -696,6 +731,27 @@ func (u *AppUsecase) GetWidgetAppInfo(ctx context.Context, kbID string) (*domain
|
|||
return appInfo, nil
|
||||
}
|
||||
|
||||
func (u *AppUsecase) GetWechatAppInfo(ctx context.Context, kbID string) (*v1.WechatAppInfoResp, error) {
|
||||
wechatApp, err := u.repo.GetOrCreateAppByKBIDAndType(ctx, kbID, domain.AppTypeWechatBot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &v1.WechatAppInfoResp{}
|
||||
|
||||
if wechatApp.Settings.WeChatAppIsEnabled != nil {
|
||||
resp.WeChatAppIsEnabled = *wechatApp.Settings.WeChatAppIsEnabled
|
||||
}
|
||||
|
||||
if domain.GetBaseEditionLimitation(ctx).AllowAdvancedBot {
|
||||
resp.FeedbackEnable = wechatApp.Settings.WeChatAppAdvancedSetting.FeedbackEnable
|
||||
resp.FeedbackType = wechatApp.Settings.WeChatAppAdvancedSetting.FeedbackType
|
||||
resp.DisclaimerContent = wechatApp.Settings.WeChatAppAdvancedSetting.DisclaimerContent
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (u *AppUsecase) handleBotAuths(ctx context.Context, id string, newSettings *domain.AppSettings) error {
|
||||
|
||||
currentApp, err := u.repo.GetAppDetail(ctx, id)
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ func (u *ChatUsecase) Chat(ctx context.Context, req *domain.ChatRequest) (<-chan
|
|||
}
|
||||
|
||||
// 4. retrieve documents and format prompt
|
||||
messages, rankedNodes, err := u.llmUsecase.FormatConversationMessages(ctx, req.ConversationID, req.KBID, groupIds)
|
||||
messages, rankedNodes, err := u.llmUsecase.FormatConversationMessages(ctx, req.ConversationID, req.KBID, groupIds, req.Prompt)
|
||||
if err != nil {
|
||||
u.logger.Error("failed to format chat messages", log.Error(err))
|
||||
eventCh <- domain.SSEEvent{Type: "error", Content: "failed to format chat messages"}
|
||||
|
|
@ -301,6 +301,64 @@ func (u *ChatUsecase) Chat(ctx context.Context, req *domain.ChatRequest) (<-chan
|
|||
return eventCh, nil
|
||||
}
|
||||
|
||||
func (u *ChatUsecase) ChatRagOnly(ctx context.Context, req *domain.ChatRagOnlyRequest) (<-chan domain.SSEEvent, error) {
|
||||
eventCh := make(chan domain.SSEEvent, 100)
|
||||
go func() {
|
||||
defer close(eventCh)
|
||||
|
||||
// extra1. if user set question block words then check it
|
||||
blockWords, err := u.blockWordRepo.GetBlockWords(ctx, req.KBID)
|
||||
if err != nil {
|
||||
u.logger.Error("failed to get question block words", log.Error(err))
|
||||
eventCh <- domain.SSEEvent{Type: "error", Content: "failed to get question block words"}
|
||||
return
|
||||
}
|
||||
if len(blockWords) > 0 { // check --> filter
|
||||
questionFilter := utils.GetDFA(req.KBID)
|
||||
if err := questionFilter.DFA.Check(req.Message); err != nil { // exist then return err
|
||||
answer := "**您的问题包含敏感词, AI 无法回答您的问题。**"
|
||||
eventCh <- domain.SSEEvent{Type: "error", Content: answer}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if req.UserInfo.AuthUserID == 0 {
|
||||
auth, _ := u.AuthRepo.GetAuthBySourceType(ctx, req.AppType.ToSourceType())
|
||||
if auth != nil {
|
||||
req.UserInfo.AuthUserID = auth.ID
|
||||
}
|
||||
}
|
||||
|
||||
groupIds, err := u.AuthRepo.GetAuthGroupIdsWithParentsByAuthId(ctx, req.UserInfo.AuthUserID)
|
||||
if err != nil {
|
||||
u.logger.Error("failed to get auth groupIds", log.Error(err))
|
||||
eventCh <- domain.SSEEvent{Type: "error", Content: "failed to get auth groupIds"}
|
||||
return
|
||||
}
|
||||
|
||||
// retrieve documents
|
||||
kb, err := u.kbRepo.GetKnowledgeBaseByID(ctx, req.KBID)
|
||||
if err != nil {
|
||||
u.logger.Error("failed to get kb", log.Error(err))
|
||||
eventCh <- domain.SSEEvent{Type: "error", Content: "failed to get kb"}
|
||||
return
|
||||
}
|
||||
rankedNodes, err := u.llmUsecase.GetRankNodes(ctx, []string{kb.DatasetID}, req.Message, groupIds, 0, nil)
|
||||
if err != nil {
|
||||
u.logger.Error("failed to get rank nodes", log.Error(err))
|
||||
eventCh <- domain.SSEEvent{Type: "error", Content: "failed to get rank nodes"}
|
||||
return
|
||||
}
|
||||
documents := domain.FormatNodeChunks(rankedNodes, kb.AccessSettings.BaseURL)
|
||||
u.logger.Debug("documents", log.String("documents", documents))
|
||||
|
||||
// send only the documents part
|
||||
eventCh <- domain.SSEEvent{Type: "data", Content: documents}
|
||||
eventCh <- domain.SSEEvent{Type: "done"}
|
||||
}()
|
||||
return eventCh, nil
|
||||
}
|
||||
|
||||
func (u *ChatUsecase) CreateAcOnChunk(ctx context.Context, kbID string, answer *string, eventCh chan<- domain.SSEEvent, blockWords []string) (func(ctx context.Context, dataType, chunk string) error,
|
||||
func(ctx context.Context, dataType string)) {
|
||||
var buffer strings.Builder
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ func (u *LLMUsecase) FormatConversationMessages(
|
|||
conversationID string,
|
||||
kbID string,
|
||||
groupIDs []int,
|
||||
systemPrompt string,
|
||||
) ([]*schema.Message, []*domain.RankedNodeChunks, error) {
|
||||
messages := make([]*schema.Message, 0)
|
||||
rankedNodes := make([]*domain.RankedNodeChunks, 0)
|
||||
|
|
@ -85,12 +86,15 @@ func (u *LLMUsecase) FormatConversationMessages(
|
|||
if len(historyMessages) > 0 {
|
||||
question := historyMessages[len(historyMessages)-1].Content
|
||||
|
||||
systemPrompt := domain.SystemPrompt
|
||||
if prompt, err := u.promptRepo.GetPrompt(ctx, kbID); err != nil {
|
||||
u.logger.Error("get prompt from settings failed", log.Error(err))
|
||||
} else {
|
||||
if prompt != "" {
|
||||
systemPrompt = prompt
|
||||
if systemPrompt == "" {
|
||||
if settingPrompt, err := u.promptRepo.GetPrompt(ctx, kbID); err != nil {
|
||||
u.logger.Error("get prompt from settings failed", log.Error(err))
|
||||
} else {
|
||||
if settingPrompt != "" {
|
||||
systemPrompt = settingPrompt
|
||||
} else {
|
||||
systemPrompt = domain.SystemDefaultPrompt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -119,6 +119,12 @@ func (u *NodeUsecase) GetNodeByKBID(ctx context.Context, id, kbId, format string
|
|||
node.PublisherAccount = nodeRelease.PublisherAccount
|
||||
}
|
||||
|
||||
nodeStat, err := u.nodeRepo.GetNodeStatsByNodeId(ctx, node.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node.PV = nodeStat.PV
|
||||
|
||||
if node.Meta.ContentType == domain.ContentTypeMD {
|
||||
return node, nil
|
||||
}
|
||||
|
|
@ -221,6 +227,21 @@ func (u *NodeUsecase) GetNodeReleaseDetailByKBIDAndID(ctx context.Context, kbID,
|
|||
node.PublisherAccount = account
|
||||
}
|
||||
|
||||
if domain.GetBaseEditionLimitation(ctx).AllowNodeStats {
|
||||
webApp, err := u.appRepo.GetOrCreateAppByKBIDAndType(ctx, kbID, domain.AppTypeWeb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if webApp.Settings.StatsSetting.PVEnable {
|
||||
nodeStat, err := u.nodeRepo.GetNodeStatsByNodeId(ctx, nodeId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node.PV = nodeStat.PV
|
||||
}
|
||||
}
|
||||
|
||||
if node.Meta.ContentType == domain.ContentTypeMD {
|
||||
return node, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -472,3 +472,28 @@ func (u *StatUseCase) AggregateHourlyStats(ctx context.Context) error {
|
|||
func (u *StatUseCase) CleanupOldHourlyStats(ctx context.Context) error {
|
||||
return u.repo.CleanupOldHourlyStats(ctx)
|
||||
}
|
||||
|
||||
// MigrateYesterdayPVToNodeStats 将昨天的PV数据从stat_page迁移到node_stats
|
||||
func (u *StatUseCase) MigrateYesterdayPVToNodeStats(ctx context.Context) error {
|
||||
// 获取昨天的PV数据,按node_id分组
|
||||
pvMap, err := u.repo.GetYesterdayPVByNode(ctx)
|
||||
if err != nil {
|
||||
u.logger.Error("failed to get yesterday PV data", log.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 遍历并插入/更新到node_stats表
|
||||
for nodeID, pvCount := range pvMap {
|
||||
if err := u.repo.UpsertNodeStats(ctx, nodeID, pvCount); err != nil {
|
||||
u.logger.Error("failed to upsert node stats",
|
||||
log.Error(err),
|
||||
log.String("node_id", nodeID),
|
||||
log.Int64("pv_count", pvCount))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
u.logger.Info("successfully migrated yesterday PV data to node_stats",
|
||||
log.Int("node_count", len(pvMap)))
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,18 +13,20 @@ import (
|
|||
type WechatAppUsecase struct {
|
||||
logger *log.Logger
|
||||
AppUsecase *AppUsecase
|
||||
authRepo *pg.AuthRepo
|
||||
chatUsecase *ChatUsecase
|
||||
appRepo *pg.AppRepository
|
||||
authRepo *pg.AuthRepo
|
||||
weRepo *pg.WechatRepository
|
||||
}
|
||||
|
||||
func NewWechatAppUsecase(logger *log.Logger, AppUsecase *AppUsecase, chatUsecase *ChatUsecase, weRepo *pg.WechatRepository, authRepo *pg.AuthRepo) *WechatAppUsecase {
|
||||
func NewWechatAppUsecase(logger *log.Logger, AppUsecase *AppUsecase, chatUsecase *ChatUsecase, weRepo *pg.WechatRepository, authRepo *pg.AuthRepo, appRepo *pg.AppRepository) *WechatAppUsecase {
|
||||
return &WechatAppUsecase{
|
||||
logger: logger.WithModule("usecase.wechatAppUsecase"),
|
||||
AppUsecase: AppUsecase,
|
||||
chatUsecase: chatUsecase,
|
||||
weRepo: weRepo,
|
||||
authRepo: authRepo,
|
||||
appRepo: appRepo,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +39,7 @@ func (u *WechatAppUsecase) VerifyUrlWechatAPP(ctx context.Context, signature, ti
|
|||
return body, nil
|
||||
}
|
||||
|
||||
func (u *WechatAppUsecase) Wechat(ctx context.Context, msg *wechat.ReceivedMessage, wc *wechat.WechatConfig, KbId string) error {
|
||||
func (u *WechatAppUsecase) Wechat(ctx context.Context, msg *wechat.ReceivedMessage, wc *wechat.WechatConfig, KbId string, weChatAppAdvancedSetting *domain.WeChatAppAdvancedSetting) error {
|
||||
getQA := u.getQAFunc(KbId, domain.AppTypeWechatBot)
|
||||
|
||||
// 调用接口,获取到用户的详细消息
|
||||
|
|
@ -49,8 +51,10 @@ func (u *WechatAppUsecase) Wechat(ctx context.Context, msg *wechat.ReceivedMessa
|
|||
u.logger.Info("get userinfo success", log.Any("userinfo", userinfo))
|
||||
wc.WeRepo = u.weRepo
|
||||
|
||||
useTextResponse := domain.GetBaseEditionLimitation(ctx).AllowAdvancedBot && (weChatAppAdvancedSetting != nil && weChatAppAdvancedSetting.TextResponseEnable)
|
||||
|
||||
// 发送消息给用户
|
||||
err = wc.Wechat(*msg, getQA, userinfo)
|
||||
err = wc.Wechat(*msg, getQA, userinfo, useTextResponse, weChatAppAdvancedSetting)
|
||||
|
||||
if err != nil {
|
||||
u.logger.Error("wc wechat failed", log.Error(err))
|
||||
|
|
@ -79,6 +83,12 @@ func (u *WechatAppUsecase) getQAFunc(kbID string, appType domain.AppType) bot.Ge
|
|||
u.logger.Error("get auth failed", log.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
wechatApp, err := u.appRepo.GetOrCreateAppByKBIDAndType(ctx, kbID, domain.AppTypeWechatBot)
|
||||
if err != nil {
|
||||
u.logger.Error("failed to get wechat app", log.Error(err), log.String("kb_id", kbID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info.UserInfo.AuthUserID = auth.ID
|
||||
|
||||
eventCh, err := u.chatUsecase.Chat(ctx, &domain.ChatRequest{
|
||||
|
|
@ -88,6 +98,7 @@ func (u *WechatAppUsecase) getQAFunc(kbID string, appType domain.AppType) bot.Ge
|
|||
RemoteIP: "",
|
||||
ConversationID: ConversationID,
|
||||
Info: info,
|
||||
Prompt: wechatApp.Settings.WeChatAppAdvancedSetting.Prompt,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -2,13 +2,18 @@ package utils
|
|||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func GetClientIPFromRemoteAddr(c echo.Context) string {
|
||||
addr := c.Request().RemoteAddr
|
||||
return ExtractHostFromRemoteAddr(c.Request())
|
||||
}
|
||||
|
||||
func ExtractHostFromRemoteAddr(r *http.Request) string {
|
||||
addr := r.RemoteAddr
|
||||
if addr == "" {
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -323,7 +323,6 @@ export type WelcomeSetting = {
|
|||
export type SEOSetting = {
|
||||
keyword: string;
|
||||
desc: string;
|
||||
auto_sitemap: boolean;
|
||||
};
|
||||
|
||||
export type CustomCodeSetting = {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ const Step1Model: React.FC<Step1ModelProps> = ({ ref }) => {
|
|||
}, [modelList]);
|
||||
|
||||
const onSubmit = async () => {
|
||||
await modelConfigRef.current?.onSubmit?.();
|
||||
// 检查模型模式设置
|
||||
try {
|
||||
const modeSetting = await getApiV1ModelModeSetting();
|
||||
|
|
@ -112,6 +113,7 @@ const Step1Model: React.FC<Step1ModelProps> = ({ ref }) => {
|
|||
getModelList={getModelList}
|
||||
hideDocumentationHint={true}
|
||||
showTip={true}
|
||||
showSaveBtn={false}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { copyText } from '@/utils';
|
|||
import { Box, Stack } from '@mui/material';
|
||||
import { Ellipsis } from '@ctzhian/ui';
|
||||
import { IconFuzhi } from '@panda-wiki/icons';
|
||||
import { message } from '@ctzhian/ui';
|
||||
|
||||
interface ShowTextProps {
|
||||
text: string[];
|
||||
|
|
@ -10,6 +11,7 @@ interface ShowTextProps {
|
|||
noEllipsis?: boolean;
|
||||
icon?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
forceCopy?: boolean;
|
||||
}
|
||||
|
||||
const ShowText = ({
|
||||
|
|
@ -21,6 +23,7 @@ const ShowText = ({
|
|||
),
|
||||
onClick,
|
||||
noEllipsis = false,
|
||||
forceCopy = false,
|
||||
}: ShowTextProps) => {
|
||||
return (
|
||||
<Stack
|
||||
|
|
@ -48,8 +51,32 @@ const ShowText = ({
|
|||
onClick={
|
||||
copyable
|
||||
? () => {
|
||||
copyText(text.join('\n'));
|
||||
onClick?.();
|
||||
const content = text.join('\n');
|
||||
if (forceCopy) {
|
||||
try {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(content);
|
||||
message.success('复制成功');
|
||||
} else {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.opacity = '0';
|
||||
ta.style.left = '-9999px';
|
||||
ta.style.top = '-9999px';
|
||||
ta.value = content;
|
||||
document.body.appendChild(ta);
|
||||
ta.focus();
|
||||
ta.select();
|
||||
const ok = document.execCommand('copy');
|
||||
if (ok) message.success('复制成功');
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
} catch (e) {}
|
||||
onClick?.();
|
||||
} else {
|
||||
copyText(content);
|
||||
onClick?.();
|
||||
}
|
||||
}
|
||||
: onClick
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ const Sidebar = () => {
|
|||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
|
|
@ -156,7 +157,7 @@ const Sidebar = () => {
|
|||
>
|
||||
PandaWiki
|
||||
</Box>
|
||||
<Stack sx={{ pt: 2, flexGrow: 1 }} gap={1}>
|
||||
<Stack sx={{ py: 2, flexGrow: 1 }} gap={1}>
|
||||
{menus.map(it => {
|
||||
let isActive = false;
|
||||
if (it.value === '/') {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ const ModelModal = lazy(() =>
|
|||
export interface ModelConfigRef {
|
||||
getAutoConfigFormData: () => { apiKey: string; selectedModel: string } | null;
|
||||
handleClose: () => void;
|
||||
onSubmit: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface ModelConfigProps {
|
||||
|
|
@ -57,6 +58,7 @@ interface ModelConfigProps {
|
|||
autoSwitchToAutoMode?: boolean;
|
||||
hideDocumentationHint?: boolean;
|
||||
showTip?: boolean;
|
||||
showSaveBtn?: boolean;
|
||||
}
|
||||
|
||||
const ModelConfig = forwardRef<ModelConfigRef, ModelConfigProps>(
|
||||
|
|
@ -73,6 +75,7 @@ const ModelConfig = forwardRef<ModelConfigRef, ModelConfigProps>(
|
|||
autoSwitchToAutoMode = false,
|
||||
hideDocumentationHint = false,
|
||||
showTip = false,
|
||||
showSaveBtn = true,
|
||||
} = props;
|
||||
|
||||
const [autoConfigMode, setAutoConfigMode] = useState(false);
|
||||
|
|
@ -182,51 +185,58 @@ const ModelConfig = forwardRef<ModelConfigRef, ModelConfigProps>(
|
|||
}
|
||||
return null;
|
||||
},
|
||||
onSubmit: handleSave,
|
||||
handleClose: handleCloseModal,
|
||||
}));
|
||||
|
||||
const handleSave = async () => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const requestData: {
|
||||
mode: 'auto' | 'manual';
|
||||
auto_mode_api_key?: string;
|
||||
chat_model?: string;
|
||||
} = {
|
||||
mode: tempMode,
|
||||
};
|
||||
if (tempMode !== savedMode || hasConfigChanged) {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const requestData: {
|
||||
mode: 'auto' | 'manual';
|
||||
auto_mode_api_key?: string;
|
||||
chat_model?: string;
|
||||
} = {
|
||||
mode: tempMode,
|
||||
};
|
||||
|
||||
// 如果是自动模式,获取用户输入的 API Key 和 model
|
||||
if (tempMode === 'auto' && autoConfigRef.current) {
|
||||
const formData = autoConfigRef.current.getFormData();
|
||||
if (formData) {
|
||||
requestData.auto_mode_api_key = formData.apiKey;
|
||||
requestData.chat_model = formData.selectedModel;
|
||||
// 如果是自动模式,获取用户输入的 API Key 和 model
|
||||
if (tempMode === 'auto' && autoConfigRef.current) {
|
||||
const formData = autoConfigRef.current.getFormData();
|
||||
if (formData) {
|
||||
requestData.auto_mode_api_key = formData.apiKey;
|
||||
requestData.chat_model = formData.selectedModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await postApiV1ModelSwitchMode(requestData);
|
||||
setSavedMode(tempMode);
|
||||
setAutoConfigMode(tempMode === 'auto');
|
||||
setHasConfigChanged(false); // 重置变更标记
|
||||
await postApiV1ModelSwitchMode(requestData);
|
||||
setSavedMode(tempMode);
|
||||
setAutoConfigMode(tempMode === 'auto');
|
||||
setHasConfigChanged(false); // 重置变更标记
|
||||
|
||||
// 更新保存的初始值
|
||||
if (tempMode === 'auto' && autoConfigRef.current) {
|
||||
const formData = autoConfigRef.current.getFormData();
|
||||
if (formData) {
|
||||
setInitialApiKey(formData.apiKey);
|
||||
setInitialChatModel(formData.selectedModel);
|
||||
// 更新保存的初始值
|
||||
if (tempMode === 'auto' && autoConfigRef.current) {
|
||||
const formData = autoConfigRef.current.getFormData();
|
||||
if (formData) {
|
||||
setInitialApiKey(formData.apiKey);
|
||||
setInitialChatModel(formData.selectedModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message.success(
|
||||
tempMode === 'auto' ? '已切换为自动配置模式' : '已切换为手动配置模式',
|
||||
);
|
||||
getModelList(); // 刷新模型列表
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
if (showSaveBtn) {
|
||||
message.success(
|
||||
tempMode === 'auto'
|
||||
? '已切换为自动配置模式'
|
||||
: '已切换为手动配置模式',
|
||||
);
|
||||
}
|
||||
getModelList(); // 刷新模型列表
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -310,7 +320,7 @@ const ModelConfig = forwardRef<ModelConfigRef, ModelConfigProps>(
|
|||
/>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
{(tempMode !== savedMode || hasConfigChanged) && (
|
||||
{(tempMode !== savedMode || hasConfigChanged) && showSaveBtn && (
|
||||
<Button
|
||||
variant='contained'
|
||||
size='small'
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
IconTianjiawendang,
|
||||
IconZiti,
|
||||
} from '@panda-wiki/icons';
|
||||
import IconPageview1 from '@panda-wiki/icons/IconPageview1';
|
||||
import dayjs from 'dayjs';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
|
@ -202,23 +203,32 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
onAiWritingGetSuggestion: handleAiWritingGetSuggestion,
|
||||
});
|
||||
|
||||
const exportFile = (value: string, type: string) => {
|
||||
if (!value) return;
|
||||
const content = completeIncompleteLinks(value);
|
||||
const blob = new Blob([content], { type: `text/${type}` });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${nodeDetail?.name}.${type}`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
message.success('导出成功');
|
||||
};
|
||||
|
||||
const handleExport = useCallback(
|
||||
async (type: string) => {
|
||||
if (editorRef) {
|
||||
let value = nodeDetail?.content || '';
|
||||
if (!isMarkdown) {
|
||||
value = editorRef.getContent() || '';
|
||||
if (type === 'html') {
|
||||
const value = editorRef.getHTML() || '';
|
||||
exportFile(value, type);
|
||||
} else if (type === 'md') {
|
||||
if (isMarkdown) {
|
||||
let value = nodeDetail?.content || '';
|
||||
exportFile(value, type);
|
||||
} else if (editorRef) {
|
||||
const value = editorRef.getMarkdown() || '';
|
||||
exportFile(value, type);
|
||||
}
|
||||
if (!value) return;
|
||||
const content = completeIncompleteLinks(value);
|
||||
const blob = new Blob([content], { type: `text/${type}` });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${nodeDetail?.name}.${type}`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
message.success('导出成功');
|
||||
}
|
||||
},
|
||||
[editorRef, nodeDetail?.content, nodeDetail?.name, isMarkdown],
|
||||
|
|
@ -425,6 +435,15 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
<IconZiti sx={{ fontSize: 12 }} />
|
||||
{characterCount} 字
|
||||
</Stack>
|
||||
<Stack
|
||||
direction={'row'}
|
||||
alignItems={'center'}
|
||||
gap={0.5}
|
||||
sx={{ fontSize: 12, color: 'text.tertiary' }}
|
||||
>
|
||||
<IconPageview1 sx={{ fontSize: 12 }} />
|
||||
浏览量 {nodeDetail?.pv}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -673,9 +692,13 @@ const Wrap = ({ detail: defaultDetail }: WrapProps) => {
|
|||
<Box
|
||||
sx={{ ...(fixedToc && { display: 'flex' }) }}
|
||||
onKeyDown={event => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 's') {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isMarkdown &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
(event.key === 's' || event.key === 'b')
|
||||
event.key === 'b'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { getApiProV1Prompt, postApiProV1Prompt } from '@/request/pro/Prompt';
|
|||
import { DomainKnowledgeBaseDetail } from '@/request/types';
|
||||
import { PROFESSION_VERSION_PERMISSION } from '@/constant/version';
|
||||
import { useAppSelector } from '@/store';
|
||||
import { message } from '@ctzhian/ui';
|
||||
import { message, Modal } from '@ctzhian/ui';
|
||||
import { Box, Slider, TextField } from '@mui/material';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
|
|
@ -45,6 +45,23 @@ const CardAI = ({ kb }: CardAIProps) => {
|
|||
});
|
||||
}, [kb, isPro]);
|
||||
|
||||
const onResetPrompt = () => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要重置为默认提示词吗?',
|
||||
onOk: () => {
|
||||
postApiProV1Prompt({
|
||||
kb_id: kb.id!,
|
||||
content: '',
|
||||
}).then(() => {
|
||||
getApiProV1Prompt({ kb_id: kb.id! }).then(res => {
|
||||
setValue('content', res.content || '');
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -65,10 +82,7 @@ const CardAI = ({ kb }: CardAIProps) => {
|
|||
display: 'block',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
setValue('content', '');
|
||||
setIsEdit(true);
|
||||
}}
|
||||
onClick={onResetPrompt}
|
||||
>
|
||||
重置为默认提示词
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,276 @@
|
|||
import { DomainKnowledgeBaseDetail } from '@/request/types';
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
TextField,
|
||||
Stack,
|
||||
} from '@mui/material';
|
||||
import { SettingCardItem, FormItem } from './Common';
|
||||
import ShowText from '@/components/ShowText';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useMemo, useState, useEffect } from 'react';
|
||||
import { message } from '@ctzhian/ui';
|
||||
import { getApiV1AppDetail, putApiV1App } from '@/request/App';
|
||||
import { DomainAppDetailResp, ConstsLicenseEdition } from '@/request/types';
|
||||
|
||||
interface CardMCPProps {
|
||||
kb: DomainKnowledgeBaseDetail;
|
||||
}
|
||||
|
||||
const CardMCP = ({ kb }: CardMCPProps) => {
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
is_enabled: false,
|
||||
access: 'open' as 'open' | 'auth',
|
||||
token: '',
|
||||
tool_name: 'get_docs',
|
||||
tool_desc: '为解决用户的问题从知识库中检索文档',
|
||||
},
|
||||
});
|
||||
|
||||
const isEnabled = watch('is_enabled');
|
||||
const access = watch('access');
|
||||
const [detail, setDetail] = useState<DomainAppDetailResp | null>(null);
|
||||
|
||||
const mcpUrl = useMemo(() => {
|
||||
const hostRaw = kb?.access_settings?.hosts?.[0] || window.location.hostname;
|
||||
const host = hostRaw === '*' ? window.location.hostname : hostRaw;
|
||||
const sslPorts = kb?.access_settings?.ssl_ports || [];
|
||||
const httpPorts = kb?.access_settings?.ports || [];
|
||||
const isHttps = sslPorts.length > 0;
|
||||
const protocol = isHttps ? 'https' : 'http';
|
||||
if (!host) {
|
||||
return `${protocol}://${window.location.hostname}${isHttps ? '' : `:${window.location.port}`}/mcp`;
|
||||
}
|
||||
if (isHttps) {
|
||||
return `${protocol}://${host}/mcp`;
|
||||
}
|
||||
const port = httpPorts[0];
|
||||
if (!port) return `${protocol}://${host}/mcp`;
|
||||
return `${protocol}://${host}:${port}/mcp`;
|
||||
}, [kb]);
|
||||
|
||||
const onSubmit = handleSubmit(() => {
|
||||
if (!kb || !detail) return;
|
||||
const payload: any = {
|
||||
kb_id: kb.id!,
|
||||
settings: {
|
||||
mcp_server_settings: {
|
||||
is_enabled: isEnabled,
|
||||
docs_tool_settings: {
|
||||
name: watch('tool_name'),
|
||||
desc: watch('tool_desc'),
|
||||
},
|
||||
sample_auth: {
|
||||
enabled: access === 'auth',
|
||||
password: access === 'auth' ? watch('token') : '',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
putApiV1App({ id: detail.id! }, payload).then(() => {
|
||||
message.success('保存成功');
|
||||
setIsEdit(false);
|
||||
getDetail();
|
||||
});
|
||||
});
|
||||
|
||||
const getDetail = () => {
|
||||
getApiV1AppDetail({ kb_id: kb.id!, type: '12' }).then(res => {
|
||||
setDetail(res);
|
||||
const is_enabled =
|
||||
(res.settings as any)?.mcp_server_settings?.is_enabled ?? false;
|
||||
const auth =
|
||||
(res.settings as any)?.mcp_server_settings?.sample_auth ?? {};
|
||||
const accessVal = auth.enabled ? 'auth' : 'open';
|
||||
const tokenVal = auth.password ?? '';
|
||||
const toolNameRaw =
|
||||
(res.settings as any)?.mcp_server_settings?.docs_tool_settings?.name ??
|
||||
'';
|
||||
const toolDescRaw =
|
||||
(res.settings as any)?.mcp_server_settings?.docs_tool_settings?.desc ??
|
||||
'';
|
||||
const toolName = toolNameRaw.trim() ? toolNameRaw : 'get_docs';
|
||||
const toolDesc = toolDescRaw.trim()
|
||||
? toolDescRaw
|
||||
: '为解决用户的问题从知识库中检索文档';
|
||||
setValue('is_enabled', is_enabled);
|
||||
setValue('access', accessVal);
|
||||
setValue('token', tokenVal);
|
||||
setValue('tool_name', toolName);
|
||||
setValue('tool_desc', toolDesc);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!kb) return;
|
||||
getDetail();
|
||||
}, [kb]);
|
||||
|
||||
return (
|
||||
<Box sx={{ width: 1000, margin: 'auto', pb: 4 }}>
|
||||
<SettingCardItem
|
||||
title='MCP 设置'
|
||||
isEdit={isEdit}
|
||||
onSubmit={onSubmit}
|
||||
permission={[
|
||||
ConstsLicenseEdition.LicenseEditionBusiness,
|
||||
ConstsLicenseEdition.LicenseEditionEnterprise,
|
||||
]}
|
||||
more={{
|
||||
type: 'link',
|
||||
href: 'https://pandawiki.docs.baizhi.cloud/node/019aa45c-90c1-7e6f-b17a-74ab1b200153',
|
||||
target: '_blank',
|
||||
text: '使用方法',
|
||||
}}
|
||||
>
|
||||
<FormItem label='MCP Server'>
|
||||
<FormControl>
|
||||
<Controller
|
||||
control={control}
|
||||
name='is_enabled'
|
||||
render={({ field }) => (
|
||||
<RadioGroup
|
||||
{...field}
|
||||
onChange={e => {
|
||||
field.onChange(e.target.value === 'true');
|
||||
setIsEdit(true);
|
||||
}}
|
||||
>
|
||||
<Stack direction={'row'}>
|
||||
<FormControlLabel
|
||||
value={true}
|
||||
control={<Radio size='small' />}
|
||||
label={<Box sx={{ width: 100 }}>启用</Box>}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value={false}
|
||||
control={<Radio size='small' />}
|
||||
label={<Box sx={{ width: 100 }}>禁用</Box>}
|
||||
/>
|
||||
</Stack>
|
||||
</RadioGroup>
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
|
||||
{isEnabled && (
|
||||
<>
|
||||
<FormItem label='MCP URL'>
|
||||
<ShowText
|
||||
text={[mcpUrl]}
|
||||
copyable={true}
|
||||
noEllipsis={true}
|
||||
forceCopy={true}
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label='MCP Tool名称'>
|
||||
<Controller
|
||||
control={control}
|
||||
name='tool_name'
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder='自定义检索文档MCP Tool名称'
|
||||
onChange={e => {
|
||||
field.onChange(e.target.value);
|
||||
setIsEdit(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label='MCP Tool描述'>
|
||||
<Controller
|
||||
control={control}
|
||||
name='tool_desc'
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder='自定义检索文档MCP Tool描述'
|
||||
onChange={e => {
|
||||
field.onChange(e.target.value);
|
||||
setIsEdit(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label='访问控制'>
|
||||
<FormControl>
|
||||
<Controller
|
||||
control={control}
|
||||
name='access'
|
||||
render={({ field }) => (
|
||||
<RadioGroup
|
||||
{...field}
|
||||
onChange={e => {
|
||||
field.onChange(e.target.value);
|
||||
setIsEdit(true);
|
||||
}}
|
||||
>
|
||||
<Stack direction={'row'}>
|
||||
<FormControlLabel
|
||||
value={'open'}
|
||||
control={<Radio size='small' />}
|
||||
label={<Box sx={{ width: 100 }}>完全公开</Box>}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value={'auth'}
|
||||
control={<Radio size='small' />}
|
||||
label={<Box sx={{ width: 100 }}>需要认证</Box>}
|
||||
/>
|
||||
</Stack>
|
||||
</RadioGroup>
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
|
||||
{access === 'auth' && (
|
||||
<FormItem label='访问口令' required>
|
||||
<Controller
|
||||
control={control}
|
||||
name='token'
|
||||
rules={{ required: '访问口令不能为空' }}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
placeholder='访问口令'
|
||||
onChange={e => {
|
||||
field.onChange(e.target.value);
|
||||
setIsEdit(true);
|
||||
}}
|
||||
error={!!errors.token}
|
||||
helperText={errors.token?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</SettingCardItem>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardMCP;
|
||||
|
|
@ -5,8 +5,12 @@ import {
|
|||
Radio,
|
||||
RadioGroup,
|
||||
TextField,
|
||||
Autocomplete,
|
||||
Chip,
|
||||
} from '@mui/material';
|
||||
import { message } from '@ctzhian/ui';
|
||||
import { PROFESSION_VERSION_PERMISSION } from '@/constant/version';
|
||||
import VersionMask from '@/components/VersionMask';
|
||||
import { message, Modal } from '@ctzhian/ui';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import {
|
||||
|
|
@ -17,6 +21,8 @@ import { getApiV1AppDetail, putApiV1App } from '@/request/App';
|
|||
import { FormItem, SettingCardItem } from './Common';
|
||||
import { useAppSelector } from '@/store';
|
||||
|
||||
const AI_FEEDBACK_OPTIONS = ['内容不准确', '答非所问', '其他'];
|
||||
|
||||
const CardRobotWecom = ({
|
||||
kb,
|
||||
url,
|
||||
|
|
@ -28,12 +34,14 @@ const CardRobotWecom = ({
|
|||
const [detail, setDetail] = useState<DomainAppDetailResp | null>(null);
|
||||
const [isEnabled, setIsEnabled] = useState(false);
|
||||
const { kb_id } = useAppSelector(state => state.config);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
reset,
|
||||
setValue,
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
wechat_app_is_enabled: false,
|
||||
|
|
@ -42,6 +50,11 @@ const CardRobotWecom = ({
|
|||
wechat_app_token: '',
|
||||
wechat_app_encodingaeskey: '',
|
||||
wechat_app_corpid: '',
|
||||
text_response_enable: false,
|
||||
feedback_enable: false,
|
||||
feedback_type: [] as string[],
|
||||
prompt: '',
|
||||
disclaimer_content: '',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -57,6 +70,16 @@ const CardRobotWecom = ({
|
|||
wechat_app_encodingaeskey:
|
||||
res.settings?.wechat_app_encodingaeskey ?? '',
|
||||
wechat_app_corpid: res.settings?.wechat_app_corpid ?? '',
|
||||
text_response_enable:
|
||||
res.settings?.wechat_app_advanced_setting?.text_response_enable ??
|
||||
false,
|
||||
feedback_enable:
|
||||
res.settings?.wechat_app_advanced_setting?.feedback_enable ?? false,
|
||||
feedback_type:
|
||||
res.settings?.wechat_app_advanced_setting?.feedback_type ?? [],
|
||||
prompt: res.settings?.wechat_app_advanced_setting?.prompt ?? '',
|
||||
disclaimer_content:
|
||||
res.settings?.wechat_app_advanced_setting?.disclaimer_content ?? '',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -74,6 +97,13 @@ const CardRobotWecom = ({
|
|||
wechat_app_token: data.wechat_app_token,
|
||||
wechat_app_encodingaeskey: data.wechat_app_encodingaeskey,
|
||||
wechat_app_corpid: data.wechat_app_corpid,
|
||||
wechat_app_advanced_setting: {
|
||||
text_response_enable: data.text_response_enable,
|
||||
feedback_enable: data.feedback_enable,
|
||||
feedback_type: data.feedback_type,
|
||||
prompt: data.prompt,
|
||||
disclaimer_content: data.disclaimer_content,
|
||||
},
|
||||
},
|
||||
},
|
||||
).then(() => {
|
||||
|
|
@ -88,6 +118,37 @@ const CardRobotWecom = ({
|
|||
getDetail();
|
||||
}, [kb]);
|
||||
|
||||
const onResetPrompt = () => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要重置为默认提示词吗?',
|
||||
onOk: () => {
|
||||
putApiV1App(
|
||||
{ id: detail!.id! },
|
||||
{
|
||||
kb_id,
|
||||
settings: {
|
||||
...detail?.settings,
|
||||
wechat_app_advanced_setting: {
|
||||
...detail?.settings?.wechat_app_advanced_setting,
|
||||
prompt: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
).then(() => {
|
||||
getApiV1AppDetail({ kb_id: kb.id!, type: '5' }).then(res => {
|
||||
setDetail(res);
|
||||
setValue(
|
||||
'prompt',
|
||||
res.settings?.wechat_app_advanced_setting?.prompt ?? '',
|
||||
);
|
||||
});
|
||||
message.success('保存成功');
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingCardItem
|
||||
title='企业微信机器人'
|
||||
|
|
@ -249,6 +310,169 @@ const CardRobotWecom = ({
|
|||
)}
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<VersionMask permission={PROFESSION_VERSION_PERMISSION}>
|
||||
<FormItem label='问答返回类型'>
|
||||
<Controller
|
||||
control={control}
|
||||
name='text_response_enable'
|
||||
render={({ field }) => (
|
||||
<RadioGroup
|
||||
row
|
||||
{...field}
|
||||
onChange={e => {
|
||||
field.onChange(e.target.value === 'true');
|
||||
setIsEdit(true);
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
value={false}
|
||||
control={<Radio size='small' />}
|
||||
label={<Box sx={{ width: 100 }}>卡片</Box>}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value={true}
|
||||
control={<Radio size='small' />}
|
||||
label={<Box sx={{ width: 100 }}>文本</Box>}
|
||||
/>
|
||||
</RadioGroup>
|
||||
)}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label='智能问答提示词' sx={{ alignItems: 'flex-start' }}>
|
||||
<Controller
|
||||
control={control}
|
||||
name='prompt'
|
||||
render={({ field }) => (
|
||||
<Box sx={{ position: 'relative', flex: 1 }}>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 12,
|
||||
left: 12,
|
||||
fontSize: 12,
|
||||
color: 'primary.main',
|
||||
display: 'block',
|
||||
cursor: 'pointer',
|
||||
zIndex: 1,
|
||||
}}
|
||||
onClick={onResetPrompt}
|
||||
>
|
||||
重置为默认提示词
|
||||
</Box>
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={6}
|
||||
maxRows={20}
|
||||
slotProps={{
|
||||
input: {
|
||||
sx: { pt: '36px' },
|
||||
},
|
||||
}}
|
||||
placeholder='智能问答提示词'
|
||||
onChange={e => {
|
||||
field.onChange(e.target.value);
|
||||
setIsEdit(true);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label='AI 问答评价'>
|
||||
<Controller
|
||||
control={control}
|
||||
name='feedback_type'
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...field}
|
||||
multiple
|
||||
freeSolo
|
||||
fullWidth
|
||||
options={AI_FEEDBACK_OPTIONS}
|
||||
inputValue={inputValue}
|
||||
onInputChange={(_, newInputValue) =>
|
||||
setInputValue(newInputValue)
|
||||
}
|
||||
onChange={(_, newValue) => {
|
||||
setIsEdit(true);
|
||||
const newValues = [...new Set(newValue as string[])];
|
||||
field.onChange(newValues);
|
||||
}}
|
||||
renderValue={(value, getTagProps) => {
|
||||
return value.map((option, index: number) => {
|
||||
return (
|
||||
<Chip
|
||||
variant='outlined'
|
||||
size='small'
|
||||
label={
|
||||
<Box sx={{ fontSize: '12px' }}>{option}</Box>
|
||||
}
|
||||
{...getTagProps({ index })}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder='选择或输入评价,可多选,回车确认'
|
||||
variant='outlined'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label='评价开关'>
|
||||
<Controller
|
||||
control={control}
|
||||
name='feedback_enable'
|
||||
render={({ field }) => (
|
||||
<RadioGroup
|
||||
row
|
||||
{...field}
|
||||
onChange={e => {
|
||||
setIsEdit(true);
|
||||
field.onChange(e.target.value === 'true');
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
value={true}
|
||||
control={<Radio size='small' />}
|
||||
label={<Box sx={{ width: 100 }}>启用</Box>}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value={false}
|
||||
control={<Radio size='small' />}
|
||||
label={<Box sx={{ width: 100 }}>禁用</Box>}
|
||||
/>
|
||||
</RadioGroup>
|
||||
)}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label='免责声明'>
|
||||
<Controller
|
||||
control={control}
|
||||
name='disclaimer_content'
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
fullWidth
|
||||
value={field.value || ''}
|
||||
placeholder='请输入免责声明'
|
||||
onChange={e => {
|
||||
setIsEdit(true);
|
||||
field.onChange(e.target.value);
|
||||
}}
|
||||
></TextField>
|
||||
)}
|
||||
/>
|
||||
</FormItem>
|
||||
</VersionMask>
|
||||
</>
|
||||
)}
|
||||
</SettingCardItem>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import CardStyle from './CardStyle';
|
|||
import CardWebCustomCode from './CardWebCustomCode';
|
||||
import CardWebSEO from './CardWebSEO';
|
||||
import CardQaCopyright from './CardQaCopyright';
|
||||
import CardWebStats from './CardWebStats';
|
||||
|
||||
interface CardWebProps {
|
||||
kb: DomainKnowledgeBaseDetail;
|
||||
|
|
@ -134,6 +135,22 @@ const CardWeb = ({ kb, refresh }: CardWebProps) => {
|
|||
});
|
||||
}}
|
||||
/>
|
||||
<CardWebStats
|
||||
id={info.id}
|
||||
data={info}
|
||||
refresh={value => {
|
||||
setInfo({
|
||||
...info,
|
||||
settings: {
|
||||
...info.settings,
|
||||
stats_setting: {
|
||||
...info.settings?.stats_setting,
|
||||
...value,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
import { message } from '@ctzhian/ui';
|
||||
import { Box, FormControlLabel, Radio, RadioGroup } from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { DomainAppDetailResp } from '@/request/types';
|
||||
import { SettingCardItem, FormItem } from './Common';
|
||||
import { useAppSelector } from '@/store';
|
||||
import { putApiV1App } from '@/request/App';
|
||||
import { PROFESSION_VERSION_PERMISSION } from '@/constant/version.ts';
|
||||
import VersionMask from '@/components/VersionMask';
|
||||
|
||||
interface CardWebStatsProps {
|
||||
id: string;
|
||||
data: DomainAppDetailResp;
|
||||
refresh: (value: { pv_enable?: boolean }) => void;
|
||||
}
|
||||
|
||||
interface StatsFormData {
|
||||
pv_enable: 1 | 2;
|
||||
}
|
||||
|
||||
const CardWebStats = ({ data, id, refresh }: CardWebStatsProps) => {
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
const { kb_id } = useAppSelector(state => state.config);
|
||||
const { handleSubmit, control, setValue } = useForm<StatsFormData>({
|
||||
defaultValues: {
|
||||
pv_enable: 2,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit((value: StatsFormData) => {
|
||||
const submitValue = {
|
||||
pv_enable: value.pv_enable === 1,
|
||||
};
|
||||
putApiV1App(
|
||||
{ id },
|
||||
{ kb_id, settings: { ...data.settings, stats_setting: submitValue } },
|
||||
).then(() => {
|
||||
message.success('保存成功');
|
||||
refresh(submitValue);
|
||||
setIsEdit(false);
|
||||
});
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const pvEnable = data.settings?.stats_setting?.pv_enable;
|
||||
setValue('pv_enable', pvEnable === true ? 1 : 2);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<SettingCardItem title='统计分析' isEdit={isEdit} onSubmit={onSubmit}>
|
||||
<VersionMask permission={PROFESSION_VERSION_PERMISSION}>
|
||||
<FormItem label='文档浏览量'>
|
||||
<Controller
|
||||
control={control}
|
||||
name='pv_enable'
|
||||
render={({ field }) => (
|
||||
<RadioGroup
|
||||
row
|
||||
{...field}
|
||||
onChange={e => {
|
||||
field.onChange(+e.target.value as 1 | 2);
|
||||
setIsEdit(true);
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
value={1}
|
||||
control={<Radio size='small' />}
|
||||
label={<Box sx={{ width: 100 }}>展示</Box>}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value={2}
|
||||
control={<Radio size='small' />}
|
||||
label={<Box sx={{ width: 100 }}>隐藏</Box>}
|
||||
/>
|
||||
</RadioGroup>
|
||||
)}
|
||||
/>
|
||||
</FormItem>
|
||||
</VersionMask>
|
||||
</SettingCardItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardWebStats;
|
||||
|
|
@ -15,6 +15,7 @@ import CardKB from './component/CardKB';
|
|||
import CardRobot from './component/CardRobot';
|
||||
import CardSecurity from './component/CardSecurity';
|
||||
import CardWeb from './component/CardWeb';
|
||||
import CardMCP from './component/CardMCP';
|
||||
|
||||
const SettingTabs: { label: string; id: string }[] = [
|
||||
{ label: '门户网站', id: 'portal-website' },
|
||||
|
|
@ -23,6 +24,7 @@ const SettingTabs: { label: string; id: string }[] = [
|
|||
{ label: '反馈设置', id: 'feedback' },
|
||||
{ label: '安全设置', id: 'security' },
|
||||
{ label: '访问控制', id: 'backend-info' },
|
||||
{ label: 'MCP 设置', id: 'mcp' },
|
||||
];
|
||||
|
||||
const Setting = () => {
|
||||
|
|
@ -116,6 +118,7 @@ const Setting = () => {
|
|||
{activeTab === 'feedback' && <CardFeedback kb={kb} />}
|
||||
{activeTab === 'robot' && <CardRobot kb={kb} url={url} />}
|
||||
{activeTab === 'portal-website' && <CardWeb kb={kb} refresh={getKb} />}
|
||||
{activeTab === 'mcp' && <CardMCP kb={kb} />}
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ export enum ConstsSourceType {
|
|||
SourceTypeDiscordBot = "discord_bot",
|
||||
SourceTypeWechatOfficialAccount = "wechat_official_account",
|
||||
SourceTypeOpenAIAPI = "openai_api",
|
||||
SourceTypeMcpServer = "mcp_server",
|
||||
}
|
||||
|
||||
/** @format int32 */
|
||||
|
|
@ -591,7 +592,8 @@ export interface GetApiProV1AuthGetParams {
|
|||
| "wechat_service_bot"
|
||||
| "discord_bot"
|
||||
| "wechat_official_account"
|
||||
| "openai_api";
|
||||
| "openai_api"
|
||||
| "mcp_server";
|
||||
}
|
||||
|
||||
export interface DeleteApiProV1AuthGroupDeleteParams {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ export enum DomainAppType {
|
|||
AppTypeOpenAIAPI = 9,
|
||||
AppTypeWecomAIBot = 10,
|
||||
AppTypeLarkBot = 11,
|
||||
AppTypeMcpServer = 12,
|
||||
}
|
||||
|
||||
export enum ConstsWatermarkSetting {
|
||||
|
|
@ -132,6 +133,7 @@ export enum ConstsSourceType {
|
|||
SourceTypeDiscordBot = "discord_bot",
|
||||
SourceTypeWechatOfficialAccount = "wechat_official_account",
|
||||
SourceTypeOpenAIAPI = "openai_api",
|
||||
SourceTypeMcpServer = "mcp_server",
|
||||
}
|
||||
|
||||
export enum ConstsNodeRagInfoStatus {
|
||||
|
|
@ -298,7 +300,6 @@ export interface DomainAppInfoResp {
|
|||
export interface DomainAppSettings {
|
||||
/** AI feedback */
|
||||
ai_feedback_settings?: DomainAIFeedbackSettings;
|
||||
auto_sitemap?: boolean;
|
||||
body_code?: string;
|
||||
btns?: unknown[];
|
||||
/** catalog settings */
|
||||
|
|
@ -333,11 +334,14 @@ export interface DomainAppSettings {
|
|||
keyword?: string;
|
||||
/** LarkBot */
|
||||
lark_bot_settings?: DomainLarkBotSettings;
|
||||
/** MCP Server Settings */
|
||||
mcp_server_settings?: DomainMCPServerSettings;
|
||||
/** OpenAI API Bot settings */
|
||||
openai_api_bot_settings?: DomainOpenAIAPIBotSettings;
|
||||
recommend_node_ids?: string[];
|
||||
recommend_questions?: string[];
|
||||
search_placeholder?: string;
|
||||
stats_setting?: DomainStatsSetting;
|
||||
theme_and_style?: DomainThemeAndStyle;
|
||||
/** theme */
|
||||
theme_mode?: string;
|
||||
|
|
@ -352,6 +356,7 @@ export interface DomainAppSettings {
|
|||
/** WebAppLandingConfigs */
|
||||
web_app_landing_configs?: DomainWebAppLandingConfig[];
|
||||
web_app_landing_theme?: DomainWebAppLandingTheme;
|
||||
wechat_app_advanced_setting?: DomainWeChatAppAdvancedSetting;
|
||||
wechat_app_agent_id?: string;
|
||||
wechat_app_corpid?: string;
|
||||
wechat_app_encodingaeskey?: string;
|
||||
|
|
@ -384,7 +389,6 @@ export interface DomainAppSettings {
|
|||
export interface DomainAppSettingsResp {
|
||||
/** AI feedback */
|
||||
ai_feedback_settings?: DomainAIFeedbackSettings;
|
||||
auto_sitemap?: boolean;
|
||||
body_code?: string;
|
||||
btns?: unknown[];
|
||||
/** catalog settings */
|
||||
|
|
@ -419,11 +423,14 @@ export interface DomainAppSettingsResp {
|
|||
keyword?: string;
|
||||
/** LarkBot */
|
||||
lark_bot_settings?: DomainLarkBotSettings;
|
||||
/** MCP Server Settings */
|
||||
mcp_server_settings?: DomainMCPServerSettings;
|
||||
/** OpenAI API settings */
|
||||
openai_api_bot_settings?: DomainOpenAIAPIBotSettings;
|
||||
recommend_node_ids?: string[];
|
||||
recommend_questions?: string[];
|
||||
search_placeholder?: string;
|
||||
stats_setting?: DomainStatsSetting;
|
||||
theme_and_style?: DomainThemeAndStyle;
|
||||
/** theme */
|
||||
theme_mode?: string;
|
||||
|
|
@ -438,6 +445,7 @@ export interface DomainAppSettingsResp {
|
|||
/** WebApp Landing Settings */
|
||||
web_app_landing_configs?: DomainWebAppLandingConfigResp[];
|
||||
web_app_landing_theme?: DomainWebAppLandingTheme;
|
||||
wechat_app_advanced_setting?: DomainWeChatAppAdvancedSetting;
|
||||
wechat_app_agent_id?: string;
|
||||
wechat_app_corpid?: string;
|
||||
wechat_app_encodingaeskey?: string;
|
||||
|
|
@ -926,6 +934,17 @@ export interface DomainLink {
|
|||
url?: string;
|
||||
}
|
||||
|
||||
export interface DomainMCPServerSettings {
|
||||
docs_tool_settings?: DomainMCPToolSettings;
|
||||
is_enabled?: boolean;
|
||||
sample_auth?: DomainSimpleAuth;
|
||||
}
|
||||
|
||||
export interface DomainMCPToolSettings {
|
||||
desc?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export type DomainMessageContent = Record<string, any>;
|
||||
|
||||
export interface DomainMetricsConfig {
|
||||
|
|
@ -1241,6 +1260,10 @@ export interface DomainStatPageReq {
|
|||
scene: 1 | 2 | 3 | 4;
|
||||
}
|
||||
|
||||
export interface DomainStatsSetting {
|
||||
pv_enable?: boolean;
|
||||
}
|
||||
|
||||
export interface DomainSwitchModeReq {
|
||||
/** 百智云 API Key */
|
||||
auto_mode_api_key?: string;
|
||||
|
|
@ -1327,6 +1350,14 @@ export interface DomainUserInfo {
|
|||
user_id?: string;
|
||||
}
|
||||
|
||||
export interface DomainWeChatAppAdvancedSetting {
|
||||
disclaimer_content?: string;
|
||||
feedback_enable?: boolean;
|
||||
feedback_type?: string[];
|
||||
prompt?: string;
|
||||
text_response_enable?: boolean;
|
||||
}
|
||||
|
||||
export interface DomainWebAppCommentSettings {
|
||||
is_enable?: boolean;
|
||||
moderation_enable?: boolean;
|
||||
|
|
@ -1660,6 +1691,7 @@ export interface V1NodeDetailResp {
|
|||
permissions?: DomainNodePermissions;
|
||||
publisher_account?: string;
|
||||
publisher_id?: string;
|
||||
pv?: number;
|
||||
status?: DomainNodeStatus;
|
||||
type?: DomainNodeType;
|
||||
updated_at?: string;
|
||||
|
|
@ -1720,6 +1752,7 @@ export interface V1ShareNodeDetailResp {
|
|||
permissions?: DomainNodePermissions;
|
||||
publisher_account?: string;
|
||||
publisher_id?: string;
|
||||
pv?: number;
|
||||
status?: DomainNodeStatus;
|
||||
type?: DomainNodeType;
|
||||
updated_at?: string;
|
||||
|
|
@ -1796,7 +1829,8 @@ export interface GetApiV1AuthGetParams {
|
|||
| "wechat_service_bot"
|
||||
| "discord_bot"
|
||||
| "wechat_official_account"
|
||||
| "openai_api";
|
||||
| "openai_api"
|
||||
| "mcp_server";
|
||||
}
|
||||
|
||||
export interface GetApiV1CommentParams {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ export interface KBDetail {
|
|||
recommend_node_ids: string[];
|
||||
desc: string;
|
||||
keyword: string;
|
||||
auto_sitemap: boolean;
|
||||
head_code: string;
|
||||
body_code: string;
|
||||
theme_mode?: 'light' | 'dark';
|
||||
|
|
|
|||
|
|
@ -13,17 +13,25 @@ interface FeedbackProps {
|
|||
type: string,
|
||||
content?: string,
|
||||
) => void;
|
||||
data: ConversationItem | null;
|
||||
data: ConversationItem | { message_id: string } | null;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
const Feedback = ({ open, onClose, onSubmit, data }: FeedbackProps) => {
|
||||
const Feedback = ({
|
||||
open,
|
||||
onClose,
|
||||
onSubmit,
|
||||
data,
|
||||
tags: propsTags,
|
||||
}: FeedbackProps) => {
|
||||
const { themeMode, kbDetail } = useStore();
|
||||
const [type, setType] = useState<string>('');
|
||||
const [content, setContent] = useState('');
|
||||
|
||||
const tags: string[] =
|
||||
propsTags ??
|
||||
// @ts-ignore
|
||||
kbDetail?.settings?.ai_feedback_settings?.ai_feedback_type || [];
|
||||
(kbDetail?.settings?.ai_feedback_settings?.ai_feedback_type || []);
|
||||
|
||||
const handleCancel = () => {
|
||||
setContent('');
|
||||
|
|
|
|||
|
|
@ -18,8 +18,35 @@ import {
|
|||
DomainOpenAICompletionsResponse,
|
||||
DomainResponse,
|
||||
PostShareV1ChatMessageParams,
|
||||
V1WechatAppInfoResp,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* @description WechatAppInfo
|
||||
*
|
||||
* @tags share_chat
|
||||
* @name GetShareV1AppWechatInfo
|
||||
* @summary WechatAppInfo
|
||||
* @request GET:/share/v1/app/wechat/info
|
||||
* @response `200` `(DomainResponse & {
|
||||
data?: V1WechatAppInfoResp,
|
||||
|
||||
})` OK
|
||||
*/
|
||||
|
||||
export const getShareV1AppWechatInfo = (params: RequestParams = {}) =>
|
||||
httpRequest<
|
||||
DomainResponse & {
|
||||
data?: V1WechatAppInfoResp;
|
||||
}
|
||||
>({
|
||||
path: `/share/v1/app/wechat/info`,
|
||||
method: "GET",
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description OpenAI API compatible chat completions endpoint
|
||||
*
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ export enum ConstsSourceType {
|
|||
SourceTypeDiscordBot = "discord_bot",
|
||||
SourceTypeWechatOfficialAccount = "wechat_official_account",
|
||||
SourceTypeOpenAIAPI = "openai_api",
|
||||
SourceTypeMcpServer = "mcp_server",
|
||||
}
|
||||
|
||||
/** @format int32 */
|
||||
|
|
@ -591,7 +592,8 @@ export interface GetApiProV1AuthGetParams {
|
|||
| "wechat_service_bot"
|
||||
| "discord_bot"
|
||||
| "wechat_official_account"
|
||||
| "openai_api";
|
||||
| "openai_api"
|
||||
| "mcp_server";
|
||||
}
|
||||
|
||||
export interface DeleteApiProV1AuthGroupDeleteParams {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ export enum DomainAppType {
|
|||
AppTypeOpenAIAPI = 9,
|
||||
AppTypeWecomAIBot = 10,
|
||||
AppTypeLarkBot = 11,
|
||||
AppTypeMcpServer = 12,
|
||||
}
|
||||
|
||||
export enum ConstsWatermarkSetting {
|
||||
|
|
@ -132,6 +133,7 @@ export enum ConstsSourceType {
|
|||
SourceTypeDiscordBot = "discord_bot",
|
||||
SourceTypeWechatOfficialAccount = "wechat_official_account",
|
||||
SourceTypeOpenAIAPI = "openai_api",
|
||||
SourceTypeMcpServer = "mcp_server",
|
||||
}
|
||||
|
||||
export enum ConstsNodeRagInfoStatus {
|
||||
|
|
@ -298,7 +300,6 @@ export interface DomainAppInfoResp {
|
|||
export interface DomainAppSettings {
|
||||
/** AI feedback */
|
||||
ai_feedback_settings?: DomainAIFeedbackSettings;
|
||||
auto_sitemap?: boolean;
|
||||
body_code?: string;
|
||||
btns?: unknown[];
|
||||
/** catalog settings */
|
||||
|
|
@ -333,11 +334,14 @@ export interface DomainAppSettings {
|
|||
keyword?: string;
|
||||
/** LarkBot */
|
||||
lark_bot_settings?: DomainLarkBotSettings;
|
||||
/** MCP Server Settings */
|
||||
mcp_server_settings?: DomainMCPServerSettings;
|
||||
/** OpenAI API Bot settings */
|
||||
openai_api_bot_settings?: DomainOpenAIAPIBotSettings;
|
||||
recommend_node_ids?: string[];
|
||||
recommend_questions?: string[];
|
||||
search_placeholder?: string;
|
||||
stats_setting?: DomainStatsSetting;
|
||||
theme_and_style?: DomainThemeAndStyle;
|
||||
/** theme */
|
||||
theme_mode?: string;
|
||||
|
|
@ -352,6 +356,7 @@ export interface DomainAppSettings {
|
|||
/** WebAppLandingConfigs */
|
||||
web_app_landing_configs?: DomainWebAppLandingConfig[];
|
||||
web_app_landing_theme?: DomainWebAppLandingTheme;
|
||||
wechat_app_advanced_setting?: DomainWeChatAppAdvancedSetting;
|
||||
wechat_app_agent_id?: string;
|
||||
wechat_app_corpid?: string;
|
||||
wechat_app_encodingaeskey?: string;
|
||||
|
|
@ -384,7 +389,6 @@ export interface DomainAppSettings {
|
|||
export interface DomainAppSettingsResp {
|
||||
/** AI feedback */
|
||||
ai_feedback_settings?: DomainAIFeedbackSettings;
|
||||
auto_sitemap?: boolean;
|
||||
body_code?: string;
|
||||
btns?: unknown[];
|
||||
/** catalog settings */
|
||||
|
|
@ -419,11 +423,14 @@ export interface DomainAppSettingsResp {
|
|||
keyword?: string;
|
||||
/** LarkBot */
|
||||
lark_bot_settings?: DomainLarkBotSettings;
|
||||
/** MCP Server Settings */
|
||||
mcp_server_settings?: DomainMCPServerSettings;
|
||||
/** OpenAI API settings */
|
||||
openai_api_bot_settings?: DomainOpenAIAPIBotSettings;
|
||||
recommend_node_ids?: string[];
|
||||
recommend_questions?: string[];
|
||||
search_placeholder?: string;
|
||||
stats_setting?: DomainStatsSetting;
|
||||
theme_and_style?: DomainThemeAndStyle;
|
||||
/** theme */
|
||||
theme_mode?: string;
|
||||
|
|
@ -438,6 +445,7 @@ export interface DomainAppSettingsResp {
|
|||
/** WebApp Landing Settings */
|
||||
web_app_landing_configs?: DomainWebAppLandingConfigResp[];
|
||||
web_app_landing_theme?: DomainWebAppLandingTheme;
|
||||
wechat_app_advanced_setting?: DomainWeChatAppAdvancedSetting;
|
||||
wechat_app_agent_id?: string;
|
||||
wechat_app_corpid?: string;
|
||||
wechat_app_encodingaeskey?: string;
|
||||
|
|
@ -926,6 +934,17 @@ export interface DomainLink {
|
|||
url?: string;
|
||||
}
|
||||
|
||||
export interface DomainMCPServerSettings {
|
||||
docs_tool_settings?: DomainMCPToolSettings;
|
||||
is_enabled?: boolean;
|
||||
sample_auth?: DomainSimpleAuth;
|
||||
}
|
||||
|
||||
export interface DomainMCPToolSettings {
|
||||
desc?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export type DomainMessageContent = Record<string, any>;
|
||||
|
||||
export interface DomainMetricsConfig {
|
||||
|
|
@ -1241,6 +1260,10 @@ export interface DomainStatPageReq {
|
|||
scene: 1 | 2 | 3 | 4;
|
||||
}
|
||||
|
||||
export interface DomainStatsSetting {
|
||||
pv_enable?: boolean;
|
||||
}
|
||||
|
||||
export interface DomainSwitchModeReq {
|
||||
/** 百智云 API Key */
|
||||
auto_mode_api_key?: string;
|
||||
|
|
@ -1327,6 +1350,14 @@ export interface DomainUserInfo {
|
|||
user_id?: string;
|
||||
}
|
||||
|
||||
export interface DomainWeChatAppAdvancedSetting {
|
||||
disclaimer_content?: string;
|
||||
feedback_enable?: boolean;
|
||||
feedback_type?: string[];
|
||||
prompt?: string;
|
||||
text_response_enable?: boolean;
|
||||
}
|
||||
|
||||
export interface DomainWebAppCommentSettings {
|
||||
is_enable?: boolean;
|
||||
moderation_enable?: boolean;
|
||||
|
|
@ -1660,6 +1691,7 @@ export interface V1NodeDetailResp {
|
|||
permissions?: DomainNodePermissions;
|
||||
publisher_account?: string;
|
||||
publisher_id?: string;
|
||||
pv?: number;
|
||||
status?: DomainNodeStatus;
|
||||
type?: DomainNodeType;
|
||||
updated_at?: string;
|
||||
|
|
@ -1720,6 +1752,7 @@ export interface V1ShareNodeDetailResp {
|
|||
permissions?: DomainNodePermissions;
|
||||
publisher_account?: string;
|
||||
publisher_id?: string;
|
||||
pv?: number;
|
||||
status?: DomainNodeStatus;
|
||||
type?: DomainNodeType;
|
||||
updated_at?: string;
|
||||
|
|
@ -1753,6 +1786,13 @@ export interface V1UserListResp {
|
|||
users?: V1UserListItemResp[];
|
||||
}
|
||||
|
||||
export interface V1WechatAppInfoResp {
|
||||
disclaimer_content?: string;
|
||||
feedback_enable?: boolean;
|
||||
feedback_type?: string[];
|
||||
wechat_app_is_enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface PutApiV1AppParams {
|
||||
/** id */
|
||||
id: string;
|
||||
|
|
@ -1796,7 +1836,8 @@ export interface GetApiV1AuthGetParams {
|
|||
| "wechat_service_bot"
|
||||
| "discord_bot"
|
||||
| "wechat_official_account"
|
||||
| "openai_api";
|
||||
| "openai_api"
|
||||
| "mcp_server";
|
||||
}
|
||||
|
||||
export interface GetApiV1CommentParams {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import ConfirmModal from './ConfirmModal';
|
|||
import Header from './Header';
|
||||
import Toc from './Toc';
|
||||
import Toolbar from './Toolbar';
|
||||
import IconPageview1 from '@panda-wiki/icons/IconPageview1';
|
||||
|
||||
interface WrapProps {
|
||||
detail: V1NodeDetailResp;
|
||||
|
|
@ -295,6 +296,15 @@ const Wrap = ({ detail: defaultDetail = {} }: WrapProps) => {
|
|||
<IconZiti />
|
||||
{characterCount} 字
|
||||
</Stack>
|
||||
<Stack
|
||||
direction={'row'}
|
||||
alignItems={'center'}
|
||||
gap={0.5}
|
||||
sx={{ fontSize: 12, color: 'text.tertiary' }}
|
||||
>
|
||||
<IconPageview1 sx={{ fontSize: 12 }} />
|
||||
浏览量 {nodeDetail?.pv}
|
||||
</Stack>
|
||||
</Stack>
|
||||
{editorRef.editor && (
|
||||
<Box sx={{ ...(fixedToc && { display: 'flex' }) }}>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,28 @@
|
|||
'use client';
|
||||
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useState, useRef, useEffect, useMemo } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { Box, Paper, Fade, Stack, Fab, Zoom } from '@mui/material';
|
||||
import { styled, alpha } from '@mui/material/styles';
|
||||
import MarkDown2 from '@/components/markdown2';
|
||||
import SSEClient from '@/utils/fetch';
|
||||
import { message } from '@ctzhian/ui';
|
||||
import { getShareV1AppWechatInfo } from '@/request/ShareChat';
|
||||
import { IconCopy } from '@/components/icons';
|
||||
import { postShareV1ChatFeedback } from '@/request/ShareChat';
|
||||
import { copyText } from '@/utils';
|
||||
import LoadingIcon from '@/assets/images/loading.png';
|
||||
import Image from 'next/image';
|
||||
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
||||
import { useStore } from '@/provider';
|
||||
import Feedback from '@/components/feedback';
|
||||
import { ConstsSourceType, V1WechatAppInfoResp } from '@/request/types';
|
||||
import {
|
||||
IconADiancaiWeixuanzhong2,
|
||||
IconDiancaiWeixuanzhong,
|
||||
IconDianzanXuanzhong1,
|
||||
IconDianzanWeixuanzhong,
|
||||
} from '@panda-wiki/icons';
|
||||
|
||||
interface Message {
|
||||
id: string;
|
||||
|
|
@ -158,6 +168,10 @@ const StyledTyping = styled('div')(({ theme }) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
const SOURCE_TO_API = {
|
||||
[ConstsSourceType.SourceTypeWechatBot]: getShareV1AppWechatInfo,
|
||||
};
|
||||
|
||||
const ChatLoading = ({ onClick }: { onClick: () => void }) => {
|
||||
return (
|
||||
<Stack
|
||||
|
|
@ -216,13 +230,31 @@ const H5Chat = () => {
|
|||
content: string;
|
||||
chunk_result: Message[];
|
||||
}> | null>(null);
|
||||
const [appSetting, setAppSetting] = useState<V1WechatAppInfoResp | null>(
|
||||
null,
|
||||
);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [answer, setAnswer] = useState('');
|
||||
const [score, setScore] = useState(0);
|
||||
const [message_id, setMessageId] = useState('');
|
||||
const [open, setOpen] = useState(false);
|
||||
const [question, setQuestion] = useState('');
|
||||
const { kbDetail } = useStore();
|
||||
|
||||
const [showScrollTop, setShowScrollTop] = useState(false);
|
||||
|
||||
const isFeedbackEnabled = useMemo(() => {
|
||||
return appSetting?.feedback_enable ?? false;
|
||||
}, [appSetting]);
|
||||
|
||||
const disclaimerContent = useMemo(() => {
|
||||
return (
|
||||
appSetting?.disclaimer_content ??
|
||||
kbDetail?.settings?.disclaimer_settings?.content ??
|
||||
''
|
||||
);
|
||||
}, [appSetting, kbDetail]);
|
||||
|
||||
useEffect(() => {
|
||||
messagesContainerRef.current?.scrollTo({
|
||||
top: messagesContainerRef.current.scrollHeight,
|
||||
|
|
@ -236,6 +268,34 @@ const H5Chat = () => {
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const source = searchParams.get('source_type');
|
||||
const api = SOURCE_TO_API[source as keyof typeof SOURCE_TO_API];
|
||||
if (api) {
|
||||
api().then(res => {
|
||||
setAppSetting(res);
|
||||
});
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
const handleScore = async (
|
||||
message_id: string,
|
||||
score: number,
|
||||
type?: string,
|
||||
content?: string,
|
||||
) => {
|
||||
const data: any = {
|
||||
conversation_id: searchParams.get('id'),
|
||||
message_id,
|
||||
score,
|
||||
};
|
||||
if (type) data.type = type;
|
||||
if (content) data.feedback_content = content;
|
||||
await postShareV1ChatFeedback(data);
|
||||
setScore(score);
|
||||
message.success('反馈成功');
|
||||
};
|
||||
|
||||
const scrollToTop = () => {
|
||||
messagesContainerRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
};
|
||||
|
|
@ -267,7 +327,13 @@ const H5Chat = () => {
|
|||
setLoading(true);
|
||||
if (sseClientRef.current) {
|
||||
sseClientRef.current.subscribe('', ({ type, content }) => {
|
||||
if (type === 'question') {
|
||||
if (type === 'message_id') {
|
||||
setMessageId(prev => {
|
||||
return prev + content;
|
||||
});
|
||||
} else if (type === 'feedback_score') {
|
||||
setScore(+content);
|
||||
} else if (type === 'question') {
|
||||
setQuestion(prev => {
|
||||
return prev + content;
|
||||
});
|
||||
|
|
@ -351,25 +417,47 @@ const H5Chat = () => {
|
|||
mt: 1,
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
{kbDetail?.settings?.disclaimer_settings?.content}
|
||||
</Box>
|
||||
<Stack
|
||||
direction='row'
|
||||
alignItems='center'
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
p: 0.5,
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
<Box>{disclaimerContent}</Box>
|
||||
<Stack direction='row' alignItems='center' gap={3}>
|
||||
<IconCopy
|
||||
sx={{ cursor: 'pointer', color: 'text.primary' }}
|
||||
onClick={() => {
|
||||
copyText(answer);
|
||||
}}
|
||||
/>
|
||||
|
||||
{isFeedbackEnabled && (
|
||||
<>
|
||||
{score === 1 && (
|
||||
<IconDianzanXuanzhong1
|
||||
sx={{ cursor: 'pointer' }}
|
||||
/>
|
||||
)}
|
||||
{score !== 1 && (
|
||||
<IconDianzanWeixuanzhong
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
if (score === 0) handleScore(message_id, 1);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{score !== -1 && (
|
||||
<IconDiancaiWeixuanzhong
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
if (score === 0) {
|
||||
setOpen(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{score === -1 && (
|
||||
<IconADiancaiWeixuanzhong2
|
||||
sx={{ cursor: 'pointer' }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
|
|
@ -379,6 +467,13 @@ const H5Chat = () => {
|
|||
)}
|
||||
</StyledMessages>
|
||||
</StyledMessagesContainer>
|
||||
<Feedback
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onSubmit={handleScore}
|
||||
data={{ message_id: message_id }}
|
||||
tags={appSetting?.feedback_type}
|
||||
/>
|
||||
{loading && <ChatLoading onClick={handleSearchAbort} />}
|
||||
<Zoom in={showScrollTop}>
|
||||
<Fab
|
||||
|
|
|
|||
|
|
@ -236,6 +236,12 @@ const DocContent = ({
|
|||
<Box>{characterCount} 字</Box>
|
||||
</>
|
||||
)}
|
||||
{(info.pv ?? 0) > 0 && (
|
||||
<>
|
||||
<Box>·</Box>
|
||||
<Box>浏览量 {info.pv}</Box>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
{info?.meta?.summary && (
|
||||
<Box
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.12.1",
|
||||
"dependencies": {
|
||||
"@ctzhian/tiptap": "^2.1.2",
|
||||
"@ctzhian/tiptap": "^2.1.17",
|
||||
"@ctzhian/ui": "^7.0.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon';
|
||||
|
||||
const IconPageview1 = (props: SvgIconProps) => (
|
||||
<SvgIcon
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 1024 1024'
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d='M512 192c206.752 0 419.872 254.016 446.496 320-26.56 66.016-239.744 320-446.496 320-207.456 0-420.416-254.176-446.496-320 26.24-65.792 239.2-320 446.496-320zM512 128c-255.808 0-512 319.808-512 384 0 64.064 255.104 384 512 384 256.32 0 512-320.384 512-384 0-64-256.864-384-512-384l0 0z'
|
||||
fill='#444444'
|
||||
></path>
|
||||
<path
|
||||
d='M512 352c-88.416 0-160 71.648-160 160 0 88.384 71.584 160 160 160 88.448 0 160-71.616 160-160 0-88.352-71.552-160-160-160zM512 608c-52.992 0-96-43.008-96-96 0-53.024 43.008-96 96-96s96 42.976 96 96c0 52.992-42.944 96-96 96z'
|
||||
fill='#444444'
|
||||
></path>
|
||||
</SvgIcon>
|
||||
);
|
||||
|
||||
IconPageview1.displayName = 'icon-pageview1';
|
||||
|
||||
export default IconPageview1;
|
||||
|
|
@ -153,6 +153,7 @@ export { default as IconOllama } from './IconOllama';
|
|||
export { default as IconOpenrouter } from './IconOpenrouter';
|
||||
export { default as IconPCduan } from './IconPCduan';
|
||||
export { default as IconPDF } from './IconPDF';
|
||||
export { default as IconPageview1 } from './IconPageview1';
|
||||
export { default as IconPaperFull } from './IconPaperFull';
|
||||
export { default as IconPaperPlaneFill } from './IconPaperPlaneFill';
|
||||
export { default as IconPeizhi } from './IconPeizhi';
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ importers:
|
|||
.:
|
||||
dependencies:
|
||||
'@ctzhian/tiptap':
|
||||
specifier: ^2.1.2
|
||||
version: 2.1.2(474df993ec7b9e2d073b831f21aaac02)
|
||||
specifier: ^2.1.17
|
||||
version: 2.1.17(474df993ec7b9e2d073b831f21aaac02)
|
||||
'@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)
|
||||
|
|
@ -523,8 +523,8 @@ packages:
|
|||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@ctzhian/tiptap@2.1.2':
|
||||
resolution: {integrity: sha512-GXfJJV8ExkvTzmbOB20s2G3Hoy6SIecJlr79mDJd2Stw5LQjN0BAQBJTFnk13yCkspOTy+agHTzoWm2T+UtWOA==}
|
||||
'@ctzhian/tiptap@2.1.17':
|
||||
resolution: {integrity: sha512-HpNMvujB3ThAR2N0ek1mdSjUC144DAme+VqylO9bMROvRsqQnzgir806fvm/+/dhbKefh4iRYlvvb05m/UVq4Q==}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11
|
||||
'@emotion/styled': ^11
|
||||
|
|
@ -6156,7 +6156,7 @@ snapshots:
|
|||
- react-native
|
||||
- typescript
|
||||
|
||||
'@ctzhian/tiptap@2.1.2(474df993ec7b9e2d073b831f21aaac02)':
|
||||
'@ctzhian/tiptap@2.1.17(474df993ec7b9e2d073b831f21aaac02)':
|
||||
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