Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat react css components lib #133

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
removeCodeBlockFences,
extractJsonFromText,
formatResponse,
mergePaths,
} from 'src/build-system/utils/strings';
import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper';
import {
Expand Down Expand Up @@ -109,7 +110,7 @@ export const prompts = {
switch (projectPart.toLowerCase()) {
case 'frontend':
roleDescription = 'an expert frontend developer';
includeSections = `
includeSections = `
Non-SPA Folder Structure example:
src/
contexts/ - Global state management
Expand Down Expand Up @@ -280,7 +281,17 @@ Output Format:
- Organize the output in a \`files\` object where keys are file paths, and values are their dependency objects.
- For the router, remember to include all the page components as dependencies, as the router imports them to define the application routes.

4. **Output Requirements**:
4. **UI Component Dependencies**:
- This project uses the shadcn UI component library.
- Components that likely need UI elements (forms, buttons, inputs, etc.) should include appropriate shadcn component dependencies.
- Shadcn components are imported with the syntax @/components/ui/[component-name].tsx
- Analyze component purposes carefully to determine which shadcn components they should depend on
- Examples:
- A login form component should depend on form.tsx, input.tsx, and button.tsx
- A data table component should depend on table.tsx
- A navigation component with dropdowns should depend on dropdown-menu.tsx

5. **Output Requirements**:
- The JSON object must strictly follow this structure:
\`\`\`json
<GENERATE>
Expand Down Expand Up @@ -457,6 +468,22 @@ export class FileStructureAndArchitectureHandler
};
}

let added_structure = '';
try {
added_structure = mergePaths(fileStructureJsonContent);
if (!added_structure) {
this.logger.error('Failed to add directory.' + added_structure);
throw new ResponseParsingError('Failed to add directory.');
}
} catch (error) {
return {
success: false,
error: new ResponseParsingError(
`Failed to add directory. ${error.message}`,
),
};
}

context.virtualDirectory.getAllFiles().forEach((file) => {
this.logger.log(file);
});
Expand All @@ -466,7 +493,7 @@ export class FileStructureAndArchitectureHandler
this.logger.log('Generating File Architecture Document...');

this.virtualDir = context.virtualDirectory;
const fileStructure = removeCodeBlockFences(fileStructureContent);
const fileStructure = removeCodeBlockFences(added_structure);
if (!fileStructure || !datamapDoc) {
return {
success: false,
Expand Down Expand Up @@ -598,18 +625,32 @@ export class FileStructureAndArchitectureHandler
}
}

/**
* Validates the structure and content of the JSON data.
* @param jsonData The JSON data to validate.
* @returns A boolean indicating whether the JSON data is valid.
*/
private validateJsonData(jsonData: {
files: Record<string, { dependsOn: string[] }>;
}): boolean {
const validPathRegex = /^[a-zA-Z0-9_\-/.]+$/;

const shouldIgnore = (filePath: string) => {
// this.logger.log(`Checking if should ignore: ${filePath}`);
return filePath.startsWith('@/components/ui/');
};

for (const [file, details] of Object.entries(jsonData.files)) {
if (!validPathRegex.test(file)) {
this.logger.error(`Invalid file path: ${file}`);
return false;
}

for (const dependency of details.dependsOn) {
if (shouldIgnore(dependency)) {
continue;
}

if (!validPathRegex.test(dependency)) {
this.logger.error(
`Invalid dependency path "${dependency}" in file "${file}".`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import { readFileSync } from 'fs';
import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper';
import { createFileWithRetries } from 'src/build-system/utils/files';
import { BuilderContext } from 'src/build-system/context';
import { removeCodeBlockFences } from 'src/build-system/utils/strings';
import {
parseGenerateTag,
removeCodeBlockFences,
} from 'src/build-system/utils/strings';
import {
generateCommonErrorPrompt,
generateFileOperationPrompt,
Expand Down Expand Up @@ -227,7 +230,7 @@ export class FrontendQueueProcessor {
},
{
role: 'assistant',
content: `Let me check my result and I must follow the output format.`,
content: `Let me check my result and I must follow the output format I shouldn't write explain outside the json.`,
},
],
},
Expand All @@ -238,11 +241,8 @@ export class FrontendQueueProcessor {
this.logger.debug('Fix Response: ' + fixResponse);
this.logger.debug('dependency file Paths ' + task.dependenciesPath);
const parsed_fixResponse = removeCodeBlockFences(fixResponse);

let operations = fileOperationManager.parse(
parsed_fixResponse,
task.filePath,
);
const cleaned_Data = parseGenerateTag(parsed_fixResponse);
let operations = fileOperationManager.parse(cleaned_Data, task.filePath);

// **If LLM requested additional files, read them**
if (operations.some((op) => op.action === 'read')) {
Expand Down Expand Up @@ -307,7 +307,7 @@ export class FrontendQueueProcessor {
},
{
role: 'assistant',
content: `Let me check my result and I must follow the output format`,
content: `Let me check my result and I must follow the output format I shouldn't write explain outside the json`,
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@
const { concurrencyLayers, fileInfos } =
await generateFilesDependencyWithLayers(fileArchDoc, this.virtualDir);

// concurrencyLayers.forEach((layer, index) => {
// console.log(`Layer #${index + 1} has ${layer.length} file(s):`, layer);
// });

// Object.entries(fileInfos).forEach(([filePath, info]) => {
// this.logger.debug(`File: ${filePath}`);
// this.logger.debug(`Depends On: ${info.dependsOn.join(', ')}`);
// });

const validator = new FrontendCodeValidator(frontendPath);
// validator.installDependencies();

Expand Down Expand Up @@ -116,7 +125,7 @@
`Layer #${layerIndex + 1}, generating code for file: ${file}`,
);

const currentFullFilePath = normalizePath(

Check warning on line 128 in backend/src/build-system/handlers/frontend-code-generate/index.ts

View workflow job for this annotation

GitHub Actions / autofix

'currentFullFilePath' is assigned a value but never used. Allowed unused vars must match /^_/u
path.resolve(frontendPath, file),
);

Expand Down Expand Up @@ -229,7 +238,19 @@

for (const dep of directDepsArray) {
try {
const resolvedDepPath = normalizePath(path.resolve(frontendPath, dep));
let resolvedDepPath = dep;

// Resolve alias-based paths (assuming `@/` maps to `frontendPath/src/`)
if (dep.startsWith('@/')) {
resolvedDepPath = path.join(
frontendPath,
'src',
dep.replace(/^@\//, ''),
);
} else {
resolvedDepPath = normalizePath(path.resolve(frontendPath, dep));
}

const depContent = await readFileWithRetries(resolvedDepPath, 3, 200);
dependenciesText += `\n\n<dependency> File path: ${dep}\n\`\`\`typescript\n${depContent}\n\`\`\`\n</dependency>`;
} catch (err) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ ${theme}
10. Mock the response if the API returns an empty or undefined value, and you don't need to explicitly show that it is mock data.
11. Write EVERY CODE DETAIL, DON'T LEAVE TODO.
12. Image Assets: If your implementation requires any images except some button logo, you can use placeholder image URLs from https://picsum.photos/<width>/<height>. Note that the width and height values (e.g., 500/300) are adjustable as needed.
13. RESPONSIVE DESIGN: Ensure all components are fully responsive:
- The main container and all top-level components MUST use responsive design principles
- Use responsive prefixes (sm:, md:, lg:, xl:, 2xl:) for breakpoints
- Use flex or grid layouts with appropriate responsive settings
- Ensure text is readable across all device sizes

## Library:
"react-router": "^6",
Expand Down Expand Up @@ -169,6 +174,7 @@ Available operations:
Not assignable to parameter of type → Use the READ tool if you dont have enough information to fix.

**Output format:**
Return only the JSON object wrapped in \`<GENERATE></GENERATE>\` tags.
To keep the structure consistent, other operations remain single-action:

1. Read File
Expand Down Expand Up @@ -204,6 +210,8 @@ If a file needs to be renamed:
}
}
}

Do not forget <GENERATE></GENERATE> tags.
`;
}

Expand Down
19 changes: 18 additions & 1 deletion backend/src/build-system/utils/file_generator_util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,16 @@
const fileInfos: Record<string, FileDependencyInfo> = {};
const nodes = new Set<string>();

const shouldIgnore = (filePath: string) => {
// this.logger.log(`Checking if should ignore: ${filePath}`);
return filePath.startsWith('@/components/ui/');
};

Object.entries(jsonData.files).forEach(([fileName, details]) => {
if (shouldIgnore(fileName)) {
return;
}

nodes.add(fileName);

// Initialize the record
Expand All @@ -69,8 +78,16 @@
dependsOn: [],
};

const filteredDeps = (details.dependsOn || []).filter(
(dep) => !shouldIgnore(dep),
);

// In the JSON, "dependsOn" is an array of file paths
details.dependsOn.forEach((dep) => {
// details.dependsOn.forEach((dep) => {
// nodes.add(dep);
// fileInfos[fileName].dependsOn.push(dep);
// });
filteredDeps.forEach((dep) => {
nodes.add(dep);
fileInfos[fileName].dependsOn.push(dep);
});
Expand Down Expand Up @@ -256,7 +273,7 @@
// So the child’s in-degree = # of dependencies
for (const child of nodes) {
const deps = fileInfos[child]?.dependsOn || [];
for (const dep of deps) {

Check warning on line 276 in backend/src/build-system/utils/file_generator_util.ts

View workflow job for this annotation

GitHub Actions / autofix

'dep' is assigned a value but never used. Allowed unused vars must match /^_/u
inDegree[child] = (inDegree[child] ?? 0) + 1;
}
}
Expand Down
2 changes: 1 addition & 1 deletion backend/src/build-system/utils/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export async function readFileWithRetries(
}

// If we exhausted all retries, re-throw the last error
throw lastError;
return null;
}

/**
Expand Down
72 changes: 72 additions & 0 deletions backend/src/build-system/utils/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,78 @@ import { ResponseTagError } from '../errors';

const logger = new Logger('common-utils');

export const SHADCN_COMPONENT_PATHS: string[] = [
'@/components/ui/accordion.tsx',
'@/components/ui/alert.tsx',
'@/components/ui/alert-dialog.tsx',
'@/components/ui/aspect-ratio.tsx',
'@/components/ui/avatar.tsx',
'@/components/ui/badge.tsx',
'@/components/ui/breadcrumb.tsx',
'@/components/ui/button.tsx',
'@/components/ui/calendar.tsx',
'@/components/ui/card.tsx',
'@/components/ui/carousel.tsx',
'@/components/ui/chart.tsx',
'@/components/ui/checkbox.tsx',
'@/components/ui/collapsible.tsx',
'@/components/ui/command.tsx',
'@/components/ui/context-menu.tsx',
'@/components/ui/dialog.tsx',
'@/components/ui/drawer.tsx',
'@/components/ui/dropdown-menu.tsx',
'@/components/ui/form.tsx',
'@/components/ui/hover-card.tsx',
'@/components/ui/input.tsx',
'@/components/ui/input-otp.tsx',
'@/components/ui/label.tsx',
'@/components/ui/menubar.tsx',
'@/components/ui/navigation-menu.tsx',
'@/components/ui/pagination.tsx',
'@/components/ui/popover.tsx',
'@/components/ui/progress.tsx',
'@/components/ui/radio-group.tsx',
'@/components/ui/resizable.tsx',
'@/components/ui/scroll-area.tsx',
'@/components/ui/select.tsx',
'@/components/ui/separator.tsx',
'@/components/ui/sheet.tsx',
'@/components/ui/sidebar.tsx',
'@/components/ui/skeleton.tsx',
'@/components/ui/slider.tsx',
'@/components/ui/sonner.tsx',
'@/components/ui/switch.tsx',
'@/components/ui/table.tsx',
'@/components/ui/tabs.tsx',
'@/components/ui/textarea.tsx',
'@/components/ui/toast.tsx',
'@/components/ui/toggle.tsx',
'@/components/ui/toggle-group.tsx',
'@/components/ui/tooltip.tsx',
];

/**
* Function to merge Paths with SHADCN UI component paths
* and write the result to a new JSON file.
*/
export function mergePaths(input: string) {
try {
// Parse the input string into a JSON object
const parsedData = JSON.parse(input) as { Paths: string[] };

// Merge the existing paths with the SHADCN components
const updatedPaths = {
Paths: [...parsedData.Paths, ...SHADCN_COMPONENT_PATHS],
};

// Convert back to JSON string with formatting
return JSON.stringify(updatedPaths, null, 2);
} catch (error) {
console.error('Error parsing JSON input:', error);
return '{}'; // Return empty object in case of an error
}
}

/**
* Extract JSON data from Markdown content.
* @param markdownContent The Markdown content containing the JSON.
Expand Down
3 changes: 2 additions & 1 deletion backend/src/build-system/virtual-dir/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Logger } from '@nestjs/common';
import { SHADCN_COMPONENT_PATHS } from '../utils/strings';

export class VirtualDirectory {
private filePaths: Set<string> = new Set();
Expand All @@ -14,7 +15,7 @@ export class VirtualDirectory {
return false;
}

this.filePaths = new Set(structure.Paths);
this.filePaths = new Set([...structure.Paths, ...SHADCN_COMPONENT_PATHS]);
return true;
} catch (error) {
this.logger.error('Failed to parse JSON structure:', error);
Expand Down
21 changes: 21 additions & 0 deletions backend/template/react-ts/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
Loading
Loading