Spaces:
Build error
Build error
| import { FileManifest, EditIntent, EditType } from '@/types/file-manifest'; | |
| import { analyzeEditIntent } from '@/lib/edit-intent-analyzer'; | |
| import { getEditExamplesPrompt, getComponentPatternPrompt } from '@/lib/edit-examples'; | |
| export interface FileContext { | |
| primaryFiles: string[]; // Files to edit | |
| contextFiles: string[]; // Files to include for reference | |
| systemPrompt: string; // Enhanced prompt with file info | |
| editIntent: EditIntent; | |
| } | |
| /** | |
| * Select files and build context based on user prompt | |
| */ | |
| export function selectFilesForEdit( | |
| userPrompt: string, | |
| manifest: FileManifest | |
| ): FileContext { | |
| // Analyze the edit intent | |
| const editIntent = analyzeEditIntent(userPrompt, manifest); | |
| // Get the files based on intent - only edit target files, but provide all others as context | |
| const primaryFiles = editIntent.targetFiles; | |
| const allFiles = Object.keys(manifest.files); | |
| let contextFiles = allFiles.filter(file => !primaryFiles.includes(file)); | |
| // ALWAYS include key files in context if they exist and aren't already primary files | |
| const keyFiles: string[] = []; | |
| // App.jsx is most important - shows component structure | |
| const appFile = allFiles.find(f => f.endsWith('App.jsx') || f.endsWith('App.tsx')); | |
| if (appFile && !primaryFiles.includes(appFile)) { | |
| keyFiles.push(appFile); | |
| } | |
| // Include design system files for style context | |
| const tailwindConfig = allFiles.find(f => f.endsWith('tailwind.config.js') || f.endsWith('tailwind.config.ts')); | |
| if (tailwindConfig && !primaryFiles.includes(tailwindConfig)) { | |
| keyFiles.push(tailwindConfig); | |
| } | |
| const indexCss = allFiles.find(f => f.endsWith('index.css') || f.endsWith('globals.css')); | |
| if (indexCss && !primaryFiles.includes(indexCss)) { | |
| keyFiles.push(indexCss); | |
| } | |
| // Include package.json to understand dependencies | |
| const packageJson = allFiles.find(f => f.endsWith('package.json')); | |
| if (packageJson && !primaryFiles.includes(packageJson)) { | |
| keyFiles.push(packageJson); | |
| } | |
| // Put key files at the beginning of context for visibility | |
| contextFiles = [...keyFiles, ...contextFiles.filter(f => !keyFiles.includes(f))]; | |
| // Build enhanced system prompt | |
| const systemPrompt = buildSystemPrompt( | |
| userPrompt, | |
| editIntent, | |
| primaryFiles, | |
| contextFiles, | |
| manifest | |
| ); | |
| return { | |
| primaryFiles, | |
| contextFiles, | |
| systemPrompt, | |
| editIntent, | |
| }; | |
| } | |
| /** | |
| * Build an enhanced system prompt with file structure context | |
| */ | |
| function buildSystemPrompt( | |
| userPrompt: string, | |
| editIntent: EditIntent, | |
| primaryFiles: string[], | |
| contextFiles: string[], | |
| manifest: FileManifest | |
| ): string { | |
| const sections: string[] = []; | |
| // Add edit examples first for better understanding | |
| if (editIntent.type !== EditType.FULL_REBUILD) { | |
| sections.push(getEditExamplesPrompt()); | |
| } | |
| // Add edit intent section | |
| sections.push(`## Edit Intent | |
| Type: ${editIntent.type} | |
| Description: ${editIntent.description} | |
| Confidence: ${(editIntent.confidence * 100).toFixed(0)}% | |
| User Request: "${userPrompt}"`); | |
| // Add file structure overview | |
| sections.push(buildFileStructureSection(manifest)); | |
| // Add component patterns | |
| const fileList = Object.keys(manifest.files).map(f => f.replace('/home/user/app/', '')).join('\n'); | |
| sections.push(getComponentPatternPrompt(fileList)); | |
| // Add primary files section | |
| if (primaryFiles.length > 0) { | |
| sections.push(`## Files to Edit | |
| ${primaryFiles.map(f => { | |
| const fileInfo = manifest.files[f]; | |
| return `- ${f}${fileInfo?.componentInfo ? ` (${fileInfo.componentInfo.name} component)` : ''}`; | |
| }).join('\n')}`); | |
| } | |
| // Add context files section | |
| if (contextFiles.length > 0) { | |
| sections.push(`## Context Files (for reference only) | |
| ${contextFiles.map(f => { | |
| const fileInfo = manifest.files[f]; | |
| return `- ${f}${fileInfo?.componentInfo ? ` (${fileInfo.componentInfo.name} component)` : ''}`; | |
| }).join('\n')}`); | |
| } | |
| // Add specific instructions based on edit type | |
| sections.push(buildEditInstructions(editIntent.type)); | |
| // Add component relationships if relevant | |
| if (editIntent.type === EditType.UPDATE_COMPONENT || | |
| editIntent.type === EditType.ADD_FEATURE) { | |
| sections.push(buildComponentRelationships(primaryFiles, manifest)); | |
| } | |
| return sections.join('\n\n'); | |
| } | |
| /** | |
| * Build file structure overview section | |
| */ | |
| function buildFileStructureSection(manifest: FileManifest): string { | |
| const allFiles = Object.entries(manifest.files) | |
| .map(([path]) => path.replace('/home/user/app/', '')) | |
| .filter(path => !path.includes('node_modules')) | |
| .sort(); | |
| const componentFiles = Object.entries(manifest.files) | |
| .filter(([, info]) => info.type === 'component' || info.type === 'page') | |
| .map(([path, info]) => ({ | |
| path: path.replace('/home/user/app/', ''), | |
| name: info.componentInfo?.name || path.split('/').pop(), | |
| type: info.type, | |
| })); | |
| return `## 🚨 EXISTING PROJECT FILES - DO NOT CREATE NEW FILES WITH SIMILAR NAMES 🚨 | |
| ### ALL PROJECT FILES (${allFiles.length} files) | |
| \`\`\` | |
| ${allFiles.join('\n')} | |
| \`\`\` | |
| ### Component Files (USE THESE EXACT NAMES) | |
| ${componentFiles.map(f => | |
| `- ${f.name} → ${f.path} (${f.type})` | |
| ).join('\n')} | |
| ### CRITICAL: Component Relationships | |
| **ALWAYS CHECK App.jsx FIRST** to understand what components exist and how they're imported! | |
| Common component overlaps to watch for: | |
| - "nav" or "navigation" → Often INSIDE Header.jsx, not a separate file | |
| - "menu" → Usually part of Header/Nav, not separate | |
| - "logo" → Typically in Header, not standalone | |
| When user says "nav" or "navigation": | |
| 1. First check if Header.jsx exists | |
| 2. Look inside Header.jsx for navigation elements | |
| 3. Only create Nav.jsx if navigation doesn't exist anywhere | |
| Entry Point: ${manifest.entryPoint} | |
| ### Routes | |
| ${manifest.routes.map(r => | |
| `- ${r.path} → ${r.component.split('/').pop()}` | |
| ).join('\n') || 'No routes detected'}`; | |
| } | |
| /** | |
| * Build edit-type specific instructions | |
| */ | |
| function buildEditInstructions(editType: EditType): string { | |
| const instructions: Record<EditType, string> = { | |
| [EditType.UPDATE_COMPONENT]: `## SURGICAL EDIT INSTRUCTIONS | |
| - You MUST preserve 99% of the original code | |
| - ONLY edit the specific component(s) mentioned | |
| - Make ONLY the minimal change requested | |
| - DO NOT rewrite or refactor unless explicitly asked | |
| - DO NOT remove any existing code unless explicitly asked | |
| - DO NOT change formatting or structure | |
| - Preserve all imports and exports | |
| - Maintain the existing code style | |
| - Return the COMPLETE file with the surgical change applied | |
| - Think of yourself as a surgeon making a precise incision, not an artist repainting`, | |
| [EditType.ADD_FEATURE]: `## Instructions | |
| - Create new components in appropriate directories | |
| - IMPORTANT: Update parent components to import and use the new component | |
| - Update routing if adding new pages | |
| - Follow existing patterns and conventions | |
| - Add necessary styles to match existing design | |
| - Example workflow: | |
| 1. Create NewComponent.jsx | |
| 2. Import it in the parent: import NewComponent from './NewComponent' | |
| 3. Use it in the parent's render: <NewComponent />`, | |
| [EditType.FIX_ISSUE]: `## Instructions | |
| - Identify and fix the specific issue | |
| - Test the fix doesn't break other functionality | |
| - Preserve existing behavior except for the bug | |
| - Add error handling if needed`, | |
| [EditType.UPDATE_STYLE]: `## SURGICAL STYLE EDIT INSTRUCTIONS | |
| - Change ONLY the specific style/class mentioned | |
| - If user says "change background to blue", change ONLY the background class | |
| - DO NOT touch any other styles, classes, or attributes | |
| - DO NOT refactor or "improve" the styling | |
| - DO NOT change the component structure | |
| - Preserve ALL other classes and styles exactly as they are | |
| - Return the COMPLETE file with only the specific style change`, | |
| [EditType.REFACTOR]: `## Instructions | |
| - Improve code quality without changing functionality | |
| - Follow project conventions | |
| - Maintain all existing features | |
| - Improve readability and maintainability`, | |
| [EditType.FULL_REBUILD]: `## Instructions | |
| - You may rebuild the entire application | |
| - Keep the same core functionality | |
| - Improve upon the existing design | |
| - Use modern best practices`, | |
| [EditType.ADD_DEPENDENCY]: `## Instructions | |
| - Update package.json with new dependency | |
| - Add necessary import statements | |
| - Configure the dependency if needed | |
| - Update any build configuration`, | |
| }; | |
| return instructions[editType] || instructions[EditType.UPDATE_COMPONENT]; | |
| } | |
| /** | |
| * Build component relationship information | |
| */ | |
| function buildComponentRelationships( | |
| files: string[], | |
| manifest: FileManifest | |
| ): string { | |
| const relationships: string[] = ['## Component Relationships']; | |
| for (const file of files) { | |
| const fileInfo = manifest.files[file]; | |
| if (!fileInfo?.componentInfo) continue; | |
| const componentName = fileInfo.componentInfo.name; | |
| const treeNode = manifest.componentTree[componentName]; | |
| if (treeNode) { | |
| relationships.push(`\n### ${componentName}`); | |
| if (treeNode.imports.length > 0) { | |
| relationships.push(`Imports: ${treeNode.imports.join(', ')}`); | |
| } | |
| if (treeNode.importedBy.length > 0) { | |
| relationships.push(`Used by: ${treeNode.importedBy.join(', ')}`); | |
| } | |
| if (fileInfo.componentInfo.childComponents?.length) { | |
| relationships.push(`Renders: ${fileInfo.componentInfo.childComponents.join(', ')}`); | |
| } | |
| } | |
| } | |
| return relationships.join('\n'); | |
| } | |
| /** | |
| * Get file content for selected files | |
| */ | |
| export async function getFileContents( | |
| files: string[], | |
| manifest: FileManifest | |
| ): Promise<Record<string, string>> { | |
| const contents: Record<string, string> = {}; | |
| for (const file of files) { | |
| const fileInfo = manifest.files[file]; | |
| if (fileInfo) { | |
| contents[file] = fileInfo.content; | |
| } | |
| } | |
| return contents; | |
| } | |
| /** | |
| * Format files for AI context | |
| */ | |
| export function formatFilesForAI( | |
| primaryFiles: Record<string, string>, | |
| contextFiles: Record<string, string> | |
| ): string { | |
| const sections: string[] = []; | |
| // Add primary files | |
| sections.push('## Files to Edit (ONLY OUTPUT THESE FILES)\n'); | |
| sections.push('🚨 You MUST ONLY generate the files listed below. Do NOT generate any other files! 🚨\n'); | |
| sections.push('⚠️ CRITICAL: Return the COMPLETE file - NEVER truncate with "..." or skip any lines! ⚠️\n'); | |
| sections.push('The file MUST include ALL imports, ALL functions, ALL JSX, and ALL closing tags.\n\n'); | |
| for (const [path, content] of Object.entries(primaryFiles)) { | |
| sections.push(`### ${path} | |
| **IMPORTANT: This is the COMPLETE file. Your output must include EVERY line shown below, modified only where necessary.** | |
| \`\`\`${getFileExtension(path)} | |
| ${content} | |
| \`\`\` | |
| `); | |
| } | |
| // Add context files if any - but truncate large files | |
| if (Object.keys(contextFiles).length > 0) { | |
| sections.push('\n## Context Files (Reference Only - Do Not Edit)\n'); | |
| for (const [path, content] of Object.entries(contextFiles)) { | |
| // Truncate very large context files to save tokens | |
| let truncatedContent = content; | |
| if (content.length > 2000) { | |
| truncatedContent = content.substring(0, 2000) + '\n// ... [truncated for context length]'; | |
| } | |
| sections.push(`### ${path} | |
| \`\`\`${getFileExtension(path)} | |
| ${truncatedContent} | |
| \`\`\` | |
| `); | |
| } | |
| } | |
| return sections.join('\n'); | |
| } | |
| /** | |
| * Get file extension for syntax highlighting | |
| */ | |
| function getFileExtension(path: string): string { | |
| const ext = path.split('.').pop() || ''; | |
| const mapping: Record<string, string> = { | |
| 'js': 'javascript', | |
| 'jsx': 'javascript', | |
| 'ts': 'typescript', | |
| 'tsx': 'typescript', | |
| 'css': 'css', | |
| 'json': 'json', | |
| }; | |
| return mapping[ext] || ext; | |
| } |