Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 120 additions & 38 deletions src/lib/hooks/useCodeRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ interface UseCodeRunnerOptions {
onRunStart?: () => void;
onRunComplete?: () => void;
onRunError?: (error: Error) => void;
timeoutMs?: number;
maxRetries?: number;
}

class CodeExecutionError extends Error {
constructor(
message: string,
public readonly type: 'network' | 'timeout' | 'simulation' | 'unknown',
public readonly originalError?: Error
) {
super(message);
this.name = 'CodeExecutionError';
}
}

export function useCodeRunner(options: UseCodeRunnerOptions = {}) {
Expand All @@ -21,6 +34,11 @@ export function useCodeRunner(options: UseCodeRunnerOptions = {}) {
timestamp: number;
} | null>(null);

const {
timeoutMs = 30000, // 30 seconds default
maxRetries = 2,
} = options;

const updateCode = useCallback((newCode: string) => {
setCode(newCode);
}, []);
Expand All @@ -40,52 +58,116 @@ export function useCodeRunner(options: UseCodeRunnerOptions = {}) {
setOutputs([
{
type: "log",
content: "Running code...",
content: `🚀 Starting execution on ${network.name}...`,
timestamp: Date.now(),
},
]);

try {
setProgress(30);
await new Promise((resolve) => setTimeout(resolve, 300));
setProgress(50);

const simulatedOutputs = await simulateCodeExecution(example, network);
setProgress(90);

await new Promise((resolve) => setTimeout(resolve, 200));

setOutputs(simulatedOutputs);
setLastRun({
example,
network,
timestamp: Date.now(),
});

options.onRunComplete?.();
} catch (error) {
setOutputs([
{
type: "error",
content:
error instanceof Error
? `Error: ${error.message}`
: "An unknown error occurred",
let attempt = 0;
let lastError: Error | null = null;

while (attempt <= maxRetries) {
try {
setProgress(20 + (attempt * 10));

if (attempt > 0) {
setOutputs(prev => [...prev, {
type: "warning",
content: `🔄 Retry attempt ${attempt}/${maxRetries}...`,
timestamp: Date.now(),
}]);
}

// Create a timeout promise
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => {
reject(new CodeExecutionError(
`Execution timed out after ${timeoutMs}ms`,
'timeout'
));
}, timeoutMs);
});

// Race between simulation and timeout
const simulatedOutputs = await Promise.race([
simulateCodeExecution(example, network),
timeoutPromise
]);

setProgress(90);

// Add success message
const successOutputs = [
...simulatedOutputs,
{
type: "log" as const,
content: `✅ Execution completed successfully on ${network.name}`,
timestamp: Date.now(),
}
];

setOutputs(successOutputs);
setLastRun({
example,
network,
timestamp: Date.now(),
},
]);
});

options.onRunComplete?.();
return; // Success, exit retry loop

} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
attempt++;

let errorType: CodeExecutionError['type'] = 'unknown';
let errorMessage = lastError.message;

// Categorize errors
if (lastError instanceof CodeExecutionError) {
errorType = lastError.type;
} else if (lastError.message.includes('network') || lastError.message.includes('connection')) {
errorType = 'network';
errorMessage = `Network error: ${lastError.message}. Please check your internet connection and try again.`;
} else if (lastError.message.includes('timeout')) {
errorType = 'timeout';
errorMessage = `Execution timed out: ${lastError.message}. The operation took too long to complete.`;
} else {
errorType = 'simulation';
errorMessage = `Simulation error: ${lastError.message}`;
}

const categorizedError = new CodeExecutionError(errorMessage, errorType, lastError);

// If this was the last attempt, report the error
if (attempt > maxRetries) {
setOutputs(prev => [...prev, {
type: "error",
content: `❌ ${categorizedError.message}${errorType === 'network' ? ' (Check network connection)' : ''}${errorType === 'timeout' ? ' (Try simplifying the code)' : ''}`,
timestamp: Date.now(),
}]);

options.onRunError?.(categorizedError);
}

// Wait before retry (exponential backoff)
if (attempt <= maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}

options.onRunError?.(
error instanceof Error ? error : new Error("Unknown error"),
);
} finally {
setIsRunning(false);
setProgress(100);
// If we get here, all retries failed
setOutputs(prev => [...prev, {
type: "error",
content: `❌ All ${maxRetries + 1} attempts failed. Last error: ${lastError?.message || 'Unknown error'}`,
timestamp: Date.now(),
}]);

setTimeout(() => setProgress(0), 500);
}
options.onRunError?.(lastError || new Error('All retry attempts failed'));
},
[options],
[options, timeoutMs, maxRetries],
);

const clearOutput = useCallback(() => {
Expand Down