Skip to content

Commit 940119c

Browse files
committed
Concurrent frontend code generate
1 parent be2aa22 commit 940119c

File tree

4 files changed

+303
-113
lines changed

4 files changed

+303
-113
lines changed

backend/src/build-system/__tests__/fullstack-gen.spec.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,22 @@ import { BackendFileReviewHandler } from '../handlers/backend/file-review/file-r
4343
// requires: ['op:UX:SMD'],
4444
},
4545
{
46-
handler: UXDMDHandler,
47-
name: 'UX DataMap Document Node',
48-
// requires: ['op:UX:SMD'],
46+
handler: DBRequirementHandler,
47+
name: 'Database Requirements Node',
48+
// requires: ['op:UX:DATAMAP:DOC'],
49+
},
50+
{
51+
handler: FileStructureHandler,
52+
name: 'File Structure Generation',
53+
// requires: ['op:UX:SMD', 'op:UX:DATAMAP:DOC'],
54+
options: {
55+
projectPart: 'frontend',
56+
},
57+
},
58+
{
59+
handler: UXSMSPageByPageHandler,
60+
name: 'Level 2 UX Sitemap Structure Node details',
61+
// requires: ['op:UX:SMS'],
4962
},
5063
{
5164
handler: DBRequirementHandler,

backend/src/build-system/handlers/frontend-code-generate/index.ts

Lines changed: 144 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
import { BuildHandler, BuildResult } from 'src/build-system/types';
22
import { BuilderContext } from 'src/build-system/context';
33
import { Logger } from '@nestjs/common';
4+
import { batchChatSyncWithClock } from 'src/build-system/utils/handler-helper';
45
import {
5-
generateFilesDependency,
66
createFile,
7+
generateFilesDependencyWithLayers,
78
} from '../../utils/file_generator_util';
89
import { VirtualDirectory } from '../../virtual-dir';
9-
import normalizePath from 'normalize-path';
10-
import * as path from 'path';
11-
import { readFile } from 'fs/promises';
12-
13-
import { parseGenerateTag } from 'src/build-system/utils/strings';
1410

15-
import { generateFrontEndCodePrompt, generateCSSPrompt } from './prompt';
1611
import { UXSMSHandler } from '../ux/sitemap-structure';
1712
import { UXDMDHandler } from '../ux/datamap';
1813
import { BackendRequirementHandler } from '../backend/requirements-document';
1914
import { FileFAHandler } from '../file-manager/file-arch';
2015
import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager';
16+
import normalizePath from 'normalize-path';
17+
import path from 'path';
18+
import { readFile } from 'fs-extra';
19+
import { generateCSSPrompt, generateFrontEndCodePrompt } from './prompt';
20+
import { parseGenerateTag } from 'src/build-system/utils/strings';
21+
import { ResponseParsingError } from 'src/build-system/errors';
2122

2223
/**
2324
* FrontendCodeHandler is responsible for generating the frontend codebase
@@ -56,111 +57,159 @@ export class FrontendCodeHandler implements BuildHandler<string> {
5657
this.virtualDir = context.virtualDirectory;
5758
const frontendPath = context.getGlobalContext('frontendPath');
5859

60+
if (
61+
!sitemapStruct ||
62+
!uxDataMapDoc ||
63+
!backendRequirementDoc ||
64+
!fileArchDoc
65+
) {
66+
this.logger.error(sitemapStruct);
67+
this.logger.error(uxDataMapDoc);
68+
this.logger.error(backendRequirementDoc);
69+
this.logger.error(fileArchDoc);
70+
throw new Error('Missing required parameters.');
71+
}
72+
5973
// Dependency
60-
const { sortedFiles, fileInfos } = await generateFilesDependency(
61-
fileArchDoc,
62-
this.virtualDir,
63-
);
74+
const { concurrencyLayers, fileInfos } =
75+
await generateFilesDependencyWithLayers(fileArchDoc, this.virtualDir);
6476

65-
// Iterate the sortedFiles
66-
for (const file of sortedFiles) {
67-
const currentFullFilePath = normalizePath(
68-
path.resolve(frontendPath, 'src', file),
77+
// 4. Process each "layer" in sequence; files in a layer in parallel
78+
for (const [layerIndex, layer] of concurrencyLayers.entries()) {
79+
this.logger.log(
80+
`\n==== Concurrency Layer #${layerIndex + 1} ====\nFiles: [${layer.join(
81+
', ',
82+
)}]\n`,
6983
);
7084

71-
const extension = currentFullFilePath.split('.').pop() || '';
72-
73-
// Retrieve the direct dependencies for this file
74-
const directDepsArray = fileInfos[file]?.dependsOn || [];
75-
76-
//gather the contents of each dependency into a single string.
77-
let dependenciesContext = '';
78-
for (const dep of directDepsArray) {
79-
try {
80-
// Resolve against frontendPath to get the absolute path
81-
const resolvedDepPath = normalizePath(
82-
path.resolve(frontendPath, 'src', dep),
85+
await Promise.all(
86+
layer.map(async (file) => {
87+
this.logger.log(
88+
`Layer #${layerIndex + 1}, generating code for file: ${file}`,
8389
);
8490

85-
// Read the file. (may want to guard so only read certain file types.)
86-
const fileContent = await readFile(resolvedDepPath, 'utf-8');
87-
88-
//just append a code:
89-
dependenciesContext += `\n\n[Dependency: ${dep}]\n\`\`\`\n${fileContent}\n\`\`\`\n`;
90-
} catch (readError) {
91-
// If the file doesn't exist or can't be read, log a warning.
92-
this.logger.warn(
93-
`Failed to read dependency "${dep}" for file "${file}": ${readError}`,
91+
// Resolve the absolute path where this file should be generated
92+
const currentFullFilePath = normalizePath(
93+
path.resolve(frontendPath, 'src', file),
9494
);
95-
}
96-
}
9795

98-
// Format for the prompt
99-
const directDependencies = directDepsArray.join('\n');
96+
// Gather direct dependencies
97+
const directDepsArray = fileInfos[file]?.dependsOn || [];
98+
const dependenciesContext = '';
99+
100+
// Read each dependency and append to dependenciesContext
101+
let dependenciesText = '';
102+
for (const dep of directDepsArray) {
103+
try {
104+
const resolvedDepPath = normalizePath(
105+
path.resolve(frontendPath, 'src', dep),
106+
);
107+
const depContent = await readFile(resolvedDepPath, 'utf-8');
108+
dependenciesText += `\n\nprevious code **${dep}** is:\n\`\`\`typescript\n${depContent}\n\`\`\`\n`;
109+
} catch (err) {
110+
this.logger.warn(
111+
`Failed to read dependency "${dep}" for file "${file}": ${err}`,
112+
);
113+
throw new ResponseParsingError(
114+
`Error generating code for ${file}:`,
115+
);
116+
}
117+
}
118+
119+
// 5. Build prompt text depending on file extension
120+
const fileExtension = path.extname(file);
121+
let frontendCodePrompt = '';
122+
if (fileExtension === '.css') {
123+
frontendCodePrompt = generateCSSPrompt(
124+
file,
125+
directDepsArray.join('\n'),
126+
);
127+
} else {
128+
// default: treat as e.g. .ts, .js, .vue, .jsx, etc.
129+
frontendCodePrompt = generateFrontEndCodePrompt(
130+
file,
131+
directDepsArray.join('\n'),
132+
);
133+
}
134+
this.logger.log(
135+
`Prompt for file "${file}":\n${frontendCodePrompt}\n`,
136+
);
100137

101-
this.logger.log(
102-
`Generating file in dependency order: ${currentFullFilePath}`,
103-
);
104-
this.logger.log(
105-
`2 Generating file in dependency order directDependencies: ${directDependencies}`,
138+
const messages = [
139+
{
140+
role: 'system' as const,
141+
content: frontendCodePrompt,
142+
},
143+
{
144+
role: 'user' as const,
145+
content: `This is the Sitemap Structure:
146+
${sitemapStruct}
147+
148+
Next will provide Sitemap Structure.`,
149+
},
150+
{
151+
role: 'user' as const,
152+
content: `This is the UX Datamap Documentation:
153+
${uxDataMapDoc}
154+
155+
Next will provide UX Datamap Documentation.`,
156+
},
157+
{
158+
role: 'user' as const,
159+
content: `This is the Backend Requirement Documentation:
160+
${backendRequirementDoc}
161+
162+
Next will provide Backend Requirement Documentation.`,
163+
},
164+
165+
{
166+
role: 'user' as const,
167+
content: `Dependencies for ${file}:\n${dependenciesText}\n
168+
169+
Now generate code for "${file}".`,
170+
},
171+
];
172+
173+
// 6. Call your Chat Model
174+
let generatedCode = '';
175+
try {
176+
const modelResponse = await batchChatSyncWithClock(
177+
context,
178+
'generate frontend code',
179+
FrontendCodeHandler.name,
180+
[
181+
{
182+
model: 'gpt-4o',
183+
messages,
184+
},
185+
],
186+
);
187+
188+
generatedCode = parseGenerateTag(modelResponse[0]);
189+
} catch (err) {
190+
this.logger.error(`Error generating code for ${file}:`, err);
191+
throw new ResponseParsingError(
192+
`Error generating code for ${file}:`,
193+
);
194+
}
195+
196+
// 7. Write the file to the filesystem
197+
await createFile(currentFullFilePath, generatedCode);
198+
199+
this.logger.log(
200+
`Layer #${layerIndex + 1}, completed generation for file: ${file}`,
201+
);
202+
}),
106203
);
107204

108-
let frontendCodePrompt = '';
109-
110-
if (extension === 'css') {
111-
frontendCodePrompt = generateCSSPrompt(
112-
sitemapStruct,
113-
uxDataMapDoc,
114-
file,
115-
directDependencies,
116-
dependenciesContext,
117-
);
118-
} else {
119-
// Generate the prompt
120-
frontendCodePrompt = generateFrontEndCodePrompt(
121-
sitemapStruct,
122-
uxDataMapDoc,
123-
backendRequirementDoc.overview,
124-
file,
125-
directDependencies,
126-
dependenciesContext,
127-
);
128-
}
129205
this.logger.log(
130-
'generate code prompt for frontendCodePrompt or css: ' +
131-
frontendCodePrompt,
206+
`\n==== Finished concurrency layer #${layerIndex + 1} ====\n`,
132207
);
133-
134-
this.logger.debug('Generated frontend code prompt.');
135-
136-
let generatedCode = '';
137-
const model = 'gpt-4o-mini';
138-
try {
139-
// Call the model
140-
const modelResponse = await context.model.chatSync({
141-
model,
142-
messages: [{ content: frontendCodePrompt, role: 'system' }],
143-
});
144-
145-
// Parse the output
146-
generatedCode = parseGenerateTag(modelResponse);
147-
148-
this.logger.debug('Frontend code generated and parsed successfully.');
149-
} catch (error) {
150-
// Return error
151-
this.logger.error('Error during frontend code generation:', error);
152-
return {
153-
success: false,
154-
error: new Error('Failed to generate frontend code.'),
155-
};
156-
}
157-
158-
await createFile(currentFullFilePath, generatedCode);
159208
}
160209

161210
return {
162211
success: true,
163-
data: 'test',
212+
data: frontendPath,
164213
error: new Error('Frontend code generated and parsed successfully.'),
165214
};
166215
}

backend/src/build-system/handlers/frontend-code-generate/prompt.ts

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
export const generateFrontEndCodePrompt = (
2-
sitemapStruct: string,
3-
uxDatamapDoc: string,
4-
backendRequirementDoc: string,
52
currentFile: string,
63
dependencyFilePath: string,
7-
dependenciesContext: string,
84
): string => {
95
return `You are an expert frontend developer.
106
Your task is to generate complete and production-ready React frontend code based on the provided inputs using typescript.
117
The code should include all necessary files, folders, and logic to cover UI components, API integration, routing, and state management while ensuring scalability and maintainability.
128
139
Based on following inputs:
1410
15-
- Sitemap Structure: ${sitemapStruct}
16-
- UX Datamap Documentation: ${uxDatamapDoc}
17-
- Backend Requirement Documentation: ${backendRequirementDoc}
11+
- Sitemap Structure:
12+
- UX Datamap Documentation:
13+
- Backend Requirement Documentation:
1814
- Current File: ${currentFile}
1915
- dependencyFilePath: ${dependencyFilePath}
20-
- Dependency File: ${dependenciesContext}
16+
- Dependency File Code:
2117
2218
### Instructions and Rules:
2319
File Requirements:
@@ -62,24 +58,20 @@ export const generateFrontEndCodePrompt = (
6258
};
6359

6460
export function generateCSSPrompt(
65-
sitemapStruct: string,
66-
uxDatamapDoc: string,
6761
fileName: string,
6862
directDependencies: string,
69-
dependenciesContext: string,
7063
): string {
7164
return `
7265
You are an expert CSS developer. Generate valid, production-ready CSS for the file "${fileName}".
7366
7467
## Context
75-
- Sitemap Strucutrue: ${sitemapStruct}
76-
- UX Datamap Documentation: ${uxDatamapDoc}
68+
- Sitemap Strucutrue:
69+
- UX Datamap Documentation:
7770
7871
- Direct Dependencies (if any and may include references to other styles or partials):
7972
${directDependencies}
8073
81-
- Direct Dependencies Context (if any):
82-
${dependenciesContext}
74+
- Direct Dependencies Context:
8375
8476
## Rules & Guidelines
8577
1. **Do NOT** include any JavaScript or React code—only plain CSS.

0 commit comments

Comments
 (0)