initial
This commit is contained in:
@@ -0,0 +1,363 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user