Spaces:
Running
Running
| import { chromium } from '@playwright/test'; | |
| import { dirname, resolve } from 'node:path'; | |
| import { fileURLToPath } from 'node:url'; | |
| import { createServer as createViteServer } from 'vite'; | |
| const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); | |
| const memoryKey = '__bonsai_persistence_probe__'; | |
| const conversationId = '__bonsai_conversation_probe__'; | |
| const artifactId = '__bonsai_artifact_persistence_probe__'; | |
| let browser; | |
| let page; | |
| let vite; | |
| try { | |
| vite = await createViteServer({ | |
| root, | |
| logLevel: 'error', | |
| server: { host: '127.0.0.1', port: 0 }, | |
| plugins: [{ | |
| name: 'persistence-probe-page', | |
| configureServer(server) { | |
| server.middlewares.use('/persistence-probe.html', (_request, response) => { | |
| response.setHeader('Content-Type', 'text/html; charset=utf-8'); | |
| response.end('<!doctype html><title>Persistence probe</title>'); | |
| }); | |
| }, | |
| }], | |
| }); | |
| await vite.listen(); | |
| const address = vite.httpServer?.address(); | |
| if (!address || typeof address === 'string') throw new Error('Vite persistence probe did not expose a TCP port'); | |
| browser = await chromium.launch({ headless: true }); | |
| page = await browser.newPage(); | |
| await page.goto(`http://127.0.0.1:${address.port}/persistence-probe.html`); | |
| await page.evaluate(async ({ memoryKey, conversationId, artifactId }) => { | |
| const idb = await import('/src/lib/idb.ts'); | |
| const { | |
| evaluateHtmlArtifact, | |
| prepareArtifactDeployment, | |
| } = await import('/src/lib/artifact-runtime.ts'); | |
| await idb.deleteMemory(memoryKey); | |
| await idb.putMemory(memoryKey, 'survives reload'); | |
| const artifact = prepareArtifactDeployment([ | |
| { | |
| path: 'index.html', | |
| content: '<main id="app">Loading</main><script type="module" src="./src/main.js"></script>', | |
| }, | |
| { | |
| path: 'src/main.js', | |
| content: 'const data = await fetch("../data/state.json").then((response) => response.json()); document.querySelector("#app").textContent = data.message;', | |
| }, | |
| { path: 'data/state.json', content: '{"message":"workspace survives reload"}' }, | |
| ], 'Persistent workspace', 'index.html', artifactId); | |
| artifact.evaluation = await evaluateHtmlArtifact(artifact); | |
| if (artifact.evaluation.status !== 'passed') { | |
| throw new Error(`Artifact setup failed: ${JSON.stringify(artifact.evaluation)}`); | |
| } | |
| const staleRecoverySnapshot = { | |
| ...artifact, | |
| files: artifact.files.map((file) => file.path === 'data/state.json' | |
| ? { | |
| ...file, | |
| content: '{"message":"stale conversation snapshot"}', | |
| bytes: new TextEncoder().encode('{"message":"stale conversation snapshot"}').byteLength, | |
| } | |
| : file), | |
| }; | |
| await idb.saveConversation({ | |
| id: conversationId, | |
| title: 'Persistence probe', | |
| modelId: 'bonsai-1.7b', | |
| toolsEnabled: true, | |
| updatedAt: 0, | |
| messages: [ | |
| { role: 'user', content: 'keep locally' }, | |
| { role: 'assistant', content: '', artifacts: [staleRecoverySnapshot] }, | |
| ], | |
| }); | |
| }, { memoryKey, conversationId, artifactId }); | |
| await page.reload({ waitUntil: 'domcontentloaded' }); | |
| const persisted = await page.evaluate(async ({ memoryKey, conversationId }) => { | |
| const idb = await import('/src/lib/idb.ts'); | |
| const { | |
| evaluateHtmlArtifact, | |
| hydrateArtifactDocument, | |
| } = await import('/src/lib/artifact-runtime.ts'); | |
| const memory = await idb.getMemory(memoryKey); | |
| const conversation = await idb.loadConversation(conversationId); | |
| const snapshot = conversation?.messages?.[1]?.artifacts?.[0]; | |
| if (!snapshot) throw new Error('Saved conversation lost its artifact snapshot'); | |
| const artifact = await hydrateArtifactDocument(snapshot); | |
| const evaluation = await evaluateHtmlArtifact(artifact); | |
| return { | |
| memory, | |
| conversation, | |
| artifact: { | |
| id: artifact.id, | |
| storage: artifact.workspaceStorage, | |
| files: artifact.files?.map(({ path, content }) => ({ path, content })), | |
| sandboxContainsCurrent: artifact.sandboxedSource.includes('workspace survives reload'), | |
| sandboxContainsStale: artifact.sandboxedSource.includes('stale conversation snapshot'), | |
| evaluation, | |
| }, | |
| }; | |
| }, { memoryKey, conversationId }); | |
| if (persisted.memory?.value !== 'survives reload') { | |
| throw new Error(`Memory did not survive reload: ${JSON.stringify(persisted.memory)}`); | |
| } | |
| if (persisted.conversation?.messages?.[0]?.content !== 'keep locally') { | |
| throw new Error(`Conversation did not survive reload: ${JSON.stringify(persisted.conversation)}`); | |
| } | |
| if (persisted.conversation?.toolsEnabled !== true) { | |
| throw new Error(`Per-chat tools state did not survive reload: ${JSON.stringify(persisted.conversation)}`); | |
| } | |
| if (persisted.artifact?.id !== artifactId | |
| || persisted.artifact.storage !== 'opfs' | |
| || persisted.artifact.files?.length !== 3 | |
| || !persisted.artifact.files.some((file) => file.path === 'data/state.json' && file.content.includes('survives reload')) | |
| || persisted.artifact.sandboxContainsCurrent !== true | |
| || persisted.artifact.sandboxContainsStale !== false | |
| || persisted.artifact.evaluation?.status !== 'passed' | |
| || persisted.artifact.evaluation?.dom?.textPreview !== 'workspace survives reload') { | |
| throw new Error(`Artifact workspace did not survive reload: ${JSON.stringify(persisted.artifact)}`); | |
| } | |
| console.log('Persistence passed: memory, per-chat tools state, conversation, and its three-file OPFS artifact survived reload and re-evaluated'); | |
| } finally { | |
| if (page) { | |
| await page.evaluate(async ({ memoryKey, conversationId, artifactId }) => { | |
| const idb = await import('/src/lib/idb.ts'); | |
| const { deleteArtifactDocumentStorage } = await import('/src/lib/artifact-runtime.ts'); | |
| await idb.deleteMemory(memoryKey).catch(() => undefined); | |
| await deleteArtifactDocumentStorage(artifactId).catch(() => undefined); | |
| const request = indexedDB.open('bonsai-webgpu-chat', 1); | |
| await new Promise((resolve) => { | |
| request.onsuccess = () => { | |
| const database = request.result; | |
| const transaction = database.transaction('conversations', 'readwrite'); | |
| transaction.objectStore('conversations').delete(conversationId); | |
| transaction.oncomplete = () => { database.close(); resolve(); }; | |
| transaction.onerror = () => { database.close(); resolve(); }; | |
| }; | |
| request.onerror = () => resolve(); | |
| }); | |
| }, { memoryKey, conversationId, artifactId }).catch(() => undefined); | |
| } | |
| await browser?.close(); | |
| await vite?.close(); | |
| } | |