Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linked pyodide to run python code #49

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions ui/components/Artifacts/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ export default function ArtifactContent({
return <HtmlPreview content={activeArtifactData.content} language={activeArtifactData.language || ''} />;
}

if (showLogs) {
return activeArtifactData && <Logs entries={logEntries[activeArtifactData.id] || []} />;
if (showLogs && activeArtifactData) {
return <Logs entries={logEntries[activeArtifactData.id] || []} />;
}

return activeArtifact && renderArtifact(artifacts.find(a => a.id === activeArtifact)!);
Expand Down
57 changes: 11 additions & 46 deletions ui/components/Artifacts/Logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,15 @@ interface OutputEntry {
type: 'log' | 'info' | 'warning' | 'error' | 'success' | 'output'
}

export default function Logs() {
interface LogsProps {
entries: OutputEntry[];
}

export default function Logs({ entries }: LogsProps) {
const outputRef = useRef<HTMLDivElement>(null)
const [entries, setEntries] = useState<OutputEntry[]>([])
const [isScrolledToBottom, setIsScrolledToBottom] = useState(true)
const [isMinimized, setIsMinimized] = useState(false)

const addEntry = useCallback((content: string, type: OutputEntry['type'] = 'output') => {
setEntries(prevEntries => [...prevEntries, {
id: crypto.randomUUID(),
timestamp: new Date().toISOString(),
content,
type,
}])
}, [])

useEffect(() => {
if (outputRef.current && isScrolledToBottom) {
outputRef.current.scrollTop = outputRef.current.scrollHeight
Expand All @@ -39,39 +33,6 @@ export default function Logs() {
setIsScrolledToBottom(scrollHeight - scrollTop === clientHeight)
}, [])

// Simulating entries for demonstration
useEffect(() => {
const types: OutputEntry['type'][] = ['log', 'info', 'warning', 'error', 'success', 'output']
const messages = [
"Initializing quantum neural network...",
"Syncing with decentralized cloud nodes...",
"Optimizing AI-driven microservices...",
"Establishing secure blockchain connection...",
"Deploying edge computing resources...",
"Received request: GET /api/v3/quantum-data",
"Error: Temporal anomaly detected in data stream",
"Successfully processed 1 million records in 0.1 seconds",
"> npm run future",
"$ next quantum-dev",
"- Quantum server ready on 0.0.0.0:3000, multiverse url: http://localhost:3000"
]

const addRandomEntry = () => {
const randomType = types[Math.floor(Math.random() * types.length)]
const randomMessage = messages[Math.floor(Math.random() * messages.length)]
addEntry(randomMessage, randomType)
}

// Add initial entries
for (let i = 0; i < 5; i++) {
addRandomEntry()
}

const timer = setInterval(addRandomEntry, 2000)

return () => clearInterval(timer)
}, [addEntry])

const typeStyles = {
error: 'bg-red-500/20 text-red-700 dark:text-red-300 border-red-500/30',
warning: 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-300 border-yellow-500/30',
Expand Down Expand Up @@ -104,8 +65,12 @@ export default function Logs() {
>
<div className="font-mono text-sm space-y-3">
<AnimatePresence>
{entries.map((entry) => (
<LogEntry key={entry.id} entry={entry} typeStyles={typeStyles} />
{entries.map((entry, index) => (
<LogEntry
key={entry.id || `entry-${index}`}
entry={entry}
typeStyles={typeStyles}
/>
))}
</AnimatePresence>
</div>
Expand Down
68 changes: 55 additions & 13 deletions ui/components/Artifacts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ArtifactActions from './Actions'
import ArtifactSidebar from './Sidebar'
import Code from './Code'
import TextPreview from './TextPreview'
import usePyodide from '@/hooks/usePyodide'

interface ArtifactContent {
id: string;
Expand Down Expand Up @@ -74,6 +75,7 @@ export default function Artifacts({
const [logEntries, setLogEntries] = useState<{[key: string]: OutputEntry[]}>({});
const [copied, setCopied] = useState(false);
const { resolvedTheme } = useTheme();
const { pyodide } = usePyodide();

const activeArtifactData = artifacts.find(a => a.id === activeArtifact);

Expand All @@ -90,20 +92,60 @@ export default function Artifacts({
}
};

const runCode = () => {
const runCode = async () => {
if (activeArtifactData?.type === 'code') {
onRun(activeArtifactData.id, activeArtifactData.content);
setLogEntries(prev => ({
...prev,
[activeArtifactData.id]: [
...(prev[activeArtifactData.id] || []),
{
timestamp: new Date().toISOString(),
content: `Running ${activeArtifactData.name}...`,
type: 'info'
}
]
}));
if (activeArtifactData.language === 'python' && pyodide) {
try {
// Capture console output
let output = '';
const originalConsoleLog = console.log;
console.log = (...args) => {
output += args.join(' ') + '\n';
originalConsoleLog.apply(console, args);
};

const result = await pyodide.runPython(activeArtifactData.content);

// Restore original console.log
console.log = originalConsoleLog;

setLogEntries(prev => ({
...prev,
[activeArtifactData.id]: [
...(prev[activeArtifactData.id] || []),
{
id: crypto.randomUUID(),
timestamp: new Date().toISOString(),
content: output.trim(),
type: 'output'
},
// Only add the result if it's not undefined
...(result !== undefined ? [{
id: crypto.randomUUID(),
timestamp: new Date().toISOString(),
content: `Result: ${result}`,
type: 'output'
}] : [])
]
}));
} catch (error) {
setLogEntries(prev => ({
...prev,
[activeArtifactData.id]: [
...(prev[activeArtifactData.id] || []),
{
id: crypto.randomUUID(),
timestamp: new Date().toISOString(),
content: `Error: ${error.message}`,
type: 'error'
}
]
}));
}
} else {
// Handle other languages or call the original onRun function
onRun(activeArtifactData.id, activeArtifactData.content);
}
setShowLogs(true);
}
};
Expand Down
39 changes: 39 additions & 0 deletions ui/hooks/usePyodide.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useState, useEffect } from "react";

declare global {
interface Window {
loadPyodide?: () => Promise<any>;
}
}

const PYODIDE_VERSION = "0.25.0";

export default function usePyodide() {
const [pyodide, setPyodide] = useState<any>(null);

useEffect(() => {
const loadPyodide = async () => {
if (typeof window !== 'undefined' && !window.loadPyodide) {
const script = document.createElement('script');
script.src = `https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/pyodide.js`;
script.async = true;
script.onload = async () => {
const loadedPyodide = await (window as any).loadPyodide({
indexURL: `https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/`,
});
setPyodide(loadedPyodide);
};
document.body.appendChild(script);
} else if (typeof window !== 'undefined' && window.loadPyodide) {
const loadedPyodide = await (window as any).loadPyodide({
indexURL: `https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/`,
});
setPyodide(loadedPyodide);
}
};

loadPyodide();
}, []);

return { pyodide };
}