6a949b54ba
Co-authored-by: Cursor <cursoragent@cursor.com>
224 lines
7.1 KiB
TypeScript
224 lines
7.1 KiB
TypeScript
import { randomUUID } from 'crypto'
|
|
import type {
|
|
AutoWritingConfig,
|
|
InteractiveFlow,
|
|
InteractiveFlowStatus,
|
|
InteractiveScene,
|
|
NamingPause,
|
|
WizardWritingConfig,
|
|
WritingFlowMode,
|
|
WritingStep
|
|
} from '../../../shared/types'
|
|
import type { SqliteDb } from '../types'
|
|
|
|
export class InteractiveRepository {
|
|
constructor(private db: SqliteDb) {}
|
|
|
|
getBySession(sessionId: string): InteractiveFlow | null {
|
|
const row = this.db
|
|
.prepare(
|
|
`SELECT * FROM interactive_flows WHERE session_id = ? AND status = 'in_progress' ORDER BY updated_at DESC LIMIT 1`
|
|
)
|
|
.get(sessionId) as Record<string, unknown> | undefined
|
|
return row ? this.mapFlow(row) : null
|
|
}
|
|
|
|
get(flowId: string): InteractiveFlow | null {
|
|
const row = this.db.prepare('SELECT * FROM interactive_flows WHERE id = ?').get(flowId) as
|
|
| Record<string, unknown>
|
|
| undefined
|
|
return row ? this.mapFlow(row) : null
|
|
}
|
|
|
|
create(
|
|
sessionId: string,
|
|
flowMode: WritingFlowMode = 'interactive',
|
|
initialStep?: WritingStep
|
|
): InteractiveFlow {
|
|
const id = randomUUID()
|
|
const step =
|
|
initialStep ??
|
|
(flowMode === 'auto'
|
|
? 'goal_setup'
|
|
: flowMode === 'wizard'
|
|
? 'wizard_goal'
|
|
: 'context_confirm')
|
|
this.db
|
|
.prepare(
|
|
`INSERT INTO interactive_flows (id, session_id, step, status, context_json, flow_mode, mode_config_json)
|
|
VALUES (?, ?, ?, 'in_progress', '{}', ?, '{}')`
|
|
)
|
|
.run(id, sessionId, step, flowMode)
|
|
return this.get(id)!
|
|
}
|
|
|
|
resetInProgress(sessionId: string, flowMode: WritingFlowMode = 'interactive'): InteractiveFlow {
|
|
this.db
|
|
.prepare(
|
|
`UPDATE interactive_flows SET status = 'abandoned', updated_at = datetime('now') WHERE session_id = ? AND status = 'in_progress'`
|
|
)
|
|
.run(sessionId)
|
|
return this.create(sessionId, flowMode)
|
|
}
|
|
|
|
updateStep(
|
|
flowId: string,
|
|
step: WritingStep,
|
|
patch: {
|
|
contextJson?: string
|
|
plotSelectionJson?: string | null
|
|
namingPendingJson?: string | null
|
|
sceneDraftHtml?: string | null
|
|
modeConfigJson?: string
|
|
flowMode?: WritingFlowMode
|
|
status?: InteractiveFlowStatus
|
|
} = {}
|
|
): InteractiveFlow {
|
|
const sets: string[] = ['step = ?', `updated_at = datetime('now')`]
|
|
const vals: unknown[] = [step]
|
|
if (patch.contextJson !== undefined) {
|
|
sets.push('context_json = ?')
|
|
vals.push(patch.contextJson)
|
|
}
|
|
if (patch.plotSelectionJson !== undefined) {
|
|
sets.push('plot_selection_json = ?')
|
|
vals.push(patch.plotSelectionJson)
|
|
}
|
|
if (patch.namingPendingJson !== undefined) {
|
|
sets.push('naming_pending_json = ?')
|
|
vals.push(patch.namingPendingJson)
|
|
}
|
|
if (patch.sceneDraftHtml !== undefined) {
|
|
sets.push('scene_draft_html = ?')
|
|
vals.push(patch.sceneDraftHtml)
|
|
}
|
|
if (patch.modeConfigJson !== undefined) {
|
|
sets.push('mode_config_json = ?')
|
|
vals.push(patch.modeConfigJson)
|
|
}
|
|
if (patch.flowMode !== undefined) {
|
|
sets.push('flow_mode = ?')
|
|
vals.push(patch.flowMode)
|
|
}
|
|
if (patch.status !== undefined) {
|
|
sets.push('status = ?')
|
|
vals.push(patch.status)
|
|
}
|
|
vals.push(flowId)
|
|
this.db.prepare(`UPDATE interactive_flows SET ${sets.join(', ')} WHERE id = ?`).run(...vals)
|
|
return this.get(flowId)!
|
|
}
|
|
|
|
getModeConfig<T extends AutoWritingConfig | WizardWritingConfig>(
|
|
flowId: string
|
|
): T {
|
|
const row = this.db
|
|
.prepare('SELECT mode_config_json FROM interactive_flows WHERE id = ?')
|
|
.get(flowId) as { mode_config_json: string } | undefined
|
|
try {
|
|
return JSON.parse(row?.mode_config_json ?? '{}') as T
|
|
} catch {
|
|
return {} as T
|
|
}
|
|
}
|
|
|
|
setModeConfig(flowId: string, config: AutoWritingConfig | WizardWritingConfig): void {
|
|
this.db
|
|
.prepare(
|
|
`UPDATE interactive_flows SET mode_config_json = ?, updated_at = datetime('now') WHERE id = ?`
|
|
)
|
|
.run(JSON.stringify(config), flowId)
|
|
}
|
|
|
|
listScenes(flowId: string): InteractiveScene[] {
|
|
const rows = this.db
|
|
.prepare('SELECT * FROM interactive_scenes WHERE flow_id = ? ORDER BY sort_order')
|
|
.all(flowId) as Record<string, unknown>[]
|
|
return rows.map((r) => ({
|
|
id: r.id as string,
|
|
sortOrder: r.sort_order as number,
|
|
contentHtml: r.content_html as string,
|
|
plotLabel: r.plot_label as string,
|
|
refinedCount: r.refined_count as number
|
|
}))
|
|
}
|
|
|
|
addScene(flowId: string, contentHtml: string, plotLabel: string): InteractiveScene {
|
|
const id = randomUUID()
|
|
const max = this.db
|
|
.prepare('SELECT COALESCE(MAX(sort_order), -1) AS m FROM interactive_scenes WHERE flow_id = ?')
|
|
.get(flowId) as { m: number }
|
|
const sortOrder = max.m + 1
|
|
this.db
|
|
.prepare(
|
|
`INSERT INTO interactive_scenes (id, flow_id, sort_order, content_html, plot_label) VALUES (?, ?, ?, ?, ?)`
|
|
)
|
|
.run(id, flowId, sortOrder, contentHtml, plotLabel)
|
|
return this.listScenes(flowId).find((s) => s.id === id)!
|
|
}
|
|
|
|
updateSceneContent(flowId: string, sceneId: string, contentHtml: string, refinedCount: number): void {
|
|
this.db
|
|
.prepare(
|
|
`UPDATE interactive_scenes SET content_html = ?, refined_count = ? WHERE id = ? AND flow_id = ?`
|
|
)
|
|
.run(contentHtml, refinedCount, sceneId, flowId)
|
|
}
|
|
|
|
getSceneDraft(flowId: string): string {
|
|
const row = this.db
|
|
.prepare('SELECT scene_draft_html FROM interactive_flows WHERE id = ?')
|
|
.get(flowId) as { scene_draft_html: string | null } | undefined
|
|
return row?.scene_draft_html ?? ''
|
|
}
|
|
|
|
setSceneDraft(flowId: string, html: string): void {
|
|
this.db
|
|
.prepare(`UPDATE interactive_flows SET scene_draft_html = ?, updated_at = datetime('now') WHERE id = ?`)
|
|
.run(html, flowId)
|
|
}
|
|
|
|
getPlotSelection(flowId: string): string {
|
|
const row = this.db
|
|
.prepare('SELECT plot_selection_json FROM interactive_flows WHERE id = ?')
|
|
.get(flowId) as { plot_selection_json: string | null } | undefined
|
|
return row?.plot_selection_json ?? '{}'
|
|
}
|
|
|
|
getContextJson(flowId: string): string {
|
|
const row = this.db
|
|
.prepare('SELECT context_json FROM interactive_flows WHERE id = ?')
|
|
.get(flowId) as { context_json: string } | undefined
|
|
return row?.context_json ?? '{}'
|
|
}
|
|
|
|
private mapFlow(row: Record<string, unknown>): InteractiveFlow {
|
|
const flowId = row.id as string
|
|
let namingPending: NamingPause | null = null
|
|
if (row.naming_pending_json) {
|
|
try {
|
|
namingPending = JSON.parse(row.naming_pending_json as string) as NamingPause
|
|
} catch {
|
|
namingPending = null
|
|
}
|
|
}
|
|
let modeConfig: AutoWritingConfig | WizardWritingConfig | Record<string, never> = {}
|
|
try {
|
|
modeConfig = JSON.parse((row.mode_config_json as string) ?? '{}')
|
|
} catch {
|
|
modeConfig = {}
|
|
}
|
|
return {
|
|
id: flowId,
|
|
sessionId: row.session_id as string,
|
|
flowMode: (row.flow_mode as WritingFlowMode) ?? 'interactive',
|
|
step: row.step as WritingStep,
|
|
status: row.status as InteractiveFlowStatus,
|
|
modeConfig,
|
|
scenes: this.listScenes(flowId),
|
|
namingPending,
|
|
contextSummary: undefined
|
|
}
|
|
}
|
|
}
|