feat(w4): ship v1.5.0 timeline, character arc, and compliance

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 17:40:42 +08:00
parent c5c0b7329b
commit cb6b4c3731
51 changed files with 1528 additions and 42 deletions
+47
View File
@@ -0,0 +1,47 @@
import type { CharacterArcNode } from '../../shared/arc'
import { KnowledgeRepository } from '../db/repositories/knowledge.repo'
import { SettingRepository } from '../db/repositories/setting.repo'
import { ChapterRepository } from '../db/repositories/chapter.repo'
import type { SqliteDb } from '../db/types'
export class ArcService {
constructor(private db: SqliteDb) {}
getForCharacter(settingId: string): CharacterArcNode[] {
const setting = new SettingRepository(this.db).get(settingId)
if (!setting || setting.type !== 'character') return []
const knowledge = new KnowledgeRepository(this.db)
.list()
.filter((k) => k.type === 'character' && k.title.includes(setting.name))
const chapterRepo = new ChapterRepository(this.db)
const chapters = new Map(chapterRepo.list().map((c) => [c.id, c]))
const nodes: CharacterArcNode[] = knowledge
.map((k) => {
const chapterId = k.lastMentionChapterId ?? k.sourceChapterId
const chapter = chapterId ? chapters.get(chapterId) : undefined
return {
id: k.id,
title: k.title,
content: k.content,
chapterId: chapterId ?? null,
chapterTitle: chapter?.title ?? null,
updatedAt: k.updatedAt
}
})
.sort((a, b) => a.updatedAt.localeCompare(b.updatedAt))
if (nodes.length === 0 && setting.description.trim()) {
nodes.push({
id: `setting:${setting.id}`,
title: '角色设定',
content: setting.description.replace(/<[^>]+>/g, '').slice(0, 200),
chapterId: null,
chapterTitle: null,
updatedAt: setting.updatedAt
})
}
return nodes
}
}