- 数据层:messages 表增加 part_type 字段,新建 file_versions 表支持版本追踪 - 后端:saveWorkspace 版本追踪、saveAgentOutput 源头分离、generateBriefMessage 成员简报 - 后端:applyDocumentEdit 增量编辑、buildWorkflowStep phase-aware 工作流引擎 - API:文件版本查询/回退接口 - 前端:part_type 驱动渲染,产物面板版本历史 - 新增写手团队(主编/搜索员/策划编辑/合规审查员)配置 - store 模块、scheduler 模块、web-search skill Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
122 lines
3.2 KiB
Go
122 lines
3.2 KiB
Go
package store
|
|
|
|
type Message struct {
|
|
ID int64
|
|
RoomID string
|
|
Agent string
|
|
Role string
|
|
Content string
|
|
Filename string
|
|
Title string
|
|
GroupID *int64
|
|
CreatedAt string
|
|
PartType string
|
|
}
|
|
|
|
type FileVersion struct {
|
|
ID int64
|
|
RoomID string
|
|
Filename string
|
|
Content string
|
|
Agent string
|
|
Version int
|
|
CreatedAt string
|
|
}
|
|
|
|
func (s *Store) InsertMessage(msg *Message) (int64, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
partType := msg.PartType
|
|
if partType == "" {
|
|
partType = "text"
|
|
}
|
|
res, err := s.db.Exec(
|
|
`INSERT INTO messages (room_id, agent, role, content, filename, title, group_id, part_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
msg.RoomID, msg.Agent, msg.Role, msg.Content, nilIfEmpty(msg.Filename), nilIfEmpty(msg.Title), msg.GroupID, partType,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return res.LastInsertId()
|
|
}
|
|
|
|
func (s *Store) GetMessages(roomID string, limit, offset int) ([]Message, error) {
|
|
if limit <= 0 {
|
|
limit = 200
|
|
}
|
|
rows, err := s.db.Query(
|
|
`SELECT id, room_id, agent, role, content, COALESCE(filename,''), COALESCE(title,''), group_id, created_at, COALESCE(part_type,'text')
|
|
FROM messages WHERE room_id = ? ORDER BY created_at ASC, id ASC LIMIT ? OFFSET ?`,
|
|
roomID, limit, offset,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var msgs []Message
|
|
for rows.Next() {
|
|
var m Message
|
|
if err := rows.Scan(&m.ID, &m.RoomID, &m.Agent, &m.Role, &m.Content, &m.Filename, &m.Title, &m.GroupID, &m.CreatedAt, &m.PartType); err != nil {
|
|
return nil, err
|
|
}
|
|
msgs = append(msgs, m)
|
|
}
|
|
return msgs, nil
|
|
}
|
|
|
|
func (s *Store) InsertFileVersion(roomID, filename, content, agent string) (int64, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
// 获取当前最大版本号
|
|
var maxVer int
|
|
s.db.QueryRow(`SELECT COALESCE(MAX(version), 0) FROM file_versions WHERE room_id = ? AND filename = ?`, roomID, filename).Scan(&maxVer)
|
|
res, err := s.db.Exec(
|
|
`INSERT INTO file_versions (room_id, filename, content, agent, version) VALUES (?, ?, ?, ?, ?)`,
|
|
roomID, filename, content, agent, maxVer+1,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return res.LastInsertId()
|
|
}
|
|
|
|
func (s *Store) GetFileVersions(roomID, filename string) ([]FileVersion, error) {
|
|
rows, err := s.db.Query(
|
|
`SELECT id, room_id, filename, content, agent, version, created_at FROM file_versions WHERE room_id = ? AND filename = ? ORDER BY version DESC`,
|
|
roomID, filename,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var versions []FileVersion
|
|
for rows.Next() {
|
|
var v FileVersion
|
|
if err := rows.Scan(&v.ID, &v.RoomID, &v.Filename, &v.Content, &v.Agent, &v.Version, &v.CreatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
versions = append(versions, v)
|
|
}
|
|
return versions, nil
|
|
}
|
|
|
|
func (s *Store) GetFileVersion(roomID, filename string, version int) (*FileVersion, error) {
|
|
var v FileVersion
|
|
err := s.db.QueryRow(
|
|
`SELECT id, room_id, filename, content, agent, version, created_at FROM file_versions WHERE room_id = ? AND filename = ? AND version = ?`,
|
|
roomID, filename, version,
|
|
).Scan(&v.ID, &v.RoomID, &v.Filename, &v.Content, &v.Agent, &v.Version, &v.CreatedAt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &v, nil
|
|
}
|
|
|
|
func nilIfEmpty(s string) interface{} {
|
|
if s == "" {
|
|
return nil
|
|
}
|
|
return s
|
|
}
|