- 数据层: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>
117 lines
2.9 KiB
Go
117 lines
2.9 KiB
Go
package store
|
||
|
||
import (
|
||
"database/sql"
|
||
"sync"
|
||
|
||
_ "modernc.org/sqlite"
|
||
)
|
||
|
||
type Store struct {
|
||
db *sql.DB
|
||
mu sync.Mutex
|
||
}
|
||
|
||
func New(dbPath string) (*Store, error) {
|
||
db, err := sql.Open("sqlite", dbPath+"?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
s := &Store{db: db}
|
||
if err := s.migrate(); err != nil {
|
||
db.Close()
|
||
return nil, err
|
||
}
|
||
return s, nil
|
||
}
|
||
|
||
func (s *Store) Close() error {
|
||
return s.db.Close()
|
||
}
|
||
|
||
func (s *Store) migrate() error {
|
||
_, err := s.db.Exec(`
|
||
CREATE TABLE IF NOT EXISTS messages (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
room_id TEXT NOT NULL,
|
||
agent TEXT NOT NULL,
|
||
role TEXT NOT NULL,
|
||
content TEXT NOT NULL DEFAULT '',
|
||
filename TEXT,
|
||
title TEXT,
|
||
group_id INTEGER,
|
||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_msg_room ON messages(room_id, created_at);
|
||
CREATE INDEX IF NOT EXISTS idx_msg_group ON messages(group_id);
|
||
|
||
CREATE TABLE IF NOT EXISTS token_usage (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
room_id TEXT NOT NULL,
|
||
agent TEXT NOT NULL,
|
||
prompt_tokens INTEGER NOT NULL DEFAULT 0,
|
||
completion_tokens INTEGER NOT NULL DEFAULT 0,
|
||
total_tokens INTEGER NOT NULL DEFAULT 0,
|
||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_tu_room ON token_usage(room_id);
|
||
|
||
CREATE TABLE IF NOT EXISTS schedules (
|
||
id TEXT PRIMARY KEY,
|
||
room_id TEXT NOT NULL,
|
||
cron TEXT NOT NULL,
|
||
message TEXT NOT NULL,
|
||
user_name TEXT NOT NULL DEFAULT 'scheduler',
|
||
enabled INTEGER NOT NULL DEFAULT 1,
|
||
once INTEGER NOT NULL DEFAULT 0,
|
||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||
last_run_at TEXT
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_sch_room ON schedules(room_id);
|
||
|
||
CREATE TABLE IF NOT EXISTS file_versions (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
room_id TEXT NOT NULL,
|
||
filename TEXT NOT NULL,
|
||
content TEXT NOT NULL,
|
||
agent TEXT NOT NULL DEFAULT '',
|
||
version INTEGER NOT NULL DEFAULT 1,
|
||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_fv_room_file ON file_versions(room_id, filename);
|
||
`)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 安全添加 part_type 列(SQLite 不支持 ALTER TABLE ... IF NOT EXISTS)
|
||
var hasPartType bool
|
||
rows, err := s.db.Query(`PRAGMA table_info(messages)`)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer rows.Close()
|
||
for rows.Next() {
|
||
var cid int
|
||
var name, typ string
|
||
var notnull int
|
||
var dfltValue *string
|
||
var pk int
|
||
if err := rows.Scan(&cid, &name, &typ, ¬null, &dfltValue, &pk); err != nil {
|
||
return err
|
||
}
|
||
if name == "part_type" {
|
||
hasPartType = true
|
||
break
|
||
}
|
||
}
|
||
if !hasPartType {
|
||
_, err = s.db.Exec(`ALTER TABLE messages ADD COLUMN part_type TEXT NOT NULL DEFAULT 'text'`)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|