Skip to content

Commit 67f7a19

Browse files
authored
Feat/v0.12.0 (#57)
* feat: support blog
1 parent 06ba869 commit 67f7a19

File tree

37 files changed

+2027
-1134
lines changed

37 files changed

+2027
-1134
lines changed

backend/docs/docs.go

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,7 @@ const docTemplate = `{
7272
{
7373
"type": "string",
7474
"name": "unknown_prompt",
75-
"in": "formData",
76-
"required": true
75+
"in": "formData"
7776
}
7877
],
7978
"responses": {
@@ -2765,6 +2764,11 @@ const docTemplate = `{
27652764
"name": "name",
27662765
"in": "query"
27672766
},
2767+
{
2768+
"type": "integer",
2769+
"name": "org_id",
2770+
"in": "query"
2771+
},
27682772
{
27692773
"minimum": 1,
27702774
"type": "integer",
@@ -2845,7 +2849,7 @@ const docTemplate = `{
28452849
"type": "object",
28462850
"properties": {
28472851
"data": {
2848-
"$ref": "#/definitions/svc.UserListItem"
2852+
"$ref": "#/definitions/model.User"
28492853
}
28502854
}
28512855
}
@@ -4874,6 +4878,50 @@ const docTemplate = `{
48744878
}
48754879
}
48764880
},
4881+
"model.User": {
4882+
"type": "object",
4883+
"properties": {
4884+
"avatar": {
4885+
"type": "string"
4886+
},
4887+
"builtin": {
4888+
"type": "boolean"
4889+
},
4890+
"created_at": {
4891+
"type": "integer"
4892+
},
4893+
"email": {
4894+
"type": "string"
4895+
},
4896+
"id": {
4897+
"type": "integer"
4898+
},
4899+
"invisible": {
4900+
"type": "boolean"
4901+
},
4902+
"key": {
4903+
"type": "string"
4904+
},
4905+
"last_login": {
4906+
"type": "integer"
4907+
},
4908+
"name": {
4909+
"type": "string"
4910+
},
4911+
"org_id": {
4912+
"type": "integer"
4913+
},
4914+
"password": {
4915+
"type": "string"
4916+
},
4917+
"role": {
4918+
"$ref": "#/definitions/model.UserRole"
4919+
},
4920+
"updated_at": {
4921+
"type": "integer"
4922+
}
4923+
}
4924+
},
48774925
"model.UserInfo": {
48784926
"type": "object",
48794927
"properties": {
@@ -5897,6 +5945,12 @@ const docTemplate = `{
58975945
"name": {
58985946
"type": "string"
58995947
},
5948+
"org_id": {
5949+
"type": "integer"
5950+
},
5951+
"org_name": {
5952+
"type": "string"
5953+
},
59005954
"role": {
59015955
"$ref": "#/definitions/model.UserRole"
59025956
},

backend/model/org.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package model
2+
3+
type Org struct {
4+
Base
5+
6+
Name string `json:"name" gorm:"cloumn:name;type:text"`
7+
}
8+
9+
func init() {
10+
registerAutoMigrate(&Org{})
11+
}

backend/model/user.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const (
1313
type User struct {
1414
Base
1515

16+
OrgID uint `gorm:"column:org_id;type:bigint;default:0" json:"org_id"`
1617
Name string `gorm:"column:name;type:text" json:"name"`
1718
Email string `gorm:"column:email;type:text;default:null;uniqueIndex" json:"email"`
1819
Avatar string `gorm:"column:avatar;type:text" json:"avatar"`

backend/pkg/llm/discussion.go

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -42,26 +42,13 @@ type KnowledgeDocument struct {
4242

4343
// 帖子模版常量(包含帖子信息和评论楼层结构)
4444
const discussionPostTemplate = `
45-
## 当前帖子信息
46-
### 帖子ID:{{.Discussion.ID}}
47-
### 帖子标题:{{.Discussion.Title}}
48-
### 帖子内容:{{.Discussion.Content}}
49-
### 发帖人:{{.Discussion.UserName}}
50-
### 发帖时间:{{formatTime .Discussion.CreatedAt}}
45+
### ID:{{.Discussion.ID}}
46+
### 标题:{{.Discussion.Title}}
47+
### 内容:{{.Discussion.Content}}
48+
### 时间:{{formatTime .Discussion.CreatedAt}}
5149
{{- if .Discussion.Tags}}
52-
### 帖子标签:{{join .Discussion.Tags ", "}}
50+
### 标签:{{join .Discussion.Tags ", "}}
5351
{{- end}}
54-
### 解决状态:{{if .Discussion.Resolved}}已解决{{else}}待解决{{end}}
55-
56-
## 评论楼层结构
57-
{{- if .CommentTree}}
58-
{{- range $i, $node := .CommentTree}}
59-
楼层{{add $i 1}} {{renderComment $node ""}}
60-
{{- end}}
61-
{{- else}}
62-
暂无评论
63-
{{- end}}
64-
6552
`
6653

6754
// 回复模版常量(针对新评论的回复)
@@ -137,20 +124,13 @@ func (t *DiscussionPromptTemplate) BuildPrompt() (string, error) {
137124
return buf.String(), nil
138125
}
139126

140-
// BuildPostPrompt 构建帖子提示词(用于回复帖子)
127+
// BuildPostPrompt 构建帖子提示词
141128
func (t *DiscussionPromptTemplate) BuildPostPrompt() (string, error) {
142129
// 初始化帖子模版
143130
if err := t.initPostTemplate(); err != nil {
144131
return "", fmt.Errorf("初始化帖子模版失败: %w", err)
145132
}
146133

147-
// 构建评论树
148-
t.CommentTree = t.buildCommentTree()
149-
150-
// 提取BOT历史回复
151-
t.ExtractBotReplies()
152-
153-
// 执行模版
154134
var buf bytes.Buffer
155135
if err := t.template.Execute(&buf, t); err != nil {
156136
return "", fmt.Errorf("执行帖子模版失败: %w", err)

backend/pkg/llm/prompt.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,20 @@ var SystemAnswerSummaryPrompt = `
6969
## 回复要求
7070
只输出答案内容,不要任何解释、分析过程或其他额外内容,不要添加 "## 最佳答案" 等标题格式
7171
`
72+
73+
var SystemBlogSummaryPrompt = `
74+
你是一个专业的博客内容总结助手,负责为技术博客生成简洁的内容概览。
75+
76+
## 任务要求
77+
根据文章标题和内容,生成一段简洁的内容概览,帮助读者快速了解此文章的主要内容和价值。
78+
79+
## 总结规则
80+
1. **客观准确**:严格基于博客原文内容,不添加任何博客中没有的信息
81+
2. **简洁明了**:用 2-3 句话概括博客的核心内容和主要观点
82+
3. **突出价值**:重点说明博客对读者的实用价值或解决的问题
83+
84+
## 输出要求
85+
- 只输出内容概览,不要标题、解释或其他额外内容
86+
- 使用简洁的文字描述,无需 Markdown 格式
87+
- 字数控制在 100-150 字以内
88+
`

backend/pkg/webhook/message/discuss.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ func NewCreateBlog(body Common) Message {
114114
return &discussMsg{
115115
Header: Header{
116116
MsgType: TypeNewBlog,
117-
MsgTitle: "你有新的博客",
118-
HeadingPrefix: "博客",
117+
MsgTitle: "你有新的文章",
118+
HeadingPrefix: "标题",
119119
},
120120
Common: body,
121121
}

backend/repo/org.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package repo
2+
3+
import (
4+
"context"
5+
6+
"github.com/chaitin/koalaqa/model"
7+
"github.com/chaitin/koalaqa/pkg/database"
8+
"gorm.io/gorm/clause"
9+
)
10+
11+
type Org struct {
12+
base[*model.Org]
13+
}
14+
15+
func (o *Org) List(ctx context.Context, res any, queryFuncs ...QueryOptFunc) error {
16+
quertOpt := getQueryOpt(queryFuncs...)
17+
18+
return o.model(ctx).Joins("LEFT JOIN (SELECT org_id, COUNT(*) AS count FROM users GROUP BY org_id) AS org_user ON org_user.org_id = orgs.id").
19+
Scopes(quertOpt.Scopes()...).
20+
Find(&res).Error
21+
}
22+
23+
func (o *Org) Create(ctx context.Context, org *model.Org) error {
24+
return o.model(ctx).Clauses(clause.OnConflict{
25+
Columns: []clause.Column{{Name: "id"}},
26+
DoUpdates: clause.AssignmentColumns([]string{"name"}),
27+
}).Create(org).Error
28+
}
29+
30+
func newOrg(db *database.DB) *Org {
31+
return &Org{
32+
base: base[*model.Org]{
33+
m: &model.Org{}, db: db,
34+
},
35+
}
36+
}
37+
38+
func init() {
39+
register(newOrg)
40+
}

backend/repo/user.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ type User struct {
1616
base[*model.User]
1717
}
1818

19+
func (u *User) ListWithOrg(ctx context.Context, res any, queryFuncs ...QueryOptFunc) error {
20+
queryOpt := getQueryOpt(queryFuncs...)
21+
return u.model(ctx).Select("users.*, orgs.name AS org_name").
22+
Joins("LEFT JOIN orgs ON orgs.id = users.org_id").
23+
Scopes(queryOpt.Scopes()...).Find(&res).Error
24+
}
25+
1926
func (u *User) GetByEmail(ctx context.Context, res any, email string) error {
2027
return u.model(ctx).Where("email = ?", email).First(&res).Error
2128
}

backend/repo/util.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const (
1818
EqualOPInclude
1919
EqualOPContainAny
2020
EqualOPValIn
21+
EqualOPNotEqAny
2122
)
2223

2324
type eqVal struct {
@@ -131,6 +132,8 @@ func equalScope(kv []kv) database.Scope {
131132
db = db.Where(data.key+" IN (?)", data.value.v)
132133
case EqualOPEqAny:
133134
db = db.Where(data.key+" = ANY(?)", data.value.v)
135+
case EqualOPNotEqAny:
136+
db = db.Where(data.key+" != ALL(?)", data.value.v)
134137
case EqualOPIntesect:
135138
db = db.Where(data.key+" && ?", data.value.v)
136139
case EqualOPInclude:

backend/router/admin/org.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package admin
2+
3+
import (
4+
"github.com/chaitin/koalaqa/pkg/context"
5+
"github.com/chaitin/koalaqa/server"
6+
"github.com/chaitin/koalaqa/svc"
7+
)
8+
9+
type org struct {
10+
svcOrg *svc.Org
11+
}
12+
13+
func (o *org) List(ctx *context.Context) {
14+
res, err := o.svcOrg.List(ctx)
15+
if err != nil {
16+
ctx.InternalError(err, "list org failed")
17+
return
18+
}
19+
20+
ctx.Success(res)
21+
}
22+
23+
func (o *org) Upsert(ctx *context.Context) {
24+
var req svc.OrgUpsertReq
25+
err := ctx.ShouldBindJSON(&req)
26+
if err != nil {
27+
ctx.BadRequest(err)
28+
return
29+
}
30+
31+
res, err := o.svcOrg.Upsert(ctx, req)
32+
if err != nil {
33+
ctx.InternalError(err, "upsert org failed")
34+
return
35+
}
36+
37+
ctx.Success(res)
38+
}
39+
40+
func (o *org) Delete(ctx *context.Context) {
41+
orgID, err := ctx.ParamUint("org_id")
42+
if err != nil {
43+
ctx.BadRequest(err)
44+
return
45+
}
46+
47+
err = o.svcOrg.Delete(ctx, orgID)
48+
if err != nil {
49+
ctx.InternalError(err, "delete org failed")
50+
return
51+
}
52+
53+
ctx.Success(nil)
54+
}
55+
56+
func (o *org) Route(h server.Handler) {
57+
g := h.Group("/org")
58+
g.GET("", o.List)
59+
g.POST("", o.Upsert)
60+
{
61+
detailG := g.Group("/:org_id")
62+
detailG.DELETE("", o.Delete)
63+
}
64+
}
65+
66+
func newOrg(o *svc.Org) server.Router {
67+
return &org{
68+
svcOrg: o,
69+
}
70+
}
71+
72+
func init() {
73+
registerAdminAPIRouter(newOrg)
74+
}

0 commit comments

Comments
 (0)