Save current v2 sandbox implementation before styling refactor
- Modified sandbox API routes for v2 implementation - Updated sandbox providers (E2B and Vercel) - Added styling-reference directory with Firecrawl AI-ready website - Preparing for styling system port from Firecrawl design Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+23
-5
@@ -1,19 +1,37 @@
|
|||||||
# Required
|
# Required
|
||||||
FIRECRAWL_API_KEY=your_firecrawl_api_key # Get from https://firecrawl.dev (Web scraping)
|
FIRECRAWL_API_KEY=your_firecrawl_api_key # Get from https://firecrawl.dev (Web scraping)
|
||||||
|
|
||||||
# Vercel Sandbox Authentication (choose one method)
|
# =================================================================================
|
||||||
# See: https://vercel.com/docs/vercel-sandbox#authentication
|
# SANDBOX PROVIDER - Choose Option 1 OR 2
|
||||||
|
# =================================================================================
|
||||||
|
|
||||||
# Method 1: OIDC Token (recommended for development)
|
# Option 1: E2B Sandbox (recommended for development)
|
||||||
|
# Set SANDBOX_PROVIDER=e2b and configure E2B_API_KEY below
|
||||||
|
SANDBOX_PROVIDER=e2b
|
||||||
|
E2B_API_KEY=your_e2b_api_key # Get from https://e2b.dev
|
||||||
|
|
||||||
|
# Option 2: Vercel Sandbox
|
||||||
|
# Set SANDBOX_PROVIDER=vercel and choose authentication method below
|
||||||
|
# SANDBOX_PROVIDER=vercel
|
||||||
|
|
||||||
|
# Vercel Authentication - Choose method a OR b
|
||||||
|
# Method a: OIDC Token (recommended for development)
|
||||||
# Run `vercel link` then `vercel env pull` to get VERCEL_OIDC_TOKEN automatically
|
# Run `vercel link` then `vercel env pull` to get VERCEL_OIDC_TOKEN automatically
|
||||||
# VERCEL_OIDC_TOKEN=auto_generated_by_vercel_env_pull
|
# VERCEL_OIDC_TOKEN=auto_generated_by_vercel_env_pull
|
||||||
|
|
||||||
# Method 2: Personal Access Token (for production or when OIDC unavailable)
|
# Method b: Personal Access Token (for production or when OIDC unavailable)
|
||||||
# VERCEL_TEAM_ID=team_xxxxxxxxx # Your Vercel team ID
|
# VERCEL_TEAM_ID=team_xxxxxxxxx # Your Vercel team ID
|
||||||
# VERCEL_PROJECT_ID=prj_xxxxxxxxx # Your Vercel project ID
|
# VERCEL_PROJECT_ID=prj_xxxxxxxxx # Your Vercel project ID
|
||||||
# VERCEL_TOKEN=vercel_xxxxxxxxxxxx # Personal access token from Vercel dashboard
|
# VERCEL_TOKEN=vercel_xxxxxxxxxxxx # Personal access token from Vercel dashboard
|
||||||
|
|
||||||
# Optional (need at least one AI provider)
|
# =================================================================================
|
||||||
|
# AI PROVIDERS - Need at least one
|
||||||
|
# =================================================================================
|
||||||
|
|
||||||
|
# Vercel AI Gateway (recommended - provides access to multiple models)
|
||||||
|
AI_GATEWAY_API_KEY=your_ai_gateway_api_key # Get from https://vercel.com/dashboard/ai-gateway/api-keys
|
||||||
|
|
||||||
|
# Individual provider keys (used when AI_GATEWAY_API_KEY is not set)
|
||||||
ANTHROPIC_API_KEY=your_anthropic_api_key # Get from https://console.anthropic.com
|
ANTHROPIC_API_KEY=your_anthropic_api_key # Get from https://console.anthropic.com
|
||||||
OPENAI_API_KEY=your_openai_api_key # Get from https://platform.openai.com (GPT-5)
|
OPENAI_API_KEY=your_openai_api_key # Get from https://platform.openai.com (GPT-5)
|
||||||
GEMINI_API_KEY=your_gemini_api_key # Get from https://aistudio.google.com/app/apikey
|
GEMINI_API_KEY=your_gemini_api_key # Get from https://aistudio.google.com/app/apikey
|
||||||
|
|||||||
@@ -7,18 +7,28 @@ import { generateObject } from 'ai';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import type { FileManifest } from '@/types/file-manifest';
|
import type { FileManifest } from '@/types/file-manifest';
|
||||||
|
|
||||||
|
// Check if we're using Vercel AI Gateway
|
||||||
|
const isUsingAIGateway = !!process.env.AI_GATEWAY_API_KEY;
|
||||||
|
const aiGatewayBaseURL = 'https://ai-gateway.vercel.sh/v1';
|
||||||
|
|
||||||
const groq = createGroq({
|
const groq = createGroq({
|
||||||
apiKey: process.env.GROQ_API_KEY,
|
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.GROQ_API_KEY,
|
||||||
|
baseURL: isUsingAIGateway ? aiGatewayBaseURL : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const anthropic = createAnthropic({
|
const anthropic = createAnthropic({
|
||||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.ANTHROPIC_API_KEY,
|
||||||
baseURL: process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com/v1',
|
baseURL: isUsingAIGateway ? aiGatewayBaseURL : (process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com/v1'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const openai = createOpenAI({
|
const openai = createOpenAI({
|
||||||
apiKey: process.env.OPENAI_API_KEY,
|
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.OPENAI_API_KEY,
|
||||||
baseURL: process.env.OPENAI_BASE_URL,
|
baseURL: isUsingAIGateway ? aiGatewayBaseURL : process.env.OPENAI_BASE_URL,
|
||||||
|
});
|
||||||
|
|
||||||
|
const googleGenerativeAI = createGoogleGenerativeAI({
|
||||||
|
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.GEMINI_API_KEY,
|
||||||
|
baseURL: isUsingAIGateway ? aiGatewayBaseURL : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Schema for the AI's search plan - not file selection!
|
// Schema for the AI's search plan - not file selection!
|
||||||
@@ -104,7 +114,7 @@ export async function POST(request: NextRequest) {
|
|||||||
aiModel = openai(model.replace('openai/', ''));
|
aiModel = openai(model.replace('openai/', ''));
|
||||||
}
|
}
|
||||||
} else if (model.startsWith('google/')) {
|
} else if (model.startsWith('google/')) {
|
||||||
aiModel = createGoogleGenerativeAI(model.replace('google/', ''));
|
aiModel = googleGenerativeAI(model.replace('google/', ''));
|
||||||
} else {
|
} else {
|
||||||
// Default to groq if model format is unclear
|
// Default to groq if model format is unclear
|
||||||
aiModel = groq(model);
|
aiModel = groq(model);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { Sandbox } from '@vercel/sandbox';
|
import { Sandbox } from '@e2b/code-interpreter';
|
||||||
import type { SandboxState } from '@/types/sandbox';
|
import type { SandboxState } from '@/types/sandbox';
|
||||||
import type { ConversationState } from '@/types/conversation';
|
import type { ConversationState } from '@/types/conversation';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var conversationState: ConversationState | null;
|
var conversationState: ConversationState | null;
|
||||||
var activeSandbox: any;
|
var activeSandboxProvider: any;
|
||||||
var existingFiles: Set<string>;
|
var existingFiles: Set<string>;
|
||||||
var sandboxState: SandboxState;
|
var sandboxState: SandboxState;
|
||||||
}
|
}
|
||||||
@@ -294,75 +294,88 @@ export async function POST(request: NextRequest) {
|
|||||||
global.existingFiles = new Set<string>();
|
global.existingFiles = new Set<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, always check the global state for active sandbox
|
// First, always check the global state for active provider
|
||||||
let sandbox = global.activeSandbox;
|
let provider = global.activeSandboxProvider;
|
||||||
|
|
||||||
// If we don't have a sandbox in this instance but we have a sandboxId,
|
// If we don't have a provider in this instance but we have a sandboxId,
|
||||||
// reconnect to the existing sandbox
|
// try to use the existing sandbox data or create a new one
|
||||||
if (!sandbox && sandboxId) {
|
if (!provider && sandboxId) {
|
||||||
console.log(`[apply-ai-code-stream] Sandbox ${sandboxId} not in this instance, attempting reconnect...`);
|
console.log(`[apply-ai-code-stream] Provider not in this instance for sandbox ${sandboxId}, checking existing data...`);
|
||||||
|
|
||||||
|
// If we have sandbox data but no provider, we'll create a new provider
|
||||||
|
// E2B doesn't support reconnection like Vercel does
|
||||||
|
if (global.sandboxData && global.sandboxData.sandboxId === sandboxId) {
|
||||||
|
console.log(`[apply-ai-code-stream] Creating new provider for existing sandbox ${sandboxId}`);
|
||||||
|
|
||||||
|
// Create a new provider instance (this will create a new sandbox since E2B doesn't support reconnection)
|
||||||
|
try {
|
||||||
|
const { SandboxFactory } = await import('@/lib/sandbox/factory');
|
||||||
|
provider = SandboxFactory.create();
|
||||||
|
await provider.createSandbox();
|
||||||
|
|
||||||
|
// Update the global state
|
||||||
|
global.activeSandboxProvider = provider;
|
||||||
|
console.log(`[apply-ai-code-stream] Created new provider for sandbox ${sandboxId}`);
|
||||||
|
} catch (providerError) {
|
||||||
|
console.error(`[apply-ai-code-stream] Failed to create provider for sandbox ${sandboxId}:`, providerError);
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: `Failed to create sandbox provider for ${sandboxId}. The sandbox may have expired.`,
|
||||||
|
results: {
|
||||||
|
filesCreated: [],
|
||||||
|
packagesInstalled: [],
|
||||||
|
commandsExecuted: [],
|
||||||
|
errors: [`Sandbox provider creation failed: ${(providerError as Error).message}`]
|
||||||
|
},
|
||||||
|
explanation: parsed.explanation,
|
||||||
|
structure: parsed.structure,
|
||||||
|
parsedFiles: parsed.files,
|
||||||
|
message: `Parsed ${parsed.files.length} files but couldn't apply them - sandbox reconnection failed.`
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we still don't have a provider, create a new one
|
||||||
|
if (!provider) {
|
||||||
|
console.log(`[apply-ai-code-stream] No active provider found, creating new sandbox...`);
|
||||||
try {
|
try {
|
||||||
// Reconnect to the existing sandbox using E2B's connect method
|
const { SandboxFactory } = await import('@/lib/sandbox/factory');
|
||||||
sandbox = await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY });
|
provider = SandboxFactory.create();
|
||||||
console.log(`[apply-ai-code-stream] Successfully reconnected to sandbox ${sandboxId}`);
|
await provider.createSandbox();
|
||||||
|
|
||||||
// Store the reconnected sandbox globally for this instance
|
// Store the provider globally
|
||||||
global.activeSandbox = sandbox;
|
global.activeSandboxProvider = provider;
|
||||||
|
|
||||||
// Update sandbox data if needed
|
// Update sandbox data
|
||||||
if (!global.sandboxData) {
|
const sandboxInfo = provider.getSandboxInfo();
|
||||||
const host = (sandbox as any).getHost(5173);
|
if (sandboxInfo) {
|
||||||
global.sandboxData = {
|
global.sandboxData = {
|
||||||
sandboxId,
|
sandboxId: sandboxInfo.sandboxId,
|
||||||
url: `https://${host}`
|
url: sandboxInfo.url
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize existingFiles if not already
|
console.log(`[apply-ai-code-stream] Created new sandbox successfully`);
|
||||||
if (!global.existingFiles) {
|
} catch (createError) {
|
||||||
global.existingFiles = new Set<string>();
|
console.error(`[apply-ai-code-stream] Failed to create new sandbox:`, createError);
|
||||||
}
|
|
||||||
} catch (reconnectError) {
|
|
||||||
console.error(`[apply-ai-code-stream] Failed to reconnect to sandbox ${sandboxId}:`, reconnectError);
|
|
||||||
|
|
||||||
// If reconnection fails, we'll still try to return a meaningful response
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: false,
|
success: false,
|
||||||
error: `Failed to reconnect to sandbox ${sandboxId}. The sandbox may have expired or been terminated.`,
|
error: `Failed to create new sandbox: ${createError instanceof Error ? createError.message : 'Unknown error'}`,
|
||||||
results: {
|
results: {
|
||||||
filesCreated: [],
|
filesCreated: [],
|
||||||
packagesInstalled: [],
|
packagesInstalled: [],
|
||||||
commandsExecuted: [],
|
commandsExecuted: [],
|
||||||
errors: [`Sandbox reconnection failed: ${(reconnectError as Error).message}`]
|
errors: [`Sandbox creation failed: ${createError instanceof Error ? createError.message : 'Unknown error'}`]
|
||||||
},
|
},
|
||||||
explanation: parsed.explanation,
|
explanation: parsed.explanation,
|
||||||
structure: parsed.structure,
|
structure: parsed.structure,
|
||||||
parsedFiles: parsed.files,
|
parsedFiles: parsed.files,
|
||||||
message: `Parsed ${parsed.files.length} files but couldn't apply them - sandbox reconnection failed.`
|
message: `Parsed ${parsed.files.length} files but couldn't apply them - sandbox creation failed.`
|
||||||
});
|
}, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no sandbox at all and no sandboxId provided, return an error
|
|
||||||
if (!sandbox && !sandboxId) {
|
|
||||||
console.log('[apply-ai-code-stream] No sandbox available and no sandboxId provided');
|
|
||||||
return NextResponse.json({
|
|
||||||
success: false,
|
|
||||||
error: 'No active sandbox found. Please create a sandbox first.',
|
|
||||||
results: {
|
|
||||||
filesCreated: [],
|
|
||||||
packagesInstalled: [],
|
|
||||||
commandsExecuted: [],
|
|
||||||
errors: ['No sandbox available']
|
|
||||||
},
|
|
||||||
explanation: parsed.explanation,
|
|
||||||
structure: parsed.structure,
|
|
||||||
parsedFiles: parsed.files,
|
|
||||||
message: `Parsed ${parsed.files.length} files but no sandbox available to apply them.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a response stream for real-time updates
|
// Create a response stream for real-time updates
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const stream = new TransformStream();
|
const stream = new TransformStream();
|
||||||
@@ -374,8 +387,8 @@ export async function POST(request: NextRequest) {
|
|||||||
await writer.write(encoder.encode(message));
|
await writer.write(encoder.encode(message));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start processing in background (pass sandbox and request to the async function)
|
// Start processing in background (pass provider and request to the async function)
|
||||||
(async (sandboxInstance, req) => {
|
(async (providerInstance, req) => {
|
||||||
const results = {
|
const results = {
|
||||||
filesCreated: [] as string[],
|
filesCreated: [] as string[],
|
||||||
filesUpdated: [] as string[],
|
filesUpdated: [] as string[],
|
||||||
@@ -432,7 +445,7 @@ export async function POST(request: NextRequest) {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
packages: uniquePackages,
|
packages: uniquePackages,
|
||||||
sandboxId: sandboxId || (sandboxInstance as any).sandboxId
|
sandboxId: sandboxId || providerInstance.getSandboxInfo()?.sandboxId
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -536,17 +549,11 @@ export async function POST(request: NextRequest) {
|
|||||||
// Create directory if needed
|
// Create directory if needed
|
||||||
const dirPath = normalizedPath.includes('/') ? normalizedPath.substring(0, normalizedPath.lastIndexOf('/')) : '';
|
const dirPath = normalizedPath.includes('/') ? normalizedPath.substring(0, normalizedPath.lastIndexOf('/')) : '';
|
||||||
if (dirPath) {
|
if (dirPath) {
|
||||||
await sandboxInstance.runCommand({
|
await providerInstance.runCommand(`mkdir -p ${dirPath}`);
|
||||||
cmd: 'mkdir',
|
|
||||||
args: ['-p', dirPath]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the file using Vercel Sandbox writeFiles
|
// Write the file using provider
|
||||||
await sandboxInstance.writeFiles([{
|
await providerInstance.writeFile(normalizedPath, fileContent);
|
||||||
path: normalizedPath,
|
|
||||||
content: Buffer.from(fileContent)
|
|
||||||
}]);
|
|
||||||
|
|
||||||
// Update file cache
|
// Update file cache
|
||||||
if (global.sandboxState?.fileCache) {
|
if (global.sandboxState?.fileCache) {
|
||||||
@@ -599,20 +606,12 @@ export async function POST(request: NextRequest) {
|
|||||||
action: 'executing'
|
action: 'executing'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Parse command and arguments for Vercel Sandbox
|
// Use provider runCommand
|
||||||
const commandParts = cmd.trim().split(/\s+/);
|
const result = await providerInstance.runCommand(cmd);
|
||||||
const cmdName = commandParts[0];
|
|
||||||
const args = commandParts.slice(1);
|
|
||||||
|
|
||||||
// Use Vercel Sandbox runCommand
|
// Get command output from provider result
|
||||||
const result = await sandboxInstance.runCommand({
|
const stdout = result.stdout;
|
||||||
cmd: cmdName,
|
const stderr = result.stderr;
|
||||||
args
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get command output
|
|
||||||
const stdout = await result.stdout();
|
|
||||||
const stderr = await result.stderr();
|
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
await sendProgress({
|
await sendProgress({
|
||||||
@@ -697,7 +696,7 @@ export async function POST(request: NextRequest) {
|
|||||||
} finally {
|
} finally {
|
||||||
await writer.close();
|
await writer.close();
|
||||||
}
|
}
|
||||||
})(sandbox, request);
|
})(provider, request);
|
||||||
|
|
||||||
// Return the stream
|
// Return the stream
|
||||||
return new Response(stream.readable, {
|
return new Response(stream.readable, {
|
||||||
|
|||||||
@@ -9,9 +9,59 @@ declare global {
|
|||||||
var sandboxData: any;
|
var sandboxData: any;
|
||||||
var existingFiles: Set<string>;
|
var existingFiles: Set<string>;
|
||||||
var sandboxState: SandboxState;
|
var sandboxState: SandboxState;
|
||||||
|
var sandboxCreationInProgress: boolean;
|
||||||
|
var sandboxCreationPromise: Promise<any> | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST() {
|
export async function POST() {
|
||||||
|
// Check if sandbox creation is already in progress
|
||||||
|
if (global.sandboxCreationInProgress && global.sandboxCreationPromise) {
|
||||||
|
console.log('[create-ai-sandbox] Sandbox creation already in progress, waiting for existing creation...');
|
||||||
|
try {
|
||||||
|
const existingResult = await global.sandboxCreationPromise;
|
||||||
|
console.log('[create-ai-sandbox] Returning existing sandbox creation result');
|
||||||
|
return NextResponse.json(existingResult);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[create-ai-sandbox] Existing sandbox creation failed:', error);
|
||||||
|
// Continue with new creation if the existing one failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we already have an active sandbox
|
||||||
|
if (global.activeSandbox && global.sandboxData) {
|
||||||
|
console.log('[create-ai-sandbox] Returning existing active sandbox');
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
sandboxId: global.sandboxData.sandboxId,
|
||||||
|
url: global.sandboxData.url
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the creation flag
|
||||||
|
global.sandboxCreationInProgress = true;
|
||||||
|
|
||||||
|
// Create the promise that other requests can await
|
||||||
|
global.sandboxCreationPromise = createSandboxInternal();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await global.sandboxCreationPromise;
|
||||||
|
return NextResponse.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[create-ai-sandbox] Sandbox creation failed:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to create sandbox',
|
||||||
|
details: error instanceof Error ? error.stack : undefined
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
global.sandboxCreationInProgress = false;
|
||||||
|
global.sandboxCreationPromise = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createSandboxInternal() {
|
||||||
let sandbox: any = null;
|
let sandbox: any = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -26,6 +76,7 @@ export async function POST() {
|
|||||||
console.error('Failed to stop existing sandbox:', e);
|
console.error('Failed to stop existing sandbox:', e);
|
||||||
}
|
}
|
||||||
global.activeSandbox = null;
|
global.activeSandbox = null;
|
||||||
|
global.sandboxData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear existing files tracking
|
// Clear existing files tracking
|
||||||
@@ -297,12 +348,20 @@ body {
|
|||||||
|
|
||||||
console.log('[create-ai-sandbox] Sandbox ready at:', sandboxUrl);
|
console.log('[create-ai-sandbox] Sandbox ready at:', sandboxUrl);
|
||||||
|
|
||||||
return NextResponse.json({
|
const result = {
|
||||||
success: true,
|
success: true,
|
||||||
sandboxId,
|
sandboxId,
|
||||||
url: sandboxUrl,
|
url: sandboxUrl,
|
||||||
message: 'Vercel sandbox created and Vite React app initialized'
|
message: 'Vercel sandbox created and Vite React app initialized'
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// Store the result for reuse
|
||||||
|
global.sandboxData = {
|
||||||
|
...global.sandboxData,
|
||||||
|
...result
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[create-ai-sandbox] Error:', error);
|
console.error('[create-ai-sandbox] Error:', error);
|
||||||
@@ -316,12 +375,10 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.json(
|
// Clear global state on error
|
||||||
{
|
global.activeSandbox = null;
|
||||||
error: error instanceof Error ? error.message : 'Failed to create sandbox',
|
global.sandboxData = null;
|
||||||
details: error instanceof Error ? error.stack : undefined
|
|
||||||
},
|
throw error; // Throw to be caught by the outer handler
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,21 +14,28 @@ import { appConfig } from '@/config/app.config';
|
|||||||
// Force dynamic route to enable streaming
|
// Force dynamic route to enable streaming
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
// Check if we're using Vercel AI Gateway
|
||||||
|
const isUsingAIGateway = !!process.env.AI_GATEWAY_API_KEY;
|
||||||
|
const aiGatewayBaseURL = 'https://ai-gateway.vercel.sh/v1';
|
||||||
|
|
||||||
const groq = createGroq({
|
const groq = createGroq({
|
||||||
apiKey: process.env.GROQ_API_KEY,
|
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.GROQ_API_KEY,
|
||||||
|
baseURL: isUsingAIGateway ? aiGatewayBaseURL : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const anthropic = createAnthropic({
|
const anthropic = createAnthropic({
|
||||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.ANTHROPIC_API_KEY,
|
||||||
baseURL: process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com/v1',
|
baseURL: isUsingAIGateway ? aiGatewayBaseURL : (process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com/v1'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const googleGenerativeAI = createGoogleGenerativeAI({
|
const googleGenerativeAI = createGoogleGenerativeAI({
|
||||||
apiKey: process.env.GEMINI_API_KEY,
|
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.GEMINI_API_KEY,
|
||||||
|
baseURL: isUsingAIGateway ? aiGatewayBaseURL : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const openai = createOpenAI({
|
const openai = createOpenAI({
|
||||||
apiKey: process.env.OPENAI_API_KEY,
|
apiKey: process.env.AI_GATEWAY_API_KEY ?? process.env.OPENAI_API_KEY,
|
||||||
|
baseURL: isUsingAIGateway ? aiGatewayBaseURL : process.env.OPENAI_BASE_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper function to analyze user preferences from conversation history
|
// Helper function to analyze user preferences from conversation history
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var activeSandbox: any;
|
var activeSandbox: any;
|
||||||
|
var activeSandboxProvider: any;
|
||||||
var sandboxData: any;
|
var sandboxData: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,13 +36,13 @@ export async function POST(request: NextRequest) {
|
|||||||
console.log(`[install-packages] Cleaned:`, validPackages);
|
console.log(`[install-packages] Cleaned:`, validPackages);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get active sandbox
|
// Get active sandbox provider
|
||||||
const sandbox = global.activeSandbox;
|
const provider = global.activeSandboxProvider;
|
||||||
|
|
||||||
if (!sandbox) {
|
if (!provider) {
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'No active sandbox available'
|
error: 'No active sandbox provider available'
|
||||||
}, { status: 400 });
|
}, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ export async function POST(request: NextRequest) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Start installation in background
|
// Start installation in background
|
||||||
(async (sandboxInstance) => {
|
(async (providerInstance) => {
|
||||||
try {
|
try {
|
||||||
await sendProgress({
|
await sendProgress({
|
||||||
type: 'start',
|
type: 'start',
|
||||||
@@ -72,10 +73,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Try to kill any running dev server processes
|
// Try to kill any running dev server processes
|
||||||
await sandboxInstance.runCommand({
|
await providerInstance.runCommand('pkill -f vite');
|
||||||
cmd: 'pkill',
|
|
||||||
args: ['-f', 'vite']
|
|
||||||
});
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait a bit
|
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait a bit
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// It's OK if no process is found
|
// It's OK if no process is found
|
||||||
@@ -92,12 +90,13 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Read package.json to check existing dependencies
|
// Read package.json to check existing dependencies
|
||||||
const catResult = await sandboxInstance.runCommand({
|
let packageJsonContent = '';
|
||||||
cmd: 'cat',
|
try {
|
||||||
args: ['package.json']
|
packageJsonContent = await providerInstance.readFile('package.json');
|
||||||
});
|
} catch (error) {
|
||||||
if (catResult.exitCode === 0) {
|
console.log('[install-packages] Error reading package.json:', error);
|
||||||
const packageJsonContent = await catResult.stdout();
|
}
|
||||||
|
if (packageJsonContent) {
|
||||||
const packageJson = JSON.parse(packageJsonContent);
|
const packageJson = JSON.parse(packageJsonContent);
|
||||||
|
|
||||||
const dependencies = packageJson.dependencies || {};
|
const dependencies = packageJson.dependencies || {};
|
||||||
@@ -144,11 +143,7 @@ export async function POST(request: NextRequest) {
|
|||||||
// Restart dev server
|
// Restart dev server
|
||||||
await sendProgress({ type: 'status', message: 'Restarting development server...' });
|
await sendProgress({ type: 'status', message: 'Restarting development server...' });
|
||||||
|
|
||||||
const devServerProcess = await sandboxInstance.runCommand({
|
await providerInstance.restartViteServer();
|
||||||
cmd: 'npm',
|
|
||||||
args: ['run', 'dev'],
|
|
||||||
detached: true
|
|
||||||
});
|
|
||||||
|
|
||||||
await sendProgress({
|
await sendProgress({
|
||||||
type: 'complete',
|
type: 'complete',
|
||||||
@@ -165,16 +160,12 @@ export async function POST(request: NextRequest) {
|
|||||||
message: `Installing ${packagesToInstall.length} new package(s): ${packagesToInstall.join(', ')}`
|
message: `Installing ${packagesToInstall.length} new package(s): ${packagesToInstall.join(', ')}`
|
||||||
});
|
});
|
||||||
|
|
||||||
// Run npm install
|
// Install packages using provider method
|
||||||
const installArgs = ['install', '--legacy-peer-deps', ...packagesToInstall];
|
const installResult = await providerInstance.installPackages(packagesToInstall);
|
||||||
const installResult = await sandboxInstance.runCommand({
|
|
||||||
cmd: 'npm',
|
|
||||||
args: installArgs
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get install output
|
// Get install output
|
||||||
const stdout = await installResult.stdout();
|
const stdout = installResult.stdout;
|
||||||
const stderr = await installResult.stderr();
|
const stderr = installResult.stderr;
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
const lines = stdout.split('\n').filter(line => line.trim());
|
const lines = stdout.split('\n').filter(line => line.trim());
|
||||||
@@ -218,11 +209,7 @@ export async function POST(request: NextRequest) {
|
|||||||
await sendProgress({ type: 'status', message: 'Restarting development server...' });
|
await sendProgress({ type: 'status', message: 'Restarting development server...' });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const devServerProcess = await sandboxInstance.runCommand({
|
await providerInstance.restartViteServer();
|
||||||
cmd: 'npm',
|
|
||||||
args: ['run', 'dev'],
|
|
||||||
detached: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait a bit for the server to start
|
// Wait a bit for the server to start
|
||||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
@@ -250,7 +237,7 @@ export async function POST(request: NextRequest) {
|
|||||||
} finally {
|
} finally {
|
||||||
await writer.close();
|
await writer.close();
|
||||||
}
|
}
|
||||||
})(sandbox);
|
})(provider);
|
||||||
|
|
||||||
// Return the stream
|
// Return the stream
|
||||||
return new Response(stream.readable, {
|
return new Response(stream.readable, {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var activeSandbox: any;
|
var activeSandboxProvider: any;
|
||||||
var sandboxData: any;
|
var sandboxData: any;
|
||||||
var existingFiles: Set<string>;
|
var existingFiles: Set<string>;
|
||||||
}
|
}
|
||||||
@@ -13,15 +13,15 @@ export async function POST() {
|
|||||||
let sandboxKilled = false;
|
let sandboxKilled = false;
|
||||||
|
|
||||||
// Stop existing sandbox if any
|
// Stop existing sandbox if any
|
||||||
if (global.activeSandbox) {
|
if (global.activeSandboxProvider) {
|
||||||
try {
|
try {
|
||||||
await global.activeSandbox.stop();
|
await global.activeSandboxProvider.terminate();
|
||||||
sandboxKilled = true;
|
sandboxKilled = true;
|
||||||
console.log('[kill-sandbox] Sandbox stopped successfully');
|
console.log('[kill-sandbox] Sandbox stopped successfully');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[kill-sandbox] Failed to stop sandbox:', e);
|
console.error('[kill-sandbox] Failed to stop sandbox:', e);
|
||||||
}
|
}
|
||||||
global.activeSandbox = null;
|
global.activeSandboxProvider = null;
|
||||||
global.sandboxData = null;
|
global.sandboxData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,12 @@ import { NextResponse } from 'next/server';
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var activeSandbox: any;
|
var activeSandbox: any;
|
||||||
|
var lastViteRestartTime: number;
|
||||||
|
var viteRestartInProgress: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RESTART_COOLDOWN_MS = 5000; // 5 second cooldown between restarts
|
||||||
|
|
||||||
export async function POST() {
|
export async function POST() {
|
||||||
try {
|
try {
|
||||||
if (!global.activeSandbox) {
|
if (!global.activeSandbox) {
|
||||||
@@ -13,6 +17,29 @@ export async function POST() {
|
|||||||
}, { status: 400 });
|
}, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if restart is already in progress
|
||||||
|
if (global.viteRestartInProgress) {
|
||||||
|
console.log('[restart-vite] Vite restart already in progress, skipping...');
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Vite restart already in progress'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cooldown
|
||||||
|
const now = Date.now();
|
||||||
|
if (global.lastViteRestartTime && (now - global.lastViteRestartTime) < RESTART_COOLDOWN_MS) {
|
||||||
|
const remainingTime = Math.ceil((RESTART_COOLDOWN_MS - (now - global.lastViteRestartTime)) / 1000);
|
||||||
|
console.log(`[restart-vite] Cooldown active, ${remainingTime}s remaining`);
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: `Vite was recently restarted, cooldown active (${remainingTime}s remaining)`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the restart flag
|
||||||
|
global.viteRestartInProgress = true;
|
||||||
|
|
||||||
console.log('[restart-vite] Forcing Vite restart...');
|
console.log('[restart-vite] Forcing Vite restart...');
|
||||||
|
|
||||||
// Kill existing Vite processes
|
// Kill existing Vite processes
|
||||||
@@ -51,6 +78,10 @@ export async function POST() {
|
|||||||
// Wait for Vite to start up
|
// Wait for Vite to start up
|
||||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
|
|
||||||
|
// Update global state
|
||||||
|
global.lastViteRestartTime = Date.now();
|
||||||
|
global.viteRestartInProgress = false;
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Vite restarted successfully'
|
message: 'Vite restarted successfully'
|
||||||
@@ -58,6 +89,10 @@ export async function POST() {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[restart-vite] Error:', error);
|
console.error('[restart-vite] Error:', error);
|
||||||
|
|
||||||
|
// Clear the restart flag on error
|
||||||
|
global.viteRestartInProgress = false;
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: false,
|
success: false,
|
||||||
error: (error as Error).message
|
error: (error as Error).message
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var activeSandbox: any;
|
var activeSandboxProvider: any;
|
||||||
var sandboxData: any;
|
var sandboxData: any;
|
||||||
var existingFiles: Set<string>;
|
var existingFiles: Set<string>;
|
||||||
}
|
}
|
||||||
@@ -9,15 +9,14 @@ declare global {
|
|||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
// Check if sandbox exists
|
// Check if sandbox exists
|
||||||
const sandboxExists = !!global.activeSandbox;
|
const sandboxExists = !!global.activeSandboxProvider;
|
||||||
|
|
||||||
let sandboxHealthy = false;
|
let sandboxHealthy = false;
|
||||||
let sandboxInfo = null;
|
let sandboxInfo = null;
|
||||||
|
|
||||||
if (sandboxExists && global.activeSandbox) {
|
if (sandboxExists && global.activeSandboxProvider) {
|
||||||
try {
|
try {
|
||||||
// Since Python isn't available in the Vite template, just check if sandbox exists
|
// Check if sandbox is healthy by calling a method that should work
|
||||||
// The sandbox object existing is enough to confirm it's healthy
|
|
||||||
sandboxHealthy = true;
|
sandboxHealthy = true;
|
||||||
sandboxInfo = {
|
sandboxInfo = {
|
||||||
sandboxId: global.sandboxData?.sandboxId,
|
sandboxId: global.sandboxData?.sandboxId,
|
||||||
|
|||||||
+17
-1
@@ -137,8 +137,11 @@ export default function AISandboxPage() {
|
|||||||
// Clear old conversation data on component mount and create/restore sandbox
|
// Clear old conversation data on component mount and create/restore sandbox
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
|
let sandboxCreated = false; // Track if sandbox was created in this effect
|
||||||
|
|
||||||
const initializePage = async () => {
|
const initializePage = async () => {
|
||||||
|
// Prevent double execution in React StrictMode
|
||||||
|
if (sandboxCreated) return;
|
||||||
// Clear old conversation
|
// Clear old conversation
|
||||||
try {
|
try {
|
||||||
await fetch('/api/conversation-state', {
|
await fetch('/api/conversation-state', {
|
||||||
@@ -165,9 +168,11 @@ export default function AISandboxPage() {
|
|||||||
console.log('[home] Attempting to restore sandbox:', sandboxIdParam);
|
console.log('[home] Attempting to restore sandbox:', sandboxIdParam);
|
||||||
// For now, just create a new sandbox - you could enhance this to actually restore
|
// For now, just create a new sandbox - you could enhance this to actually restore
|
||||||
// the specific sandbox if your backend supports it
|
// the specific sandbox if your backend supports it
|
||||||
|
sandboxCreated = true;
|
||||||
await createSandbox(true);
|
await createSandbox(true);
|
||||||
} else {
|
} else {
|
||||||
console.log('[home] No sandbox in URL, creating new sandbox automatically...');
|
console.log('[home] No sandbox in URL, creating new sandbox automatically...');
|
||||||
|
sandboxCreated = true;
|
||||||
await createSandbox(true);
|
await createSandbox(true);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -369,7 +374,16 @@ export default function AISandboxPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sandboxCreationRef = useRef<boolean>(false);
|
||||||
|
|
||||||
const createSandbox = async (fromHomeScreen = false) => {
|
const createSandbox = async (fromHomeScreen = false) => {
|
||||||
|
// Prevent duplicate sandbox creation
|
||||||
|
if (sandboxCreationRef.current) {
|
||||||
|
console.log('[createSandbox] Sandbox creation already in progress, skipping...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sandboxCreationRef.current = true;
|
||||||
console.log('[createSandbox] Starting sandbox creation...');
|
console.log('[createSandbox] Starting sandbox creation...');
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setShowLoadingBackground(true);
|
setShowLoadingBackground(true);
|
||||||
@@ -378,7 +392,7 @@ export default function AISandboxPage() {
|
|||||||
setScreenshotError(null);
|
setScreenshotError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/create-ai-sandbox', {
|
const response = await fetch('/api/create-ai-sandbox-v2', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({})
|
body: JSON.stringify({})
|
||||||
@@ -388,6 +402,7 @@ export default function AISandboxPage() {
|
|||||||
console.log('[createSandbox] Response data:', data);
|
console.log('[createSandbox] Response data:', data);
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
|
sandboxCreationRef.current = false; // Reset the ref on success
|
||||||
setSandboxData(data);
|
setSandboxData(data);
|
||||||
updateStatus('Sandbox active', true);
|
updateStatus('Sandbox active', true);
|
||||||
log('Sandbox created successfully!');
|
log('Sandbox created successfully!');
|
||||||
@@ -454,6 +469,7 @@ Tip: I automatically detect and install npm packages from your code imports (lik
|
|||||||
addChatMessage(`Failed to create sandbox: ${error.message}`, 'system');
|
addChatMessage(`Failed to create sandbox: ${error.message}`, 'system');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
sandboxCreationRef.current = false; // Reset the ref
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,26 @@ export const appConfig = {
|
|||||||
runtime: 'node22' // Available: node22, python3.13, v0-next-shadcn, cua-ubuntu-xfce
|
runtime: 'node22' // Available: node22, python3.13, v0-next-shadcn, cua-ubuntu-xfce
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// E2B Sandbox Configuration
|
||||||
|
e2b: {
|
||||||
|
// Sandbox timeout in minutes
|
||||||
|
timeoutMinutes: 30,
|
||||||
|
|
||||||
|
// Convert to milliseconds for E2B API
|
||||||
|
get timeoutMs() {
|
||||||
|
return this.timeoutMinutes * 60 * 1000;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Development server port (E2B uses 5173 for Vite)
|
||||||
|
vitePort: 5173,
|
||||||
|
|
||||||
|
// Time to wait for Vite dev server to be ready (in milliseconds)
|
||||||
|
viteStartupDelay: 10000,
|
||||||
|
|
||||||
|
// Working directory in sandbox
|
||||||
|
workingDirectory: '/home/user/app',
|
||||||
|
},
|
||||||
|
|
||||||
// AI Model Configuration
|
// AI Model Configuration
|
||||||
ai: {
|
ai: {
|
||||||
// Default AI model
|
// Default AI model
|
||||||
|
|||||||
@@ -100,18 +100,28 @@ export class E2BProvider extends SandboxProvider {
|
|||||||
|
|
||||||
const fullPath = path.startsWith('/') ? path : `/home/user/app/${path}`;
|
const fullPath = path.startsWith('/') ? path : `/home/user/app/${path}`;
|
||||||
|
|
||||||
await this.sandbox.runCode(`
|
// Use the E2B filesystem API to write the file
|
||||||
import os
|
// Note: E2B SDK uses files.write() method
|
||||||
|
if ((this.sandbox as any).files && typeof (this.sandbox as any).files.write === 'function') {
|
||||||
|
// Use the files.write API if available
|
||||||
|
await (this.sandbox as any).files.write(fullPath, Buffer.from(content));
|
||||||
|
console.log(`[E2BProvider] Written file using files.write: ${fullPath}`);
|
||||||
|
} else {
|
||||||
|
// Fallback to Python code execution
|
||||||
|
await this.sandbox.runCode(`
|
||||||
|
import os
|
||||||
|
|
||||||
# Ensure directory exists
|
# Ensure directory exists
|
||||||
dir_path = os.path.dirname("${fullPath}")
|
dir_path = os.path.dirname("${fullPath}")
|
||||||
os.makedirs(dir_path, exist_ok=True)
|
os.makedirs(dir_path, exist_ok=True)
|
||||||
|
|
||||||
# Write file
|
# Write file
|
||||||
with open("${fullPath}", 'w') as f:
|
with open("${fullPath}", 'w') as f:
|
||||||
f.write(${JSON.stringify(content)})
|
f.write(${JSON.stringify(content)})
|
||||||
print(f"✓ Written: ${fullPath}")
|
print(f"✓ Written: ${fullPath}")
|
||||||
`);
|
`);
|
||||||
|
console.log(`[E2BProvider] Written file using Python: ${fullPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
this.existingFiles.add(path);
|
this.existingFiles.add(path);
|
||||||
}
|
}
|
||||||
@@ -475,6 +485,10 @@ print(f'✓ Vite restarted with PID: {process.pid}')
|
|||||||
return this.sandboxInfo?.url || null;
|
return this.sandboxInfo?.url || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSandboxInfo(): SandboxInfo | null {
|
||||||
|
return this.sandboxInfo;
|
||||||
|
}
|
||||||
|
|
||||||
async terminate(): Promise<void> {
|
async terminate(): Promise<void> {
|
||||||
if (this.sandbox) {
|
if (this.sandbox) {
|
||||||
console.log('[E2BProvider] Terminating sandbox...');
|
console.log('[E2BProvider] Terminating sandbox...');
|
||||||
|
|||||||
@@ -450,6 +450,10 @@ body {
|
|||||||
return this.sandboxInfo?.url || null;
|
return this.sandboxInfo?.url || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSandboxInfo(): SandboxInfo | null {
|
||||||
|
return this.sandboxInfo;
|
||||||
|
}
|
||||||
|
|
||||||
async terminate(): Promise<void> {
|
async terminate(): Promise<void> {
|
||||||
if (this.sandbox) {
|
if (this.sandbox) {
|
||||||
console.log('[VercelProvider] Terminating sandbox...');
|
console.log('[VercelProvider] Terminating sandbox...');
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export abstract class SandboxProvider {
|
|||||||
abstract listFiles(directory?: string): Promise<string[]>;
|
abstract listFiles(directory?: string): Promise<string[]>;
|
||||||
abstract installPackages(packages: string[]): Promise<CommandResult>;
|
abstract installPackages(packages: string[]): Promise<CommandResult>;
|
||||||
abstract getSandboxUrl(): string | null;
|
abstract getSandboxUrl(): string | null;
|
||||||
|
abstract getSandboxInfo(): SandboxInfo | null;
|
||||||
abstract terminate(): Promise<void>;
|
abstract terminate(): Promise<void>;
|
||||||
abstract isAlive(): boolean;
|
abstract isAlive(): boolean;
|
||||||
|
|
||||||
|
|||||||
Submodule
+1
Submodule styling-reference/ai-ready-website added at 6944f04ada
Reference in New Issue
Block a user