diff --git a/src/lib/components/chat/Messages/CodeBlock.svelte b/src/lib/components/chat/Messages/CodeBlock.svelte index ef7c823dd..1be70cdeb 100644 --- a/src/lib/components/chat/Messages/CodeBlock.svelte +++ b/src/lib/components/chat/Messages/CodeBlock.svelte @@ -2,7 +2,7 @@ import hljs from 'highlight.js'; import { toast } from 'svelte-sonner'; import { getContext, onMount, tick, onDestroy } from 'svelte'; - import { config } from '$lib/stores'; + import { config, pyodideWorker as pyodideWorkerStore } from '$lib/stores'; import PyodideWorker from '$lib/workers/pyodide.worker?worker'; import { executeCode } from '$lib/apis/utils'; @@ -47,7 +47,7 @@ export let editorClassName = ''; export let stickyButtonsClassName = 'top-0'; - let pyodideWorker = null; + let localPyodideWorker = null; let _code = ''; $: if (code) { @@ -237,27 +237,42 @@ console.log(packages); - pyodideWorker = new PyodideWorker(); + // Reuse the shared Pyodide worker when code interpreter is active, + // so files written here are immediately visible in PyodideFileNav. + // Otherwise fall back to a throwaway worker. + const sharedWorker = $pyodideWorkerStore; + const isShared = !!sharedWorker; + const worker = sharedWorker ?? new PyodideWorker(); - pyodideWorker.postMessage({ + if (!isShared) { + localPyodideWorker = worker; + } + + worker.postMessage({ id: id, code: code, packages: packages }); - setTimeout(() => { + const timeoutId = setTimeout(() => { if (executing) { executing = false; stderr = 'Execution Time Limit Exceeded'; - pyodideWorker.terminate(); + if (!isShared) { + worker.terminate(); + localPyodideWorker = null; + } } }, 60000); - pyodideWorker.onmessage = (event) => { - console.log('pyodideWorker.onmessage', event); - const { id, ...data } = event.data; + const handler = (event) => { + // Ignore messages from other requests on the shared worker + if (event.data?.id !== id) return; - console.log(id, data); + console.log('pyodideWorker.onmessage', event); + const { id: _id, ...data } = event.data; + + console.log(_id, data); if (data['stdout']) { stdout = data['stdout']; @@ -320,11 +335,20 @@ data['stderr'] && (stderr = data['stderr']); data['result'] && (result = data['result']); + clearTimeout(timeoutId); + worker.removeEventListener('message', handler); executing = false; + + // Signal PyodideFileNav to auto-refresh after execution + window.dispatchEvent(new Event('pyodide:files')); }; - pyodideWorker.onerror = (event) => { + worker.addEventListener('message', handler); + + worker.onerror = (event) => { console.log('pyodideWorker.onerror', event); + clearTimeout(timeoutId); + worker.removeEventListener('message', handler); executing = false; }; }; @@ -412,8 +436,9 @@ }); onDestroy(() => { - if (pyodideWorker) { - pyodideWorker.terminate(); + if (localPyodideWorker) { + localPyodideWorker.terminate(); + localPyodideWorker = null; } }); diff --git a/src/lib/components/chat/PyodideFileNav.svelte b/src/lib/components/chat/PyodideFileNav.svelte index 73a10325d..ca2c5eb46 100644 --- a/src/lib/components/chat/PyodideFileNav.svelte +++ b/src/lib/components/chat/PyodideFileNav.svelte @@ -3,7 +3,7 @@ @@ -335,14 +347,18 @@ {selectedFile} {loading} onNavigate={(path) => loadDir(path)} - onRefresh={() => { - if (selectedFile) { - const name = selectedFile.split('/').pop() ?? ''; - openEntry({ name, type: 'file', size: 0 }); - } else { - loadDir(currentPath); - } - }} + onRefresh={async () => { + // Sync from IndexedDB first to pick up files written by code execution + try { + await sendWorkerMessage({ type: 'fs:sync' }); + } catch {} + if (selectedFile) { + const name = selectedFile.split('/').pop() ?? ''; + openEntry({ name, type: 'file', size: 0 }); + } else { + loadDir(currentPath); + } + }} onNewFolder={startNewFolder} onNewFile={startNewFile} onUploadFiles={uploadFiles} diff --git a/src/lib/workers/pyodide.worker.ts b/src/lib/workers/pyodide.worker.ts index 7ef391673..cdbd9006e 100644 --- a/src/lib/workers/pyodide.worker.ts +++ b/src/lib/workers/pyodide.worker.ts @@ -305,6 +305,17 @@ self.onmessage = async (event) => { break; } + case 'fs:sync': { + // Re-read from IndexedDB into memory to pick up externally written files + (self.pyodide.FS as any).syncfs(true, (err: Error | null) => { + if (err) { + console.error('Error syncing from IndexedDB:', err); + } + self.postMessage({ id, type: 'fs:sync', success: !err }); + }); + break; + } + default: console.warn('Unknown message type:', type); }