Skip to content

Commit cd4fe5f

Browse files
committed
update frontend code generator for fully generate
1 parent 2d610e9 commit cd4fe5f

File tree

2 files changed

+131
-57
lines changed

2 files changed

+131
-57
lines changed

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

Lines changed: 90 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
import { BuildHandler, BuildResult } from 'src/build-system/types';
33
import { BuilderContext } from 'src/build-system/context';
44
import { Logger } from '@nestjs/common';
5+
import {
6+
generateFilesDependency,
7+
createFile,
8+
} from '../../utils/file_generator_util';
9+
import { VirtualDirectory } from '../../virtual-dir';
10+
import normalizePath from 'normalize-path';
11+
import * as path from 'path';
12+
import { readFile } from 'fs/promises';
513

614
// Utility functions (similar to your parseGenerateTag, removeCodeBlockFences)
715
import {
@@ -20,6 +28,7 @@ import { generateFrontEndCodePrompt } from './prompt';
2028
export class FrontendCodeHandler implements BuildHandler<string> {
2129
readonly id = 'op:FRONTEND:CODE';
2230
readonly logger: Logger = new Logger('FrontendCodeHandler');
31+
private virtualDir: VirtualDirectory;
2332

2433
/**
2534
* Executes the handler to generate frontend code.
@@ -34,56 +43,95 @@ export class FrontendCodeHandler implements BuildHandler<string> {
3443
const sitemapDoc = context.getNodeData('op:UX:SMD');
3544
const uxDataMapDoc = context.getNodeData('op:UX:DATAMAP:DOC');
3645
const backendRequirementDoc = context.getNodeData('op:BACKEND:REQ');
46+
const fileArchDoc = context.getNodeData('op:FILE:ARCH');
3747

3848
// 2. Grab any globally stored context as needed
39-
const currentFilePath =
40-
context.getGlobalContext('currentFrontendFile') ||
41-
'src/pages/Home/index.tsx';
42-
const dependencyFilePath =
43-
context.getGlobalContext('frontendDependencyFile') || 'dependencies.json';
44-
const dependenciesContext =
45-
context.getGlobalContext('frontendDependenciesContext') || '';
46-
47-
// 3. Generate the prompt
48-
const frontendCodePrompt = generateFrontEndCodePrompt(
49-
sitemapDoc,
50-
uxDataMapDoc,
51-
backendRequirementDoc.overview,
52-
currentFilePath,
53-
dependencyFilePath,
54-
dependenciesContext,
55-
);
49+
this.virtualDir = context.virtualDirectory;
50+
const frontendPath = context.getGlobalContext('frontendPath');
5651

57-
this.logger.debug('Generated frontend code prompt.');
52+
// Dependency
53+
const { sortedFiles, fileInfos } = await generateFilesDependency(
54+
fileArchDoc,
55+
this.virtualDir,
56+
);
5857

59-
try {
60-
// 4. Call the model
61-
const modelResponse = await context.model.chatSync(
62-
{
63-
content: frontendCodePrompt,
64-
},
65-
'gpt-4o-mini', // or whichever model you need
58+
// Iterate the sortedFiles
59+
for (const file of sortedFiles) {
60+
const currentFullFilePath = normalizePath(
61+
path.resolve(frontendPath, file),
62+
);
63+
this.logger.log(
64+
`Generating file in dependency order: ${currentFullFilePath}`,
6665
);
6766

68-
// 5. Parse the output
69-
const generatedCode = removeCodeBlockFences(
70-
parseGenerateTag(modelResponse),
67+
// Retrieve the direct dependencies for this file
68+
const directDepsArray = fileInfos[file]?.dependsOn || [];
69+
70+
//gather the contents of each dependency into a single string.
71+
let dependenciesContext = '';
72+
for (const dep of directDepsArray) {
73+
try {
74+
// Resolve against frontendPath to get the absolute path
75+
const resolvedDepPath = normalizePath(
76+
path.resolve(frontendPath, dep),
77+
);
78+
79+
// Read the file. (may want to guard so only read certain file types.)
80+
const fileContent = await readFile(resolvedDepPath, 'utf-8');
81+
82+
//just append a code:
83+
dependenciesContext += `\n\n[Dependency: ${dep}]\n\`\`\`\n${fileContent}\n\`\`\`\n`;
84+
} catch (readError) {
85+
// If the file doesn't exist or can't be read, log a warning.
86+
this.logger.warn(
87+
`Failed to read dependency "${dep}" for file "${file}": ${readError}`,
88+
);
89+
}
90+
}
91+
92+
// Format for the prompt
93+
const directDependencies = directDepsArray.join('\n');
94+
95+
// Generate the prompt
96+
const frontendCodePrompt = generateFrontEndCodePrompt(
97+
sitemapDoc,
98+
uxDataMapDoc,
99+
backendRequirementDoc.overview,
100+
currentFullFilePath,
101+
directDependencies,
102+
dependenciesContext,
71103
);
104+
this.logger.debug('Generated frontend code prompt.');
105+
106+
let generatedCode = '';
107+
try {
108+
// Call the model
109+
const modelResponse = await context.model.chatSync(
110+
{
111+
content: frontendCodePrompt,
112+
},
113+
'gpt-4o-mini', // or whichever model you need
114+
);
72115

73-
this.logger.debug('Frontend code generated and parsed successfully.');
74-
75-
// 6. Return success
76-
return {
77-
success: true,
78-
data: generatedCode,
79-
};
80-
} catch (error) {
81-
// 7. Return error
82-
this.logger.error('Error during frontend code generation:', error);
83-
return {
84-
success: false,
85-
error: new Error('Failed to generate frontend code.'),
86-
};
116+
// Parse the output
117+
generatedCode = removeCodeBlockFences(parseGenerateTag(modelResponse));
118+
119+
this.logger.debug('Frontend code generated and parsed successfully.');
120+
} catch (error) {
121+
// Return error
122+
this.logger.error('Error during frontend code generation:', error);
123+
return {
124+
success: false,
125+
error: new Error('Failed to generate frontend code.'),
126+
};
127+
}
128+
129+
await createFile(currentFullFilePath, generatedCode);
87130
}
131+
132+
return {
133+
success: true,
134+
error: new Error('Frontend code generated and parsed successfully.'),
135+
};
88136
}
89137
}

backend/src/build-system/utils/file_generator_util.ts

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,42 @@ import { extractJsonFromMarkdown } from 'src/build-system/utils/strings';
66
import normalizePath from 'normalize-path';
77
import toposort from 'toposort';
88

9+
interface FileDependencyInfo {
10+
filePath: string;
11+
dependsOn: string[];
12+
}
13+
14+
interface GenerateFilesDependencyResult {
15+
sortedFiles: string[];
16+
fileInfos: Record<string, FileDependencyInfo>;
17+
}
18+
919
const logger = new Logger('FileGeneratorUtil');
1020
let virtualDir: VirtualDirectory;
1121

1222
/**
1323
* Generates files based on JSON extracted from a Markdown document.
1424
* Ensures dependency order is maintained during file creation.
1525
*/
16-
export async function generateFiles(
26+
export async function generateFilesDependency(
1727
markdownContent: string,
18-
projectSrcPath: string,
1928
virtualDirectory: VirtualDirectory,
20-
): Promise<void> {
29+
): Promise<GenerateFilesDependencyResult> {
2130
virtualDir = virtualDirectory;
2231
const jsonData = extractJsonFromMarkdown(markdownContent);
2332

24-
const { graph, nodes } = buildDependencyGraph(jsonData);
33+
const { graph, nodes, fileInfos } = buildDependencyGraph(jsonData);
2534
detectCycles(graph);
2635
validateAgainstVirtualDirectory(nodes);
2736

2837
const sortedFiles = getSortedFiles(graph, nodes);
2938

30-
for (const file of sortedFiles) {
31-
const fullPath = normalizePath(path.resolve(projectSrcPath, file));
32-
logger.log(`Generating file in dependency order: ${fullPath}`);
33-
await createFile(fullPath);
34-
}
39+
logger.log('All files dependency generated successfully.');
3540

36-
logger.log('All files generated successfully.');
41+
return {
42+
sortedFiles,
43+
fileInfos,
44+
};
3745
}
3846

3947
/**
@@ -42,22 +50,38 @@ export async function generateFiles(
4250
*/
4351
export function buildDependencyGraph(jsonData: {
4452
files: Record<string, { dependsOn: string[] }>;
45-
}): { graph: [string, string][]; nodes: Set<string> } {
53+
}): {
54+
graph: [string, string][];
55+
nodes: Set<string>;
56+
fileInfos: Record<string, { filePath: string; dependsOn: string[] }>;
57+
} {
4658
const graph: [string, string][] = [];
4759
const nodes = new Set<string>();
60+
const fileInfos: Record<string, { filePath: string; dependsOn: string[] }> =
61+
{};
4862

4963
logger.log('Parsing JSON data to build dependency graph');
5064

5165
Object.entries(jsonData.files).forEach(([fileName, details]) => {
5266
nodes.add(fileName);
67+
68+
// store file info
69+
fileInfos[fileName] = {
70+
filePath: fileName,
71+
dependsOn: [],
72+
};
73+
5374
details.dependsOn.forEach((dep) => {
5475
const resolvedDep = resolveDependency(fileName, dep);
5576
graph.push([resolvedDep, fileName]);
5677
nodes.add(resolvedDep);
78+
79+
// store dependsOn
80+
fileInfos[fileName].dependsOn.push(resolvedDep);
5781
});
5882
});
5983

60-
return { graph, nodes };
84+
return { graph, nodes, fileInfos };
6185
}
6286

6387
/**
@@ -137,12 +161,14 @@ export function validateAgainstVirtualDirectory(nodes: Set<string>): void {
137161
* Creates a file at the specified path, ensuring required directories exist first.
138162
* The file is created with a simple placeholder comment.
139163
*/
140-
export async function createFile(filePath: string): Promise<void> {
164+
export async function createFile(
165+
filePath: string,
166+
generatedCode: string,
167+
): Promise<void> {
141168
const dir = path.dirname(filePath);
142169
await fs.mkdir(dir, { recursive: true });
143170

144-
const content = `// Generated file: ${path.basename(filePath)}`;
145-
await fs.writeFile(filePath, content, 'utf8');
171+
await fs.writeFile(filePath, generatedCode, 'utf8');
146172

147173
logger.log(`File created: ${filePath}`);
148174
}

0 commit comments

Comments
 (0)