mirror of
https://github.com/johndoe6345789/snippet-pastebin.git
synced 2026-04-24 13:34:55 +00:00
Generated by Spark: Sometimes my dad writes Python programs with a input: prompt. To cope with this, we might need to simulate a terminal / command prompt.
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import { Play, CircleNotch } from '@phosphor-icons/react'
|
||||
import { Play, CircleNotch, Terminal } from '@phosphor-icons/react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { runPythonCode, getPyodide, isPyodideReady } from '@/lib/pyodide-runner'
|
||||
import { PythonTerminal } from '@/components/PythonTerminal'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
interface PythonOutputProps {
|
||||
@@ -15,6 +17,7 @@ export function PythonOutput({ code }: PythonOutputProps) {
|
||||
const [error, setError] = useState<string>('')
|
||||
const [isRunning, setIsRunning] = useState(false)
|
||||
const [isInitializing, setIsInitializing] = useState(!isPyodideReady())
|
||||
const [hasInput, setHasInput] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPyodideReady()) {
|
||||
@@ -32,6 +35,11 @@ export function PythonOutput({ code }: PythonOutputProps) {
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const codeToCheck = code.toLowerCase()
|
||||
setHasInput(codeToCheck.includes('input('))
|
||||
}, [code])
|
||||
|
||||
const handleRun = async () => {
|
||||
if (isInitializing) {
|
||||
toast.info('Python environment is still loading...')
|
||||
@@ -55,6 +63,10 @@ export function PythonOutput({ code }: PythonOutputProps) {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasInput) {
|
||||
return <PythonTerminal code={code} />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-card">
|
||||
<div className="flex items-center justify-between p-4 border-b border-border">
|
||||
|
||||
199
src/components/PythonTerminal.tsx
Normal file
199
src/components/PythonTerminal.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import { Play, CircleNotch, Terminal as TerminalIcon } from '@phosphor-icons/react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { runPythonCodeInteractive, getPyodide, isPyodideReady } from '@/lib/pyodide-runner'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
interface PythonTerminalProps {
|
||||
code: string
|
||||
}
|
||||
|
||||
interface TerminalLine {
|
||||
type: 'output' | 'error' | 'input-prompt' | 'input-value'
|
||||
content: string
|
||||
id: string
|
||||
}
|
||||
|
||||
export function PythonTerminal({ code }: PythonTerminalProps) {
|
||||
const [lines, setLines] = useState<TerminalLine[]>([])
|
||||
const [isRunning, setIsRunning] = useState(false)
|
||||
const [isInitializing, setIsInitializing] = useState(!isPyodideReady())
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
const [waitingForInput, setWaitingForInput] = useState(false)
|
||||
const [inputPrompt, setInputPrompt] = useState('')
|
||||
const inputResolveRef = useRef<((value: string) => void) | null>(null)
|
||||
const terminalEndRef = useRef<HTMLDivElement>(null)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPyodideReady()) {
|
||||
setIsInitializing(true)
|
||||
getPyodide()
|
||||
.then(() => {
|
||||
setIsInitializing(false)
|
||||
toast.success('Python environment ready!')
|
||||
})
|
||||
.catch((err) => {
|
||||
setIsInitializing(false)
|
||||
toast.error('Failed to load Python environment')
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
terminalEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
||||
}, [lines])
|
||||
|
||||
useEffect(() => {
|
||||
if (waitingForInput && inputRef.current) {
|
||||
inputRef.current.focus()
|
||||
}
|
||||
}, [waitingForInput])
|
||||
|
||||
const addLine = (type: TerminalLine['type'], content: string) => {
|
||||
setLines((prev) => [
|
||||
...prev,
|
||||
{ type, content, id: `${Date.now()}-${Math.random()}` },
|
||||
])
|
||||
}
|
||||
|
||||
const handleInputPrompt = (prompt: string): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
setInputPrompt(prompt)
|
||||
addLine('input-prompt', prompt)
|
||||
setWaitingForInput(true)
|
||||
inputResolveRef.current = resolve
|
||||
})
|
||||
}
|
||||
|
||||
const handleInputSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!waitingForInput || !inputResolveRef.current) return
|
||||
|
||||
const value = inputValue
|
||||
addLine('input-value', value)
|
||||
setInputValue('')
|
||||
setWaitingForInput(false)
|
||||
|
||||
const resolve = inputResolveRef.current
|
||||
inputResolveRef.current = null
|
||||
resolve(value)
|
||||
}
|
||||
|
||||
const handleRun = async () => {
|
||||
if (isInitializing) {
|
||||
toast.info('Python environment is still loading...')
|
||||
return
|
||||
}
|
||||
|
||||
setIsRunning(true)
|
||||
setLines([])
|
||||
setWaitingForInput(false)
|
||||
setInputValue('')
|
||||
|
||||
try {
|
||||
await runPythonCodeInteractive(code, {
|
||||
onOutput: (text) => {
|
||||
addLine('output', text)
|
||||
},
|
||||
onError: (text) => {
|
||||
addLine('error', text)
|
||||
},
|
||||
onInputRequest: handleInputPrompt,
|
||||
})
|
||||
} catch (err) {
|
||||
addLine('error', err instanceof Error ? err.message : String(err))
|
||||
} finally {
|
||||
setIsRunning(false)
|
||||
setWaitingForInput(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-card">
|
||||
<div className="flex items-center justify-between p-4 border-b border-border bg-muted/30">
|
||||
<div className="flex items-center gap-2">
|
||||
<TerminalIcon size={18} weight="bold" className="text-primary" />
|
||||
<h3 className="text-sm font-semibold text-foreground">Python Terminal</h3>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleRun}
|
||||
disabled={isRunning || isInitializing || waitingForInput}
|
||||
size="sm"
|
||||
className="gap-2"
|
||||
>
|
||||
{isRunning || isInitializing ? (
|
||||
<>
|
||||
<CircleNotch className="animate-spin" size={16} />
|
||||
{isInitializing ? 'Loading...' : 'Running...'}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play size={16} weight="fill" />
|
||||
Run
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto p-4 font-mono text-sm bg-background/50">
|
||||
{lines.length === 0 && !isRunning && (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
||||
Click "Run" to execute the Python code
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-1">
|
||||
{lines.map((line) => (
|
||||
<motion.div
|
||||
key={line.id}
|
||||
initial={{ opacity: 0, y: 5 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
className="leading-relaxed"
|
||||
>
|
||||
{line.type === 'output' && (
|
||||
<div className="text-foreground whitespace-pre-wrap">{line.content}</div>
|
||||
)}
|
||||
{line.type === 'error' && (
|
||||
<div className="text-destructive whitespace-pre-wrap">{line.content}</div>
|
||||
)}
|
||||
{line.type === 'input-prompt' && (
|
||||
<div className="text-accent font-medium whitespace-pre-wrap">{line.content}</div>
|
||||
)}
|
||||
{line.type === 'input-value' && (
|
||||
<div className="text-primary whitespace-pre-wrap">{'> ' + line.content}</div>
|
||||
)}
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
{waitingForInput && (
|
||||
<motion.form
|
||||
onSubmit={handleInputSubmit}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="flex items-center gap-2 mt-2"
|
||||
>
|
||||
<span className="text-primary font-bold">{'>'}</span>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
className="flex-1 font-mono bg-background border-accent/50 focus:border-accent"
|
||||
placeholder="Enter input..."
|
||||
disabled={!waitingForInput}
|
||||
/>
|
||||
</motion.form>
|
||||
)}
|
||||
|
||||
<div ref={terminalEndRef} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -179,5 +179,59 @@
|
||||
"category": "basics",
|
||||
"hasPreview": true,
|
||||
"code": "# Python file operations (simulated for web environment)\n\n# Note: In a real environment, these would work with actual files\n# Here we demonstrate the patterns\n\nprint(\"File Operations Patterns:\\n\")\n\n# Writing to a file\nprint(\"1. Writing to a file:\")\nfile_content = \"\"\"# Example of writing to a file\ntry:\n with open('example.txt', 'w') as f:\n f.write('Hello, World!\\\\n')\n f.write('This is a second line.\\\\n')\n print(' ✓ File written successfully')\nexcept IOError as e:\n print(f' ✗ Error writing file: {e}')\n\"\"\"\nprint(file_content)\n\n# Reading from a file\nprint(\"\\n2. Reading from a file:\")\nread_example = \"\"\"try:\n with open('example.txt', 'r') as f:\n content = f.read()\n print(content)\nexcept FileNotFoundError:\n print(' ✗ File not found')\nexcept IOError as e:\n print(f' ✗ Error reading file: {e}')\n\"\"\"\nprint(read_example)\n\n# Reading lines\nprint(\"\\n3. Reading line by line:\")\nlines_example = \"\"\"with open('example.txt', 'r') as f:\n for line_num, line in enumerate(f, 1):\n print(f' Line {line_num}: {line.strip()}')\n\"\"\"\nprint(lines_example)\n\n# Demonstrate string operations as alternative\nprint(\"\\n4. Working with text data:\")\ntext_data = \"Hello, World!\\nThis is a second line.\\nAnd a third!\"\nlines = text_data.split('\\n')\nprint(f\" Total lines: {len(lines)}\")\nfor i, line in enumerate(lines, 1):\n print(f\" Line {i}: {line}\")"
|
||||
},
|
||||
{
|
||||
"id": "python-interactive-hello",
|
||||
"title": "Interactive: Hello User",
|
||||
"description": "Simple interactive program that greets the user by name",
|
||||
"language": "Python",
|
||||
"category": "interactive",
|
||||
"hasPreview": true,
|
||||
"code": "# Interactive program - asks for user's name and greets them\n\nprint(\"Welcome to the greeting program!\")\nprint()\n\nname = input(\"What is your name? \")\nprint(f\"\\nHello, {name}! Nice to meet you!\")\n\nage = input(\"How old are you? \")\nprint(f\"\\nWow, {age} years old! That's awesome!\")\n\ncolor = input(\"What's your favorite color? \")\nprint(f\"\\n{color} is a great choice! I love that color too.\")\n\nprint(f\"\\nThanks for chatting, {name}! Have a wonderful day!\")"
|
||||
},
|
||||
{
|
||||
"id": "python-calculator-input",
|
||||
"title": "Interactive: Simple Calculator",
|
||||
"description": "Calculator that takes user input for operations",
|
||||
"language": "Python",
|
||||
"category": "interactive",
|
||||
"hasPreview": true,
|
||||
"code": "# Interactive calculator\n\nprint(\"=== Simple Calculator ===\")\nprint()\n\nnum1 = float(input(\"Enter first number: \"))\nnum2 = float(input(\"Enter second number: \"))\n\nprint(\"\\nAvailable operations:\")\nprint(\" + : Addition\")\nprint(\" - : Subtraction\")\nprint(\" * : Multiplication\")\nprint(\" / : Division\")\nprint()\n\noperation = input(\"Choose operation (+, -, *, /): \")\n\nif operation == '+':\n result = num1 + num2\n print(f\"\\n{num1} + {num2} = {result}\")\nelif operation == '-':\n result = num1 - num2\n print(f\"\\n{num1} - {num2} = {result}\")\nelif operation == '*':\n result = num1 * num2\n print(f\"\\n{num1} × {num2} = {result}\")\nelif operation == '/':\n if num2 != 0:\n result = num1 / num2\n print(f\"\\n{num1} ÷ {num2} = {result}\")\n else:\n print(\"\\nError: Cannot divide by zero!\")\nelse:\n print(\"\\nInvalid operation!\")"
|
||||
},
|
||||
{
|
||||
"id": "python-guess-number",
|
||||
"title": "Interactive: Number Guessing Game",
|
||||
"description": "Classic number guessing game with user input",
|
||||
"language": "Python",
|
||||
"category": "interactive",
|
||||
"hasPreview": true,
|
||||
"code": "# Number guessing game\nimport random\n\nprint(\"=== Number Guessing Game ===\")\nprint(\"I'm thinking of a number between 1 and 100...\")\nprint()\n\ntarget = random.randint(1, 100)\nattempts = 0\nmax_attempts = 7\n\nwhile attempts < max_attempts:\n guess = int(input(f\"Attempt {attempts + 1}/{max_attempts} - Enter your guess: \"))\n attempts += 1\n \n if guess < target:\n print(\"Too low! Try a higher number.\\n\")\n elif guess > target:\n print(\"Too high! Try a lower number.\\n\")\n else:\n print(f\"\\n🎉 Congratulations! You guessed it in {attempts} attempts!\")\n break\nelse:\n print(f\"\\n😞 Game over! The number was {target}.\")\n print(f\"You used all {max_attempts} attempts.\")"
|
||||
},
|
||||
{
|
||||
"id": "python-mad-libs",
|
||||
"title": "Interactive: Mad Libs Story",
|
||||
"description": "Fun interactive story generator using user input",
|
||||
"language": "Python",
|
||||
"category": "interactive",
|
||||
"hasPreview": true,
|
||||
"code": "# Mad Libs - Interactive story generator\n\nprint(\"=== Mad Libs Story Generator ===\")\nprint(\"Fill in the blanks to create a funny story!\")\nprint()\n\nnoun1 = input(\"Enter a noun (thing): \")\nadjective1 = input(\"Enter an adjective (describing word): \")\nverb1 = input(\"Enter a verb ending in -ing: \")\nnoun2 = input(\"Enter another noun: \")\nadjective2 = input(\"Enter another adjective: \")\nnoun3 = input(\"Enter a plural noun: \")\nverb2 = input(\"Enter a verb: \")\nnoun4 = input(\"Enter one more noun: \")\n\nprint(\"\\n\" + \"=\" * 50)\nprint(\"YOUR STORY:\")\nprint(\"=\" * 50)\nprint()\nprint(f\"Once upon a time, there was a {adjective1} {noun1}.\")\nprint(f\"It loved {verb1} in the park every morning.\")\nprint(f\"One day, it found a mysterious {noun2} on the ground.\")\nprint(f\"The {noun2} was so {adjective2} that it started to glow!\")\nprint(f\"Suddenly, hundreds of tiny {noun3} appeared and began to {verb2}.\")\nprint(f\"The {noun1} was amazed and took the {noun2} to the nearest {noun4}.\")\nprint(f\"And they all lived happily ever after!\")\nprint()\nprint(\"=\" * 50)\nprint(\"The End!\")"
|
||||
},
|
||||
{
|
||||
"id": "python-todo-cli",
|
||||
"title": "Interactive: Todo List CLI",
|
||||
"description": "Command-line todo list with interactive menu",
|
||||
"language": "Python",
|
||||
"category": "interactive",
|
||||
"hasPreview": true,
|
||||
"code": "# Interactive CLI Todo List\n\ntodos = []\n\nprint(\"=== Todo List Manager ===\")\nprint()\n\nwhile True:\n print(\"\\nCurrent Todos:\")\n if not todos:\n print(\" (No todos yet)\")\n else:\n for i, todo in enumerate(todos, 1):\n print(f\" {i}. {todo}\")\n \n print(\"\\nOptions:\")\n print(\" 1. Add todo\")\n print(\" 2. Remove todo\")\n print(\" 3. Exit\")\n \n choice = input(\"\\nEnter choice (1-3): \")\n \n if choice == '1':\n todo = input(\"Enter new todo: \")\n if todo.strip():\n todos.append(todo)\n print(f\"✓ Added: {todo}\")\n else:\n print(\"✗ Todo cannot be empty\")\n \n elif choice == '2':\n if not todos:\n print(\"✗ No todos to remove\")\n else:\n try:\n num = int(input(f\"Enter todo number (1-{len(todos)}): \"))\n if 1 <= num <= len(todos):\n removed = todos.pop(num - 1)\n print(f\"✓ Removed: {removed}\")\n else:\n print(\"✗ Invalid number\")\n except ValueError:\n print(\"✗ Please enter a valid number\")\n \n elif choice == '3':\n print(\"\\nGoodbye! Final todo count:\", len(todos))\n break\n \n else:\n print(\"✗ Invalid choice\")"
|
||||
},
|
||||
{
|
||||
"id": "python-quiz-game",
|
||||
"title": "Interactive: Quiz Game",
|
||||
"description": "Multiple choice quiz with score tracking",
|
||||
"language": "Python",
|
||||
"category": "interactive",
|
||||
"hasPreview": true,
|
||||
"code": "# Interactive Quiz Game\n\nprint(\"=== Python Programming Quiz ===\")\nprint(\"Answer the following questions!\")\nprint()\n\nscore = 0\ntotal_questions = 5\n\n# Question 1\nprint(\"Question 1: What does CPU stand for?\")\nprint(\" A) Central Process Unit\")\nprint(\" B) Central Processing Unit\")\nprint(\" C) Computer Personal Unit\")\nprint(\" D) Central Processor Unit\")\nanswer = input(\"Your answer (A/B/C/D): \").upper()\nif answer == 'B':\n print(\"✓ Correct!\\n\")\n score += 1\nelse:\n print(\"✗ Wrong! The answer was B\\n\")\n\n# Question 2\nprint(\"Question 2: Which programming language is known as the 'language of the web'?\")\nprint(\" A) Python\")\nprint(\" B) Java\")\nprint(\" C) JavaScript\")\nprint(\" D) C++\")\nanswer = input(\"Your answer (A/B/C/D): \").upper()\nif answer == 'C':\n print(\"✓ Correct!\\n\")\n score += 1\nelse:\n print(\"✗ Wrong! The answer was C\\n\")\n\n# Question 3\nprint(\"Question 3: What is 2 to the power of 8?\")\nprint(\" A) 128\")\nprint(\" B) 256\")\nprint(\" C) 512\")\nprint(\" D) 64\")\nanswer = input(\"Your answer (A/B/C/D): \").upper()\nif answer == 'B':\n print(\"✓ Correct!\\n\")\n score += 1\nelse:\n print(\"✗ Wrong! The answer was B\\n\")\n\n# Question 4\nprint(\"Question 4: Which data structure uses LIFO (Last In, First Out)?\")\nprint(\" A) Queue\")\nprint(\" B) Stack\")\nprint(\" C) Array\")\nprint(\" D) Tree\")\nanswer = input(\"Your answer (A/B/C/D): \").upper()\nif answer == 'B':\n print(\"✓ Correct!\\n\")\n score += 1\nelse:\n print(\"✗ Wrong! The answer was B\\n\")\n\n# Question 5\nprint(\"Question 5: What does HTML stand for?\")\nprint(\" A) Hyper Text Markup Language\")\nprint(\" B) High Tech Modern Language\")\nprint(\" C) Home Tool Markup Language\")\nprint(\" D) Hyperlinks and Text Markup Language\")\nanswer = input(\"Your answer (A/B/C/D): \").upper()\nif answer == 'A':\n print(\"✓ Correct!\\n\")\n score += 1\nelse:\n print(\"✗ Wrong! The answer was A\\n\")\n\n# Final score\nprint(\"=\" * 40)\nprint(f\"Quiz Complete! Your score: {score}/{total_questions}\")\npercentage = (score / total_questions) * 100\nprint(f\"Percentage: {percentage:.1f}%\")\n\nif score == total_questions:\n print(\"🌟 Perfect score! You're a genius!\")\nelif score >= 3:\n print(\"👍 Good job! Keep learning!\")\nelse:\n print(\"📚 Keep studying! You'll do better next time!\")\nprint(\"=\" * 40)"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -50,6 +50,123 @@ sys.stderr = StringIO()
|
||||
}
|
||||
}
|
||||
|
||||
export interface InteractiveCallbacks {
|
||||
onOutput?: (text: string) => void
|
||||
onError?: (text: string) => void
|
||||
onInputRequest?: (prompt: string) => Promise<string>
|
||||
}
|
||||
|
||||
export async function runPythonCodeInteractive(
|
||||
code: string,
|
||||
callbacks: InteractiveCallbacks
|
||||
): Promise<void> {
|
||||
const pyodide = await getPyodide()
|
||||
|
||||
const inputQueue: string[] = []
|
||||
let inputResolve: ((value: string) => void) | null = null
|
||||
|
||||
const customInput = async (prompt = '') => {
|
||||
if (callbacks.onOutput && prompt) {
|
||||
callbacks.onOutput(prompt)
|
||||
}
|
||||
|
||||
if (callbacks.onInputRequest) {
|
||||
const value = await callbacks.onInputRequest(prompt)
|
||||
return value
|
||||
}
|
||||
|
||||
return new Promise<string>((resolve) => {
|
||||
if (inputQueue.length > 0) {
|
||||
resolve(inputQueue.shift()!)
|
||||
} else {
|
||||
inputResolve = resolve
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pyodide.globals.set('__custom_input__', customInput)
|
||||
|
||||
pyodide.runPython(`
|
||||
import sys
|
||||
from io import StringIO
|
||||
|
||||
class InteractiveStdout:
|
||||
def __init__(self, callback):
|
||||
self.callback = callback
|
||||
self.buffer = ""
|
||||
|
||||
def write(self, text):
|
||||
self.buffer += text
|
||||
if "\\n" in text:
|
||||
lines = self.buffer.split("\\n")
|
||||
for line in lines[:-1]:
|
||||
if line:
|
||||
self.callback(line)
|
||||
self.buffer = lines[-1]
|
||||
return len(text)
|
||||
|
||||
def flush(self):
|
||||
if self.buffer:
|
||||
self.callback(self.buffer)
|
||||
self.buffer = ""
|
||||
|
||||
class InteractiveStderr:
|
||||
def __init__(self, callback):
|
||||
self.callback = callback
|
||||
self.buffer = ""
|
||||
|
||||
def write(self, text):
|
||||
self.buffer += text
|
||||
if "\\n" in text:
|
||||
lines = self.buffer.split("\\n")
|
||||
for line in lines[:-1]:
|
||||
if line:
|
||||
self.callback(line)
|
||||
self.buffer = lines[-1]
|
||||
return len(text)
|
||||
|
||||
def flush(self):
|
||||
if self.buffer:
|
||||
self.callback(self.buffer)
|
||||
self.buffer = ""
|
||||
`)
|
||||
|
||||
const outputCallback = (text: string) => {
|
||||
if (callbacks.onOutput) {
|
||||
callbacks.onOutput(text)
|
||||
}
|
||||
}
|
||||
|
||||
const errorCallback = (text: string) => {
|
||||
if (callbacks.onError) {
|
||||
callbacks.onError(text)
|
||||
}
|
||||
}
|
||||
|
||||
pyodide.globals.set('__output_callback__', outputCallback)
|
||||
pyodide.globals.set('__error_callback__', errorCallback)
|
||||
|
||||
pyodide.runPython(`
|
||||
sys.stdout = InteractiveStdout(__output_callback__)
|
||||
sys.stderr = InteractiveStderr(__error_callback__)
|
||||
|
||||
import builtins
|
||||
builtins.input = __custom_input__
|
||||
`)
|
||||
|
||||
try {
|
||||
await pyodide.runPythonAsync(code)
|
||||
|
||||
pyodide.runPython('sys.stdout.flush()')
|
||||
pyodide.runPython('sys.stderr.flush()')
|
||||
} catch (err) {
|
||||
if (callbacks.onError) {
|
||||
callbacks.onError(err instanceof Error ? err.message : String(err))
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
export function isPyodideReady(): boolean {
|
||||
return pyodideInstance !== null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user