feat(kb): Obsidian 式文库 —— 笔记浏览 + [[双链]] + 反向链接(Tab 化)
把知识库做出 Obsidian 感:入库的每份文件/笔记留原文,可浏览、可读、可互链。 - store: sundynix_doc(owner+kb+name 唯一,存原文),SaveDoc(OnConflict 覆盖)/ListVault。 - gateway: runIngest 留存原文(文件用文件名、文本用首行作笔记名);GET /kb/vault?kb= 取文库(owner 隔离)。 - Markdown 组件:解析 [[名称]] / [[名称|别名]] → onLink 可点(Obsidian 双链)。 - KbView 改 Tab(入库 / 文库 / 检索 / 图谱): - 文库 = 左文档列表 + 右 Markdown 笔记([[双链]]点击跳转)+ 反向链接面板(扫全库 [[本笔记]])。 - 检索、图谱各占整页;图谱放大到 460。 验证(Preview):入两条带 [[双链]] 的笔记 → 文库列出 2 篇 → 打开「项目A概述」渲染出可点的 [[模块X]][[模块Y]] + 反向链接显示「模块X」→ 点 [[模块X]] 跳转到该笔记、其 [[项目A概述]] 亦可点。 curl 证隔离:alice 取 wt 的 vault → 空。tsc+vite+gateway build 通过;重建 .app 重启窗口。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,40 @@ func (p *Postgres) EnsureKB(ctx context.Context, owner, name, kind string) error
|
||||
}).Create(&KB{Owner: owner, Name: name, Kind: kind}).Error
|
||||
}
|
||||
|
||||
// Doc 是入库的一份原始文档/笔记(供 Obsidian 式"文库"浏览:列表 + Markdown 阅读 + 双链)。
|
||||
// 表名 sundynix_doc。(owner,kb,name) 唯一;按 owner 隔离。
|
||||
type Doc struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
Owner string `gorm:"size:64;uniqueIndex:idx_doc_okn"`
|
||||
KB string `gorm:"size:64;uniqueIndex:idx_doc_okn"`
|
||||
Name string `gorm:"size:160;uniqueIndex:idx_doc_okn"`
|
||||
Content string `gorm:"type:text"`
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
func (Doc) TableName() string { return "sundynix_doc" }
|
||||
|
||||
// SaveDoc 写入/更新一份文档(owner+kb+name 唯一,重名覆盖内容)。
|
||||
func (p *Postgres) SaveDoc(ctx context.Context, owner, kb, name, content string) error {
|
||||
if p.db == nil {
|
||||
return nil
|
||||
}
|
||||
return p.db.WithContext(ctx).Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "owner"}, {Name: "kb"}, {Name: "name"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"content"}),
|
||||
}).Create(&Doc{Owner: owner, KB: kb, Name: name, Content: content}).Error
|
||||
}
|
||||
|
||||
// ListVault 返回某 owner 某 kb 的全部文档(名+内容),供文库浏览/双链/反链。
|
||||
func (p *Postgres) ListVault(ctx context.Context, owner, kb string) ([]Doc, error) {
|
||||
if p.db == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var rows []Doc
|
||||
err := p.db.WithContext(ctx).Where("owner = ? AND kb = ?", owner, kb).Order("id").Find(&rows).Error
|
||||
return rows, err
|
||||
}
|
||||
|
||||
// LLMModel 是一个模型后端配置(控制面:管理员在此登记可用模型)。
|
||||
// 表名 sundynix_model(遵守前缀约定)。每个 kind 同一时刻仅一条 Active=true。
|
||||
type LLMModel struct {
|
||||
|
||||
@@ -34,7 +34,7 @@ func OpenPostgres(dsn string) *Postgres {
|
||||
log.Printf("[store] postgres 不可用,降级运行(不持久化): %v", err)
|
||||
return &Postgres{}
|
||||
}
|
||||
if err := db.AutoMigrate(&User{}, &Task{}, &LLMModel{}, &KB{}); err != nil {
|
||||
if err := db.AutoMigrate(&User{}, &Task{}, &LLMModel{}, &KB{}, &Doc{}); err != nil {
|
||||
log.Printf("[store] postgres AutoMigrate 失败,降级运行: %v", err)
|
||||
return &Postgres{}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user