Spaces:
Build error
Build error
| import { FileManifest, EditType, EditIntent, IntentPattern } from '@/types/file-manifest'; | |
| /** | |
| * Analyze user prompts to determine edit intent and select relevant files | |
| */ | |
| export function analyzeEditIntent( | |
| prompt: string, | |
| manifest: FileManifest | |
| ): EditIntent { | |
| const lowerPrompt = prompt.toLowerCase(); | |
| // Define intent patterns | |
| const patterns: IntentPattern[] = [ | |
| { | |
| patterns: [ | |
| /update\s+(the\s+)?(\w+)\s+(component|section|page)/i, | |
| /change\s+(the\s+)?(\w+)/i, | |
| /modify\s+(the\s+)?(\w+)/i, | |
| /edit\s+(the\s+)?(\w+)/i, | |
| /fix\s+(the\s+)?(\w+)\s+(styling|style|css|layout)/i, | |
| /remove\s+.*\s+(button|link|text|element|section)/i, | |
| /delete\s+.*\s+(button|link|text|element|section)/i, | |
| /hide\s+.*\s+(button|link|text|element|section)/i, | |
| ], | |
| type: EditType.UPDATE_COMPONENT, | |
| fileResolver: (p, m) => findComponentByContent(p, m), | |
| }, | |
| { | |
| patterns: [ | |
| /add\s+(a\s+)?new\s+(\w+)\s+(page|section|feature|component)/i, | |
| /create\s+(a\s+)?(\w+)\s+(page|section|feature|component)/i, | |
| /implement\s+(a\s+)?(\w+)\s+(page|section|feature)/i, | |
| /build\s+(a\s+)?(\w+)\s+(page|section|feature)/i, | |
| /add\s+(\w+)\s+to\s+(?:the\s+)?(\w+)/i, | |
| /add\s+(?:a\s+)?(\w+)\s+(?:component|section)/i, | |
| /include\s+(?:a\s+)?(\w+)/i, | |
| ], | |
| type: EditType.ADD_FEATURE, | |
| fileResolver: (p, m) => findFeatureInsertionPoints(p, m), | |
| }, | |
| { | |
| patterns: [ | |
| /fix\s+(the\s+)?(\w+|\w+\s+\w+)(?!\s+styling|\s+style)/i, | |
| /resolve\s+(the\s+)?error/i, | |
| /debug\s+(the\s+)?(\w+)/i, | |
| /repair\s+(the\s+)?(\w+)/i, | |
| ], | |
| type: EditType.FIX_ISSUE, | |
| fileResolver: (p, m) => findProblemFiles(p, m), | |
| }, | |
| { | |
| patterns: [ | |
| /change\s+(the\s+)?(color|theme|style|styling|css)/i, | |
| /update\s+(the\s+)?(color|theme|style|styling|css)/i, | |
| /make\s+it\s+(dark|light|blue|red|green)/i, | |
| /style\s+(the\s+)?(\w+)/i, | |
| ], | |
| type: EditType.UPDATE_STYLE, | |
| fileResolver: (p, m) => findStyleFiles(p, m), | |
| }, | |
| { | |
| patterns: [ | |
| /refactor\s+(the\s+)?(\w+)/i, | |
| /clean\s+up\s+(the\s+)?code/i, | |
| /reorganize\s+(the\s+)?(\w+)/i, | |
| /optimize\s+(the\s+)?(\w+)/i, | |
| ], | |
| type: EditType.REFACTOR, | |
| fileResolver: (p, m) => findRefactorTargets(p, m), | |
| }, | |
| { | |
| patterns: [ | |
| /start\s+over/i, | |
| /recreate\s+everything/i, | |
| /rebuild\s+(the\s+)?app/i, | |
| /new\s+app/i, | |
| /from\s+scratch/i, | |
| ], | |
| type: EditType.FULL_REBUILD, | |
| fileResolver: (p, m) => [m.entryPoint], | |
| }, | |
| { | |
| patterns: [ | |
| /install\s+(\w+)/i, | |
| /add\s+(\w+)\s+(package|library|dependency)/i, | |
| /use\s+(\w+)\s+(library|framework)/i, | |
| ], | |
| type: EditType.ADD_DEPENDENCY, | |
| fileResolver: (p, m) => findPackageFiles(m), | |
| }, | |
| ]; | |
| // Find matching pattern | |
| for (const pattern of patterns) { | |
| for (const regex of pattern.patterns) { | |
| if (regex.test(lowerPrompt)) { | |
| const targetFiles = pattern.fileResolver(prompt, manifest); | |
| const suggestedContext = getSuggestedContext(targetFiles, manifest); | |
| return { | |
| type: pattern.type, | |
| targetFiles, | |
| confidence: calculateConfidence(prompt, pattern, targetFiles), | |
| description: generateDescription(pattern.type, prompt, targetFiles), | |
| suggestedContext, | |
| }; | |
| } | |
| } | |
| } | |
| // Default to component update if no pattern matches | |
| return { | |
| type: EditType.UPDATE_COMPONENT, | |
| targetFiles: [manifest.entryPoint], | |
| confidence: 0.3, | |
| description: 'General update to application', | |
| suggestedContext: [], | |
| }; | |
| } | |
| /** | |
| * Find component files mentioned in the prompt | |
| */ | |
| function findComponentFiles(prompt: string, manifest: FileManifest): string[] { | |
| const files: string[] = []; | |
| const lowerPrompt = prompt.toLowerCase(); | |
| // Extract component names from prompt | |
| const componentWords = extractComponentNames(prompt); | |
| console.log('[findComponentFiles] Extracted words:', componentWords); | |
| // First pass: Look for exact component file matches | |
| for (const [path, fileInfo] of Object.entries(manifest.files)) { | |
| // Check if file name or component name matches | |
| const fileName = path.split('/').pop()?.toLowerCase() || ''; | |
| const componentName = fileInfo.componentInfo?.name.toLowerCase(); | |
| for (const word of componentWords) { | |
| if (fileName.includes(word) || componentName?.includes(word)) { | |
| console.log(`[findComponentFiles] Match found: word="${word}" in file="${path}"`); | |
| files.push(path); | |
| break; // Stop after first match to avoid duplicates | |
| } | |
| } | |
| } | |
| // If no specific component found, check for common UI elements | |
| if (files.length === 0) { | |
| const uiElements = ['header', 'footer', 'nav', 'sidebar', 'button', 'card', 'modal', 'hero', 'banner', 'about', 'services', 'features', 'testimonials', 'gallery', 'contact', 'team', 'pricing']; | |
| for (const element of uiElements) { | |
| if (lowerPrompt.includes(element)) { | |
| // Look for exact component file matches first | |
| for (const [path, fileInfo] of Object.entries(manifest.files)) { | |
| const fileName = path.split('/').pop()?.toLowerCase() || ''; | |
| // Only match if the filename contains the element name | |
| if (fileName.includes(element + '.') || fileName === element) { | |
| files.push(path); | |
| console.log(`[findComponentFiles] UI element match: element="${element}" in file="${path}"`); | |
| return files; // Return immediately with just this file | |
| } | |
| } | |
| // If no exact file match, look for the element in file names (but be more selective) | |
| for (const [path, fileInfo] of Object.entries(manifest.files)) { | |
| const fileName = path.split('/').pop()?.toLowerCase() || ''; | |
| if (fileName.includes(element)) { | |
| files.push(path); | |
| console.log(`[findComponentFiles] UI element partial match: element="${element}" in file="${path}"`); | |
| return files; // Return immediately with just this file | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Limit results to most specific matches | |
| if (files.length > 1) { | |
| console.log(`[findComponentFiles] Multiple files found (${files.length}), limiting to first match`); | |
| return [files[0]]; // Only return the first match | |
| } | |
| return files.length > 0 ? files : [manifest.entryPoint]; | |
| } | |
| /** | |
| * Find where to add new features | |
| */ | |
| function findFeatureInsertionPoints(prompt: string, manifest: FileManifest): string[] { | |
| const files: string[] = []; | |
| const lowerPrompt = prompt.toLowerCase(); | |
| // For new pages, we need routing files and layout | |
| if (lowerPrompt.includes('page')) { | |
| // Find router configuration | |
| for (const [path, fileInfo] of Object.entries(manifest.files)) { | |
| if (fileInfo.content.includes('Route') || | |
| fileInfo.content.includes('createBrowserRouter') || | |
| path.includes('router') || | |
| path.includes('routes')) { | |
| files.push(path); | |
| } | |
| } | |
| // Also include App.jsx for navigation updates | |
| if (manifest.entryPoint) { | |
| files.push(manifest.entryPoint); | |
| } | |
| } | |
| // For new components, find the most appropriate parent | |
| if (lowerPrompt.includes('component') || lowerPrompt.includes('section') || | |
| lowerPrompt.includes('add') || lowerPrompt.includes('create')) { | |
| // Extract where to add it (e.g., "to the footer", "in header") | |
| const locationMatch = prompt.match(/(?:in|to|on|inside)\s+(?:the\s+)?(\w+)/i); | |
| if (locationMatch) { | |
| const location = locationMatch[1]; | |
| const parentFiles = findComponentFiles(location, manifest); | |
| files.push(...parentFiles); | |
| console.log(`[findFeatureInsertionPoints] Adding to ${location}, parent files:`, parentFiles); | |
| } else { | |
| // Look for component mentions in the prompt | |
| const componentWords = extractComponentNames(prompt); | |
| for (const word of componentWords) { | |
| const relatedFiles = findComponentFiles(word, manifest); | |
| if (relatedFiles.length > 0 && relatedFiles[0] !== manifest.entryPoint) { | |
| files.push(...relatedFiles); | |
| } | |
| } | |
| // Default to App.jsx if no specific location found | |
| if (files.length === 0) { | |
| files.push(manifest.entryPoint); | |
| } | |
| } | |
| } | |
| // Remove duplicates | |
| return [...new Set(files)]; | |
| } | |
| /** | |
| * Find files that might have problems | |
| */ | |
| function findProblemFiles(prompt: string, manifest: FileManifest): string[] { | |
| const files: string[] = []; | |
| // Look for error keywords | |
| if (prompt.match(/error|bug|issue|problem|broken|not working/i)) { | |
| // Check recently modified files first | |
| const sortedFiles = Object.entries(manifest.files) | |
| .sort(([, a], [, b]) => b.lastModified - a.lastModified) | |
| .slice(0, 5); | |
| files.push(...sortedFiles.map(([path]) => path)); | |
| } | |
| // Also check for specific component mentions | |
| const componentFiles = findComponentFiles(prompt, manifest); | |
| files.push(...componentFiles); | |
| return [...new Set(files)]; | |
| } | |
| /** | |
| * Find style-related files | |
| */ | |
| function findStyleFiles(prompt: string, manifest: FileManifest): string[] { | |
| const files: string[] = []; | |
| // Add all CSS files | |
| files.push(...manifest.styleFiles); | |
| // Check for Tailwind config | |
| const tailwindConfig = Object.keys(manifest.files).find( | |
| path => path.includes('tailwind.config') | |
| ); | |
| if (tailwindConfig) files.push(tailwindConfig); | |
| // If specific component styling mentioned, include that component | |
| const componentFiles = findComponentFiles(prompt, manifest); | |
| files.push(...componentFiles); | |
| return files; | |
| } | |
| /** | |
| * Find files to refactor | |
| */ | |
| function findRefactorTargets(prompt: string, manifest: FileManifest): string[] { | |
| // Similar to findComponentFiles but broader | |
| return findComponentFiles(prompt, manifest); | |
| } | |
| /** | |
| * Find package configuration files | |
| */ | |
| function findPackageFiles(manifest: FileManifest): string[] { | |
| const files: string[] = []; | |
| for (const path of Object.keys(manifest.files)) { | |
| if (path.endsWith('package.json') || | |
| path.endsWith('vite.config.js') || | |
| path.endsWith('tsconfig.json')) { | |
| files.push(path); | |
| } | |
| } | |
| return files; | |
| } | |
| /** | |
| * Find component by searching for content mentioned in the prompt | |
| */ | |
| function findComponentByContent(prompt: string, manifest: FileManifest): string[] { | |
| const files: string[] = []; | |
| const lowerPrompt = prompt.toLowerCase(); | |
| console.log('[findComponentByContent] Searching for content in prompt:', prompt); | |
| // Extract quoted strings or specific button/link text | |
| const quotedStrings = prompt.match(/["']([^"']+)["']/g) || []; | |
| const searchTerms: string[] = quotedStrings.map(s => s.replace(/["']/g, '')); | |
| // Also look for specific terms after 'remove', 'delete', 'hide' | |
| const actionMatch = prompt.match(/(?:remove|delete|hide)\s+(?:the\s+)?(.+?)(?:\s+button|\s+link|\s+text|\s+element|\s+section|$)/i); | |
| if (actionMatch) { | |
| searchTerms.push(actionMatch[1].trim()); | |
| } | |
| console.log('[findComponentByContent] Search terms:', searchTerms); | |
| // If we have search terms, look for them in file contents | |
| if (searchTerms.length > 0) { | |
| for (const [path, fileInfo] of Object.entries(manifest.files)) { | |
| // Only search in component files | |
| if (!path.includes('.jsx') && !path.includes('.tsx')) continue; | |
| const content = fileInfo.content.toLowerCase(); | |
| for (const term of searchTerms) { | |
| if (content.includes(term.toLowerCase())) { | |
| console.log(`[findComponentByContent] Found "${term}" in ${path}`); | |
| files.push(path); | |
| break; // Only add file once | |
| } | |
| } | |
| } | |
| } | |
| // If no files found by content, fall back to component name search | |
| if (files.length === 0) { | |
| console.log('[findComponentByContent] No files found by content, falling back to component name search'); | |
| return findComponentFiles(prompt, manifest); | |
| } | |
| // Return only the first match to avoid editing multiple files | |
| return [files[0]]; | |
| } | |
| /** | |
| * Extract component names from prompt | |
| */ | |
| function extractComponentNames(prompt: string): string[] { | |
| const words: string[] = []; | |
| // Remove common words but keep component-related words | |
| const cleanPrompt = prompt | |
| .replace(/\b(the|a|an|in|on|to|from|update|change|modify|edit|fix|make)\b/gi, '') | |
| .toLowerCase(); | |
| // Extract potential component names (words that might be components) | |
| const matches = cleanPrompt.match(/\b\w+\b/g) || []; | |
| for (const match of matches) { | |
| if (match.length > 2) { // Skip very short words | |
| words.push(match); | |
| } | |
| } | |
| return words; | |
| } | |
| /** | |
| * Get additional files for context - returns ALL files for comprehensive context | |
| */ | |
| function getSuggestedContext( | |
| targetFiles: string[], | |
| manifest: FileManifest | |
| ): string[] { | |
| // Return all files except the ones being edited | |
| const allFiles = Object.keys(manifest.files); | |
| return allFiles.filter(file => !targetFiles.includes(file)); | |
| } | |
| /** | |
| * Resolve import path to actual file path | |
| */ | |
| function resolveImportPath( | |
| fromFile: string, | |
| importPath: string, | |
| manifest: FileManifest | |
| ): string | null { | |
| // Handle relative imports | |
| if (importPath.startsWith('./') || importPath.startsWith('../')) { | |
| const fromDir = fromFile.substring(0, fromFile.lastIndexOf('/')); | |
| const resolved = resolveRelativePath(fromDir, importPath); | |
| // Try with different extensions | |
| const extensions = ['.jsx', '.js', '.tsx', '.ts', '']; | |
| for (const ext of extensions) { | |
| const fullPath = resolved + ext; | |
| if (manifest.files[fullPath]) { | |
| return fullPath; | |
| } | |
| // Try index file | |
| const indexPath = resolved + '/index' + ext; | |
| if (manifest.files[indexPath]) { | |
| return indexPath; | |
| } | |
| } | |
| } | |
| // Handle @/ alias (common in Vite projects) | |
| if (importPath.startsWith('@/')) { | |
| const srcPath = importPath.replace('@/', '/home/user/app/src/'); | |
| return resolveImportPath(fromFile, srcPath, manifest); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Resolve relative path | |
| */ | |
| function resolveRelativePath(fromDir: string, relativePath: string): string { | |
| const parts = fromDir.split('/'); | |
| const relParts = relativePath.split('/'); | |
| for (const part of relParts) { | |
| if (part === '..') { | |
| parts.pop(); | |
| } else if (part !== '.') { | |
| parts.push(part); | |
| } | |
| } | |
| return parts.join('/'); | |
| } | |
| /** | |
| * Calculate confidence score | |
| */ | |
| function calculateConfidence( | |
| prompt: string, | |
| pattern: IntentPattern, | |
| targetFiles: string[] | |
| ): number { | |
| let confidence = 0.5; // Base confidence | |
| // Higher confidence if we found specific files | |
| if (targetFiles.length > 0 && targetFiles[0] !== '') { | |
| confidence += 0.2; | |
| } | |
| // Higher confidence for more specific prompts | |
| if (prompt.split(' ').length > 5) { | |
| confidence += 0.1; | |
| } | |
| // Higher confidence for exact pattern matches | |
| for (const regex of pattern.patterns) { | |
| if (regex.test(prompt)) { | |
| confidence += 0.2; | |
| break; | |
| } | |
| } | |
| return Math.min(confidence, 1.0); | |
| } | |
| /** | |
| * Generate human-readable description | |
| */ | |
| function generateDescription( | |
| type: EditType, | |
| prompt: string, | |
| targetFiles: string[] | |
| ): string { | |
| const fileNames = targetFiles.map(f => f.split('/').pop()).join(', '); | |
| switch (type) { | |
| case EditType.UPDATE_COMPONENT: | |
| return `Updating component(s): ${fileNames}`; | |
| case EditType.ADD_FEATURE: | |
| return `Adding new feature to: ${fileNames}`; | |
| case EditType.FIX_ISSUE: | |
| return `Fixing issue in: ${fileNames}`; | |
| case EditType.UPDATE_STYLE: | |
| return `Updating styles in: ${fileNames}`; | |
| case EditType.REFACTOR: | |
| return `Refactoring: ${fileNames}`; | |
| case EditType.FULL_REBUILD: | |
| return 'Rebuilding entire application'; | |
| case EditType.ADD_DEPENDENCY: | |
| return 'Adding new dependency'; | |
| default: | |
| return `Editing: ${fileNames}`; | |
| } | |
| } |