import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs' import { join } from 'node:path' import { ownerRoot } from '~/server/utils/library' import { normalizePromptSequence, normalizePromptSequenceName, normalizeScene, emptyScene, type PromptSequence, type PromptSequenceScene } from '~/utils/promptSequences' const writeChains = new Map>() function sequencesPath(owner: string) { return join(ownerRoot(owner), 'prompt-sequences.json') } function ensureOwner(owner: string) { mkdirSync(ownerRoot(owner), { recursive: true }) } function readSequences(owner: string): PromptSequence[] { ensureOwner(owner) const path = sequencesPath(owner) if (!existsSync(path)) return [] try { const parsed = JSON.parse(readFileSync(path, 'utf8')) return Array.isArray(parsed) ? parsed.map(normalizePromptSequence).filter((item): item is PromptSequence => Boolean(item)) : [] } catch { return [] } } function writeSequences(owner: string, sequences: PromptSequence[]) { ensureOwner(owner) const path = sequencesPath(owner) const tmp = `${path}.tmp` writeFileSync(tmp, JSON.stringify(sequences, null, 2)) renameSync(tmp, path) } function mutate(owner: string, fn: (sequences: PromptSequence[]) => T): Promise { const prev = writeChains.get(owner) || Promise.resolve() const run = prev.then(() => { const sequences = readSequences(owner) const result = fn(sequences) writeSequences(owner, sequences) return result }) writeChains.set(owner, run.then(() => undefined, () => undefined)) return run } function normalizeScenesInput(raw: unknown): PromptSequenceScene[] { if (!Array.isArray(raw) || !raw.length) return [emptyScene(1)] return raw.map((scene, index) => normalizeScene(scene, index)) } export function listPromptSequences(owner: string) { return readSequences(owner).sort((a, b) => b.updatedAt - a.updatedAt || a.name.localeCompare(b.name)) } export function getPromptSequence(owner: string, id: string) { const wanted = String(id || '').trim() const sequence = readSequences(owner).find(item => item.id === wanted) if (!sequence) throw createError({ statusCode: 404, statusMessage: 'Sequence not found' }) return sequence } export async function savePromptSequence(owner: string, input: { name: string scenes?: unknown overwrite?: boolean }) { const name = normalizePromptSequenceName(input.name) if (!name) throw createError({ statusCode: 400, statusMessage: 'A sequence name is required' }) const scenes = normalizeScenesInput(input.scenes) const now = Date.now() return mutate(owner, (sequences) => { const existing = sequences.find(item => item.name.toLowerCase() === name.toLowerCase()) if (existing && !input.overwrite) { throw createError({ statusCode: 409, statusMessage: `Overwrite sequence “${existing.name}”?` }) } if (existing) { existing.scenes = scenes existing.updatedAt = now return existing } const created: PromptSequence = { id: crypto.randomUUID(), name, scenes, createdAt: now, updatedAt: now } sequences.unshift(created) return created }) } export async function updatePromptSequence(owner: string, id: string, input: { name?: string scenes?: unknown }) { const wanted = String(id || '').trim() if (!wanted) throw createError({ statusCode: 400, statusMessage: 'Missing sequence id' }) return mutate(owner, (sequences) => { const existing = sequences.find(item => item.id === wanted) if (!existing) throw createError({ statusCode: 404, statusMessage: 'Sequence not found' }) if (input.name !== undefined) { const name = normalizePromptSequenceName(input.name) if (!name) throw createError({ statusCode: 400, statusMessage: 'A sequence name is required' }) const clash = sequences.find(item => item.id !== wanted && item.name.toLowerCase() === name.toLowerCase()) if (clash) throw createError({ statusCode: 409, statusMessage: `A sequence named “${clash.name}” already exists` }) existing.name = name } if (input.scenes !== undefined) { existing.scenes = normalizeScenesInput(input.scenes) } existing.updatedAt = Date.now() return existing }) } export async function deletePromptSequence(owner: string, id: string) { const wanted = String(id || '').trim() if (!wanted) throw createError({ statusCode: 400, statusMessage: 'Missing sequence id' }) return mutate(owner, (sequences) => { const index = sequences.findIndex(item => item.id === wanted) if (index < 0) throw createError({ statusCode: 404, statusMessage: 'Sequence not found' }) const [removed] = sequences.splice(index, 1) return removed }) }