Compare commits
99 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 | |
|
|
7f58308dae | |
|
|
f34864621a | |
|
|
da9039ff37 | |
|
|
797e0c033d | |
|
|
ea6f958d24 | |
|
|
d3502e105a | |
|
|
df4937aeb2 | |
|
|
d8c869198e | |
|
|
f04f96d894 | |
|
|
74e87540e0 | |
|
|
4a011aa1d2 | |
|
|
acf17e94b2 | |
|
|
59ca885518 | |
|
|
0e64ff946f | |
|
|
19e6a66809 | |
|
|
c15272aeb2 | |
|
|
32ed999b48 | |
|
|
98e4a917e0 | |
|
|
6e5f780771 | |
|
|
95bd31b8ed | |
|
|
8457544a30 | |
|
|
55abd7452c | |
|
|
6224d713b6 | |
|
|
3f93246d85 | |
|
|
3196f2d130 | |
|
|
4a9a1ff78b | |
|
|
e80cbf9f47 | |
|
|
76f9878d55 | |
|
|
eb662208dc | |
|
|
0dcd65961b | |
|
|
dafb8de41e | |
|
|
de86adf90f | |
|
|
4c0990df8c | |
|
|
87d24e06c4 | |
|
|
b1b354b785 | |
|
|
e9d30eb3d4 | |
|
|
b8f2b95f22 | |
|
|
98c602819f | |
|
|
be163a5f80 | |
|
|
4c03078103 | |
|
|
2b372fd81d | |
|
|
74c6ba131d | |
|
|
6c77246e34 | |
|
|
d347482d31 | |
|
|
92443dfafe | |
|
|
1dd2d93010 | |
|
|
54bf1fd108 | |
|
|
c23ce398d7 | |
|
|
2c90932f60 | |
|
|
035ce0284d | |
|
|
93559125c2 | |
|
|
6c5cf256ac | |
|
|
23bdfc3d50 | |
|
|
9008c537cd | |
|
|
63a4a964b1 | |
|
|
62f2b2eaf5 | |
|
|
5c1c6368b8 | |
|
|
7282503acf | |
|
|
60a4177229 | |
|
|
2f706a6100 | |
|
|
febcb06654 |
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -7,21 +7,23 @@ import (
|
|||
)
|
||||
|
||||
type ShareNodeDetailResp struct {
|
||||
ID string `json:"id"`
|
||||
KbID string `json:"kb_id"`
|
||||
Type domain.NodeType `json:"type"`
|
||||
Status domain.NodeStatus `json:"status"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
Meta domain.NodeMeta `json:"meta"`
|
||||
ParentID string `json:"parent_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Permissions domain.NodePermissions `json:"permissions"`
|
||||
CreatorId string `json:"creator_id"`
|
||||
EditorId string `json:"editor_id"`
|
||||
PublisherId string `json:"publisher_id"`
|
||||
CreatorAccount string `json:"creator_account"`
|
||||
EditorAccount string `json:"editor_account"`
|
||||
PublisherAccount string `json:"publisher_account"`
|
||||
ID string `json:"id"`
|
||||
KbID string `json:"kb_id"`
|
||||
Type domain.NodeType `json:"type"`
|
||||
Status domain.NodeStatus `json:"status"`
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
Meta domain.NodeMeta `json:"meta"`
|
||||
ParentID string `json:"parent_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Permissions domain.NodePermissions `json:"permissions"`
|
||||
CreatorId string `json:"creator_id"`
|
||||
EditorId string `json:"editor_id"`
|
||||
PublisherId string `json:"publisher_id"`
|
||||
CreatorAccount string `json:"creator_account"`
|
||||
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"`
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
//go:build wireinject
|
||||
// +build wireinject
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
//go:build wireinject
|
||||
// +build wireinject
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
//go:build wireinject
|
||||
// +build wireinject
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package consts
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
|
|
@ -13,28 +11,13 @@ const ContextKeyEdition contextKey = "edition"
|
|||
type LicenseEdition int32
|
||||
|
||||
const (
|
||||
LicenseEditionFree LicenseEdition = 0 // 开源版
|
||||
LicenseEditionContributor LicenseEdition = 1 // 联创版
|
||||
LicenseEditionEnterprise LicenseEdition = 2 // 企业版
|
||||
LicenseEditionFree LicenseEdition = 0 // 开源版
|
||||
LicenseEditionProfession LicenseEdition = 1 // 专业版
|
||||
LicenseEditionEnterprise LicenseEdition = 2 // 企业版
|
||||
LicenseEditionBusiness LicenseEdition = 3 // 商业版
|
||||
)
|
||||
|
||||
func GetLicenseEdition(c echo.Context) LicenseEdition {
|
||||
edition, _ := c.Get("edition").(LicenseEdition)
|
||||
return edition
|
||||
}
|
||||
|
||||
func (e LicenseEdition) GetMaxAuth(sourceType SourceType) int {
|
||||
switch e {
|
||||
case LicenseEditionFree:
|
||||
if sourceType == SourceTypeGitHub {
|
||||
return 10
|
||||
}
|
||||
return 0
|
||||
case LicenseEditionContributor:
|
||||
return 10
|
||||
case LicenseEditionEnterprise:
|
||||
return math.MaxInt
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
@ -3478,7 +3524,7 @@ const docTemplate = `{
|
|||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"share_chat"
|
||||
"Widget"
|
||||
],
|
||||
"summary": "ChatWidget",
|
||||
"parameters": [
|
||||
|
|
@ -3509,6 +3555,52 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/share/v1/chat/widget/search": {
|
||||
"post": {
|
||||
"description": "WidgetSearch",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Widget"
|
||||
],
|
||||
"summary": "WidgetSearch",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Comment",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ChatSearchReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.ChatSearchResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/share/v1/comment": {
|
||||
"post": {
|
||||
"description": "CreateComment",
|
||||
|
|
@ -4067,22 +4159,26 @@ const docTemplate = `{
|
|||
"enum": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
2,
|
||||
3
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"LicenseEditionContributor": "联创版",
|
||||
"LicenseEditionBusiness": "商业版",
|
||||
"LicenseEditionEnterprise": "企业版",
|
||||
"LicenseEditionFree": "开源版"
|
||||
"LicenseEditionFree": "开源版",
|
||||
"LicenseEditionProfession": "专业版"
|
||||
},
|
||||
"x-enum-descriptions": [
|
||||
"开源版",
|
||||
"联创版",
|
||||
"企业版"
|
||||
"专业版",
|
||||
"企业版",
|
||||
"商业版"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"LicenseEditionFree",
|
||||
"LicenseEditionContributor",
|
||||
"LicenseEditionEnterprise"
|
||||
"LicenseEditionProfession",
|
||||
"LicenseEditionEnterprise",
|
||||
"LicenseEditionBusiness"
|
||||
]
|
||||
},
|
||||
"consts.ModelSettingMode": {
|
||||
|
|
@ -4218,7 +4314,8 @@ const docTemplate = `{
|
|||
"wechat_service_bot",
|
||||
"discord_bot",
|
||||
"wechat_official_account",
|
||||
"openai_api"
|
||||
"openai_api",
|
||||
"mcp_server"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"SourceTypeDingTalk",
|
||||
|
|
@ -4237,7 +4334,8 @@ const docTemplate = `{
|
|||
"SourceTypeWechatServiceBot",
|
||||
"SourceTypeDiscordBot",
|
||||
"SourceTypeWechatOfficialAccount",
|
||||
"SourceTypeOpenAIAPI"
|
||||
"SourceTypeOpenAIAPI",
|
||||
"SourceTypeMcpServer"
|
||||
]
|
||||
},
|
||||
"consts.StatDay": {
|
||||
|
|
@ -4462,9 +4560,6 @@ const docTemplate = `{
|
|||
}
|
||||
]
|
||||
},
|
||||
"auto_sitemap": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"body_code": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4483,6 +4578,9 @@ const docTemplate = `{
|
|||
"contribute_settings": {
|
||||
"$ref": "#/definitions/domain.ContributeSettings"
|
||||
},
|
||||
"conversation_setting": {
|
||||
"$ref": "#/definitions/domain.ConversationSetting"
|
||||
},
|
||||
"copy_setting": {
|
||||
"enum": [
|
||||
"",
|
||||
|
|
@ -4570,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": [
|
||||
|
|
@ -4593,6 +4699,9 @@ const docTemplate = `{
|
|||
"search_placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"stats_setting": {
|
||||
"$ref": "#/definitions/domain.StatsSetting"
|
||||
},
|
||||
"theme_and_style": {
|
||||
"$ref": "#/definitions/domain.ThemeAndStyle"
|
||||
},
|
||||
|
|
@ -4645,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"
|
||||
},
|
||||
|
|
@ -4741,9 +4853,6 @@ const docTemplate = `{
|
|||
}
|
||||
]
|
||||
},
|
||||
"auto_sitemap": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"body_code": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4762,6 +4871,9 @@ const docTemplate = `{
|
|||
"contribute_settings": {
|
||||
"$ref": "#/definitions/domain.ContributeSettings"
|
||||
},
|
||||
"conversation_setting": {
|
||||
"$ref": "#/definitions/domain.ConversationSetting"
|
||||
},
|
||||
"copy_setting": {
|
||||
"$ref": "#/definitions/consts.CopySetting"
|
||||
},
|
||||
|
|
@ -4840,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": [
|
||||
|
|
@ -4863,6 +4983,9 @@ const docTemplate = `{
|
|||
"search_placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"stats_setting": {
|
||||
"$ref": "#/definitions/domain.StatsSetting"
|
||||
},
|
||||
"theme_and_style": {
|
||||
"$ref": "#/definitions/domain.ThemeAndStyle"
|
||||
},
|
||||
|
|
@ -4906,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"
|
||||
},
|
||||
|
|
@ -5000,7 +5126,8 @@ const docTemplate = `{
|
|||
8,
|
||||
9,
|
||||
10,
|
||||
11
|
||||
11,
|
||||
12
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"AppTypeWeb",
|
||||
|
|
@ -5013,7 +5140,8 @@ const docTemplate = `{
|
|||
"AppTypeWechatOfficialAccount",
|
||||
"AppTypeOpenAIAPI",
|
||||
"AppTypeWecomAIBot",
|
||||
"AppTypeLarkBot"
|
||||
"AppTypeLarkBot",
|
||||
"AppTypeMcpServer"
|
||||
]
|
||||
},
|
||||
"domain.AuthUserInfo": {
|
||||
|
|
@ -5702,6 +5830,17 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ConversationSetting": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"copyright_hide_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"copyright_info": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateKBReleaseReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -6311,6 +6450,34 @@ 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"
|
||||
},
|
||||
"domain.MessageFrom": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
|
|
@ -6529,6 +6696,9 @@ const docTemplate = `{
|
|||
"position": {
|
||||
"type": "number"
|
||||
},
|
||||
"publisher_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"rag_info": {
|
||||
"$ref": "#/definitions/domain.RagInfo"
|
||||
},
|
||||
|
|
@ -6712,6 +6882,9 @@ const docTemplate = `{
|
|||
"stream": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"stream_options": {
|
||||
"$ref": "#/definitions/domain.OpenAIStreamOptions"
|
||||
},
|
||||
"temperature": {
|
||||
"type": "number"
|
||||
},
|
||||
|
|
@ -6834,7 +7007,7 @@ const docTemplate = `{
|
|||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/domain.MessageContent"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
|
|
@ -6864,6 +7037,14 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.OpenAIStreamOptions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"include_usage": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.OpenAITool": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -7130,6 +7311,44 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ShareNodeDetailItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"children": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.ShareNodeDetailItem"
|
||||
}
|
||||
},
|
||||
"emoji": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/domain.NodeMeta"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parent_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"$ref": "#/definitions/domain.NodePermissions"
|
||||
},
|
||||
"position": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/domain.NodeType"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.SimpleAuth": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -7214,6 +7433,14 @@ const docTemplate = `{
|
|||
"StatPageSceneLogin"
|
||||
]
|
||||
},
|
||||
"domain.StatsSetting": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pv_enable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.SwitchModeReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -7452,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": {
|
||||
|
|
@ -7647,15 +7897,39 @@ const docTemplate = `{
|
|||
"domain.WidgetBotSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"btn_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"btn_logo": {
|
||||
"type": "string"
|
||||
},
|
||||
"btn_position": {
|
||||
"type": "string"
|
||||
},
|
||||
"btn_style": {
|
||||
"type": "string"
|
||||
},
|
||||
"btn_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"copyright_hide_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"copyright_info": {
|
||||
"type": "string"
|
||||
},
|
||||
"disclaimer": {
|
||||
"type": "string"
|
||||
},
|
||||
"is_open": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"modal_position": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"recommend_node_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
|
@ -7668,6 +7942,9 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
"search_mode": {
|
||||
"type": "string"
|
||||
},
|
||||
"theme_mode": {
|
||||
"type": "string"
|
||||
}
|
||||
|
|
@ -8383,6 +8660,9 @@ const docTemplate = `{
|
|||
"publisher_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"pv": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/domain.NodeStatus"
|
||||
},
|
||||
|
|
@ -8536,6 +8816,12 @@ const docTemplate = `{
|
|||
"kb_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.ShareNodeDetailItem"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/domain.NodeMeta"
|
||||
},
|
||||
|
|
@ -8554,6 +8840,9 @@ const docTemplate = `{
|
|||
"publisher_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"pv": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/domain.NodeStatus"
|
||||
},
|
||||
|
|
@ -8635,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",
|
||||
|
|
@ -3471,7 +3517,7 @@
|
|||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"share_chat"
|
||||
"Widget"
|
||||
],
|
||||
"summary": "ChatWidget",
|
||||
"parameters": [
|
||||
|
|
@ -3502,6 +3548,52 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/share/v1/chat/widget/search": {
|
||||
"post": {
|
||||
"description": "WidgetSearch",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Widget"
|
||||
],
|
||||
"summary": "WidgetSearch",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Comment",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ChatSearchReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.ChatSearchResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/share/v1/comment": {
|
||||
"post": {
|
||||
"description": "CreateComment",
|
||||
|
|
@ -4060,22 +4152,26 @@
|
|||
"enum": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
2,
|
||||
3
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"LicenseEditionContributor": "联创版",
|
||||
"LicenseEditionBusiness": "商业版",
|
||||
"LicenseEditionEnterprise": "企业版",
|
||||
"LicenseEditionFree": "开源版"
|
||||
"LicenseEditionFree": "开源版",
|
||||
"LicenseEditionProfession": "专业版"
|
||||
},
|
||||
"x-enum-descriptions": [
|
||||
"开源版",
|
||||
"联创版",
|
||||
"企业版"
|
||||
"专业版",
|
||||
"企业版",
|
||||
"商业版"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"LicenseEditionFree",
|
||||
"LicenseEditionContributor",
|
||||
"LicenseEditionEnterprise"
|
||||
"LicenseEditionProfession",
|
||||
"LicenseEditionEnterprise",
|
||||
"LicenseEditionBusiness"
|
||||
]
|
||||
},
|
||||
"consts.ModelSettingMode": {
|
||||
|
|
@ -4211,7 +4307,8 @@
|
|||
"wechat_service_bot",
|
||||
"discord_bot",
|
||||
"wechat_official_account",
|
||||
"openai_api"
|
||||
"openai_api",
|
||||
"mcp_server"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"SourceTypeDingTalk",
|
||||
|
|
@ -4230,7 +4327,8 @@
|
|||
"SourceTypeWechatServiceBot",
|
||||
"SourceTypeDiscordBot",
|
||||
"SourceTypeWechatOfficialAccount",
|
||||
"SourceTypeOpenAIAPI"
|
||||
"SourceTypeOpenAIAPI",
|
||||
"SourceTypeMcpServer"
|
||||
]
|
||||
},
|
||||
"consts.StatDay": {
|
||||
|
|
@ -4455,9 +4553,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"auto_sitemap": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"body_code": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4476,6 +4571,9 @@
|
|||
"contribute_settings": {
|
||||
"$ref": "#/definitions/domain.ContributeSettings"
|
||||
},
|
||||
"conversation_setting": {
|
||||
"$ref": "#/definitions/domain.ConversationSetting"
|
||||
},
|
||||
"copy_setting": {
|
||||
"enum": [
|
||||
"",
|
||||
|
|
@ -4563,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": [
|
||||
|
|
@ -4586,6 +4692,9 @@
|
|||
"search_placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"stats_setting": {
|
||||
"$ref": "#/definitions/domain.StatsSetting"
|
||||
},
|
||||
"theme_and_style": {
|
||||
"$ref": "#/definitions/domain.ThemeAndStyle"
|
||||
},
|
||||
|
|
@ -4638,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"
|
||||
},
|
||||
|
|
@ -4734,9 +4846,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"auto_sitemap": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"body_code": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4755,6 +4864,9 @@
|
|||
"contribute_settings": {
|
||||
"$ref": "#/definitions/domain.ContributeSettings"
|
||||
},
|
||||
"conversation_setting": {
|
||||
"$ref": "#/definitions/domain.ConversationSetting"
|
||||
},
|
||||
"copy_setting": {
|
||||
"$ref": "#/definitions/consts.CopySetting"
|
||||
},
|
||||
|
|
@ -4833,6 +4945,14 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"mcp_server_settings": {
|
||||
"description": "MCP Server Settings",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.MCPServerSettings"
|
||||
}
|
||||
]
|
||||
},
|
||||
"openai_api_bot_settings": {
|
||||
"description": "OpenAI API settings",
|
||||
"allOf": [
|
||||
|
|
@ -4856,6 +4976,9 @@
|
|||
"search_placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"stats_setting": {
|
||||
"$ref": "#/definitions/domain.StatsSetting"
|
||||
},
|
||||
"theme_and_style": {
|
||||
"$ref": "#/definitions/domain.ThemeAndStyle"
|
||||
},
|
||||
|
|
@ -4899,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"
|
||||
},
|
||||
|
|
@ -4993,7 +5119,8 @@
|
|||
8,
|
||||
9,
|
||||
10,
|
||||
11
|
||||
11,
|
||||
12
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"AppTypeWeb",
|
||||
|
|
@ -5006,7 +5133,8 @@
|
|||
"AppTypeWechatOfficialAccount",
|
||||
"AppTypeOpenAIAPI",
|
||||
"AppTypeWecomAIBot",
|
||||
"AppTypeLarkBot"
|
||||
"AppTypeLarkBot",
|
||||
"AppTypeMcpServer"
|
||||
]
|
||||
},
|
||||
"domain.AuthUserInfo": {
|
||||
|
|
@ -5695,6 +5823,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ConversationSetting": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"copyright_hide_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"copyright_info": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateKBReleaseReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -6304,6 +6443,34 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"domain.MessageFrom": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
|
|
@ -6522,6 +6689,9 @@
|
|||
"position": {
|
||||
"type": "number"
|
||||
},
|
||||
"publisher_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"rag_info": {
|
||||
"$ref": "#/definitions/domain.RagInfo"
|
||||
},
|
||||
|
|
@ -6705,6 +6875,9 @@
|
|||
"stream": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"stream_options": {
|
||||
"$ref": "#/definitions/domain.OpenAIStreamOptions"
|
||||
},
|
||||
"temperature": {
|
||||
"type": "number"
|
||||
},
|
||||
|
|
@ -6827,7 +7000,7 @@
|
|||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/domain.MessageContent"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
|
|
@ -6857,6 +7030,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.OpenAIStreamOptions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"include_usage": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.OpenAITool": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -7123,6 +7304,44 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ShareNodeDetailItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"children": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.ShareNodeDetailItem"
|
||||
}
|
||||
},
|
||||
"emoji": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/domain.NodeMeta"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parent_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"$ref": "#/definitions/domain.NodePermissions"
|
||||
},
|
||||
"position": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/domain.NodeType"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.SimpleAuth": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -7207,6 +7426,14 @@
|
|||
"StatPageSceneLogin"
|
||||
]
|
||||
},
|
||||
"domain.StatsSetting": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pv_enable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.SwitchModeReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -7445,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": {
|
||||
|
|
@ -7640,15 +7890,39 @@
|
|||
"domain.WidgetBotSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"btn_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"btn_logo": {
|
||||
"type": "string"
|
||||
},
|
||||
"btn_position": {
|
||||
"type": "string"
|
||||
},
|
||||
"btn_style": {
|
||||
"type": "string"
|
||||
},
|
||||
"btn_text": {
|
||||
"type": "string"
|
||||
},
|
||||
"copyright_hide_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"copyright_info": {
|
||||
"type": "string"
|
||||
},
|
||||
"disclaimer": {
|
||||
"type": "string"
|
||||
},
|
||||
"is_open": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"modal_position": {
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"recommend_node_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
|
@ -7661,6 +7935,9 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
"search_mode": {
|
||||
"type": "string"
|
||||
},
|
||||
"theme_mode": {
|
||||
"type": "string"
|
||||
}
|
||||
|
|
@ -8376,6 +8653,9 @@
|
|||
"publisher_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"pv": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/domain.NodeStatus"
|
||||
},
|
||||
|
|
@ -8529,6 +8809,12 @@
|
|||
"kb_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.ShareNodeDetailItem"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/definitions/domain.NodeMeta"
|
||||
},
|
||||
|
|
@ -8547,6 +8833,9 @@
|
|||
"publisher_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"pv": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/domain.NodeStatus"
|
||||
},
|
||||
|
|
@ -8628,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": {
|
||||
|
|
|
|||
|
|
@ -117,20 +117,24 @@ definitions:
|
|||
- 0
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
format: int32
|
||||
type: integer
|
||||
x-enum-comments:
|
||||
LicenseEditionContributor: 联创版
|
||||
LicenseEditionBusiness: 商业版
|
||||
LicenseEditionEnterprise: 企业版
|
||||
LicenseEditionFree: 开源版
|
||||
LicenseEditionProfession: 专业版
|
||||
x-enum-descriptions:
|
||||
- 开源版
|
||||
- 联创版
|
||||
- 专业版
|
||||
- 企业版
|
||||
- 商业版
|
||||
x-enum-varnames:
|
||||
- LicenseEditionFree
|
||||
- LicenseEditionContributor
|
||||
- LicenseEditionProfession
|
||||
- LicenseEditionEnterprise
|
||||
- LicenseEditionBusiness
|
||||
consts.ModelSettingMode:
|
||||
enum:
|
||||
- manual
|
||||
|
|
@ -241,6 +245,7 @@ definitions:
|
|||
- discord_bot
|
||||
- wechat_official_account
|
||||
- openai_api
|
||||
- mcp_server
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- SourceTypeDingTalk
|
||||
|
|
@ -260,6 +265,7 @@ definitions:
|
|||
- SourceTypeDiscordBot
|
||||
- SourceTypeWechatOfficialAccount
|
||||
- SourceTypeOpenAIAPI
|
||||
- SourceTypeMcpServer
|
||||
consts.StatDay:
|
||||
enum:
|
||||
- 1
|
||||
|
|
@ -414,8 +420,6 @@ definitions:
|
|||
allOf:
|
||||
- $ref: '#/definitions/domain.AIFeedbackSettings'
|
||||
description: AI feedback
|
||||
auto_sitemap:
|
||||
type: boolean
|
||||
body_code:
|
||||
type: string
|
||||
btns:
|
||||
|
|
@ -427,6 +431,8 @@ definitions:
|
|||
description: catalog settings
|
||||
contribute_settings:
|
||||
$ref: '#/definitions/domain.ContributeSettings'
|
||||
conversation_setting:
|
||||
$ref: '#/definitions/domain.ConversationSetting'
|
||||
copy_setting:
|
||||
allOf:
|
||||
- $ref: '#/definitions/consts.CopySetting'
|
||||
|
|
@ -482,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'
|
||||
|
|
@ -496,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:
|
||||
|
|
@ -528,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:
|
||||
|
|
@ -589,8 +603,6 @@ definitions:
|
|||
allOf:
|
||||
- $ref: '#/definitions/domain.AIFeedbackSettings'
|
||||
description: AI feedback
|
||||
auto_sitemap:
|
||||
type: boolean
|
||||
body_code:
|
||||
type: string
|
||||
btns:
|
||||
|
|
@ -602,6 +614,8 @@ definitions:
|
|||
description: catalog settings
|
||||
contribute_settings:
|
||||
$ref: '#/definitions/domain.ContributeSettings'
|
||||
conversation_setting:
|
||||
$ref: '#/definitions/domain.ConversationSetting'
|
||||
copy_setting:
|
||||
$ref: '#/definitions/consts.CopySetting'
|
||||
desc:
|
||||
|
|
@ -652,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'
|
||||
|
|
@ -666,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:
|
||||
|
|
@ -693,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:
|
||||
|
|
@ -759,6 +781,7 @@ definitions:
|
|||
- 9
|
||||
- 10
|
||||
- 11
|
||||
- 12
|
||||
format: int32
|
||||
type: integer
|
||||
x-enum-varnames:
|
||||
|
|
@ -773,6 +796,7 @@ definitions:
|
|||
- AppTypeOpenAIAPI
|
||||
- AppTypeWecomAIBot
|
||||
- AppTypeLarkBot
|
||||
- AppTypeMcpServer
|
||||
domain.AuthUserInfo:
|
||||
properties:
|
||||
avatar_url:
|
||||
|
|
@ -1214,6 +1238,13 @@ definitions:
|
|||
url:
|
||||
type: string
|
||||
type: object
|
||||
domain.ConversationSetting:
|
||||
properties:
|
||||
copyright_hide_enabled:
|
||||
type: boolean
|
||||
copyright_info:
|
||||
type: string
|
||||
type: object
|
||||
domain.CreateKBReleaseReq:
|
||||
properties:
|
||||
kb_id:
|
||||
|
|
@ -1610,6 +1641,24 @@ 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:
|
||||
enum:
|
||||
- 1
|
||||
|
|
@ -1757,6 +1806,8 @@ definitions:
|
|||
$ref: '#/definitions/domain.NodePermissions'
|
||||
position:
|
||||
type: number
|
||||
publisher_id:
|
||||
type: string
|
||||
rag_info:
|
||||
$ref: '#/definitions/domain.RagInfo'
|
||||
status:
|
||||
|
|
@ -1871,6 +1922,8 @@ definitions:
|
|||
type: array
|
||||
stream:
|
||||
type: boolean
|
||||
stream_options:
|
||||
$ref: '#/definitions/domain.OpenAIStreamOptions'
|
||||
temperature:
|
||||
type: number
|
||||
tool_choice:
|
||||
|
|
@ -1952,7 +2005,7 @@ definitions:
|
|||
domain.OpenAIMessage:
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
$ref: '#/definitions/domain.MessageContent'
|
||||
name:
|
||||
type: string
|
||||
role:
|
||||
|
|
@ -1973,6 +2026,11 @@ definitions:
|
|||
required:
|
||||
- type
|
||||
type: object
|
||||
domain.OpenAIStreamOptions:
|
||||
properties:
|
||||
include_usage:
|
||||
type: boolean
|
||||
type: object
|
||||
domain.OpenAITool:
|
||||
properties:
|
||||
function:
|
||||
|
|
@ -2146,6 +2204,31 @@ definitions:
|
|||
role:
|
||||
$ref: '#/definitions/schema.RoleType'
|
||||
type: object
|
||||
domain.ShareNodeDetailItem:
|
||||
properties:
|
||||
children:
|
||||
items:
|
||||
$ref: '#/definitions/domain.ShareNodeDetailItem'
|
||||
type: array
|
||||
emoji:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
meta:
|
||||
$ref: '#/definitions/domain.NodeMeta'
|
||||
name:
|
||||
type: string
|
||||
parent_id:
|
||||
type: string
|
||||
permissions:
|
||||
$ref: '#/definitions/domain.NodePermissions'
|
||||
position:
|
||||
type: number
|
||||
type:
|
||||
$ref: '#/definitions/domain.NodeType'
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
domain.SimpleAuth:
|
||||
properties:
|
||||
enabled:
|
||||
|
|
@ -2202,6 +2285,11 @@ definitions:
|
|||
- StatPageSceneNodeDetail
|
||||
- StatPageSceneChat
|
||||
- StatPageSceneLogin
|
||||
domain.StatsSetting:
|
||||
properties:
|
||||
pv_enable:
|
||||
type: boolean
|
||||
type: object
|
||||
domain.SwitchModeReq:
|
||||
properties:
|
||||
auto_mode_api_key:
|
||||
|
|
@ -2360,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:
|
||||
|
|
@ -2488,12 +2591,28 @@ definitions:
|
|||
type: object
|
||||
domain.WidgetBotSettings:
|
||||
properties:
|
||||
btn_id:
|
||||
type: string
|
||||
btn_logo:
|
||||
type: string
|
||||
btn_position:
|
||||
type: string
|
||||
btn_style:
|
||||
type: string
|
||||
btn_text:
|
||||
type: string
|
||||
copyright_hide_enabled:
|
||||
type: boolean
|
||||
copyright_info:
|
||||
type: string
|
||||
disclaimer:
|
||||
type: string
|
||||
is_open:
|
||||
type: boolean
|
||||
modal_position:
|
||||
type: string
|
||||
placeholder:
|
||||
type: string
|
||||
recommend_node_ids:
|
||||
items:
|
||||
type: string
|
||||
|
|
@ -2502,6 +2621,8 @@ definitions:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
search_mode:
|
||||
type: string
|
||||
theme_mode:
|
||||
type: string
|
||||
type: object
|
||||
|
|
@ -2971,6 +3092,8 @@ definitions:
|
|||
type: string
|
||||
publisher_id:
|
||||
type: string
|
||||
pv:
|
||||
type: integer
|
||||
status:
|
||||
$ref: '#/definitions/domain.NodeStatus'
|
||||
type:
|
||||
|
|
@ -3075,6 +3198,10 @@ definitions:
|
|||
type: string
|
||||
kb_id:
|
||||
type: string
|
||||
list:
|
||||
items:
|
||||
$ref: '#/definitions/domain.ShareNodeDetailItem'
|
||||
type: array
|
||||
meta:
|
||||
$ref: '#/definitions/domain.NodeMeta'
|
||||
name:
|
||||
|
|
@ -3087,6 +3214,8 @@ definitions:
|
|||
type: string
|
||||
publisher_id:
|
||||
type: string
|
||||
pv:
|
||||
type: integer
|
||||
status:
|
||||
$ref: '#/definitions/domain.NodeStatus'
|
||||
type:
|
||||
|
|
@ -3140,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
|
||||
|
|
@ -3286,6 +3428,7 @@ paths:
|
|||
- discord_bot
|
||||
- wechat_official_account
|
||||
- openai_api
|
||||
- mcp_server
|
||||
in: query
|
||||
name: source_type
|
||||
required: true
|
||||
|
|
@ -3308,6 +3451,7 @@ paths:
|
|||
- SourceTypeDiscordBot
|
||||
- SourceTypeWechatOfficialAccount
|
||||
- SourceTypeOpenAIAPI
|
||||
- SourceTypeMcpServer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
|
@ -4985,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:
|
||||
|
|
@ -5296,7 +5466,34 @@ paths:
|
|||
$ref: '#/definitions/domain.Response'
|
||||
summary: ChatWidget
|
||||
tags:
|
||||
- share_chat
|
||||
- Widget
|
||||
/share/v1/chat/widget/search:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: WidgetSearch
|
||||
parameters:
|
||||
- description: Comment
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ChatSearchReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/domain.ChatSearchResp'
|
||||
type: object
|
||||
summary: WidgetSearch
|
||||
tags:
|
||||
- Widget
|
||||
/share/v1/comment:
|
||||
post:
|
||||
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
|
||||
|
|
@ -161,17 +163,49 @@ type AppSettings struct {
|
|||
WebAppLandingConfigs []WebAppLandingConfig `json:"web_app_landing_configs,omitempty"`
|
||||
WebAppLandingTheme WebAppLandingTheme `json:"web_app_landing_theme"`
|
||||
|
||||
WatermarkContent string `json:"watermark_content"`
|
||||
WatermarkSetting consts.WatermarkSetting `json:"watermark_setting" validate:"omitempty,oneof='' hidden visible"`
|
||||
CopySetting consts.CopySetting `json:"copy_setting" validate:"omitempty,oneof='' append disabled"`
|
||||
ContributeSettings ContributeSettings `json:"contribute_settings"`
|
||||
HomePageSetting consts.HomePageSetting `json:"home_page_setting"`
|
||||
WatermarkContent string `json:"watermark_content"`
|
||||
WatermarkSetting consts.WatermarkSetting `json:"watermark_setting" validate:"omitempty,oneof='' hidden visible"`
|
||||
CopySetting consts.CopySetting `json:"copy_setting" validate:"omitempty,oneof='' append disabled"`
|
||||
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 {
|
||||
CopyrightInfo string `json:"copyright_info"`
|
||||
CopyrightHideEnabled bool `json:"copyright_hide_enabled"`
|
||||
}
|
||||
|
||||
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"`
|
||||
|
|
@ -399,12 +433,21 @@ type FooterSettings struct {
|
|||
}
|
||||
|
||||
type WidgetBotSettings struct {
|
||||
IsOpen bool `json:"is_open,omitempty"`
|
||||
ThemeMode string `json:"theme_mode,omitempty"`
|
||||
BtnText string `json:"btn_text,omitempty"`
|
||||
BtnLogo string `json:"btn_logo,omitempty"`
|
||||
RecommendQuestions []string `json:"recommend_questions,omitempty"`
|
||||
RecommendNodeIDs []string `json:"recommend_node_ids,omitempty"`
|
||||
IsOpen bool `json:"is_open,omitempty"`
|
||||
ThemeMode string `json:"theme_mode,omitempty"`
|
||||
BtnText string `json:"btn_text,omitempty"`
|
||||
BtnLogo string `json:"btn_logo,omitempty"`
|
||||
RecommendQuestions []string `json:"recommend_questions,omitempty"`
|
||||
RecommendNodeIDs []string `json:"recommend_node_ids,omitempty"`
|
||||
BtnStyle string `json:"btn_style,omitempty"`
|
||||
BtnID string `json:"btn_id,omitempty"`
|
||||
BtnPosition string `json:"btn_position,omitempty"`
|
||||
ModalPosition string `json:"modal_position,omitempty"`
|
||||
SearchMode string `json:"search_mode,omitempty"`
|
||||
Placeholder string `json:"placeholder,omitempty"`
|
||||
Disclaimer string `json:"disclaimer,omitempty"`
|
||||
CopyrightInfo string `json:"copyright_info,omitempty"`
|
||||
CopyrightHideEnabled bool `json:"copyright_hide_enabled,omitempty"`
|
||||
}
|
||||
|
||||
type BrandGroup struct {
|
||||
|
|
@ -452,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"`
|
||||
|
|
@ -470,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"`
|
||||
|
|
@ -528,6 +571,10 @@ type AppSettingsResp struct {
|
|||
WebAppLandingConfigs []WebAppLandingConfigResp `json:"web_app_landing_configs,omitempty"`
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
const ContextKeyEditionLimitation contextKey = "edition_limitation"
|
||||
|
||||
type BaseEditionLimitation struct {
|
||||
MaxKb int `json:"max_kb"` // 知识库站点数量
|
||||
MaxNode int `json:"max_node"` // 单个知识库下文档数量
|
||||
MaxSSOUser int `json:"max_sso_users"` // SSO认证用户数量
|
||||
MaxAdmin int64 `json:"max_admin"` // 后台管理员数量
|
||||
AllowAdminPerm bool `json:"allow_admin_perm"` // 支持管理员分权控制
|
||||
AllowCustomCopyright bool `json:"allow_custom_copyright"` // 支持自定义版权信息
|
||||
AllowCommentAudit bool `json:"allow_comment_audit"` // 支持评论审核
|
||||
AllowAdvancedBot bool `json:"allow_advanced_bot"` // 支持高级机器人配置
|
||||
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{
|
||||
MaxKb: 1,
|
||||
MaxAdmin: 1,
|
||||
MaxNode: 300,
|
||||
}
|
||||
|
||||
func GetBaseEditionLimitation(c context.Context) BaseEditionLimitation {
|
||||
|
||||
edition, ok := c.Value(ContextKeyEditionLimitation).([]byte)
|
||||
if !ok {
|
||||
return baseEditionLimitationDefault
|
||||
}
|
||||
|
||||
var editionLimitation BaseEditionLimitation
|
||||
if err := json.Unmarshal(edition, &editionLimitation); err != nil {
|
||||
return baseEditionLimitationDefault
|
||||
}
|
||||
|
||||
return editionLimitation
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
var SystemPrompt = `
|
||||
var SystemDefaultPrompt = `
|
||||
你是一个专业的AI知识库问答助手,要按照以下步骤回答用户问题。
|
||||
|
||||
请仔细阅读以下信息:
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ type NodeListItemResp struct {
|
|||
EditorId string `json:"editor_id"`
|
||||
Creator string `json:"creator"`
|
||||
Editor string `json:"editor"`
|
||||
PublisherId string `json:"publisher_id" gorm:"-"`
|
||||
Permissions NodePermissions `json:"permissions" gorm:"type:jsonb"`
|
||||
}
|
||||
|
||||
|
|
@ -251,10 +252,24 @@ type ShareNodeListItemResp struct {
|
|||
ParentID string `json:"parent_id"`
|
||||
Position float64 `json:"position"`
|
||||
Emoji string `json:"emoji"`
|
||||
Meta NodeMeta `json:"meta"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Permissions NodePermissions `json:"permissions" gorm:"type:jsonb"`
|
||||
}
|
||||
|
||||
type ShareNodeDetailItem struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type NodeType `json:"type"`
|
||||
ParentID string `json:"parent_id"`
|
||||
Position float64 `json:"position"`
|
||||
Emoji string `json:"emoji"`
|
||||
Meta NodeMeta `json:"meta"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Permissions NodePermissions `json:"permissions" gorm:"type:jsonb"`
|
||||
Children []*ShareNodeDetailItem `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
func (n *ShareNodeListItemResp) GetURL(baseURL string) string {
|
||||
return fmt.Sprintf("%s/node/%s", baseURL, n.ID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OpenAI API 请求结构体
|
||||
type OpenAICompletionsRequest struct {
|
||||
Model string `json:"model" validate:"required"`
|
||||
Messages []OpenAIMessage `json:"messages" validate:"required"`
|
||||
Stream bool `json:"stream,omitempty"`
|
||||
StreamOptions *OpenAIStreamOptions `json:"stream_options,omitempty"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
MaxTokens *int `json:"max_tokens,omitempty"`
|
||||
TopP *float64 `json:"top_p,omitempty"`
|
||||
|
|
@ -17,9 +24,95 @@ type OpenAICompletionsRequest struct {
|
|||
ResponseFormat *OpenAIResponseFormat `json:"response_format,omitempty"`
|
||||
}
|
||||
|
||||
type OpenAIStreamOptions struct {
|
||||
IncludeUsage bool `json:"include_usage,omitempty"`
|
||||
}
|
||||
|
||||
// MessageContent 支持字符串或内容数组
|
||||
type MessageContent struct {
|
||||
isString bool
|
||||
strValue string
|
||||
arrValue []OpenAIContentPart
|
||||
}
|
||||
|
||||
// OpenAIContentPart 表示内容数组中的单个元素
|
||||
type OpenAIContentPart struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text,omitempty"`
|
||||
ImageURL *OpenAIContentPartURL `json:"image_url,omitempty"`
|
||||
}
|
||||
|
||||
// OpenAIContentPartURL represents the image_url field in content parts
|
||||
type OpenAIContentPartURL struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON 自定义解析,支持 string 或 array 格式
|
||||
func (mc *MessageContent) UnmarshalJSON(data []byte) error {
|
||||
// 尝试解析为字符串
|
||||
var str string
|
||||
if err := json.Unmarshal(data, &str); err == nil {
|
||||
mc.isString = true
|
||||
mc.strValue = str
|
||||
return nil
|
||||
}
|
||||
|
||||
// 尝试解析为数组
|
||||
var arr []OpenAIContentPart
|
||||
if err := json.Unmarshal(data, &arr); err == nil {
|
||||
mc.isString = false
|
||||
mc.arrValue = arr
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("content must be string or array")
|
||||
}
|
||||
|
||||
// MarshalJSON 自定义序列化
|
||||
func (mc *MessageContent) MarshalJSON() ([]byte, error) {
|
||||
if mc.isString {
|
||||
return json.Marshal(mc.strValue)
|
||||
}
|
||||
return json.Marshal(mc.arrValue)
|
||||
}
|
||||
|
||||
// NewStringContent 创建字符串类型的 MessageContent
|
||||
func NewStringContent(s string) *MessageContent {
|
||||
return &MessageContent{
|
||||
isString: true,
|
||||
strValue: s,
|
||||
}
|
||||
}
|
||||
|
||||
// NewArrayContent 创建数组类型的 MessageContent
|
||||
func NewArrayContent(parts []OpenAIContentPart) *MessageContent {
|
||||
return &MessageContent{
|
||||
isString: false,
|
||||
arrValue: parts,
|
||||
}
|
||||
}
|
||||
|
||||
// String 获取文本内容
|
||||
func (mc *MessageContent) String() string {
|
||||
if mc.isString {
|
||||
return mc.strValue
|
||||
}
|
||||
// 从数组中提取文本
|
||||
var builder strings.Builder
|
||||
for _, part := range mc.arrValue {
|
||||
if part.Type == "text" {
|
||||
if builder.Len() > 0 && part.Text != "" {
|
||||
builder.WriteString(" ")
|
||||
}
|
||||
builder.WriteString(part.Text)
|
||||
}
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
type OpenAIMessage struct {
|
||||
Role string `json:"role" validate:"required"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Content *MessageContent `json:"content,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ToolCalls []OpenAIToolCall `json:"tool_calls,omitempty"`
|
||||
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||
|
|
@ -90,6 +183,7 @@ type OpenAIStreamResponse struct {
|
|||
Created int64 `json:"created"`
|
||||
Model string `json:"model"`
|
||||
Choices []OpenAIStreamChoice `json:"choices"`
|
||||
Usage *OpenAIUsage `json:"usage,omitempty"`
|
||||
}
|
||||
|
||||
type OpenAIStreamChoice struct {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,186 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMessageContent_UnmarshalJSON_String(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
json string
|
||||
expected string
|
||||
}{
|
||||
{"simple string", `"hello"`, "hello"},
|
||||
{"with quotes", `"say \"hello\""`, `say "hello"`},
|
||||
{"with newline", `"line1\nline2"`, "line1\nline2"},
|
||||
{"empty string", `""`, ""},
|
||||
{"unicode", `"你好 🌍"`, "你好 🌍"},
|
||||
{"special chars", `"Hello \"World\"\nNew Line\tTab"`, "Hello \"World\"\nNew Line\tTab"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var mc MessageContent
|
||||
err := json.Unmarshal([]byte(tt.json), &mc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, mc.String())
|
||||
assert.True(t, mc.isString)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageContent_UnmarshalJSON_Array(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
json string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"single text part",
|
||||
`[{"type":"text","text":"Hello"}]`,
|
||||
"Hello",
|
||||
},
|
||||
{
|
||||
"multiple text parts",
|
||||
`[{"type":"text","text":"Hello"},{"type":"text","text":"World"}]`,
|
||||
"Hello World",
|
||||
},
|
||||
{
|
||||
"mixed types with image",
|
||||
`[{"type":"text","text":"Look at this"},{"type":"image_url","image_url":{"url":"https://example.com/img.png"}},{"type":"text","text":"image"}]`,
|
||||
"Look at this image",
|
||||
},
|
||||
{
|
||||
"empty array",
|
||||
`[]`,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var mc MessageContent
|
||||
err := json.Unmarshal([]byte(tt.json), &mc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, mc.String())
|
||||
assert.False(t, mc.isString)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageContent_UnmarshalJSON_Invalid(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
json string
|
||||
}{
|
||||
{"number", `123`},
|
||||
{"boolean", `true`},
|
||||
{"object", `{"key":"value"}`},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var mc MessageContent
|
||||
err := json.Unmarshal([]byte(tt.json), &mc)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "content must be string or array")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageContent_UnmarshalJSON_Null(t *testing.T) {
|
||||
var mc *MessageContent
|
||||
err := json.Unmarshal([]byte(`null`), &mc)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, mc)
|
||||
}
|
||||
|
||||
func TestMessageContent_MarshalJSON_String(t *testing.T) {
|
||||
mc := NewStringContent("Hello World")
|
||||
data, err := json.Marshal(mc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, `"Hello World"`, string(data))
|
||||
}
|
||||
|
||||
func TestMessageContent_MarshalJSON_Array(t *testing.T) {
|
||||
mc := NewArrayContent([]OpenAIContentPart{
|
||||
{Type: "text", Text: "Hello"},
|
||||
{Type: "text", Text: "World"},
|
||||
})
|
||||
data, err := json.Marshal(mc)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, `[{"type":"text","text":"Hello"},{"type":"text","text":"World"}]`, string(data))
|
||||
}
|
||||
|
||||
func TestMessageContent_Roundtrip_String(t *testing.T) {
|
||||
original := NewStringContent("Test message with \"quotes\" and \nnewlines")
|
||||
|
||||
// Marshal
|
||||
data, err := json.Marshal(original)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Unmarshal
|
||||
var decoded MessageContent
|
||||
err = json.Unmarshal(data, &decoded)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, original.String(), decoded.String())
|
||||
assert.Equal(t, original.isString, decoded.isString)
|
||||
}
|
||||
|
||||
func TestMessageContent_Roundtrip_Array(t *testing.T) {
|
||||
parts := []OpenAIContentPart{
|
||||
{Type: "text", Text: "Part 1"},
|
||||
{Type: "text", Text: "Part 2"},
|
||||
}
|
||||
original := NewArrayContent(parts)
|
||||
|
||||
// Marshal
|
||||
data, err := json.Marshal(original)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Unmarshal
|
||||
var decoded MessageContent
|
||||
err = json.Unmarshal(data, &decoded)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, original.String(), decoded.String())
|
||||
assert.Equal(t, original.isString, decoded.isString)
|
||||
}
|
||||
|
||||
func TestNewStringContent(t *testing.T) {
|
||||
mc := NewStringContent("test")
|
||||
assert.NotNil(t, mc)
|
||||
assert.True(t, mc.isString)
|
||||
assert.Equal(t, "test", mc.strValue)
|
||||
assert.Equal(t, "test", mc.String())
|
||||
}
|
||||
|
||||
func TestNewArrayContent(t *testing.T) {
|
||||
parts := []OpenAIContentPart{
|
||||
{Type: "text", Text: "Hello"},
|
||||
}
|
||||
mc := NewArrayContent(parts)
|
||||
assert.NotNil(t, mc)
|
||||
assert.False(t, mc.isString)
|
||||
assert.Equal(t, parts, mc.arrValue)
|
||||
assert.Equal(t, "Hello", mc.String())
|
||||
}
|
||||
|
||||
func TestMessageContent_String_EmptyArray(t *testing.T) {
|
||||
mc := NewArrayContent([]OpenAIContentPart{})
|
||||
assert.Equal(t, "", mc.String())
|
||||
}
|
||||
|
||||
func TestMessageContent_String_NoTextParts(t *testing.T) {
|
||||
mc := NewArrayContent([]OpenAIContentPart{
|
||||
{Type: "image_url", Text: ""},
|
||||
})
|
||||
assert.Equal(t, "", mc.String())
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import (
|
|||
const (
|
||||
SettingKeySystemPrompt = "system_prompt"
|
||||
SettingBlockWords = "block_words"
|
||||
SettingCopyrightInfo = "本网站由 PandaWiki 提供技术支持"
|
||||
)
|
||||
|
||||
// table: settings
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -49,6 +50,7 @@ require (
|
|||
github.com/sbzhu/weworkapi_golang v0.0.0-20210525081115-1799804a7c8d
|
||||
github.com/silenceper/wechat/v2 v2.1.9
|
||||
github.com/spf13/viper v1.20.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/swaggo/echo-swagger v1.4.1
|
||||
github.com/swaggo/swag v1.16.5
|
||||
github.com/tidwall/gjson v1.14.1
|
||||
|
|
@ -98,6 +100,7 @@ require (
|
|||
github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250710065240-482d48888f25 // indirect
|
||||
github.com/cloudwego/eino-ext/libs/acl/openai v0.0.0-20250626133421-3c142631c961 // indirect
|
||||
github.com/cohesion-org/deepseek-go v1.2.8 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
|
|
@ -135,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
|
||||
|
|
@ -165,6 +169,7 @@ require (
|
|||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
|
|
@ -183,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")
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ func NewShareChatHandler(
|
|||
share.POST("/search", h.ChatSearch, h.ShareAuthMiddleware.Authorize)
|
||||
share.POST("/completions", h.ChatCompletions)
|
||||
share.POST("/widget", h.ChatWidget)
|
||||
share.POST("/widget/search", h.WidgetSearch)
|
||||
share.POST("/feedback", h.FeedBack)
|
||||
return h
|
||||
}
|
||||
|
|
@ -131,7 +132,7 @@ func (h *ShareChatHandler) ChatMessage(c echo.Context) error {
|
|||
//
|
||||
// @Summary ChatWidget
|
||||
// @Description ChatWidget
|
||||
// @Tags share_chat
|
||||
// @Tags Widget
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param app_type query string true "app type"
|
||||
|
|
@ -268,7 +269,9 @@ func (h *ShareChatHandler) ChatCompletions(c echo.Context) error {
|
|||
var lastUserMessage string
|
||||
for i := len(req.Messages) - 1; i >= 0; i-- {
|
||||
if req.Messages[i].Role == "user" {
|
||||
lastUserMessage = req.Messages[i].Content
|
||||
if req.Messages[i].Content != nil {
|
||||
lastUserMessage = req.Messages[i].Content.String()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -345,11 +348,12 @@ func (h *ShareChatHandler) handleOpenAIStreamResponse(c echo.Context, eventCh <-
|
|||
Index: 0,
|
||||
Delta: domain.OpenAIMessage{
|
||||
Role: "assistant",
|
||||
Content: event.Content,
|
||||
Content: domain.NewStringContent(event.Content),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.writeOpenAIStreamEvent(c, streamResp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -397,7 +401,7 @@ func (h *ShareChatHandler) handleOpenAINonStreamResponse(c echo.Context, eventCh
|
|||
Index: 0,
|
||||
Message: domain.OpenAIMessage{
|
||||
Role: "assistant",
|
||||
Content: content,
|
||||
Content: domain.NewStringContent(content),
|
||||
},
|
||||
FinishReason: "stop",
|
||||
},
|
||||
|
|
@ -441,7 +445,7 @@ func stringPtr(s string) *string {
|
|||
return &s
|
||||
}
|
||||
|
||||
// ChatMessage chat search
|
||||
// ChatSearch searches chat messages in shared knowledge base
|
||||
//
|
||||
// @Summary ChatSearch
|
||||
// @Description ChatSearch
|
||||
|
|
@ -484,3 +488,43 @@ func (h *ShareChatHandler) ChatSearch(c echo.Context) error {
|
|||
}
|
||||
return h.NewResponseWithData(c, resp)
|
||||
}
|
||||
|
||||
// WidgetSearch
|
||||
//
|
||||
// @Summary WidgetSearch
|
||||
// @Description WidgetSearch
|
||||
// @Tags Widget
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body domain.ChatSearchReq true "Comment"
|
||||
// @Success 200 {object} domain.Response{data=domain.ChatSearchResp}
|
||||
// @Router /share/v1/chat/widget/search [post]
|
||||
func (h *ShareChatHandler) WidgetSearch(c echo.Context) error {
|
||||
var req domain.ChatSearchReq
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return h.NewResponseWithError(c, "parse request failed", err)
|
||||
}
|
||||
req.KBID = c.Request().Header.Get("X-KB-ID")
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return h.NewResponseWithError(c, "validate request failed", err)
|
||||
}
|
||||
ctx := c.Request().Context()
|
||||
|
||||
// validate widget info
|
||||
widgetAppInfo, err := h.appUsecase.GetWidgetAppInfo(c.Request().Context(), req.KBID)
|
||||
if err != nil {
|
||||
h.logger.Error("get widget app info failed", log.Error(err))
|
||||
return h.sendErrMsg(c, "get app info error")
|
||||
}
|
||||
if !widgetAppInfo.Settings.WidgetBotSettings.IsOpen {
|
||||
return h.sendErrMsg(c, "widget is not open")
|
||||
}
|
||||
|
||||
req.RemoteIP = c.RealIP()
|
||||
|
||||
resp, err := h.chatUsecase.Search(ctx, &req)
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to search docs", err)
|
||||
}
|
||||
return h.NewResponseWithData(c, resp)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/chaitin/panda-wiki/consts"
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
"github.com/chaitin/panda-wiki/handler"
|
||||
"github.com/chaitin/panda-wiki/log"
|
||||
|
|
@ -157,7 +156,7 @@ func (h *ShareCommentHandler) GetCommentList(c echo.Context) error {
|
|||
}
|
||||
|
||||
// 查询数据库获取所有评论-->0 所有, 1,2 为需要审核的评论
|
||||
commentsList, err := h.usecase.GetCommentListByNodeID(ctx, nodeID, consts.GetLicenseEdition(c))
|
||||
commentsList, err := h.usecase.GetCommentListByNodeID(ctx, nodeID)
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to get comment list", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,5 +91,15 @@ func (h *ShareNodeHandler) GetNodeDetail(c echo.Context) error {
|
|||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to get node detail", err)
|
||||
}
|
||||
|
||||
// If the node is a folder, return the list of child nodes
|
||||
if node.Type == domain.NodeTypeFolder {
|
||||
childNodes, err := h.usecase.GetNodeReleaseListByParentID(c.Request().Context(), kbID, id, domain.GetAuthID(c))
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to get child nodes", err)
|
||||
}
|
||||
node.List = childNodes
|
||||
}
|
||||
|
||||
return h.NewResponseWithData(c, node)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
v1 "github.com/chaitin/panda-wiki/api/kb/v1"
|
||||
"github.com/chaitin/panda-wiki/consts"
|
||||
"github.com/chaitin/panda-wiki/domain"
|
||||
)
|
||||
|
||||
// KBUserList
|
||||
|
|
@ -55,8 +56,8 @@ func (h *KnowledgeBaseHandler) KBUserInvite(c echo.Context) error {
|
|||
return h.NewResponseWithError(c, "validate request failed", err)
|
||||
}
|
||||
|
||||
if consts.GetLicenseEdition(c) != consts.LicenseEditionEnterprise && req.Perm != consts.UserKBPermissionFullControl {
|
||||
return h.NewResponseWithError(c, "非企业版本只能使用完全控制权限", nil)
|
||||
if !domain.GetBaseEditionLimitation(c.Request().Context()).AllowAdminPerm && req.Perm != consts.UserKBPermissionFullControl {
|
||||
return h.NewResponseWithError(c, "当前版本不支持管理员分权控制", nil)
|
||||
}
|
||||
|
||||
err := h.usecase.KBUserInvite(c.Request().Context(), req)
|
||||
|
|
@ -87,8 +88,8 @@ func (h *KnowledgeBaseHandler) KBUserUpdate(c echo.Context) error {
|
|||
return h.NewResponseWithError(c, "validate request failed", err)
|
||||
}
|
||||
|
||||
if consts.GetLicenseEdition(c) != consts.LicenseEditionEnterprise && req.Perm != consts.UserKBPermissionFullControl {
|
||||
return h.NewResponseWithError(c, "非企业版本只能使用完全控制权限", nil)
|
||||
if !domain.GetBaseEditionLimitation(c.Request().Context()).AllowAdminPerm && req.Perm != consts.UserKBPermissionFullControl {
|
||||
return h.NewResponseWithError(c, "当前版本不支持管理员分权控制", nil)
|
||||
}
|
||||
|
||||
err := h.usecase.UpdateUserKB(c.Request().Context(), req)
|
||||
|
|
|
|||
|
|
@ -91,11 +91,7 @@ func (h *KnowledgeBaseHandler) CreateKnowledgeBase(c echo.Context) error {
|
|||
return h.NewResponseWithError(c, "ports is required", nil)
|
||||
}
|
||||
|
||||
req.MaxKB = 1
|
||||
maxKB := c.Get("max_kb")
|
||||
if maxKB != nil {
|
||||
req.MaxKB = maxKB.(int)
|
||||
}
|
||||
req.MaxKB = domain.GetBaseEditionLimitation(c.Request().Context()).MaxKb
|
||||
|
||||
did, err := h.usecase.CreateKnowledgeBase(c.Request().Context(), &req)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ func NewModelHandler(echo *echo.Echo, baseHandler *handler.BaseHandler, logger *
|
|||
return handler
|
||||
}
|
||||
|
||||
// get model list
|
||||
// GetModelList
|
||||
//
|
||||
// @Summary get model list
|
||||
// @Description get model list
|
||||
|
|
@ -66,7 +66,7 @@ func (h *ModelHandler) GetModelList(c echo.Context) error {
|
|||
return h.NewResponseWithData(c, models)
|
||||
}
|
||||
|
||||
// create model
|
||||
// CreateModel
|
||||
//
|
||||
// @Summary create model
|
||||
// @Description create model
|
||||
|
|
@ -85,9 +85,6 @@ func (h *ModelHandler) CreateModel(c echo.Context) error {
|
|||
return h.NewResponseWithError(c, "invalid request", err)
|
||||
}
|
||||
|
||||
if consts.GetLicenseEdition(c) == consts.LicenseEditionContributor && req.Provider != domain.ModelProviderBrandBaiZhiCloud {
|
||||
return h.NewResponseWithError(c, "联创版只能使用百智云模型哦~", nil)
|
||||
}
|
||||
ctx := c.Request().Context()
|
||||
|
||||
param := domain.ModelParam{}
|
||||
|
|
@ -112,7 +109,7 @@ func (h *ModelHandler) CreateModel(c echo.Context) error {
|
|||
return h.NewResponseWithData(c, model)
|
||||
}
|
||||
|
||||
// update model
|
||||
// UpdateModel
|
||||
//
|
||||
// @Description update model
|
||||
// @Tags model
|
||||
|
|
@ -130,9 +127,6 @@ func (h *ModelHandler) UpdateModel(c echo.Context) error {
|
|||
return h.NewResponseWithError(c, "invalid request", err)
|
||||
}
|
||||
|
||||
if consts.GetLicenseEdition(c) == consts.LicenseEditionContributor && req.Provider != domain.ModelProviderBrandBaiZhiCloud {
|
||||
return h.NewResponseWithError(c, "联创版只能使用百智云模型哦~", nil)
|
||||
}
|
||||
ctx := c.Request().Context()
|
||||
if err := h.usecase.Update(ctx, &req); err != nil {
|
||||
return h.NewResponseWithError(c, "update model failed", err)
|
||||
|
|
@ -140,7 +134,7 @@ func (h *ModelHandler) UpdateModel(c echo.Context) error {
|
|||
return h.NewResponseWithData(c, nil)
|
||||
}
|
||||
|
||||
// check model
|
||||
// CheckModel
|
||||
//
|
||||
// @Summary check model
|
||||
// @Description check model
|
||||
|
|
|
|||
|
|
@ -81,15 +81,13 @@ func (h *NodeHandler) CreateNode(c echo.Context) error {
|
|||
if err := c.Validate(req); err != nil {
|
||||
return h.NewResponseWithError(c, "validate request body failed", err)
|
||||
}
|
||||
req.MaxNode = 300
|
||||
if maxNode := c.Get("max_node"); maxNode != nil {
|
||||
req.MaxNode = maxNode.(int)
|
||||
}
|
||||
|
||||
req.MaxNode = domain.GetBaseEditionLimitation(ctx).MaxNode
|
||||
|
||||
id, err := h.usecase.Create(c.Request().Context(), req, authInfo.UserId)
|
||||
if err != nil {
|
||||
if errors.Is(err, domain.ErrMaxNodeLimitReached) {
|
||||
return h.NewResponseWithError(c, "已达到最大文档数量限制,请升级到联创版或企业版", nil)
|
||||
return h.NewResponseWithError(c, "已达到最大文档数量限制,请升级到更高版本", nil)
|
||||
}
|
||||
return h.NewResponseWithError(c, "create node failed", err)
|
||||
}
|
||||
|
|
@ -148,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)
|
||||
|
|
|
|||
|
|
@ -192,12 +192,29 @@ func (h *UserHandler) ResetPassword(c echo.Context) error {
|
|||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to get user", err)
|
||||
}
|
||||
if user.Account == "admin" && authInfo.UserId == req.ID {
|
||||
return h.NewResponseWithError(c, "请修改安装目录下 .env 文件中的 ADMIN_PASSWORD,并重启 panda-wiki-api 容器使更改生效。", nil)
|
||||
|
||||
// 非超级管理员没有改密码权限
|
||||
if user.Role != consts.UserRoleAdmin {
|
||||
return h.NewResponseWithErrCode(c, domain.ErrCodePermissionDenied)
|
||||
}
|
||||
if user.Account != "admin" && authInfo.UserId != req.ID {
|
||||
return h.NewResponseWithError(c, "只有管理员可以重置其他用户密码", nil)
|
||||
|
||||
if user.Account == "admin" {
|
||||
// admin 改不了自己的密码
|
||||
if authInfo.UserId == req.ID {
|
||||
return h.NewResponseWithError(c, "请修改安装目录下 .env 文件中的 ADMIN_PASSWORD,并重启 panda-wiki-api 容器使更改生效。", nil)
|
||||
}
|
||||
} else {
|
||||
targetUser, err := h.usecase.GetUser(ctx, req.ID)
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to get target user", err)
|
||||
}
|
||||
|
||||
// 超级管理员不能改其他超级管理员密码
|
||||
if targetUser.Role == consts.UserRoleAdmin && targetUser.ID != authInfo.UserId {
|
||||
return h.NewResponseWithError(c, "无法修改其他超级管理员密码", nil)
|
||||
}
|
||||
}
|
||||
|
||||
err = h.usecase.ResetPassword(c.Request().Context(), &req)
|
||||
if err != nil {
|
||||
return h.NewResponseWithError(c, "failed to reset password", err)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ type AuthMiddleware interface {
|
|||
Authorize(next echo.HandlerFunc) echo.HandlerFunc
|
||||
ValidateUserRole(role consts.UserRole) echo.MiddlewareFunc
|
||||
ValidateKBUserPerm(role consts.UserKBPermission) echo.MiddlewareFunc
|
||||
ValidateLicenseEdition(edition consts.LicenseEdition) echo.MiddlewareFunc
|
||||
ValidateLicenseEdition(edition ...consts.LicenseEdition) echo.MiddlewareFunc
|
||||
MustGetUserID(c echo.Context) (string, bool)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
|
|
@ -194,7 +195,7 @@ func (m *JWTMiddleware) ValidateKBUserPerm(perm consts.UserKBPermission) echo.Mi
|
|||
}
|
||||
}
|
||||
|
||||
func (m *JWTMiddleware) ValidateLicenseEdition(needEdition consts.LicenseEdition) echo.MiddlewareFunc {
|
||||
func (m *JWTMiddleware) ValidateLicenseEdition(needEditions ...consts.LicenseEdition) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
|
||||
|
|
@ -206,7 +207,7 @@ func (m *JWTMiddleware) ValidateLicenseEdition(needEdition consts.LicenseEdition
|
|||
})
|
||||
}
|
||||
|
||||
if edition < needEdition {
|
||||
if !slices.Contains(needEditions, edition) {
|
||||
return c.JSON(http.StatusForbidden, domain.PWResponse{
|
||||
Success: false,
|
||||
Message: "Unauthorized ValidateLicenseEdition",
|
||||
|
|
|
|||
|
|
@ -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 c4dc498df094cb617d31c95580db8239a445d652
|
||||
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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -300,8 +300,8 @@ func (r *AuthRepo) GetOrCreateAuth(ctx context.Context, auth *domain.Auth, sourc
|
|||
return err
|
||||
}
|
||||
|
||||
if int(count) >= licenseEdition.GetMaxAuth(sourceType) {
|
||||
return fmt.Errorf("exceed max auth limit for kb %s, current count: %d, max limit: %d", auth.KBID, count, licenseEdition.GetMaxAuth(sourceType))
|
||||
if int(count) >= domain.GetBaseEditionLimitation(ctx).MaxSSOUser {
|
||||
return fmt.Errorf("exceed max auth limit for kb %s, current count: %d, max limit: %d", auth.KBID, count, domain.GetBaseEditionLimitation(ctx).MaxSSOUser)
|
||||
}
|
||||
|
||||
auth.LastLoginTime = time.Now()
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ func (r *CommentRepository) CreateComment(ctx context.Context, comment *domain.C
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *CommentRepository) GetCommentList(ctx context.Context, nodeID string, edition consts.LicenseEdition) ([]*domain.ShareCommentListItem, int64, error) {
|
||||
func (r *CommentRepository) GetCommentList(ctx context.Context, nodeID string) ([]*domain.ShareCommentListItem, int64, error) {
|
||||
// 按照时间排序来查询node_id的comments
|
||||
var comments []*domain.ShareCommentListItem
|
||||
query := r.db.WithContext(ctx).Model(&domain.Comment{}).Where("node_id = ?", nodeID)
|
||||
|
||||
if edition == consts.LicenseEditionContributor || edition == consts.LicenseEditionEnterprise {
|
||||
if domain.GetBaseEditionLimitation(ctx).AllowCommentAudit {
|
||||
query = query.Where("status = ?", domain.CommentStatusAccepted) //accepted
|
||||
}
|
||||
|
||||
|
|
@ -50,14 +50,14 @@ func (r *CommentRepository) GetCommentList(ctx context.Context, nodeID string, e
|
|||
|
||||
func (r *CommentRepository) GetCommentListByKbID(ctx context.Context, req *domain.CommentListReq, edition consts.LicenseEdition) ([]*domain.CommentListItem, int64, error) {
|
||||
comments := []*domain.CommentListItem{}
|
||||
query := r.db.Model(&domain.Comment{}).Where("comments.kb_id = ?", req.KbID)
|
||||
query := r.db.WithContext(ctx).Model(&domain.Comment{}).Where("comments.kb_id = ?", req.KbID)
|
||||
var count int64
|
||||
if req.Status == nil {
|
||||
if err := query.Count(&count).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
} else {
|
||||
if edition == consts.LicenseEditionContributor || edition == consts.LicenseEditionEnterprise {
|
||||
if domain.GetBaseEditionLimitation(ctx).AllowCommentAudit {
|
||||
query = query.Where("comments.status = ?", *req.Status)
|
||||
}
|
||||
// 按照时间排序来查询kb_id的comments ->reject pending accepted
|
||||
|
|
@ -84,7 +84,7 @@ func (r *CommentRepository) GetCommentListByKbID(ctx context.Context, req *domai
|
|||
|
||||
func (r *CommentRepository) DeleteCommentList(ctx context.Context, commentID []string) error {
|
||||
// 批量删除指定id的comment,获取删除的总的数量、
|
||||
query := r.db.Model(&domain.Comment{}).Where("id IN (?)", commentID)
|
||||
query := r.db.WithContext(ctx).Model(&domain.Comment{}).Where("id IN (?)", commentID)
|
||||
|
||||
if err := query.Delete(&domain.Comment{}).Error; err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -157,6 +157,32 @@ func (r *NodeRepository) GetLatestNodeReleaseByNodeIDs(ctx context.Context, kbID
|
|||
return nodeReleases, nil
|
||||
}
|
||||
|
||||
func (r *NodeRepository) GetNodeReleasePublisherMap(ctx context.Context, kbID string) (map[string]string, error) {
|
||||
type Result struct {
|
||||
NodeID string `gorm:"column:node_id"`
|
||||
PublisherID string `gorm:"column:publisher_id"`
|
||||
}
|
||||
|
||||
var results []Result
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&domain.NodeRelease{}).
|
||||
Select("node_id, publisher_id").
|
||||
Where("kb_id = ?", kbID).
|
||||
Where("node_releases.doc_id != '' ").
|
||||
Find(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publisherMap := make(map[string]string)
|
||||
for _, result := range results {
|
||||
if result.PublisherID != "" {
|
||||
publisherMap[result.NodeID] = result.PublisherID
|
||||
}
|
||||
}
|
||||
|
||||
return publisherMap, nil
|
||||
}
|
||||
|
||||
func (r *NodeRepository) UpdateNodeContent(ctx context.Context, req *domain.UpdateNodeReq, userId string) error {
|
||||
// Use transaction to ensure data consistency
|
||||
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
|
|
@ -683,7 +709,7 @@ func (r *NodeRepository) GetNodeReleaseListByKBID(ctx context.Context, kbID stri
|
|||
Where("kb_release_node_releases.kb_id = ?", kbID).
|
||||
Where("kb_release_node_releases.release_id = ?", kbRelease.ID).
|
||||
Where("nodes.permissions->>'visible' != ?", consts.NodeAccessPermClosed).
|
||||
Select("node_releases.node_id as id, node_releases.name, node_releases.type, node_releases.parent_id, node_releases.position, node_releases.meta->>'emoji' as emoji, node_releases.updated_at, nodes.permissions").
|
||||
Select("node_releases.node_id as id, node_releases.name, node_releases.type, node_releases.parent_id, nodes.position, node_releases.meta->>'emoji' as emoji, node_releases.updated_at, nodes.permissions, nodes.meta").
|
||||
Find(&nodes).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1152,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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,18 +60,14 @@ func (r *UserRepository) CreateUser(ctx context.Context, user *domain.User, edit
|
|||
}
|
||||
user.Password = string(hashedPassword)
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if edition == consts.LicenseEditionContributor || edition == consts.LicenseEditionFree {
|
||||
var count int64
|
||||
if err := tx.Model(&domain.User{}).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if edition == consts.LicenseEditionFree && count >= 1 {
|
||||
return errors.New("free edition only allows 1 user")
|
||||
}
|
||||
if edition == consts.LicenseEditionContributor && count >= 5 {
|
||||
return errors.New("contributor edition only allows 5 user")
|
||||
}
|
||||
var count int64
|
||||
if err := tx.Model(&domain.User{}).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count >= domain.GetBaseEditionLimitation(ctx).MaxAdmin {
|
||||
return fmt.Errorf("exceed max admin limit, current count: %d, max limit: %d", count, domain.GetBaseEditionLimitation(ctx).MaxAdmin)
|
||||
}
|
||||
|
||||
if err := tx.Create(user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
|
||||
|
|
@ -28,6 +28,8 @@ func NewHTML2MDConverter() *converter.Converter {
|
|||
conv.Register.RendererFor("span", converter.TagTypeInline, renderAttachment, converter.PriorityEarly)
|
||||
// task list
|
||||
conv.Register.RendererFor("ul", converter.TagTypeBlock, renderTaskList, converter.PriorityEarly)
|
||||
// flowchart/diagram to mermaid code block
|
||||
conv.Register.RendererFor("div", converter.TagTypeBlock, renderFlowchart, converter.PriorityEarly)
|
||||
return conv
|
||||
}
|
||||
|
||||
|
|
@ -126,3 +128,40 @@ func getTextFromTaskItem(node *html.Node) string {
|
|||
extractText(node)
|
||||
return strings.TrimSpace(textContent.String())
|
||||
}
|
||||
|
||||
// renderFlowchart 将流程图 div 转换为 Mermaid 代码块
|
||||
func renderFlowchart(ctx converter.Context, w converter.Writer, node *html.Node) converter.RenderStatus {
|
||||
if node.Type != html.ElementNode || node.Data != "div" {
|
||||
return converter.RenderTryNext
|
||||
}
|
||||
|
||||
// 仅处理 data-type="flow" 的 div
|
||||
dataType, ok := dom.GetAttribute(node, "data-type")
|
||||
if !ok || dataType != "flow" {
|
||||
return converter.RenderTryNext
|
||||
}
|
||||
|
||||
// 提取 data-code 属性
|
||||
code, hasCode := dom.GetAttribute(node, "data-code")
|
||||
if !hasCode || strings.TrimSpace(code) == "" {
|
||||
return converter.RenderTryNext
|
||||
}
|
||||
|
||||
// 解码 HTML 实体
|
||||
code = html.UnescapeString(code)
|
||||
// 处理转义的换行符
|
||||
code = strings.ReplaceAll(code, "\\n", "\n")
|
||||
|
||||
// 写入 Mermaid 代码块
|
||||
if _, err := w.WriteString("\n```mermaid\n"); err != nil {
|
||||
return converter.RenderTryNext
|
||||
}
|
||||
if _, err := w.WriteString(code); err != nil {
|
||||
return converter.RenderTryNext
|
||||
}
|
||||
if _, err := w.WriteString("\n```\n\n"); err != nil {
|
||||
return converter.RenderTryNext
|
||||
}
|
||||
|
||||
return converter.RenderSuccess
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,33 +88,68 @@ func NewAppUsecase(
|
|||
return u
|
||||
}
|
||||
|
||||
func (u *AppUsecase) ValidateUpdateApp(ctx context.Context, id string, req *domain.UpdateAppReq, edition consts.LicenseEdition) error {
|
||||
switch edition {
|
||||
case consts.LicenseEditionFree:
|
||||
app, err := u.repo.GetAppDetail(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if app.Settings.WatermarkContent != req.Settings.WatermarkContent ||
|
||||
app.Settings.WatermarkSetting != req.Settings.WatermarkSetting ||
|
||||
app.Settings.ContributeSettings != req.Settings.ContributeSettings ||
|
||||
app.Settings.CopySetting != req.Settings.CopySetting {
|
||||
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
|
||||
}
|
||||
|
||||
limitation := domain.GetBaseEditionLimitation(ctx)
|
||||
if !limitation.AllowCopyProtection && app.Settings.CopySetting != req.Settings.CopySetting {
|
||||
return domain.ErrPermissionDenied
|
||||
}
|
||||
|
||||
if !limitation.AllowWatermark {
|
||||
if app.Settings.WatermarkSetting != req.Settings.WatermarkSetting || app.Settings.WatermarkContent != req.Settings.WatermarkContent {
|
||||
return domain.ErrPermissionDenied
|
||||
}
|
||||
case consts.LicenseEditionContributor:
|
||||
app, err := u.repo.GetAppDetail(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if app.Settings.WatermarkContent != req.Settings.WatermarkContent ||
|
||||
app.Settings.WatermarkSetting != req.Settings.WatermarkSetting ||
|
||||
app.Settings.CopySetting != req.Settings.CopySetting {
|
||||
}
|
||||
|
||||
if !limitation.AllowAdvancedBot {
|
||||
if !slices.Equal(app.Settings.WechatServiceContainKeywords, req.Settings.WechatServiceContainKeywords) ||
|
||||
!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 {
|
||||
return domain.ErrPermissionDenied
|
||||
}
|
||||
|
||||
if !limitation.AllowOpenAIBotSettings {
|
||||
if app.Settings.OpenAIAPIBotSettings.IsEnabled != req.Settings.OpenAIAPIBotSettings.IsEnabled || app.Settings.OpenAIAPIBotSettings.SecretKey != req.Settings.OpenAIAPIBotSettings.SecretKey {
|
||||
return domain.ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
|
||||
if !limitation.AllowCustomCopyright {
|
||||
if app.Settings.WidgetBotSettings.CopyrightHideEnabled != req.Settings.WidgetBotSettings.CopyrightHideEnabled || app.Settings.WidgetBotSettings.CopyrightInfo != req.Settings.WidgetBotSettings.CopyrightInfo {
|
||||
return domain.ErrPermissionDenied
|
||||
}
|
||||
if app.Settings.ConversationSetting.CopyrightHideEnabled != req.Settings.ConversationSetting.CopyrightHideEnabled {
|
||||
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
|
||||
}
|
||||
case consts.LicenseEditionEnterprise:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unsupported license type: %d", edition)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -439,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
|
||||
|
|
@ -454,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,
|
||||
|
|
@ -502,14 +538,24 @@ func (u *AppUsecase) GetAppDetailByKBIDAndAppType(ctx context.Context, kbID stri
|
|||
WebAppLandingConfigs: webAppLandingConfigs,
|
||||
WebAppLandingTheme: app.Settings.WebAppLandingTheme,
|
||||
|
||||
WatermarkContent: app.Settings.WatermarkContent,
|
||||
WatermarkSetting: app.Settings.WatermarkSetting,
|
||||
CopySetting: app.Settings.CopySetting,
|
||||
ContributeSettings: app.Settings.ContributeSettings,
|
||||
HomePageSetting: app.Settings.HomePageSetting,
|
||||
WatermarkContent: app.Settings.WatermarkContent,
|
||||
WatermarkSetting: app.Settings.WatermarkSetting,
|
||||
CopySetting: app.Settings.CopySetting,
|
||||
ContributeSettings: app.Settings.ContributeSettings,
|
||||
HomePageSetting: app.Settings.HomePageSetting,
|
||||
ConversationSetting: app.Settings.ConversationSetting,
|
||||
|
||||
WecomAIBotSettings: app.Settings.WecomAIBotSettings,
|
||||
|
||||
MCPServerSettings: app.Settings.MCPServerSettings,
|
||||
StatsSetting: app.Settings.StatsSetting,
|
||||
}
|
||||
|
||||
if !domain.GetBaseEditionLimitation(ctx).AllowCustomCopyright {
|
||||
appDetailResp.Settings.ConversationSetting.CopyrightHideEnabled = false
|
||||
appDetailResp.Settings.ConversationSetting.CopyrightInfo = domain.SettingCopyrightInfo
|
||||
}
|
||||
|
||||
// init ai feedback string
|
||||
if app.Settings.AIFeedbackSettings.AIFeedbackType == nil {
|
||||
appDetailResp.Settings.AIFeedbackSettings.AIFeedbackType = []string{"内容不准确", "没有帮助", "其他"}
|
||||
|
|
@ -532,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 {
|
||||
|
|
@ -578,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
|
||||
|
|
@ -602,11 +660,13 @@ func (u *AppUsecase) ShareGetWebAppInfo(ctx context.Context, kbID string, authId
|
|||
WebAppLandingConfigs: webAppLandingConfigs,
|
||||
WebAppLandingTheme: app.Settings.WebAppLandingTheme,
|
||||
|
||||
WatermarkContent: app.Settings.WatermarkContent,
|
||||
WatermarkSetting: app.Settings.WatermarkSetting,
|
||||
CopySetting: app.Settings.CopySetting,
|
||||
ContributeSettings: app.Settings.ContributeSettings,
|
||||
HomePageSetting: app.Settings.HomePageSetting,
|
||||
WatermarkContent: app.Settings.WatermarkContent,
|
||||
WatermarkSetting: app.Settings.WatermarkSetting,
|
||||
CopySetting: app.Settings.CopySetting,
|
||||
ContributeSettings: app.Settings.ContributeSettings,
|
||||
HomePageSetting: app.Settings.HomePageSetting,
|
||||
ConversationSetting: app.Settings.ConversationSetting,
|
||||
StatsSetting: app.Settings.StatsSetting,
|
||||
},
|
||||
}
|
||||
// init ai feedback string
|
||||
|
|
@ -618,10 +678,12 @@ func (u *AppUsecase) ShareGetWebAppInfo(ctx context.Context, kbID string, authId
|
|||
}
|
||||
showBrand := true
|
||||
defaultDisclaimer := "本回答由 PandaWiki 基于 AI 生成,仅供参考。"
|
||||
licenseEdition, _ := ctx.Value(consts.ContextKeyEdition).(consts.LicenseEdition)
|
||||
if licenseEdition < consts.LicenseEditionEnterprise {
|
||||
|
||||
if !domain.GetBaseEditionLimitation(ctx).AllowCustomCopyright {
|
||||
appInfo.Settings.WebAppCustomSettings.ShowBrandInfo = &showBrand
|
||||
appInfo.Settings.DisclaimerSettings.Content = &defaultDisclaimer
|
||||
appInfo.Settings.ConversationSetting.CopyrightHideEnabled = false
|
||||
appInfo.Settings.ConversationSetting.CopyrightInfo = domain.SettingCopyrightInfo
|
||||
} else {
|
||||
if appInfo.Settings.DisclaimerSettings.Content == nil {
|
||||
appInfo.Settings.DisclaimerSettings.Content = &defaultDisclaimer
|
||||
|
|
@ -660,9 +722,36 @@ func (u *AppUsecase) GetWidgetAppInfo(ctx context.Context, kbID string) (*domain
|
|||
}
|
||||
appInfo.RecommendNodes = nodes
|
||||
}
|
||||
|
||||
if !domain.GetBaseEditionLimitation(ctx).AllowCustomCopyright {
|
||||
appInfo.Settings.WidgetBotSettings.CopyrightHideEnabled = false
|
||||
appInfo.Settings.WidgetBotSettings.CopyrightInfo = domain.SettingCopyrightInfo
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ func (u *CommentUsecase) CreateComment(ctx context.Context, commentReq *domain.C
|
|||
return CommentStr, nil
|
||||
}
|
||||
|
||||
func (u *CommentUsecase) GetCommentListByNodeID(ctx context.Context, nodeID string, edition consts.LicenseEdition) (*domain.PaginatedResult[[]*domain.ShareCommentListItem], error) {
|
||||
comments, total, err := u.CommentRepo.GetCommentList(ctx, nodeID, edition)
|
||||
func (u *CommentUsecase) GetCommentListByNodeID(ctx context.Context, nodeID string) (*domain.PaginatedResult[[]*domain.ShareCommentListItem], error) {
|
||||
comments, total, err := u.CommentRepo.GetCommentList(ctx, nodeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,21 @@ func (u *NodeUsecase) GetList(ctx context.Context, req *domain.GetNodeListReq) (
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
publisherMap, err := u.nodeRepo.GetNodeReleasePublisherMap(ctx, req.KBID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
if publisherID, exists := publisherMap[node.ID]; exists {
|
||||
node.PublisherId = publisherID
|
||||
}
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
|
|
@ -104,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
|
||||
}
|
||||
|
|
@ -206,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
|
||||
}
|
||||
|
|
@ -350,6 +386,75 @@ func (u *NodeUsecase) GetNodeReleaseListByKBID(ctx context.Context, kbID string,
|
|||
return items, nil
|
||||
}
|
||||
|
||||
func (u *NodeUsecase) GetNodeReleaseListByParentID(ctx context.Context, kbID, parentID string, authId uint) ([]*domain.ShareNodeDetailItem, error) {
|
||||
// 一次性查询所有节点
|
||||
allNodes, err := u.nodeRepo.GetNodeReleaseListByKBID(ctx, kbID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodeGroupIds, err := u.GetNodeIdsByAuthId(ctx, authId, consts.NodePermNameVisible)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 先过滤权限
|
||||
visibleNodes := make([]*domain.ShareNodeListItemResp, 0)
|
||||
for i, node := range allNodes {
|
||||
switch node.Permissions.Visible {
|
||||
case consts.NodeAccessPermOpen:
|
||||
visibleNodes = append(visibleNodes, allNodes[i])
|
||||
case consts.NodeAccessPermPartial:
|
||||
if slices.Contains(nodeGroupIds, node.ID) {
|
||||
visibleNodes = append(visibleNodes, allNodes[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构建父子关系映射
|
||||
childrenMap := make(map[string][]*domain.ShareNodeListItemResp)
|
||||
for _, node := range visibleNodes {
|
||||
childrenMap[node.ParentID] = append(childrenMap[node.ParentID], node)
|
||||
}
|
||||
|
||||
// 构建树结构
|
||||
result := u.buildNodeTree(parentID, childrenMap)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// buildNodeTree 递归构建节点树结构
|
||||
func (u *NodeUsecase) buildNodeTree(parentID string, childrenMap map[string][]*domain.ShareNodeListItemResp) []*domain.ShareNodeDetailItem {
|
||||
children := childrenMap[parentID]
|
||||
result := make([]*domain.ShareNodeDetailItem, 0, len(children))
|
||||
|
||||
for _, child := range children {
|
||||
node := &domain.ShareNodeDetailItem{
|
||||
ID: child.ID,
|
||||
Name: child.Name,
|
||||
Type: child.Type,
|
||||
ParentID: child.ParentID,
|
||||
Position: child.Position,
|
||||
Meta: child.Meta,
|
||||
Emoji: child.Emoji,
|
||||
UpdatedAt: child.UpdatedAt,
|
||||
Children: make([]*domain.ShareNodeDetailItem, 0),
|
||||
}
|
||||
|
||||
// 如果是文件夹,递归构建其子节点
|
||||
if child.Type == domain.NodeTypeFolder {
|
||||
childNodes := u.buildNodeTree(child.ID, childrenMap)
|
||||
if len(childNodes) > 0 {
|
||||
node.Children = append(node.Children, childNodes...)
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, node)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (u *NodeUsecase) GetNodeIdsByAuthId(ctx context.Context, authId uint, PermName consts.NodePermName) ([]string, error) {
|
||||
authGroups, err := u.authRepo.GetAuthGroupWithParentsByAuthId(ctx, authId)
|
||||
if err != nil {
|
||||
|
|
@ -407,7 +512,7 @@ func (u *NodeUsecase) GetNodePermissionsByID(ctx context.Context, id, kbID strin
|
|||
}
|
||||
|
||||
func (u *NodeUsecase) ValidateNodePermissionsEdit(req v1.NodePermissionEditReq, edition consts.LicenseEdition) error {
|
||||
if edition != consts.LicenseEditionEnterprise {
|
||||
if !slices.Contains([]consts.LicenseEdition{consts.LicenseEditionBusiness, consts.LicenseEditionEnterprise}, edition) {
|
||||
if req.Permissions.Answerable == consts.NodeAccessPermPartial || req.Permissions.Visitable == consts.NodeAccessPermPartial || req.Permissions.Visible == consts.NodeAccessPermPartial {
|
||||
return domain.ErrPermissionDenied
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
|
|
@ -67,12 +68,12 @@ func (u *StatUseCase) ValidateStatDay(statDay consts.StatDay, edition consts.Lic
|
|||
case consts.StatDay1:
|
||||
return nil
|
||||
case consts.StatDay7:
|
||||
if edition < consts.LicenseEditionContributor {
|
||||
if edition == consts.LicenseEditionFree {
|
||||
return domain.ErrPermissionDenied
|
||||
}
|
||||
return nil
|
||||
case consts.StatDay30, consts.StatDay90:
|
||||
if edition < consts.LicenseEditionEnterprise {
|
||||
if !slices.Contains([]consts.LicenseEdition{consts.LicenseEditionBusiness, consts.LicenseEditionEnterprise}, edition) {
|
||||
return domain.ErrPermissionDenied
|
||||
}
|
||||
return nil
|
||||
|
|
@ -471,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 ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
"build:dev": "vite build --m development",
|
||||
"build": "tsc -b && vite build",
|
||||
"build:analyze": "tsc -b && vite build -- --analyze",
|
||||
"icon": "node ./scripts/downLoadIcon.cjs",
|
||||
"api": "cx-swagger-api"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
async function downloadFile(url) {
|
||||
const iconPath = path.resolve(__dirname, "../src/assets/fonts/iconfont.js");
|
||||
const iconDir = path.dirname(iconPath);
|
||||
|
||||
// 检查目录是否存在,不存在则创建
|
||||
if (!fs.existsSync(iconDir)) {
|
||||
fs.mkdirSync(iconDir, { recursive: true });
|
||||
console.log(`目录 ${iconDir} 已创建`);
|
||||
}
|
||||
|
||||
const response = await fetch(`https:${url}`, {
|
||||
method: "GET",
|
||||
// responseType: "stream", // fetch 不支持此参数
|
||||
}).then((res) => res.text());
|
||||
fs.writeFileSync(iconPath, response);
|
||||
console.log("Download Icon Success");
|
||||
}
|
||||
let argument = process.argv.splice(2);
|
||||
downloadFile(argument[0]);
|
||||
|
||||
|
|
@ -323,7 +323,6 @@ export type WelcomeSetting = {
|
|||
export type SEOSetting = {
|
||||
keyword: string;
|
||||
desc: string;
|
||||
auto_sitemap: boolean;
|
||||
};
|
||||
|
||||
export type CustomCodeSetting = {
|
||||
|
|
@ -588,6 +587,7 @@ export type ChatConversationItem = {
|
|||
export type ChatConversationPair = {
|
||||
user: string;
|
||||
assistant: string;
|
||||
thinking_content: string;
|
||||
created_at: string;
|
||||
info: {
|
||||
feedback_content: string;
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 354 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 360 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
|
@ -1,6 +1,6 @@
|
|||
import Logo from '@/assets/images/logo.png';
|
||||
import { Avatar as MuiAvatar, SxProps } from '@mui/material';
|
||||
import { Icon } from '@ctzhian/ui';
|
||||
import { IconDandulogo } from '@panda-wiki/icons';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface AvatarProps {
|
||||
|
|
@ -15,9 +15,8 @@ const Avatar = (props: AvatarProps) => {
|
|||
const src = props.src;
|
||||
|
||||
const LogoIcon = (
|
||||
<Icon
|
||||
<IconDandulogo
|
||||
sx={{ width: '100%', height: '100%', color: 'text.primary' }}
|
||||
type='icon-dandulogo'
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { FooterSetting } from '@/api/type';
|
||||
import { Icon } from '@ctzhian/ui';
|
||||
import { IconShanchu2, IconDrag, IconTianjia } from '@panda-wiki/icons';
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
|
|
@ -100,7 +100,7 @@ const LinkItem = forwardRef<HTMLDivElement, LinkItemProps>(
|
|||
}}
|
||||
{...dragHandleProps}
|
||||
>
|
||||
<Icon type='icon-drag' />
|
||||
<IconDrag sx={{ fontSize: '18px' }} />
|
||||
</IconButton>
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -123,7 +123,7 @@ const LinkItem = forwardRef<HTMLDivElement, LinkItemProps>(
|
|||
ml: 'auto',
|
||||
}}
|
||||
>
|
||||
<Icon type='icon-shanchu2' sx={{ fontSize: '12px' }} />
|
||||
<IconShanchu2 sx={{ fontSize: '12px' }} />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
<Controller
|
||||
|
|
@ -373,7 +373,7 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
|
|||
}}
|
||||
{...dragHandleProps}
|
||||
>
|
||||
<Icon type='icon-drag' />
|
||||
<IconDrag sx={{ fontSize: '18px' }} />
|
||||
</IconButton>
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -396,7 +396,7 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
|
|||
ml: 'auto',
|
||||
}}
|
||||
>
|
||||
<Icon type='icon-shanchu2' sx={{ fontSize: '12px' }} />
|
||||
<IconShanchu2 sx={{ fontSize: '12px' }} />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
<Controller
|
||||
|
|
@ -513,8 +513,7 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
|
|||
}}
|
||||
onClick={handleAddLink}
|
||||
>
|
||||
<Icon
|
||||
type='icon-tianjia'
|
||||
<IconTianjia
|
||||
sx={{ fontSize: '10px !important', color: '#5F58FE' }}
|
||||
/>
|
||||
<Box
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { CardWebHeaderBtn } from '@/api';
|
||||
import UploadFile from '@/components/UploadFile';
|
||||
import { useAppDispatch, useAppSelector } from '@/store';
|
||||
import { setAppPreviewData } from '@/store/slices/config';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Checkbox,
|
||||
|
|
@ -13,16 +13,15 @@ import {
|
|||
Stack,
|
||||
TextField,
|
||||
} from '@mui/material';
|
||||
import { Icon } from '@ctzhian/ui';
|
||||
import { IconShanchu2, IconDrag } from '@panda-wiki/icons';
|
||||
import {
|
||||
CSSProperties,
|
||||
Dispatch,
|
||||
forwardRef,
|
||||
HTMLAttributes,
|
||||
SetStateAction,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import { Control, Controller, useForm } from 'react-hook-form';
|
||||
import { Control, Controller } from 'react-hook-form';
|
||||
|
||||
export type ItemProps = Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> & {
|
||||
item: CardWebHeaderBtn;
|
||||
|
|
@ -300,7 +299,7 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
|
|||
height: '28px',
|
||||
}}
|
||||
>
|
||||
<Icon type='icon-shanchu2' sx={{ fontSize: '12px' }} />
|
||||
<IconShanchu2 sx={{ fontSize: '12px' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size='small'
|
||||
|
|
@ -311,7 +310,7 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
|
|||
}}
|
||||
{...dragHandleProps}
|
||||
>
|
||||
<Icon type='icon-drag' />
|
||||
<IconDrag sx={{ fontSize: '18px' }} />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import UploadFile from '@/components/UploadFile';
|
||||
import { DomainSocialMediaAccount } from '@/request/types';
|
||||
import { Icon } from '@ctzhian/ui';
|
||||
import { IconShanchu2, IconDrag } from '@panda-wiki/icons';
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
|
|
@ -92,7 +92,7 @@ const Item = forwardRef<HTMLDivElement, SocialInfoProps>(
|
|||
}}
|
||||
{...dragHandleProps}
|
||||
>
|
||||
<Icon type='icon-drag' />
|
||||
<IconDrag sx={{ fontSize: '18px' }} />
|
||||
</IconButton>
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -120,7 +120,7 @@ const Item = forwardRef<HTMLDivElement, SocialInfoProps>(
|
|||
ml: 'auto',
|
||||
}}
|
||||
>
|
||||
<Icon type='icon-shanchu2' sx={{ fontSize: '12px' }} />
|
||||
<IconShanchu2 sx={{ fontSize: '12px' }} />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
<Stack direction={'row'} gap={1}>
|
||||
|
|
@ -142,16 +142,10 @@ const Item = forwardRef<HTMLDivElement, SocialInfoProps>(
|
|||
}}
|
||||
renderValue={selected => {
|
||||
const option = options.find(i => i.key === selected);
|
||||
const AppIcon = option?.config_type || option?.type;
|
||||
return (
|
||||
<Stack justifyContent={'center'} sx={{ mt: '2px' }}>
|
||||
<Icon
|
||||
type={
|
||||
option
|
||||
? option?.config_type || option?.type || ''
|
||||
: ''
|
||||
}
|
||||
sx={{ fontSize: '14px' }}
|
||||
/>
|
||||
{AppIcon && <AppIcon sx={{ fontSize: '14px' }} />}
|
||||
</Stack>
|
||||
);
|
||||
}}
|
||||
|
|
@ -185,30 +179,31 @@ const Item = forwardRef<HTMLDivElement, SocialInfoProps>(
|
|||
borderRadius: 1,
|
||||
}}
|
||||
>
|
||||
{options.map(item => (
|
||||
<ToggleButton
|
||||
key={item.key}
|
||||
value={item.key}
|
||||
sx={{
|
||||
p: 1,
|
||||
height: 'auto',
|
||||
border: '1px solid #ddd !important',
|
||||
borderRadius: '0px',
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction='row'
|
||||
gap={1}
|
||||
alignItems='center'
|
||||
{options.map(item => {
|
||||
const AppIcon = item?.config_type || item?.type;
|
||||
return (
|
||||
<ToggleButton
|
||||
key={item.key}
|
||||
value={item.key}
|
||||
sx={{
|
||||
p: 1,
|
||||
height: 'auto',
|
||||
border: '1px solid #ddd !important',
|
||||
borderRadius: '0px',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
type={item?.config_type || item?.type}
|
||||
sx={{ fontSize: '16px' }}
|
||||
/>
|
||||
{/* <Box>{item.value || item.key}</Box> */}
|
||||
</Stack>
|
||||
</ToggleButton>
|
||||
))}
|
||||
<Stack
|
||||
direction='row'
|
||||
gap={1}
|
||||
alignItems='center'
|
||||
>
|
||||
{AppIcon && (
|
||||
<AppIcon sx={{ fontSize: '16px' }} />
|
||||
)}
|
||||
</Stack>
|
||||
</ToggleButton>
|
||||
);
|
||||
})}
|
||||
</ToggleButtonGroup>
|
||||
</MenuItem>
|
||||
</Select>
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
import { Box, Button, IconButton, Stack } from '@mui/material';
|
||||
import { Icon } from '@ctzhian/ui';
|
||||
import { AppSetting } from '@/api';
|
||||
import { useState } from 'react';
|
||||
import { getButtonThemeStyle } from './buttonThemeUtils';
|
||||
|
||||
const NavBtns = ({ detail }: { detail?: Partial<AppSetting> }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
size='small'
|
||||
onClick={() => setOpen(!open)}
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
width: 40,
|
||||
height: 40,
|
||||
}}
|
||||
>
|
||||
<Icon type='icon-a-caidan' />
|
||||
</IconButton>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '110vh',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: 999,
|
||||
transition: 'all 0.3s ease-in-out',
|
||||
transform: 'translateX(200%) translateY(-200%)',
|
||||
...(open && {
|
||||
bgcolor: 'background.default',
|
||||
transform: 'translateX(0) translateY(0)',
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction='row'
|
||||
alignItems='center'
|
||||
gap={1.5}
|
||||
sx={{ py: '14px', cursor: 'pointer', ml: 1.5, color: 'text.primary' }}
|
||||
>
|
||||
{detail?.icon && <img src={detail?.icon} alt='logo' width={32} />}
|
||||
<Box sx={{ fontSize: 18 }}>{detail?.title}</Box>
|
||||
</Stack>
|
||||
<Stack gap={4} sx={{ px: 3, mt: 4, bgcolor: 'background.default' }}>
|
||||
{detail?.btns?.map(item => (
|
||||
<Button
|
||||
key={item.id}
|
||||
fullWidth
|
||||
variant={item.variant}
|
||||
startIcon={
|
||||
item.showIcon && item.icon ? (
|
||||
<img src={item.icon} alt='logo' width={36} height={36} />
|
||||
) : null
|
||||
}
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
justifyContent: 'flex-start',
|
||||
height: '60px',
|
||||
px: 4,
|
||||
gap: 3,
|
||||
fontSize: 18,
|
||||
'& .MuiButton-startIcon': {
|
||||
ml: 0,
|
||||
mr: 0,
|
||||
},
|
||||
...getButtonThemeStyle(detail?.theme_mode, item.variant),
|
||||
}}
|
||||
>
|
||||
{item.text}
|
||||
</Button>
|
||||
))}
|
||||
</Stack>
|
||||
<IconButton
|
||||
size='small'
|
||||
onClick={() => setOpen(!open)}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
right: 10,
|
||||
color: 'text.primary',
|
||||
width: 40,
|
||||
height: 40,
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<Icon type='icon-chahao' />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavBtns;
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import React, { Dispatch, ReactNode, SetStateAction } from 'react';
|
||||
import { Box, IconButton } from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
|
||||
interface OverlayProps {
|
||||
open: boolean;
|
||||
onClose: Dispatch<SetStateAction<boolean>>;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const Overlay: React.FC<OverlayProps> = ({ open, onClose, children }) => {
|
||||
return (
|
||||
<>
|
||||
{open && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 1300,
|
||||
}}
|
||||
onClick={() => onClose(false)}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() => onClose(false)}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 16,
|
||||
right: 16,
|
||||
color: 'white',
|
||||
zIndex: 1310,
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<Box onClick={e => e.stopPropagation()}>{children}</Box>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Overlay;
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/**
|
||||
* 根据主题模式和按钮变体生成相应的样式
|
||||
* @param themeMode 主题模式 ('light' | 'dark')
|
||||
* @param variant 按钮变体 ('contained' | 'outlined' | 'text')
|
||||
* @returns 返回相应的按钮样式对象
|
||||
*/
|
||||
export const getButtonThemeStyle = (
|
||||
themeMode: 'light' | 'dark' | undefined,
|
||||
variant: string,
|
||||
) => {
|
||||
// 只在dark主题下应用特殊样式
|
||||
if (themeMode === 'dark') {
|
||||
switch (variant) {
|
||||
case 'contained':
|
||||
return {
|
||||
bgcolor: 'primary.main',
|
||||
color: 'primary.contrastText',
|
||||
borderColor: 'primary.main',
|
||||
'&:hover': {
|
||||
bgcolor: 'primary.dark',
|
||||
borderColor: 'primary.dark',
|
||||
},
|
||||
};
|
||||
case 'outlined':
|
||||
return {
|
||||
borderColor: 'primary.main',
|
||||
color: 'primary.main',
|
||||
'&:hover': {
|
||||
bgcolor: 'action.hover',
|
||||
borderColor: 'primary.main',
|
||||
},
|
||||
};
|
||||
case 'text':
|
||||
return {
|
||||
color: 'primary.main',
|
||||
'&:hover': {
|
||||
bgcolor: 'action.hover',
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
if (themeMode === 'light') {
|
||||
switch (variant) {
|
||||
case 'text':
|
||||
return {
|
||||
color: 'text.primary',
|
||||
};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export default getButtonThemeStyle;
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
import { SwitchProps, Box } from '@mui/material';
|
||||
import { Icon } from '@ctzhian/ui';
|
||||
import MySwitch from './Switch';
|
||||
|
||||
const ThemeSwitch = (props: SwitchProps) => {
|
||||
return (
|
||||
<MySwitch
|
||||
{...props}
|
||||
icon={
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 22,
|
||||
height: 22,
|
||||
bgcolor: '#fff',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
type='icon-mingliangmoshi'
|
||||
sx={{ color: '#000', fontSize: 16 }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
checkedIcon={
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 22,
|
||||
height: 22,
|
||||
bgcolor: '#fff',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
type='icon-shensemoshi'
|
||||
sx={{ color: '#000', fontSize: 16 }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeSwitch;
|
||||
|
|
@ -10,7 +10,7 @@ import {
|
|||
alpha,
|
||||
} from '@mui/material';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Icon } from '@ctzhian/ui';
|
||||
import { IconWangyeguajian } from '@panda-wiki/icons';
|
||||
import { Dispatch, SetStateAction, useMemo, useState } from 'react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { Component } from '../../index';
|
||||
|
|
@ -185,8 +185,7 @@ const ComponentBar = ({
|
|||
}}
|
||||
{...(!item.fixed ? { ...attributes, ...listeners } : {})}
|
||||
>
|
||||
<Icon
|
||||
type='icon-wangyeguajian'
|
||||
<IconWangyeguajian
|
||||
sx={{
|
||||
color:
|
||||
item.id === curComponent.id
|
||||
|
|
@ -196,7 +195,7 @@ const ComponentBar = ({
|
|||
: 'text.secondary',
|
||||
fontSize: '14px',
|
||||
}}
|
||||
></Icon>
|
||||
></IconWangyeguajian>
|
||||
<Typography
|
||||
sx={{
|
||||
marginLeft: '8px',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Box, IconButton, Stack, TextField } from '@mui/material';
|
||||
import { Icon } from '@ctzhian/ui';
|
||||
import { IconShanchu2, IconDrag } from '@panda-wiki/icons';
|
||||
import {
|
||||
CSSProperties,
|
||||
Dispatch,
|
||||
|
|
@ -118,7 +118,7 @@ const HotSearchItem = forwardRef<HTMLDivElement, HotSearchItemProps>(
|
|||
height: '28px',
|
||||
}}
|
||||
>
|
||||
<Icon type='icon-shanchu2' sx={{ fontSize: '12px' }} />
|
||||
<IconShanchu2 sx={{ fontSize: '12px' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size='small'
|
||||
|
|
@ -129,7 +129,7 @@ const HotSearchItem = forwardRef<HTMLDivElement, HotSearchItemProps>(
|
|||
}}
|
||||
{...(dragHandleProps as any)}
|
||||
>
|
||||
<Icon type='icon-drag' />
|
||||
<IconDrag sx={{ fontSize: '18px' }} />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
Select,
|
||||
MenuItem,
|
||||
} from '@mui/material';
|
||||
import { Icon } from '@ctzhian/ui';
|
||||
import { IconShanchu2, IconDrag } from '@panda-wiki/icons';
|
||||
import {
|
||||
CSSProperties,
|
||||
Dispatch,
|
||||
|
|
@ -159,7 +159,7 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
|
|||
height: '28px',
|
||||
}}
|
||||
>
|
||||
<Icon type='icon-shanchu2' sx={{ fontSize: '12px' }} />
|
||||
<IconShanchu2 sx={{ fontSize: '12px' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size='small'
|
||||
|
|
@ -170,7 +170,7 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
|
|||
}}
|
||||
{...(dragHandleProps as any)}
|
||||
>
|
||||
<Icon type='icon-drag' />
|
||||
<IconDrag sx={{ fontSize: '18px' }} />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ const Config: React.FC<ConfigProps> = ({ setIsEdit, id }) => {
|
|||
callback();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [subscribe]);
|
||||
}, [subscribe, appPreviewData, id]);
|
||||
|
||||
return (
|
||||
<StyledCommonWrapper>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,14 @@ import { postApiV1NodeSummary } from '@/request/Node';
|
|||
import { DomainRecommendNodeListResp } from '@/request/types';
|
||||
import { useAppSelector } from '@/store';
|
||||
import { Box, IconButton, Stack } from '@mui/material';
|
||||
import { Ellipsis, Icon, message } from '@ctzhian/ui';
|
||||
import { Ellipsis, message } from '@ctzhian/ui';
|
||||
import { CSSProperties, forwardRef, HTMLAttributes, useState } from 'react';
|
||||
import {
|
||||
IconShanchu2,
|
||||
IconDrag,
|
||||
IconWenjianjia,
|
||||
IconWenjian,
|
||||
} from '@panda-wiki/icons';
|
||||
|
||||
export type ItemProps = HTMLAttributes<HTMLDivElement> & {
|
||||
item: DomainRecommendNodeListResp;
|
||||
|
|
@ -80,9 +86,12 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
|
|||
<Box sx={{ fontSize: 14, color: '#2f80f7', flexShrink: 0 }}>
|
||||
{item.emoji}
|
||||
</Box>
|
||||
) : item.type === 1 ? (
|
||||
<IconWenjianjia
|
||||
sx={{ fontSize: 14, color: '#2f80f7', flexShrink: 0 }}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
type={item.type === 1 ? 'icon-wenjianjia' : 'icon-wenjian'}
|
||||
<IconWenjian
|
||||
sx={{ fontSize: 14, color: '#2f80f7', flexShrink: 0 }}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -121,12 +130,16 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
|
|||
.slice(0, 4)
|
||||
.map(it => (
|
||||
<Stack direction={'row'} alignItems={'center'} gap={1}>
|
||||
<Icon
|
||||
type={
|
||||
it.type === 1 ? 'icon-wenjianjia' : 'icon-wenjian'
|
||||
}
|
||||
sx={{ fontSize: 14, color: '#2f80f7', flexShrink: 0 }}
|
||||
/>
|
||||
{it.type === 1 ? (
|
||||
<IconWenjianjia
|
||||
sx={{ fontSize: 14, color: '#2f80f7', flexShrink: 0 }}
|
||||
/>
|
||||
) : (
|
||||
<IconWenjian
|
||||
sx={{ fontSize: 14, color: '#2f80f7', flexShrink: 0 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Ellipsis sx={{ flex: 1, width: 0 }}>{it.name}</Ellipsis>
|
||||
</Stack>
|
||||
))}
|
||||
|
|
@ -147,7 +160,7 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
|
|||
height: '28px',
|
||||
}}
|
||||
>
|
||||
<Icon type='icon-shanchu2' sx={{ fontSize: '12px' }} />
|
||||
<IconShanchu2 sx={{ fontSize: '12px' }} />
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
|
|
@ -159,7 +172,7 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
|
|||
}}
|
||||
{...dragHandleProps}
|
||||
>
|
||||
<Icon type='icon-drag' />
|
||||
<IconDrag sx={{ fontSize: '18px' }} />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Box, IconButton, Stack, TextField } from '@mui/material';
|
||||
import { Icon } from '@ctzhian/ui';
|
||||
import { IconShanchu2, IconDrag } from '@panda-wiki/icons';
|
||||
import UploadFile from '@/components/UploadFile';
|
||||
import {
|
||||
CSSProperties,
|
||||
|
|
@ -132,7 +132,7 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
|
|||
height: '28px',
|
||||
}}
|
||||
>
|
||||
<Icon type='icon-shanchu2' sx={{ fontSize: '12px' }} />
|
||||
<IconShanchu2 sx={{ fontSize: '12px' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size='small'
|
||||
|
|
@ -143,7 +143,7 @@ const Item = forwardRef<HTMLDivElement, ItemProps>(
|
|||
}}
|
||||
{...(dragHandleProps as any)}
|
||||
>
|
||||
<Icon type='icon-drag' />
|
||||
<IconDrag sx={{ fontSize: '18px' }} />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
|
|
|||