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:
@@ -12,11 +12,13 @@ export function buildScenePlanMessages(
|
||||
outlineSummaries.length > 0
|
||||
? `关联大纲:\n${outlineSummaries.map((s, i) => `${i + 1}. ${s}`).join('\n')}\n\n`
|
||||
: ''
|
||||
const sceneRange = wordMax <= 300 ? '1-2' : '3-6'
|
||||
const summaryHint = wordMax <= 300 ? '至少40字' : '至少80字'
|
||||
return [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
'你是长篇小说章节规划助手。根据本章目标规划 3-6 个连续场景。仅返回 JSON:{"scenes":[{"label":"场景名","summary":"至少80字的场景要点"},...],"estimatedWords":数字}。estimatedWords 须在用户给定字数范围内。'
|
||||
`你是长篇小说章节规划助手。根据本章目标规划 ${sceneRange} 个连续场景。仅返回 JSON:{"scenes":[{"label":"场景名","summary":"${summaryHint}的场景要点"},...],"estimatedWords":数字}。estimatedWords 须在用户给定字数范围内。`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
@@ -35,15 +37,18 @@ export function buildAutoSceneMessages(
|
||||
priorSceneSummaries.length > 0
|
||||
? `已写场景摘要:\n${priorSceneSummaries.map((s, i) => `${i + 1}. ${s}`).join('\n')}\n\n`
|
||||
: ''
|
||||
const planLen = Math.max(1, config.scenePlan?.length ?? 1)
|
||||
const sceneMin = Math.max(60, Math.floor((config.wordMin ?? 2000) / planLen))
|
||||
const sceneMax = Math.max(sceneMin + 20, Math.ceil((config.wordMax ?? 4000) / planLen))
|
||||
return [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
'你是小说场景写作助手。若需为新元素命名且尚无定论,先仅返回 JSON:{"type":"naming_required","elementType":"类型","context":"说明","options":["名1","名2","名3","名4","名5"]}。否则输出 600-1500 字场景 HTML <p> 段落。'
|
||||
`你是小说场景写作助手。若需为新元素命名且尚无定论,先仅返回 JSON:{"type":"naming_required","elementType":"类型","context":"说明","options":["名1","名2","名3","名4","名5"]}。否则输出 ${sceneMin}-${sceneMax} 字场景 HTML <p> 段落,勿超出上限。`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `${prior}上下文:\n${contextText}\n\n本章目标:${config.chapterGoal}\n\n当前场景:${planItem.label}\n场景要点:${planItem.summary}\n\n请生成本场景。`
|
||||
content: `${prior}上下文:\n${contextText}\n\n本章目标:${config.chapterGoal}\n\n当前场景:${planItem.label}\n场景要点:${planItem.summary}\n\n请生成本场景,控制在 ${sceneMin}-${sceneMax} 字。`
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import { createRequire } from 'node:module'
|
||||
import { app, BrowserWindow } from 'electron'
|
||||
import electronUpdater from 'electron-updater'
|
||||
import type { AppUpdater } from 'electron-updater'
|
||||
import { IPC } from '../../shared/ipc-channels'
|
||||
|
||||
const { autoUpdater } = electronUpdater
|
||||
const require = createRequire(import.meta.url)
|
||||
let autoUpdaterRef: AppUpdater | null = null
|
||||
|
||||
function getAutoUpdater(): AppUpdater {
|
||||
if (!autoUpdaterRef) {
|
||||
const electronUpdater = require('electron-updater') as { autoUpdater: AppUpdater }
|
||||
autoUpdaterRef = electronUpdater.autoUpdater
|
||||
}
|
||||
return autoUpdaterRef
|
||||
}
|
||||
|
||||
export interface UpdateInfoPayload {
|
||||
version: string
|
||||
@@ -31,6 +41,7 @@ export class UpdateService {
|
||||
!app.isPackaged ||
|
||||
process.env.NODE_ENV === 'development'
|
||||
|
||||
const autoUpdater = getAutoUpdater()
|
||||
autoUpdater.autoDownload = false
|
||||
autoUpdater.autoInstallOnAppQuit = true
|
||||
|
||||
@@ -62,7 +73,7 @@ export class UpdateService {
|
||||
this.broadcast(IPC.UPDATE_AVAILABLE, payload)
|
||||
return payload
|
||||
}
|
||||
const result = await autoUpdater.checkForUpdates()
|
||||
const result = await getAutoUpdater().checkForUpdates()
|
||||
const info = result?.updateInfo
|
||||
if (!info) return null
|
||||
return {
|
||||
@@ -78,12 +89,12 @@ export class UpdateService {
|
||||
this.broadcast(IPC.UPDATE_DOWNLOADED, { version: this.mockVersion })
|
||||
return
|
||||
}
|
||||
await autoUpdater.downloadUpdate()
|
||||
await getAutoUpdater().downloadUpdate()
|
||||
}
|
||||
|
||||
install(): void {
|
||||
if (this.disabled || this.mockVersion) return
|
||||
autoUpdater.quitAndInstall()
|
||||
getAutoUpdater().quitAndInstall()
|
||||
}
|
||||
|
||||
private broadcast(channel: string, payload: unknown): void {
|
||||
|
||||
@@ -107,7 +107,16 @@ export function TopBar(): React.JSX.Element {
|
||||
>
|
||||
📊
|
||||
</button>
|
||||
<button type="button" className="top-btn" onClick={() => { openSettingsTab(); setView('settings') }}>
|
||||
<button
|
||||
type="button"
|
||||
className="top-btn"
|
||||
data-testid="topbar-settings"
|
||||
title={t('settings.title')}
|
||||
onClick={() => {
|
||||
openSettingsTab()
|
||||
setView('settings')
|
||||
}}
|
||||
>
|
||||
⚙
|
||||
</button>
|
||||
<button
|
||||
|
||||
@@ -104,6 +104,8 @@ export function initCytoscape(
|
||||
if (evt.target === cy) onSelect(null)
|
||||
})
|
||||
|
||||
;(container as HTMLElement & { __bilinCy?: cytoscape.Core }).__bilinCy = cy
|
||||
|
||||
return cy
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user