Skip to content

Commit d25cf41

Browse files
awesomeYGdhsifssxbingWawesomeYG
authored
Feat/v2.42.0 (#242)
* fix: invalid knowledge add space_id and folder_id * fix: ai_insight webhook msg type * feat: add suggest questions * feat: prompt generate * feat: prompt * feat: plugin suggest questions * fix: test case * fix: channel panic * feat: enhance AI insights and suggest questions functionality - Added support for suggest questions in the ChatConfig component. - Introduced new API endpoints for managing API tokens and fetching hot questions. - Updated the AIInsightDetailModal to build document links based on document type. - Improved the CustomerServiceContent to display support suggestions based on user interaction. - Added CSS for Tiptap table extend button placeholder. - Enhanced the dashboard to merge insights and sort them effectively. * Update ui/front/src/app/customer-service/ui/CustomerServiceContent.tsx Co-authored-by: xbingW <150229899+xbingW@users.noreply.github.com> * fix: iframe * fix: ai insight id * refactor: update ChatConfig and CustomerServiceContent styles - Adjusted spacing and padding in ChatConfig for improved layout. - Modified button dimensions and font sizes for better consistency. - Removed unused support suggestion code in CustomerServiceContent to streamline the component. - Enhanced message bubble width handling based on welcome message state. --------- Co-authored-by: 姚凯 <kai.yao@chaitin.com> Co-authored-by: xiaobing.wang <xiaobing.wang@chaitin.com> Co-authored-by: awesomeYG <gang.yang@chaitin.com> Co-authored-by: xbingW <150229899+xbingW@users.noreply.github.com>
1 parent ac4cfba commit d25cf41

39 files changed

Lines changed: 1158 additions & 460 deletions

backend/docs/docs.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10170,6 +10170,18 @@ const docTemplate = `{
1017010170
},
1017110171
"plugin": {
1017210172
"type": "boolean"
10173+
},
10174+
"plugin_suggest_questions": {
10175+
"type": "array",
10176+
"items": {
10177+
"type": "string"
10178+
}
10179+
},
10180+
"suggest_questions": {
10181+
"type": "array",
10182+
"items": {
10183+
"type": "string"
10184+
}
1017310185
}
1017410186
}
1017510187
},

backend/docs/swagger.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10163,6 +10163,18 @@
1016310163
},
1016410164
"plugin": {
1016510165
"type": "boolean"
10166+
},
10167+
"plugin_suggest_questions": {
10168+
"type": "array",
10169+
"items": {
10170+
"type": "string"
10171+
}
10172+
},
10173+
"suggest_questions": {
10174+
"type": "array",
10175+
"items": {
10176+
"type": "string"
10177+
}
1016610178
}
1016710179
}
1016810180
},

backend/docs/swagger.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,14 @@ definitions:
11871187
type: boolean
11881188
plugin:
11891189
type: boolean
1190+
plugin_suggest_questions:
1191+
items:
1192+
type: string
1193+
type: array
1194+
suggest_questions:
1195+
items:
1196+
type: string
1197+
type: array
11901198
type: object
11911199
model.Trend:
11921200
properties:

backend/model/stat.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ type StatTrend struct {
5151

5252
type StatInvalidKnowledgeDoc struct {
5353
Title string `json:"title"`
54+
SpaceID uint `json:"space_id"`
55+
FolderID uint `json:"folder"`
5456
Type DocType `json:"type"`
5557
DislikeCount int64 `json:"dislike_count"`
5658
HitCount int64 `json:"hit_count"`

backend/model/system.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,11 @@ type SystemSEO struct {
128128
}
129129

130130
type SystemWebPlugin struct {
131-
Enabled bool `json:"enabled"`
132-
Display bool `json:"display"`
133-
Plugin bool `json:"plugin"`
131+
Enabled bool `json:"enabled"`
132+
Display bool `json:"display"`
133+
Plugin bool `json:"plugin"`
134+
SuggestQuestions []string `json:"suggest_questions"`
135+
PluginSuggectQuestions []string `json:"plugin_suggest_questions"`
134136
}
135137

136138
type SystemChat struct {

backend/pkg/cron/ai_insight.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func (i *aiInsight) Run() {
4242
return
4343
}
4444

45-
msg, err := i.generator.AIInsight(ctx, message.TypeAIInsightKnowledgeGap)
45+
msg, err := i.generator.AIInsight(ctx, message.TypeAIInsight)
4646
if err != nil {
4747
i.logger.WithErr(err).Warn("generate knowledge gap msg failed")
4848
return

backend/pkg/cron/invalid_knowledge.go

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ func (i *invalidKnowledge) Run() {
8484

8585
ranks := make([]model.Rank, 0)
8686
knowledgeDocIDs := make(model.Int64Array, 0)
87+
docExtra := make(map[string]model.StatInvalidKnowledgeDoc)
8788

8889
if len(knowledges) > 0 {
8990
for _, knowledge := range knowledges {
@@ -107,16 +108,12 @@ func (i *invalidKnowledge) Run() {
107108
continue
108109
}
109110

110-
docBytes, err := json.Marshal(model.StatInvalidKnowledgeDoc{
111+
docExtra[knowledge.Key] = model.StatInvalidKnowledgeDoc{
111112
Title: knowledge.Title,
112113
Type: knowledge.Type,
113114
DislikeCount: knowledge.DislikeCount,
114115
HitCount: knowledge.HitCount,
115116
UpdatedAt: knowledge.UpdatedAt,
116-
})
117-
if err != nil {
118-
logger.WithErr(err).With("knowledge_info", knowledge).Warn("marsha json failed, skip")
119-
continue
120117
}
121118

122119
ranks = append(ranks, model.Rank{
@@ -126,7 +123,6 @@ func (i *invalidKnowledge) Run() {
126123
Type: model.RankTypeInvalidKnowledge,
127124
ScoreID: knowledge.Key,
128125
Score: (float64(knowledge.DislikeCount) + 1) / (float64(knowledge.HitCount) + 1) * t,
129-
Extra: string(docBytes),
130126
})
131127
}
132128

@@ -166,31 +162,64 @@ func (i *invalidKnowledge) Run() {
166162
continue
167163
}
168164

169-
docBytes, err := json.Marshal(model.StatInvalidKnowledgeDoc{
165+
strDocID := strconv.FormatUint(uint64(doc.ID), 10)
166+
docExtra[strDocID] = model.StatInvalidKnowledgeDoc{
170167
Title: doc.Title,
171168
Type: doc.DocType,
172169
UpdatedAt: doc.UpdatedAt,
173-
})
174-
if err != nil {
175-
logger.WithErr(err).With("knowledge_info", doc).Warn("marsha json failed, skip")
176-
continue
177170
}
178171

179172
ranks = append(ranks, model.Rank{
180173
Base: model.Base{
181174
CreatedAt: model.Timestamp(lastMonth.Unix()),
182175
},
183176
Type: model.RankTypeInvalidKnowledge,
184-
ScoreID: strconv.FormatUint(uint64(doc.ID), 10),
177+
ScoreID: strDocID,
185178
Score: t,
186-
Extra: string(docBytes),
187179
})
188180
}
189181
if len(ranks) == 0 {
190182
logger.Info("empty ranks. return")
191183
return
192184
}
193185

186+
for index, rank := range ranks {
187+
extra, ok := docExtra[rank.ScoreID]
188+
if !ok {
189+
logger.With("score_id", rank.ScoreID).Warn("not found in map, skip")
190+
continue
191+
}
192+
193+
if extra.Type == model.DocTypeSpace {
194+
docID, err := strconv.ParseUint(rank.ScoreID, 10, 64)
195+
if err != nil {
196+
logger.WithErr(err).With("score_id", rank.ScoreID).Warn("parse score_id failed, skip")
197+
} else {
198+
folderID, err := i.doc.GetFolderID(ctx, uint(docID))
199+
if err != nil {
200+
logger.WithErr(err).With("doc_id", docID).Warn("get doc folder id failed")
201+
} else {
202+
extra.FolderID = folderID
203+
204+
spaceID, err := i.doc.GetFolderID(ctx, folderID)
205+
if err != nil {
206+
logger.WithErr(err).With("folder_id", folderID).Warn("get doc space if failed")
207+
} else {
208+
extra.SpaceID = spaceID
209+
}
210+
}
211+
}
212+
}
213+
214+
extraBytes, err := json.Marshal(extra)
215+
if err != nil {
216+
logger.WithErr(err).With("extra", extra).Warn("marshal extra failed")
217+
continue
218+
}
219+
220+
ranks[index].Extra = string(extraBytes)
221+
}
222+
194223
err = i.rank.BatchCreate(ctx, &ranks)
195224
if err != nil {
196225
logger.WithErr(err).Error("create rank failed")

backend/pkg/llm/discussion.go

Lines changed: 8 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313

1414
// DiscussionPromptTemplate 论坛智能回帖提示词模版
1515
type DiscussionPromptTemplate struct {
16-
1716
// 帖子信息
1817
Discussion *model.DiscussionDetail
1918

@@ -23,9 +22,6 @@ type DiscussionPromptTemplate struct {
2322
// 触发回复的新评论
2423
NewComment *model.CommentDetail
2524

26-
// BOT的历史回复(用于保持对话连续性)
27-
BotHistoryReplies []model.CommentDetail
28-
2925
// 评论树结构
3026
CommentTree []*CommentNode
3127

@@ -40,28 +36,6 @@ type KnowledgeDocument struct {
4036
QA bool `json:"qa"`
4137
}
4238

43-
const discussionPostTemplate = `
44-
### ID:{{.Discussion.ID}}
45-
### 标题:{{.Discussion.Title}}
46-
### 内容:{{.Discussion.Content}}
47-
### 发帖人:{{.Discussion.UserName}}
48-
### 时间:{{formatTime .Discussion.CreatedAt}}
49-
{{- if .Discussion.Groups}}
50-
### 分组:{{renderGroups .Discussion.Groups}}
51-
{{- end}}
52-
{{- if .Discussion.Tags}}
53-
### 标签:{{join .Discussion.Tags ", "}}
54-
{{- end}}
55-
### 解决状态:{{getDiscState .Discussion.Resolved}}
56-
57-
{{- if .CommentTree}}
58-
## 评论楼层结构
59-
{{- range $i, $node := .CommentTree}}
60-
楼层{{add $i 1}} {{renderComment $node ""}}
61-
{{- end}}
62-
{{- end}}
63-
`
64-
6539
const discussionFullTemplate = `
6640
## 当前帖子信息
6741
帖子ID:{{.Discussion.ID}}
@@ -77,13 +51,11 @@ const discussionFullTemplate = `
7751
{{- end}}
7852
解决状态:{{getDiscState .Discussion.Resolved}}
7953
80-
## 评论楼层结构
8154
{{- if .CommentTree}}
55+
## 评论楼层结构
8256
{{- range $i, $node := .CommentTree}}
8357
楼层{{add $i 1}} {{renderComment $node ""}}
8458
{{- end}}
85-
{{- else}}
86-
暂无评论
8759
{{- end}}
8860
`
8961

@@ -107,13 +79,11 @@ const discussionsFullTemplateStr = `
10779
{{- end}}
10880
解决状态:{{getDiscState $disc.Discussion.Resolved}}
10981
110-
### 帖子{{add $j 1}}评论楼层结构
11182
{{- if $disc.CommentTree}}
83+
### 帖子{{add $j 1}}评论楼层结构
11284
{{- range $i, $node := $disc.CommentTree}}
11385
楼层{{add $i 1}} {{renderComment $node ""}}
11486
{{- end}}
115-
{{- else}}
116-
暂无评论
11787
{{- end}}
11888
{{- end}}
11989
{{- else}}
@@ -156,31 +126,6 @@ func (t *DiscussionPromptTemplate) Question() string {
156126
return q
157127
}
158128

159-
// BuildPostPrompt 构建帖子提示词
160-
func (t *DiscussionPromptTemplate) BuildPostPrompt() (string, error) {
161-
// 初始化帖子模版
162-
if err := t.initPostTemplate(); err != nil {
163-
return "", fmt.Errorf("初始化帖子模版失败: %w", err)
164-
}
165-
166-
// 清理帖子内容中的无意义内容(base64图片、冗长日志等),避免 token 超限
167-
// maxLen=8000 约为 ~3000-4000 tokens,为知识库检索和回复留出空间
168-
t.Discussion.Content = util.CleanContentForLLM(t.Discussion.Content, 8000)
169-
if t.NewComment != nil {
170-
t.NewComment.Content = util.CleanContentForLLM(t.NewComment.Content, 1000)
171-
}
172-
173-
// 构建评论树
174-
t.CommentTree = t.buildCommentTree()
175-
176-
var buf bytes.Buffer
177-
if err := t.template.Execute(&buf, t); err != nil {
178-
return "", fmt.Errorf("执行帖子模版失败: %w", err)
179-
}
180-
181-
return buf.String(), nil
182-
}
183-
184129
// BuildFullPrompt 构建完整的提示词
185130
func (t *DiscussionPromptTemplate) BuildFullPrompt() (string, error) {
186131
if err := t.initFullTemplate(); err != nil {
@@ -197,9 +142,6 @@ func (t *DiscussionPromptTemplate) BuildFullPrompt() (string, error) {
197142
// 构建评论树
198143
t.CommentTree = t.buildCommentTree()
199144

200-
// 提取BOT历史回复
201-
t.ExtractBotReplies()
202-
203145
// 执行模版
204146
var buf bytes.Buffer
205147
if err := t.template.Execute(&buf, t); err != nil {
@@ -234,39 +176,15 @@ func (t *DiscussionPromptTemplate) BuildContentForRetrieval() string {
234176
return strings.TrimSpace(builder.String())
235177
}
236178

237-
// initPostTemplate 初始化帖子模版
238-
func (t *DiscussionPromptTemplate) initPostTemplate() error {
239-
funcMap := template.FuncMap{
240-
"formatTime": formatTime,
241-
"join": strings.Join,
242-
"add": add,
243-
"renderGroups": renderGroups,
244-
"renderComment": renderComment,
245-
"findCommentByID": t.findCommentByID,
246-
"isReplyToBot": t.isReplyToBot,
247-
"getDiscState": getDiscState,
248-
}
249-
250-
tmpl, err := template.New("discussion_post_prompt").Funcs(funcMap).Parse(discussionPostTemplate)
251-
if err != nil {
252-
return err
253-
}
254-
255-
t.template = tmpl
256-
return nil
257-
}
258-
259179
// initFullTemplate 初始化完整模版
260180
func (t *DiscussionPromptTemplate) initFullTemplate() error {
261181
funcMap := template.FuncMap{
262-
"formatTime": formatTime,
263-
"join": strings.Join,
264-
"add": add,
265-
"renderGroups": renderGroups,
266-
"renderComment": renderComment,
267-
"findCommentByID": t.findCommentByID,
268-
"isReplyToBot": t.isReplyToBot,
269-
"getDiscState": getDiscState,
182+
"formatTime": formatTime,
183+
"join": strings.Join,
184+
"add": add,
185+
"renderGroups": renderGroups,
186+
"renderComment": renderComment,
187+
"getDiscState": getDiscState,
270188
}
271189

272190
tmpl, err := template.New("discussion_full_prompt").Funcs(funcMap).Parse(discussionFullTemplate)
@@ -343,41 +261,6 @@ func (t *DiscussionPromptTemplate) sortChildComments(node *CommentNode) {
343261
}
344262
}
345263

346-
// findCommentByID 根据ID查找评论(用于模版)
347-
func (t *DiscussionPromptTemplate) findCommentByID(comments []model.CommentDetail, id uint) *model.CommentDetail {
348-
for _, comment := range comments {
349-
if comment.ID == id {
350-
return &comment
351-
}
352-
}
353-
return nil
354-
}
355-
356-
// isReplyToBot 检查是否是对BOT回复的响应(用于模版)
357-
func (t *DiscussionPromptTemplate) isReplyToBot(botReplies []model.CommentDetail, parentID uint) bool {
358-
for _, botReply := range botReplies {
359-
if botReply.ID == parentID {
360-
return true
361-
}
362-
}
363-
return false
364-
}
365-
366-
// ExtractBotReplies 提取BOT的历史回复
367-
func (t *DiscussionPromptTemplate) ExtractBotReplies() {
368-
t.BotHistoryReplies = make([]model.CommentDetail, 0)
369-
for _, comment := range t.AllComments {
370-
if comment.Bot && (t.NewComment == nil || comment.ID != t.NewComment.ID) {
371-
t.BotHistoryReplies = append(t.BotHistoryReplies, comment)
372-
}
373-
}
374-
375-
// 按时间排序
376-
sort.Slice(t.BotHistoryReplies, func(i, j int) bool {
377-
return t.BotHistoryReplies[i].CreatedAt < t.BotHistoryReplies[j].CreatedAt
378-
})
379-
}
380-
381264
// NewDiscussionPromptTemplate 创建新的提示词模版实例
382265
func NewDiscussionPromptTemplate(
383266
discussion *model.DiscussionDetail,

0 commit comments

Comments
 (0)