mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 06:33:55 +01:00
[web] Add stop button for Python code execution via worker termination
GitOrigin-RevId: f44b429a1d85e66ce89719817efd7acbfc7c4540
This commit is contained in:
committed by
Copybot
parent
50db96878d
commit
19c8434653
+42
-11
@@ -19,6 +19,7 @@ export type LifecycleCallback = (
|
||||
|
||||
export class PyodideWorkerClient {
|
||||
private worker: Worker
|
||||
private baseAssetPath: string
|
||||
private listening = false
|
||||
private loaded = false
|
||||
private destroyed = false
|
||||
@@ -28,19 +29,12 @@ export class PyodideWorkerClient {
|
||||
private lifecycleCallback: LifecycleCallback | null = null
|
||||
|
||||
constructor(options: { baseAssetPath: string }) {
|
||||
const { baseAssetPath } = options
|
||||
|
||||
this.worker = new Worker(
|
||||
/* webpackChunkName: "pyodide-worker" */
|
||||
new URL('./pyodide.worker.ts', import.meta.url),
|
||||
{ type: 'module' }
|
||||
)
|
||||
|
||||
this.worker.addEventListener('message', this.receive.bind(this))
|
||||
this.baseAssetPath = options.baseAssetPath
|
||||
this.worker = this.createWorker()
|
||||
|
||||
this.queueMessage({
|
||||
type: 'init',
|
||||
baseAssetPath,
|
||||
baseAssetPath: this.baseAssetPath,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,6 +74,28 @@ export class PyodideWorkerClient {
|
||||
})
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (this.destroyed) {
|
||||
return
|
||||
}
|
||||
|
||||
// Terminate the current worker immediately
|
||||
this.worker.terminate()
|
||||
this.pendingMessages.length = 0
|
||||
|
||||
// Reset state for the new worker
|
||||
this.listening = false
|
||||
this.loaded = false
|
||||
this.loadingError = null
|
||||
|
||||
// Create a fresh worker and re-initialize Pyodide
|
||||
this.worker = this.createWorker()
|
||||
this.queueMessage({
|
||||
type: 'init',
|
||||
baseAssetPath: this.baseAssetPath,
|
||||
})
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.destroyed) {
|
||||
return
|
||||
@@ -95,6 +111,16 @@ export class PyodideWorkerClient {
|
||||
this.worker.terminate()
|
||||
}
|
||||
|
||||
private createWorker(): Worker {
|
||||
const worker = new Worker(
|
||||
/* webpackChunkName: "pyodide-worker" */
|
||||
new URL('./pyodide.worker.ts', import.meta.url),
|
||||
{ type: 'module' }
|
||||
)
|
||||
worker.addEventListener('message', this.receive)
|
||||
return worker
|
||||
}
|
||||
|
||||
private queueMessage(message: PyodideWorkerRequest) {
|
||||
if (this.listening) {
|
||||
this.worker.postMessage(message)
|
||||
@@ -103,7 +129,12 @@ export class PyodideWorkerClient {
|
||||
}
|
||||
}
|
||||
|
||||
private receive(event: MessageEvent<PyodideWorkerResponse>) {
|
||||
private receive = (event: MessageEvent<PyodideWorkerResponse>) => {
|
||||
// Discard messages from a previously terminated worker
|
||||
if (event.target !== this.worker) {
|
||||
return
|
||||
}
|
||||
|
||||
const response = event.data
|
||||
|
||||
switch (response.type) {
|
||||
|
||||
+31
-11
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import path from 'path-browserify'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import OLButtonToolbar from '@/shared/components/ol/ol-button-toolbar'
|
||||
@@ -10,6 +11,7 @@ import getMeta from '@/utils/meta'
|
||||
import { PyodideWorkerClient } from './pyodide-worker-client'
|
||||
|
||||
export default function PythonOutputPane() {
|
||||
const { t } = useTranslation()
|
||||
const { currentDocument, currentDocumentId } = useEditorOpenDocContext()
|
||||
const { pathInFolder } = useFileTreePathContext()
|
||||
const clientRef = useRef<PyodideWorkerClient | null>(null)
|
||||
@@ -17,8 +19,8 @@ export default function PythonOutputPane() {
|
||||
const [isReady, setIsReady] = useState(false)
|
||||
const [output, setOutput] = useState<string[]>([])
|
||||
const [isRunning, setIsRunning] = useState(false)
|
||||
const [isLoadingPyodide, setIsLoadingPyodide] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isLoadingPyodide, setIsLoadingPyodide] = useState(true)
|
||||
|
||||
const appendOutput = useCallback((line: string) => {
|
||||
setOutput(previousOutput => [...previousOutput, line])
|
||||
@@ -46,8 +48,8 @@ export default function PythonOutputPane() {
|
||||
|
||||
case 'loading-failed':
|
||||
debugConsole.error('Failed to load Python runtime', event.error)
|
||||
setError(formatError(event.error))
|
||||
setIsLoadingPyodide(false)
|
||||
setError(formatError(event.error))
|
||||
setIsRunning(false)
|
||||
return
|
||||
|
||||
@@ -136,21 +138,39 @@ export default function PythonOutputPane() {
|
||||
}
|
||||
}, [buildCurrentDocumentSyncFile, isReady])
|
||||
|
||||
const handleStop = useCallback(() => {
|
||||
const client = clientRef.current
|
||||
if (!client) {
|
||||
return
|
||||
}
|
||||
|
||||
currentRequestIdRef.current = null
|
||||
client.stop()
|
||||
setIsRunning(false)
|
||||
setIsReady(false)
|
||||
setIsLoadingPyodide(true)
|
||||
appendOutput(t('execution_stopped'))
|
||||
}, [appendOutput, t])
|
||||
|
||||
return (
|
||||
<div className="ide-redesign-python-output-pane">
|
||||
<OLButtonToolbar className="toolbar toolbar-pdf toolbar-pdf-hybrid">
|
||||
<div className="toolbar-pdf-left">
|
||||
<div className="compile-button-group">
|
||||
<OLButton
|
||||
onClick={handleRun}
|
||||
variant="primary"
|
||||
onClick={isRunning ? handleStop : handleRun}
|
||||
variant={isRunning ? 'danger' : 'primary'}
|
||||
className="compile-button align-items-center py-0 px-3"
|
||||
disabled={!isReady || isLoadingPyodide || isRunning}
|
||||
aria-label="Run Python Code"
|
||||
style={{ borderRadius: '12px' }}
|
||||
disabled={!isRunning && !isReady}
|
||||
aria-label={
|
||||
isRunning ? t('stop_python_execution') : t('run_python_code')
|
||||
}
|
||||
>
|
||||
{isRunning ? 'Running...' : 'Run'}
|
||||
<MaterialIcon type="play_arrow" className="ml-2" />
|
||||
{isRunning ? t('stop') : t('run')}
|
||||
<MaterialIcon
|
||||
type={isRunning ? 'stop' : 'play_arrow'}
|
||||
className="ml-2"
|
||||
/>
|
||||
</OLButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,12 +179,12 @@ export default function PythonOutputPane() {
|
||||
<div className="ide-redesign-python-output-pane-body">
|
||||
{isLoadingPyodide && (
|
||||
<div className="ide-redesign-python-output-pane-placeholder">
|
||||
Loading Python runtime...
|
||||
{t('loading_python_runtime')}
|
||||
</div>
|
||||
)}
|
||||
{!isLoadingPyodide && !error && output.length === 0 && (
|
||||
<div className="ide-redesign-python-output-pane-placeholder">
|
||||
Run the current script to see output.
|
||||
{t('run_current_script_to_see_output')}
|
||||
</div>
|
||||
)}
|
||||
{error && (
|
||||
|
||||
Reference in New Issue
Block a user