feat: add version history and AI diff highlighting in editor

This commit is contained in:
2026-06-16 15:23:16 +08:00
parent c1f7be1507
commit a21652b1ca
7 changed files with 606 additions and 10 deletions

View File

@@ -1,6 +1,22 @@
import { defineStore } from 'pinia'
import { shallowRef, ref, computed, triggerRef } from 'vue'
import type { GameData, SceneNode, Choice } from '@engine/types'
import { putVersion, getVersions } from '../db/editorDB'
export interface AIDiff {
added: string[]
modified: string[]
deleted: string[]
globalFields: string[]
}
interface EditorVersion {
id?: number
sourcePath: string
timestamp: number
label: string
gameData: GameData
}
export const useEditorStore = defineStore('editor', () => {
const gameData = shallowRef<GameData>({ scenes: {}, startScene: '', variables: {} })
@@ -11,6 +27,8 @@ export const useEditorStore = defineStore('editor', () => {
const deepseekKey = ref(localStorage.getItem('deepseek_key') || '')
const showAIPanel = ref(false)
const aiSessionId = ref('')
const aiChanges = ref<AIDiff | null>(null)
const versions = ref<EditorVersion[]>([])
const selectedScene = computed(() => {
if (!selectedNodeId.value) return null
@@ -151,11 +169,64 @@ export const useEditorStore = defineStore('editor', () => {
} catch { /* dev server not running */ }
}
async function reloadFromDisk() {
async function saveVersion(label: string) {
try {
await putVersion({
sourcePath: sourcePath.value,
timestamp: Date.now(),
label,
gameData: JSON.parse(JSON.stringify(gameData.value)),
})
await loadVersions()
} catch {}
}
async function restoreVersion(idx: number) {
const v = versions.value[idx]
if (!v) return
gameData.value = v.gameData
startSceneId.value = v.gameData.startScene || ''
selectedNodeId.value = null
aiChanges.value = null
dirty.value = false
autoSave()
}
async function loadVersions() {
try {
versions.value = await getVersions(sourcePath.value)
} catch {
versions.value = []
}
}
function clearAIMarkers() {
aiChanges.value = null
}
async function reloadFromDisk(oldGameData?: GameData) {
try {
const resp = await fetch(sourcePath.value)
const data = await resp.json()
gameData.value = data
const newData = await resp.json()
if (oldGameData) {
const { scenes: newScenes, ...newGlobal } = newData
const { scenes: oldScenes, ...oldGlobal } = oldGameData
const diff: AIDiff = { added: [], modified: [], deleted: [], globalFields: [] }
for (const id of Object.keys(newScenes)) {
if (!oldScenes[id]) diff.added.push(id)
else if (JSON.stringify(oldScenes[id]) !== JSON.stringify(newScenes[id])) diff.modified.push(id)
}
for (const id of Object.keys(oldScenes)) {
if (!newScenes[id]) diff.deleted.push(id)
}
for (const key of Object.keys({ ...(oldGlobal as any), ...(newGlobal as any) })) {
if (JSON.stringify((oldGlobal as any)[key]) !== JSON.stringify((newGlobal as any)[key])) {
diff.globalFields.push(key)
}
}
aiChanges.value = diff
}
gameData.value = newData
selectedNodeId.value = null
clearAISession()
} catch { /* failed to reload */ }
@@ -163,9 +234,10 @@ export const useEditorStore = defineStore('editor', () => {
return {
gameData, selectedNodeId, selectedScene, startSceneId, dirty, sourcePath,
deepseekKey, showAIPanel, aiSessionId,
deepseekKey, showAIPanel, aiSessionId, aiChanges, versions,
markDirty, loadJSON, exportJSON, addScene, deleteScene,
updateScene, addChoice, updateChoice, deleteChoice, generateId,
setSourcePath, setDeepseekKey, setAISessionId, clearAISession, autoSave, reloadFromDisk,
saveVersion, restoreVersion, loadVersions, clearAIMarkers,
}
})