mirror of
https://github.com/AventurasTeam/Aventuras.git
synced 2026-04-28 03:40:11 +00:00
feat: branch and checkpoint aware bg images
This commit is contained in:
parent
ba00458f12
commit
b3b63d1985
5 changed files with 129 additions and 15 deletions
21
src-tauri/migrations/024_background_images.sql
Normal file
21
src-tauri/migrations/024_background_images.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
-- Migration 024: Table-based background images
|
||||||
|
-- Supports per-branch and per-checkpoint backgrounds
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS background_images (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
story_id TEXT NOT NULL,
|
||||||
|
branch_id TEXT, -- NULL for main branch context
|
||||||
|
checkpoint_id TEXT, -- Linked to a specific checkpoint
|
||||||
|
image_data TEXT NOT NULL, -- The image data (URL or base64)
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (story_id) REFERENCES stories(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (checkpoint_id) REFERENCES checkpoints(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Index for fast lookup by story/branch
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_backgrounds_branch ON background_images(story_id, branch_id);
|
||||||
|
|
||||||
|
-- Index for fast lookup by story/checkpoint
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_backgrounds_checkpoint ON background_images(story_id, checkpoint_id);
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- Add current_background_image column to stories table
|
|
||||||
ALTER TABLE stories ADD COLUMN current_background_image TEXT;
|
|
||||||
|
|
@ -151,7 +151,7 @@ pub fn run() {
|
||||||
Migration {
|
Migration {
|
||||||
version: 24,
|
version: 24,
|
||||||
description: "story_bg_image",
|
description: "story_bg_image",
|
||||||
sql: include_str!("../migrations/024_story_bg_image.sql"),
|
sql: include_str!("../migrations/024_background_images.sql"),
|
||||||
kind: MigrationKind::Up,
|
kind: MigrationKind::Up,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -164,9 +164,8 @@ class DatabaseService {
|
||||||
retry_state,
|
retry_state,
|
||||||
style_review_state,
|
style_review_state,
|
||||||
time_tracker
|
time_tracker
|
||||||
current_background_image
|
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[
|
[
|
||||||
story.id,
|
story.id,
|
||||||
story.title,
|
story.title,
|
||||||
|
|
@ -181,7 +180,6 @@ class DatabaseService {
|
||||||
story.retryState ? JSON.stringify(story.retryState) : null,
|
story.retryState ? JSON.stringify(story.retryState) : null,
|
||||||
story.styleReviewState ? JSON.stringify(story.styleReviewState) : null,
|
story.styleReviewState ? JSON.stringify(story.styleReviewState) : null,
|
||||||
story.timeTracker ? JSON.stringify(story.timeTracker) : null,
|
story.timeTracker ? JSON.stringify(story.timeTracker) : null,
|
||||||
story.currentBgImage,
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
return { ...story, createdAt: now, updatedAt: now }
|
return { ...story, createdAt: now, updatedAt: now }
|
||||||
|
|
@ -1670,14 +1668,81 @@ class DatabaseService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the current background image for a story.
|
* Get the background image for a specific branch.
|
||||||
*/
|
*/
|
||||||
async saveCurrentBackgroundImage(storyId: string, imageData: string | null): Promise<void> {
|
async getBackgroundForBranch(storyId: string, branchId: string | null): Promise<string | null> {
|
||||||
const db = await this.getDb()
|
const db = await this.getDb()
|
||||||
await db.execute('UPDATE stories SET current_background_image = ? WHERE id = ?', [
|
const results = await db.select<{ image_data: string }[]>(
|
||||||
imageData,
|
'SELECT image_data FROM background_images WHERE story_id = ? AND branch_id IS ? AND checkpoint_id IS NULL ORDER BY created_at DESC LIMIT 1',
|
||||||
storyId,
|
[storyId, branchId],
|
||||||
])
|
)
|
||||||
|
return results.length > 0 ? results[0].image_data : null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the background image for a specific checkpoint.
|
||||||
|
*/
|
||||||
|
async getBackgroundForCheckpoint(storyId: string, checkpointId: string): Promise<string | null> {
|
||||||
|
const db = await this.getDb()
|
||||||
|
const results = await db.select<{ image_data: string }[]>(
|
||||||
|
'SELECT image_data FROM background_images WHERE story_id = ? AND checkpoint_id = ? LIMIT 1',
|
||||||
|
[storyId, checkpointId],
|
||||||
|
)
|
||||||
|
return results.length > 0 ? results[0].image_data : null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a background image for a story/branch/checkpoint.
|
||||||
|
*/
|
||||||
|
async saveBackground(
|
||||||
|
storyId: string,
|
||||||
|
branchId: string | null,
|
||||||
|
checkpointId: string | null,
|
||||||
|
imageData: string | null,
|
||||||
|
): Promise<void> {
|
||||||
|
const db = await this.getDb()
|
||||||
|
|
||||||
|
if (!imageData) {
|
||||||
|
// If clearing, delete entries for this specific context
|
||||||
|
if (checkpointId) {
|
||||||
|
await db.execute('DELETE FROM background_images WHERE story_id = ? AND checkpoint_id = ?', [
|
||||||
|
storyId,
|
||||||
|
checkpointId,
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
await db.execute(
|
||||||
|
'DELETE FROM background_images WHERE story_id = ? AND branch_id IS ? AND checkpoint_id IS NULL',
|
||||||
|
[storyId, branchId],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert or update
|
||||||
|
const id = crypto.randomUUID()
|
||||||
|
const now = Date.now()
|
||||||
|
|
||||||
|
if (checkpointId) {
|
||||||
|
// Checkpoints always get a new entry or replace existing for that checkpoint
|
||||||
|
await db.execute(
|
||||||
|
'INSERT OR REPLACE INTO background_images (id, story_id, branch_id, checkpoint_id, image_data, created_at) VALUES (?, ?, ?, ?, ?, ?)',
|
||||||
|
[id, storyId, branchId, checkpointId, imageData, now],
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// For branches (including main), we update the single "current" record for that branch
|
||||||
|
const existing = await this.getBackgroundForBranch(storyId, branchId)
|
||||||
|
if (existing) {
|
||||||
|
await db.execute(
|
||||||
|
'UPDATE background_images SET image_data = ?, created_at = ? WHERE story_id = ? AND branch_id IS ? AND checkpoint_id IS NULL',
|
||||||
|
[imageData, now, storyId, branchId],
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
await db.execute(
|
||||||
|
'INSERT INTO background_images (id, story_id, branch_id, checkpoint_id, image_data, created_at) VALUES (?, ?, ?, ?, ?, ?)',
|
||||||
|
[id, storyId, branchId, null, imageData, now],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1752,7 +1817,7 @@ class DatabaseService {
|
||||||
styleReviewState: row.style_review_state ? JSON.parse(row.style_review_state) : null,
|
styleReviewState: row.style_review_state ? JSON.parse(row.style_review_state) : null,
|
||||||
timeTracker: row.time_tracker ? JSON.parse(row.time_tracker) : null,
|
timeTracker: row.time_tracker ? JSON.parse(row.time_tracker) : null,
|
||||||
currentBranchId: row.current_branch_id || null,
|
currentBranchId: row.current_branch_id || null,
|
||||||
currentBgImage: row.current_background_image || null,
|
currentBgImage: null, // Loaded separately now
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -431,7 +431,7 @@ class StoryStore {
|
||||||
await database.cleanupOrphanedEmbeddedImages()
|
await database.cleanupOrphanedEmbeddedImages()
|
||||||
|
|
||||||
this.currentStory = story
|
this.currentStory = story
|
||||||
this.currentBgImage = story.currentBgImage
|
this.currentBgImage = await database.getBackgroundForBranch(storyId, story.currentBranchId)
|
||||||
|
|
||||||
// Load branch-independent data first
|
// Load branch-independent data first
|
||||||
const [characters, locations, items, storyBeats, checkpoints, lorebookEntries, branches] =
|
const [characters, locations, items, storyBeats, checkpoints, lorebookEntries, branches] =
|
||||||
|
|
@ -745,7 +745,12 @@ class StoryStore {
|
||||||
this.currentStory.currentBgImage = imageData
|
this.currentStory.currentBgImage = imageData
|
||||||
}
|
}
|
||||||
|
|
||||||
await database.saveCurrentBackgroundImage(this.currentStory.id, imageData)
|
await database.saveBackground(
|
||||||
|
this.currentStory.id,
|
||||||
|
this.currentStory.currentBranchId,
|
||||||
|
null,
|
||||||
|
imageData,
|
||||||
|
)
|
||||||
log('Background image updated and persisted')
|
log('Background image updated and persisted')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1915,6 +1920,18 @@ class StoryStore {
|
||||||
|
|
||||||
await database.createCheckpoint(checkpoint)
|
await database.createCheckpoint(checkpoint)
|
||||||
this.checkpoints = [checkpoint, ...this.checkpoints]
|
this.checkpoints = [checkpoint, ...this.checkpoints]
|
||||||
|
|
||||||
|
// Save current background for this checkpoint
|
||||||
|
if (this.currentBgImage) {
|
||||||
|
log('Saving background for checkpoint:', name)
|
||||||
|
await database.saveBackground(
|
||||||
|
this.currentStory.id,
|
||||||
|
this.currentStory.currentBranchId,
|
||||||
|
checkpoint.id,
|
||||||
|
this.currentBgImage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
log('Checkpoint created:', name)
|
log('Checkpoint created:', name)
|
||||||
|
|
||||||
// Emit event
|
// Emit event
|
||||||
|
|
@ -2020,6 +2037,16 @@ class StoryStore {
|
||||||
await database.addBranch(branch)
|
await database.addBranch(branch)
|
||||||
this.branches = [...this.branches, branch]
|
this.branches = [...this.branches, branch]
|
||||||
|
|
||||||
|
// Inherit background from checkpoint
|
||||||
|
const checkpointBg = await database.getBackgroundForCheckpoint(
|
||||||
|
this.currentStory.id,
|
||||||
|
checkpointId,
|
||||||
|
)
|
||||||
|
if (checkpointBg) {
|
||||||
|
log('Inheriting background from checkpoint for new branch:', branch.name)
|
||||||
|
await database.saveBackground(this.currentStory.id, branch.id, null, checkpointBg)
|
||||||
|
}
|
||||||
|
|
||||||
// Copy world state from checkpoint into database with the new branch_id
|
// Copy world state from checkpoint into database with the new branch_id
|
||||||
// This ensures the branch has its own copy of the world state at the fork point
|
// This ensures the branch has its own copy of the world state at the fork point
|
||||||
log('Copying world state from checkpoint to branch:', branch.name)
|
log('Copying world state from checkpoint to branch:', branch.name)
|
||||||
|
|
@ -2170,6 +2197,9 @@ class StoryStore {
|
||||||
this.invalidateWordCountCache()
|
this.invalidateWordCountCache()
|
||||||
this.invalidateChapterCache()
|
this.invalidateChapterCache()
|
||||||
|
|
||||||
|
// Reload background from database for the branch
|
||||||
|
this.currentBgImage = await database.getBackgroundForBranch(this.currentStory.id, branchId)
|
||||||
|
|
||||||
log('Switched to branch:', branchId ?? 'main')
|
log('Switched to branch:', branchId ?? 'main')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue