+ ${mainComponentName ? `<${mainComponentName} />` : '
\n
Welcome to your React App
\n
Your components have been created but need to be added here.
\n
'}
+ {/* Generated components: ${componentFiles.map(f => f.path).join(', ')} */}
+
+ );
+}
+
+export default App;`;
+
+ try {
+ // Use provider pattern if available
+ if (sandbox.writeFile) {
+ await sandbox.writeFile('src/App.jsx', appContent);
+ } else if (sandbox.writeFiles) {
+ await sandbox.writeFiles([{
+ path: 'src/App.jsx',
+ content: Buffer.from(appContent)
+ }]);
+ }
+
+ console.log('Auto-generated: src/App.jsx');
+ results.filesCreated.push('src/App.jsx (auto-generated)');
+ } catch (error) {
+ results.errors.push(`Failed to create App.jsx: ${(error as Error).message}`);
+ }
+
+ // Don't auto-generate App.css - we're using Tailwind CSS
+
+ // Only create index.css if it doesn't exist
+ const indexCssInParsed = parsed.files.some(f => {
+ const normalized = f.path.replace(/^\//, '').replace(/^src\//, '');
+ return normalized === 'index.css' || f.path === 'src/index.css';
+ });
+
+ const indexCssExists = global.existingFiles.has('src/index.css') ||
+ global.existingFiles.has('index.css');
+
+ if (!isEdit && !indexCssInParsed && !indexCssExists) {
+ try {
+ const indexCssContent = `@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+ color-scheme: dark;
+
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #0a0a0a;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ min-width: 320px;
+ min-height: 100vh;
+}`;
+
+ // Use provider pattern if available
+ if (sandbox.writeFile) {
+ await sandbox.writeFile('src/index.css', indexCssContent);
+ } else if (sandbox.writeFiles) {
+ await sandbox.writeFiles([{
+ path: 'src/index.css',
+ content: Buffer.from(indexCssContent)
+ }]);
+ }
+
+ console.log('Auto-generated: src/index.css');
+ results.filesCreated.push('src/index.css (with Tailwind)');
+ } catch (error) {
+ console.error('Failed to create index.css:', error);
+ results.errors.push('Failed to create index.css with Tailwind');
+ }
+ }
+ }
+
+ // Execute commands
+ for (const cmd of parsed.commands) {
+ try {
+ // Parse command and arguments
+ const commandParts = cmd.trim().split(/\s+/);
+ const cmdName = commandParts[0];
+ const args = commandParts.slice(1);
+
+ // Execute command using sandbox
+ let result;
+ if (sandbox.runCommand && typeof sandbox.runCommand === 'function') {
+ // Check if this is a provider pattern sandbox
+ const testResult = await sandbox.runCommand(cmd);
+ if (testResult && typeof testResult === 'object' && 'stdout' in testResult) {
+ // Provider returns CommandResult directly
+ result = testResult;
+ } else {
+ // Direct sandbox - expects object with cmd and args
+ result = await sandbox.runCommand({
+ cmd: cmdName,
+ args
+ });
+ }
+ }
+
+ console.log(`Executed: ${cmd}`);
+
+ // Handle result based on type
+ let stdout = '';
+ let stderr = '';
+
+ if (result) {
+ if (typeof result.stdout === 'string') {
+ stdout = result.stdout;
+ stderr = result.stderr || '';
+ } else if (typeof result.stdout === 'function') {
+ stdout = await result.stdout();
+ stderr = await result.stderr();
+ }
+ }
+
+ if (stdout) console.log(stdout);
+ if (stderr) console.log(`Errors: ${stderr}`);
+
+ results.commandsExecuted.push(cmd);
+ } catch (error) {
+ results.errors.push(`Failed to execute ${cmd}: ${(error as Error).message}`);
+ }
+ }
+
+ // Check for missing imports in App.jsx
+ const missingImports: string[] = [];
+ const appFile = parsed.files.find(f =>
+ f.path === 'src/App.jsx' || f.path === 'App.jsx'
+ );
+
+ if (appFile) {
+ // Extract imports from App.jsx
+ const importRegex = /import\s+(?:\w+|\{[^}]+\})\s+from\s+['"]([^'"]+)['"]/g;
+ let match;
+ const imports: string[] = [];
+
+ while ((match = importRegex.exec(appFile.content)) !== null) {
+ const importPath = match[1];
+ if (importPath.startsWith('./') || importPath.startsWith('../')) {
+ imports.push(importPath);
+ }
+ }
+
+ // Check if all imported files exist
+ for (const imp of imports) {
+ // Skip CSS imports for this check
+ if (imp.endsWith('.css')) continue;
+
+ // Convert import path to expected file paths
+ const basePath = imp.replace('./', 'src/');
+ const possiblePaths = [
+ basePath + '.jsx',
+ basePath + '.js',
+ basePath + '/index.jsx',
+ basePath + '/index.js'
+ ];
+
+ const fileExists = parsed.files.some(f =>
+ possiblePaths.some(path => f.path === path)
+ );
+
+ if (!fileExists) {
+ missingImports.push(imp);
+ }
+ }
+ }
+
+ // Prepare response
+ const responseData: any = {
+ success: true,
+ results,
+ explanation: parsed.explanation,
+ structure: parsed.structure,
+ message: `Applied ${results.filesCreated.length} files successfully`
+ };
+
+ // Handle missing imports automatically
+ if (missingImports.length > 0) {
+ console.warn('[apply-ai-code] Missing imports detected:', missingImports);
+
+ // Automatically generate missing components
+ try {
+ console.log('[apply-ai-code] Auto-generating missing components...');
+
+ const autoCompleteResponse = await fetch(
+ `${request.nextUrl.origin}/api/auto-complete-components`,
+ {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ missingImports,
+ model: 'claude-sonnet-4-20250514'
+ })
+ }
+ );
+
+ const autoCompleteData = await autoCompleteResponse.json();
+
+ if (autoCompleteData.success) {
+ responseData.autoCompleted = true;
+ responseData.autoCompletedComponents = autoCompleteData.components;
+ responseData.message = `Applied ${results.filesCreated.length} files + auto-generated ${autoCompleteData.files} missing components`;
+
+ // Add auto-completed files to results
+ results.filesCreated.push(...autoCompleteData.components);
+ } else {
+ // If auto-complete fails, still warn the user
+ responseData.warning = `Missing ${missingImports.length} imported components: ${missingImports.join(', ')}`;
+ responseData.missingImports = missingImports;
+ }
+ } catch (error) {
+ console.error('[apply-ai-code] Auto-complete failed:', error);
+ responseData.warning = `Missing ${missingImports.length} imported components: ${missingImports.join(', ')}`;
+ responseData.missingImports = missingImports;
+ }
+ }
+
+ // Track applied files in conversation state
+ if (global.conversationState && results.filesCreated.length > 0) {
+ // Update the last message metadata with edited files
+ const messages = global.conversationState.context.messages;
+ if (messages.length > 0) {
+ const lastMessage = messages[messages.length - 1];
+ if (lastMessage.role === 'user') {
+ lastMessage.metadata = {
+ ...lastMessage.metadata,
+ editedFiles: results.filesCreated
+ };
+ }
+ }
+
+ // Track applied code in project evolution
+ if (global.conversationState.context.projectEvolution) {
+ global.conversationState.context.projectEvolution.majorChanges.push({
+ timestamp: Date.now(),
+ description: parsed.explanation || 'Code applied',
+ filesAffected: results.filesCreated
+ });
+ }
+
+ // Update last updated timestamp
+ global.conversationState.lastUpdated = Date.now();
+
+ console.log('[apply-ai-code] Updated conversation state with applied files:', results.filesCreated);
+ }
+
+ return NextResponse.json(responseData);
+
+ } catch (error) {
+ console.error('Apply AI code error:', error);
+ return NextResponse.json(
+ { error: error instanceof Error ? error.message : 'Failed to parse AI code' },
+ { status: 500 }
+ );
+ }
+}
\ No newline at end of file
diff --git a/app/api/check-vite-errors/route.ts b/app/api/check-vite-errors/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9d4968b4bac7fc205438b1b3eefc9539ce9ad3f3
--- /dev/null
+++ b/app/api/check-vite-errors/route.ts
@@ -0,0 +1,12 @@
+import { NextResponse } from 'next/server';
+
+// Stub endpoint to prevent 404 errors
+// This endpoint is being called but the source is unknown
+// Returns empty errors array to satisfy any calling code
+export async function GET() {
+ return NextResponse.json({
+ success: true,
+ errors: [],
+ message: 'No Vite errors detected'
+ });
+}
\ No newline at end of file
diff --git a/app/api/clear-vite-errors-cache/route.ts b/app/api/clear-vite-errors-cache/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d5c87f473094440950e0b8c07523f35fe197b47b
--- /dev/null
+++ b/app/api/clear-vite-errors-cache/route.ts
@@ -0,0 +1,26 @@
+import { NextResponse } from 'next/server';
+
+declare global {
+ var viteErrorsCache: { errors: any[], timestamp: number } | null;
+}
+
+export async function POST() {
+ try {
+ // Clear the cache
+ global.viteErrorsCache = null;
+
+ console.log('[clear-vite-errors-cache] Cache cleared');
+
+ return NextResponse.json({
+ success: true,
+ message: 'Vite errors cache cleared'
+ });
+
+ } catch (error) {
+ console.error('[clear-vite-errors-cache] Error:', error);
+ return NextResponse.json({
+ success: false,
+ error: (error as Error).message
+ }, { status: 500 });
+ }
+}
\ No newline at end of file
diff --git a/app/api/conversation-state/route.ts b/app/api/conversation-state/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..969692c6a79fcddef38dfdcb4de959f4ddec44c9
--- /dev/null
+++ b/app/api/conversation-state/route.ts
@@ -0,0 +1,160 @@
+import { NextRequest, NextResponse } from 'next/server';
+import type { ConversationState } from '@/types/conversation';
+
+declare global {
+ var conversationState: ConversationState | null;
+}
+
+// GET: Retrieve current conversation state
+export async function GET() {
+ try {
+ if (!global.conversationState) {
+ return NextResponse.json({
+ success: true,
+ state: null,
+ message: 'No active conversation'
+ });
+ }
+
+ return NextResponse.json({
+ success: true,
+ state: global.conversationState
+ });
+ } catch (error) {
+ console.error('[conversation-state] Error getting state:', error);
+ return NextResponse.json({
+ success: false,
+ error: (error as Error).message
+ }, { status: 500 });
+ }
+}
+
+// POST: Reset or update conversation state
+export async function POST(request: NextRequest) {
+ try {
+ const { action, data } = await request.json();
+
+ switch (action) {
+ case 'reset':
+ global.conversationState = {
+ conversationId: `conv-${Date.now()}`,
+ startedAt: Date.now(),
+ lastUpdated: Date.now(),
+ context: {
+ messages: [],
+ edits: [],
+ projectEvolution: { majorChanges: [] },
+ userPreferences: {}
+ }
+ };
+
+ console.log('[conversation-state] Reset conversation state');
+
+ return NextResponse.json({
+ success: true,
+ message: 'Conversation state reset',
+ state: global.conversationState
+ });
+
+ case 'clear-old':
+ // Clear old conversation data but keep recent context
+ if (!global.conversationState) {
+ // Initialize conversation state if it doesn't exist
+ global.conversationState = {
+ conversationId: `conv-${Date.now()}`,
+ startedAt: Date.now(),
+ lastUpdated: Date.now(),
+ context: {
+ messages: [],
+ edits: [],
+ projectEvolution: { majorChanges: [] },
+ userPreferences: {}
+ }
+ };
+
+ console.log('[conversation-state] Initialized new conversation state for clear-old');
+
+ return NextResponse.json({
+ success: true,
+ message: 'New conversation state initialized',
+ state: global.conversationState
+ });
+ }
+
+ // Keep only recent data
+ global.conversationState.context.messages = global.conversationState.context.messages.slice(-5);
+ global.conversationState.context.edits = global.conversationState.context.edits.slice(-3);
+ global.conversationState.context.projectEvolution.majorChanges =
+ global.conversationState.context.projectEvolution.majorChanges.slice(-2);
+
+ console.log('[conversation-state] Cleared old conversation data');
+
+ return NextResponse.json({
+ success: true,
+ message: 'Old conversation data cleared',
+ state: global.conversationState
+ });
+
+ case 'update':
+ if (!global.conversationState) {
+ return NextResponse.json({
+ success: false,
+ error: 'No active conversation to update'
+ }, { status: 400 });
+ }
+
+ // Update specific fields if provided
+ if (data) {
+ if (data.currentTopic) {
+ global.conversationState.context.currentTopic = data.currentTopic;
+ }
+ if (data.userPreferences) {
+ global.conversationState.context.userPreferences = {
+ ...global.conversationState.context.userPreferences,
+ ...data.userPreferences
+ };
+ }
+
+ global.conversationState.lastUpdated = Date.now();
+ }
+
+ return NextResponse.json({
+ success: true,
+ message: 'Conversation state updated',
+ state: global.conversationState
+ });
+
+ default:
+ return NextResponse.json({
+ success: false,
+ error: 'Invalid action. Use "reset" or "update"'
+ }, { status: 400 });
+ }
+ } catch (error) {
+ console.error('[conversation-state] Error:', error);
+ return NextResponse.json({
+ success: false,
+ error: (error as Error).message
+ }, { status: 500 });
+ }
+}
+
+// DELETE: Clear conversation state
+export async function DELETE() {
+ try {
+ global.conversationState = null;
+
+ console.log('[conversation-state] Cleared conversation state');
+
+ return NextResponse.json({
+ success: true,
+ message: 'Conversation state cleared'
+ });
+ } catch (error) {
+ console.error('[conversation-state] Error clearing state:', error);
+ return NextResponse.json({
+ success: false,
+ error: (error as Error).message
+ }, { status: 500 });
+ }
+}
\ No newline at end of file
diff --git a/app/api/create-ai-sandbox-v2/route.ts b/app/api/create-ai-sandbox-v2/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cd72a74c789a3b143713cbaa64e24f0dedc85c97
--- /dev/null
+++ b/app/api/create-ai-sandbox-v2/route.ts
@@ -0,0 +1,103 @@
+import { NextResponse } from 'next/server';
+import { SandboxFactory } from '@/lib/sandbox/factory';
+// SandboxProvider type is used through SandboxFactory
+import type { SandboxState } from '@/types/sandbox';
+import { sandboxManager } from '@/lib/sandbox/sandbox-manager';
+
+// Store active sandbox globally
+declare global {
+ var activeSandboxProvider: any;
+ var sandboxData: any;
+ var existingFiles: Set