fix(test): stabilize E2E cockpit flow and add LM Studio novel integration test
Harden cockpit dismiss timing and global settings navigation in E2E helpers, tune short-chapter AI prompts for local models, fix electron-updater CJS loading, and add a 10-chapter auto-writing integration test against LM Studio. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { DatabaseSync } from 'node:sqlite'
|
||||
import { migrate } from '../../src/main/db/migrate'
|
||||
import { VolumeRepository } from '../../src/main/db/repositories/volume.repo'
|
||||
import { ChapterRepository } from '../../src/main/db/repositories/chapter.repo'
|
||||
import { AiSessionRepository } from '../../src/main/db/repositories/ai-session.repo'
|
||||
import { InteractiveRepository } from '../../src/main/db/repositories/interactive.repo'
|
||||
import { AutoWritingService } from '../../src/main/services/auto-writing.service'
|
||||
import { AiClientService } from '../../src/main/services/ai-client.service'
|
||||
import { DEFAULT_AI_CONFIG, type AutoWritingConfig, type InteractiveFlow } from '../../src/shared/types'
|
||||
import { stripHtml } from '../../src/main/services/word-count'
|
||||
|
||||
const AI_TEST_CONFIG = { ...DEFAULT_AI_CONFIG, timeoutMs: 300_000, maxTokens: 2048 }
|
||||
const CHAPTER_COUNT = 10
|
||||
const WORD_MIN = 180
|
||||
const WORD_MAX = 220
|
||||
|
||||
const CHAPTER_GOALS = [
|
||||
'林远初入修仙门派,通过入门考核',
|
||||
'林远在藏经阁发现神秘古卷',
|
||||
'林远与师兄切磋,初露锋芒',
|
||||
'林远下山历练,遭遇妖兽',
|
||||
'林远救下受伤少女,结下善缘',
|
||||
'林远进入秘境,获得灵草',
|
||||
'林远突破瓶颈,修为精进',
|
||||
'林远卷入门派纷争,被迫站队',
|
||||
'林远查明真相,化解误会',
|
||||
'林远回望来路,展望仙途'
|
||||
]
|
||||
|
||||
async function runAutoChapter(
|
||||
svc: AutoWritingService,
|
||||
repo: InteractiveRepository,
|
||||
db: DatabaseSync,
|
||||
sessionId: string,
|
||||
volumeId: string,
|
||||
chapterIndex: number,
|
||||
context: string
|
||||
): Promise<{ chapterId: string; text: string; wordCount: number }> {
|
||||
const flow = svc.start(sessionId)
|
||||
svc.setGoal(flow.id, {
|
||||
chapterGoal: CHAPTER_GOALS[chapterIndex] ?? `第${chapterIndex + 1}章续写`,
|
||||
wordMin: WORD_MIN,
|
||||
wordMax: WORD_MAX
|
||||
})
|
||||
await svc.planScenes(flow.id, context)
|
||||
|
||||
let current: InteractiveFlow = repo.get(flow.id)!
|
||||
for (let guard = 0; guard < 12; guard++) {
|
||||
if (current.step === 'completed') break
|
||||
|
||||
if (current.step === 'naming_pause' && current.namingPending) {
|
||||
current = await svc.resolveNaming(
|
||||
flow.id,
|
||||
current.namingPending.options[0]!,
|
||||
context,
|
||||
() => {}
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
const config = repo.getModeConfig<AutoWritingConfig>(flow.id)
|
||||
const planLen = config.scenePlan?.length ?? 0
|
||||
const idx = config.currentPlanIndex ?? 0
|
||||
if (idx < planLen) {
|
||||
current = await svc.generateNext(flow.id, context, () => {})
|
||||
continue
|
||||
}
|
||||
|
||||
if (repo.listScenes(flow.id).length > 0) break
|
||||
throw new Error(`chapter ${chapterIndex + 1}: no scenes generated`)
|
||||
}
|
||||
|
||||
const scenes = repo.listScenes(flow.id)
|
||||
expect(scenes.length).toBeGreaterThan(0)
|
||||
|
||||
const { chapterId } = svc.finishChapter(flow.id, volumeId, `第${chapterIndex + 1}章`)
|
||||
const chapter = new ChapterRepository(db).get(chapterId)!
|
||||
const text = stripHtml(chapter.content)
|
||||
return { chapterId, text, wordCount: text.length }
|
||||
}
|
||||
|
||||
describe('10-chapter novel via auto writing (LM Studio)', () => {
|
||||
it('IT-NOVEL-01: produces 10 chapters ~200 words each', async () => {
|
||||
const db = new DatabaseSync(':memory:')
|
||||
migrate(db)
|
||||
const volId = new VolumeRepository(db).create('第一卷', 0).id
|
||||
const chapters = new ChapterRepository(db)
|
||||
chapters.create(
|
||||
volId,
|
||||
'序章',
|
||||
0,
|
||||
'<p>林远本是山村少年,偶得仙缘,踏上修仙之路。今日,他将参加青云门的入门考核。</p>',
|
||||
'manual'
|
||||
)
|
||||
|
||||
const session = new AiSessionRepository(db).create('十章小说', DEFAULT_AI_CONFIG.model)
|
||||
const svc = new AutoWritingService(db, new AiClientService(() => AI_TEST_CONFIG))
|
||||
const repo = new InteractiveRepository(db)
|
||||
|
||||
let context =
|
||||
'林远本是山村少年,偶得仙缘,踏上修仙之路。今日,他将参加青云门的入门考核。'
|
||||
const results: { title: string; wordCount: number }[] = []
|
||||
|
||||
for (let i = 0; i < CHAPTER_COUNT; i++) {
|
||||
const { text, wordCount } = await runAutoChapter(
|
||||
svc,
|
||||
repo,
|
||||
db,
|
||||
session.id,
|
||||
volId,
|
||||
i,
|
||||
context
|
||||
)
|
||||
results.push({ title: `第${i + 1}章`, wordCount })
|
||||
context = `${context}\n\n${text.slice(-400)}`
|
||||
console.log(`[novel] 第${i + 1}章完成,${wordCount} 字`)
|
||||
}
|
||||
|
||||
expect(results).toHaveLength(CHAPTER_COUNT)
|
||||
for (const r of results) {
|
||||
expect(r.wordCount).toBeGreaterThan(80)
|
||||
expect(r.wordCount).toBeLessThan(600)
|
||||
}
|
||||
|
||||
const allChapters = chapters.listByVolume(volId)
|
||||
expect(allChapters.length).toBe(CHAPTER_COUNT + 1)
|
||||
db.close()
|
||||
}, 7_200_000)
|
||||
})
|
||||
Reference in New Issue
Block a user