diff --git a/backend/src/build-system/__tests__/fullstack-gen.spec.ts b/backend/src/build-system/__tests__/fullstack-gen.spec.ts index 5833cfee..395bcdaa 100644 --- a/backend/src/build-system/__tests__/fullstack-gen.spec.ts +++ b/backend/src/build-system/__tests__/fullstack-gen.spec.ts @@ -1,7 +1,21 @@ import { isIntegrationTest } from 'src/common/utils'; import { BuildSequence } from '../types'; -import { executeBuildSequence } from './utils'; -import { Logger } from '@nestjs/common'; +import { ProjectInitHandler } from '../handlers/project-init'; +import { PRDHandler } from '../handlers/product-manager/product-requirements-document/prd'; +import { UXSMDHandler } from '../handlers/ux/sitemap-document'; +import { UXSMSHandler } from '../handlers/ux/sitemap-structure'; +import { DBRequirementHandler } from '../handlers/database/requirements-document'; +import { FileStructureHandler } from '../handlers/file-manager/file-structure'; +import { UXSMSPageByPageHandler } from '../handlers/ux/sitemap-structure/sms-page'; +import { DBSchemaHandler } from '../handlers/database/schemas/schemas'; +import { FileFAHandler } from '../handlers/file-manager/file-arch'; +import { BackendRequirementHandler } from '../handlers/backend/requirements-document'; +import { BackendCodeHandler } from '../handlers/backend/code-generate'; +import { BackendFileReviewHandler } from '../handlers/backend/file-review/file-review'; +import { UXDMDHandler } from '../handlers/ux/datamap'; +import { BuilderContext } from '../context'; +import { FrontendCodeHandler } from '../handlers/frontend-code-generate'; + (isIntegrationTest ? describe : describe.skip)('Build Sequence Test', () => { it('should execute build sequence successfully', async () => { const sequence: BuildSequence = { @@ -10,140 +24,76 @@ import { Logger } from '@nestjs/common'; name: 'Spotify-like Music Web', description: 'Users can play music', databaseType: 'SQLite', - steps: [ + nodes: [ + { + handler: ProjectInitHandler, + name: 'Project Folders Setup', + }, + { + handler: PRDHandler, + name: 'Project Requirements Document Node', + }, + { + handler: UXSMDHandler, + name: 'UX Sitemap Document Node', + }, { - id: 'step-0', - name: 'Project Initialization', - parallel: false, - nodes: [ - { - id: 'op:PROJECT::STATE:SETUP', - name: 'Project Folders Setup', - }, - ], + handler: UXSMSHandler, + name: 'UX Sitemap Structure Node', + // requires: ['op:UX:SMD'], }, { - id: 'step-1', - name: 'Initial Analysis', - parallel: false, - nodes: [ - { - id: 'op:PRD', - name: 'Project Requirements Document Node', - }, - ], + handler: UXDMDHandler, + name: 'UX DataMap Document Node', }, { - id: 'step-2', - name: 'UX Base Document Generation', - parallel: false, - nodes: [ - { - id: 'op:UX:SMD', - name: 'UX Sitemap Document Node', - requires: ['op:PRD'], - }, - ], + handler: DBRequirementHandler, + name: 'Database Requirements Node', + // requires: ['op:UX:DATAMAP:DOC'], }, { - id: 'step-3', - name: 'Parallel UX Processing', - parallel: true, - nodes: [ - { - id: 'op:UX:SMS', - name: 'UX Sitemap Structure Node', - requires: ['op:UX:SMD'], - }, - { - id: 'op:UX:DATAMAP:DOC', - name: 'UX DataMap Document Node', - requires: ['op:UX:SMD'], - }, - ], + handler: FileStructureHandler, + name: 'File Structure Generation', + // requires: ['op:UX:SMD', 'op:UX:DATAMAP:DOC'], + options: { + projectPart: 'frontend', + }, }, { - id: 'step-4', - name: 'Parallel Project Structure', - parallel: true, - nodes: [ - { - id: 'op:DATABASE_REQ', - name: 'Database Requirements Node', - requires: ['op:UX:DATAMAP:DOC'], - }, - { - id: 'op:FILE:STRUCT', - name: 'File Structure Generation', - requires: ['op:UX:SMD', 'op:UX:DATAMAP:DOC'], - options: { - projectPart: 'frontend', - }, - }, - { - id: 'op:UX:SMS:LEVEL2', - name: 'Level 2 UX Sitemap Structure Node details', - requires: ['op:UX:SMS'], - }, - ], + handler: UXSMSPageByPageHandler, + name: 'Level 2 UX Sitemap Structure Node details', + // requires: ['op:UX:SMS'], }, { - id: 'step-5', - name: 'Parallel Implementation', - parallel: true, - nodes: [ - { - id: 'op:DATABASE:SCHEMAS', - name: 'Database Schemas Node', - requires: ['op:DATABASE_REQ'], - }, - { - id: 'op:FILE:ARCH', - name: 'File Arch', - requires: ['op:FILE:STRUCT', 'op:UX:DATAMAP:DOC'], - }, - { - id: 'op:BACKEND:REQ', - name: 'Backend Requirements Node', - requires: ['op:DATABASE_REQ', 'op:UX:DATAMAP:DOC', 'op:UX:SMD'], - }, - ], + handler: DBSchemaHandler, + name: 'Database Schemas Node', + // requires: ['op:DATABASE_REQ'], }, { - id: 'step-6', - name: 'Final Code Generation', - parallel: false, - nodes: [ - { - id: 'op:BACKEND:CODE', - name: 'Backend Code Generator Node', - requires: [ - 'op:DATABASE:SCHEMAS', - 'op:UX:DATAMAP:DOC', - 'op:BACKEND:REQ', - ], - }, - ], + handler: FileFAHandler, + name: 'File Arch', + // requires: ['op:FILE:STRUCT', 'op:UX:DATAMAP:DOC'], }, - // TODO: code reviewer { - id: 'step-7', - name: 'Backend Code Review', - parallel: false, - nodes: [ - { - id: 'op:BACKEND:FILE:REVIEW', - name: 'Backend File Review Node', - requires: ['op:BACKEND:CODE', 'op:BACKEND:REQ'], - }, - ], + handler: BackendRequirementHandler, + name: 'Backend Requirements Node', + // requires: ['op:DATABASE_REQ', 'op:UX:DATAMAP:DOC', 'op:UX:SMD'], + }, + { + handler: BackendCodeHandler, + name: 'Backend Code Generator Node', + }, + { + handler: BackendFileReviewHandler, + name: 'Backend File Review Node', + }, + { + handler: FrontendCodeHandler, + name: 'Frontend Code Generator Node', }, ], }; - - const result = await executeBuildSequence('fullstack-code-gen', sequence); - expect(result.success).toBe(true); - expect(result.metrics).toBeDefined(); - Logger.log(`Logs saved to: ${result.logFolderPath}`); + const context = new BuilderContext(sequence, 'fullstack-code-gen'); + await context.execute(); }, 300000); }); diff --git a/backend/src/build-system/__tests__/mock/MockBuilderContext.ts b/backend/src/build-system/__tests__/mock/MockBuilderContext.ts new file mode 100644 index 00000000..9abd5c29 --- /dev/null +++ b/backend/src/build-system/__tests__/mock/MockBuilderContext.ts @@ -0,0 +1,112 @@ +import { VirtualDirectory } from '../../virtual-dir'; +import { readFileSync } from 'fs'; +import { resolve } from 'path'; +import * as path from 'path'; +import { UXSMSHandler } from 'src/build-system/handlers/ux/sitemap-structure'; +import { UXDMDHandler } from 'src/build-system/handlers/ux/datamap'; +import { BackendRequirementHandler } from 'src/build-system/handlers/backend/requirements-document'; +import { FileFAHandler } from 'src/build-system/handlers/file-manager/file-arch'; +import { BuilderContext, GlobalDataKeys } from 'src/build-system/context'; +import { v4 as uuidv4 } from 'uuid'; // UUID generator for unique identifiers +import { + BuildExecutionState, + BuildHandlerConstructor, + BuildSequence, + ExtractHandlerReturnType, +} from 'src/build-system/types'; +import { buildProjectPath, copyProjectTemplate } from '../../utils/files'; + +export class MockBuilderContext extends BuilderContext { + private mockNodeData: Map = new Map(); + private mockGlobalContext: Map = new Map(); + virtualDirectory: VirtualDirectory; + + constructor(sequence: BuildSequence, id: string) { + super(sequence, id); // Call the parent constructor to initialize inherited properties + this.virtualDirectory = new VirtualDirectory(); // Initialize the mock virtual directory + const uuid = + new Date().toISOString().slice(0, 10).replace(/:/g, '-') + '-' + uuidv4(); + + // Read mock data from files + const uxSitemapStructure = this.readMockFile( + path.join(__dirname, 'test_files', 'UX_Sitemap_Structure_Node.md'), + ); + const uxDataMapDocument = this.readMockFile( + path.join(__dirname, 'test_files', 'UX_DataMap_Document_Node.md'), + ); + const backendRequirements = this.readMockFile( + path.join(__dirname, 'test_files', 'Backend_Requirements_Node.md'), + ); + const fileStructure = this.readMockFile( + path.join(__dirname, 'test_files', 'File_Structure_Generation.md'), + ); + const fileArchitecture = this.readMockFile( + path.join(__dirname, 'test_files', 'File_Arch.md'), + ); + + this.mockNodeData.set(UXSMSHandler, uxSitemapStructure); + this.mockNodeData.set(UXDMDHandler, uxDataMapDocument); + this.mockNodeData.set(BackendRequirementHandler, backendRequirements); + this.mockNodeData.set(FileFAHandler, fileArchitecture); + this.buildVirtualDirectory(fileStructure); + + copyProjectTemplate( + path.join(__dirname, '..', '..', '..', '..', 'template', 'react-ts'), + uuid, + 'frontend', + ); + + // Set up mock data for globalContext + this.mockGlobalContext.set( + 'frontendPath', + buildProjectPath(uuid, 'frontend'), + ); + } + + setGlobalContext( + key: Key, + value: any, + ): void { + this.mockGlobalContext.set(key, value); + } + + setNodeData( + handlerClass: T, + data: ExtractHandlerReturnType, + ): void { + this.mockNodeData.set(handlerClass, data); + } + + getExecutionState(): BuildExecutionState { + return {} as BuildExecutionState; // Return a mock execution state + } + + buildVirtualDirectory(jsonContent: string): boolean { + return this.virtualDirectory.parseJsonStructure(jsonContent); + } + + execute(): Promise { + return Promise.resolve(); // Mock a resolved promise for execution + } + + getNodeData(handler: any): any { + return this.mockNodeData.get(handler) || null; + } + + getGlobalContext(key: string): any { + return this.mockGlobalContext.get(key) || null; + } + + // Helper method to read files + private readMockFile(filePath: string): string { + try { + const absolutePath = resolve(filePath); // Resolve the file path + return readFileSync(absolutePath, 'utf-8'); // Read the file content + } catch (err) { + console.error(`Error reading file at ${filePath}:`, err); + return ''; // Return an empty string if file read fails + } + } + + // Add other methods as necessary for mocking +} diff --git a/backend/src/build-system/__tests__/mock/test_files/Backend_Requirements_Node.md b/backend/src/build-system/__tests__/mock/test_files/Backend_Requirements_Node.md new file mode 100644 index 00000000..0ce0db8a --- /dev/null +++ b/backend/src/build-system/__tests__/mock/test_files/Backend_Requirements_Node.md @@ -0,0 +1,250 @@ +# overview + +#### 1. System Overview + +- **Project Name**: Spotify-like Music Web +- **Technology Stack** + + - **Core technology choices**: JavaScript (Node.js), Express.js for the backend framework, using RESTful API design principles for remote communication. + - **Framework architecture**: Employ an MVC architecture to separate concerns between models (data handling), views (data representation), and controllers (business logic). + - **Key dependencies and their purposes**: + - **Express**: To create server and routing functionalities. + - **Mongoose**: For MongoDB object modeling, facilitating CRUD operations. + - **jsonwebtoken**: For user authentication and session management. + - **bcrypt**: To hash user passwords securely. + - **cors**: To enable Cross-Origin Resource Sharing for frontend and backend communication. + +- **Architecture Patterns** + + - Framework-specific patterns: Implements middleware for logging, validation, and error handling; uses Promises and/or async/await for asynchronous operations. + - Project structure: Organized into folders such as `models`, `routes`, `controllers`, `middleware`, and `utils`. + - Dependency management: Utilize npm for managing packages, specifying versions in package.json to ensure consistent setup across environments. + - Configuration management: Store environment variables (like database connection strings and API keys) in a `.env` file and use the dotenv package to load them. + - Service organization: Each functional area (authentication, track management, playlist management) encapsulated in its service to maintain modularity. + +- **Data Flow Architecture** + - Frontend-Backend data interactions: The frontend makes AJAX requests to the API to retrieve data, which is processed in the backend and sent back in JSON format. + - Caching strategy: Use an in-memory cache (like Redis) for storing frequently requested data, such as popular playlists and user profiles, to speed up access times. + - Real-time updates handling: Consider using WebSockets for real-time interactions, such as sharing updates between users in collaborative playlists. + - Data transformation layers: Validate and sanitize incoming data in controllers, ensuring it matches the expected schema before interacting with models. + +#### 2. API Endpoints + +**User Management** +Route: /api/users +Method: POST +Purpose: Create a new user account +Frontend Usage: page_view_signup +Data Requirements: + +- Required data transformations: Validate and hash password before saving the user. +- Caching requirements: None. +- Real-time update needs: Immediate response needed. + Request: + Headers: { + "Content-Type": "application/json" + } + Params: {} + Query: {} + Body: { + "username": "String", + "email": "String", + "password": "String", + "profilePicture": "String (optional)" + } + Response: + Success: { + "userId": "Integer", + "username": "String", + "email": "String" + } + Errors: { + "400": "Bad request, validation failed", + "409": "Conflict, email or username already exists" + } + Required Auth: No + Rate Limiting: None + Cache Strategy: None + Route: /api/users/login + Method: POST + Purpose: Authenticate user and receive an access token. + Frontend Usage: page_view_login + Data Requirements: +- Required data transformations: Validate username/email and password, generate JWT upon success. +- Caching requirements: None. +- Real-time update needs: Immediate response needed. + Request: + Headers: { + "Content-Type": "application/json" + } + Params: {} + Query: {} + Body: { + "identifier": "String (username/email)", + "password": "String" + } + Response: + Success: { + "token": "String", + "userId": "Integer", + "username": "String" + } + Errors: { + "401": "Unauthorized, invalid credentials" + } + Required Auth: No + Rate Limiting: None + Cache Strategy: None + +**Playlist Management** +Route: /api/playlists +Method: POST +Purpose: Create a new playlist for a user +Frontend Usage: page_view_playlist_management +Data Requirements: + +- Required data transformations: Validate incoming playlist data. +- Caching requirements: Cache newly created playlists. +- Real-time update needs: N/A, but notify the user upon successful creation. + Request: + Headers: { + "Authorization": "Bearer {token}", + "Content-Type": "application/json" + } + Params: {} + Query: {} + Body: { + "userId": "Integer", + "playlistName": "String", + "description": "String (optional)" + } + Response: + Success: { + "playlistId": "Integer", + "playlistName": "String", + "description": "String", + "createdDate": "DateTime" + } + Errors: { + "400": "Bad request, validations failed" + } + Required Auth: Yes + Rate Limiting: None + Cache Strategy: Cache playlist lists. + Route: /api/playlists/:playlistId + Method: GET + Purpose: Retrieve a specific playlist by ID + Frontend Usage: page_view_playlist_details + Data Requirements: +- Required data transformations: Retrieve playlist and its associated tracks. +- Caching requirements: Cache the playlist data for quick retrieval. +- Real-time update needs: N/A unless using collaborative features. + Request: + Headers: { + "Authorization": "Bearer {token}" + } + Params: { + "playlistId": "Integer" + } + Query: {} + Body: {} + Response: + Success: { + "playlistId": "Integer", + "playlistName": "String", + "tracks": [ + { + "trackId": "Integer", + "title": "String", + "artist": "String", + "album": "String", + "duration": "Integer" + } + ] + } + Errors: { + "404": "Not found, invalid playlist ID" + } + Required Auth: Yes + Rate Limiting: None + Cache Strategy: Cache playlist details. + +**Track Management** +Route: /api/tracks +Method: GET +Purpose: Search for tracks based on queries +Frontend Usage: page_view_search +Data Requirements: + +- Required data transformations: Filter and search for tracks. +- Caching requirements: Cache popular search results. +- Real-time update needs: N/A for static track data. + Request: + Headers: {} + Params: {} + Query: { + "search": "String (title/album/artist)", + "limit": "Integer" + } + Body: {} + Response: + Success: { + "tracks": [ + { + "trackId": "Integer", + "title": "String", + "artist": "String", + "album": "String" + } + ] + } + Errors: { + "400": "Bad request, validation failed" + } + Required Auth: No + Rate Limiting: None + Cache Strategy: Cache search results for performance. + Route: /api/tracks/:trackId + Method: GET + Purpose: Retrieve detailed information about a specific track + Frontend Usage: page_view_playlist_details (when displaying track details) + Data Requirements: +- Required data transformations: Retrieve track details. +- Caching requirements: Cache track details. +- Real-time update needs: N/A unless user interactions require it. + Request: + Headers: {} + Params: { + "trackId": "Integer" + } + Query: {} + Body: {} + Response: + Success: { + "trackId": "Integer", + "title": "String", + "artist": "String", + "album": "String", + "duration": "Integer", + "coverImage": "String" + } + Errors: { + "404": "Not found, invalid track ID" + } + Required Auth: No + Rate Limiting: None + Cache Strategy: Cache track details. + +# implementation + +# config + +## language + +javascript + +## framework + +express + +## packages diff --git a/backend/src/build-system/__tests__/mock/test_files/File_Arch.md b/backend/src/build-system/__tests__/mock/test_files/File_Arch.md new file mode 100644 index 00000000..2df70d51 --- /dev/null +++ b/backend/src/build-system/__tests__/mock/test_files/File_Arch.md @@ -0,0 +1,109 @@ +{ +"files": { +"src/components/common/index.tsx": { +"dependsOn": ["./index.css"] +}, +"src/components/common/index.css": { +"dependsOn": [] +}, +"src/components/layout/index.tsx": { +"dependsOn": ["./index.css"] +}, +"src/components/layout/index.css": { +"dependsOn": [] +}, +"src/components/specific/index.tsx": { +"dependsOn": ["./index.css"] +}, +"src/components/specific/index.css": { +"dependsOn": [] +}, +"src/contexts/AuthContext.tsx": { +"dependsOn": [] +}, +"src/contexts/ThemeContext.tsx": { +"dependsOn": [] +}, +"src/contexts/PlayerContext.tsx": { +"dependsOn": [] +}, +"src/hooks/useAuth.ts": { +"dependsOn": ["../contexts/AuthContext.tsx"] +}, +"src/hooks/useMusicPlayer.ts": { +"dependsOn": ["../contexts/PlayerContext.tsx"] +}, +"src/hooks/useFetch.ts": { +"dependsOn": [] +}, +"src/hooks/usePlaylist.ts": { +"dependsOn": [] +}, +"src/pages/Home/index.tsx": { +"dependsOn": ["./index.css", "../../components/common/index.tsx", "../../hooks/useFetch.ts"] +}, +"src/pages/Home/index.css": { +"dependsOn": [] +}, +"src/pages/MusicLibrary/index.tsx": { +"dependsOn": ["./index.css", "../../components/common/index.tsx", "../../hooks/useFetch.ts"] +}, +"src/pages/MusicLibrary/index.css": { +"dependsOn": [] +}, +"src/pages/Playlists/index.tsx": { +"dependsOn": ["./index.css", "../../components/common/index.tsx", "../../hooks/usePlaylist.ts"] +}, +"src/pages/Playlists/index.css": { +"dependsOn": [] +}, +"src/pages/PlaylistDetails/index.tsx": { +"dependsOn": ["./index.css", "../../components/common/index.tsx", "../../hooks/usePlaylist.ts"] +}, +"src/pages/PlaylistDetails/index.css": { +"dependsOn": [] +}, +"src/pages/SearchResults/index.tsx": { +"dependsOn": ["./index.css", "../../components/common/index.tsx", "../../hooks/useFetch.ts"] +}, +"src/pages/SearchResults/index.css": { +"dependsOn": [] +}, +"src/pages/AccountSettings/index.tsx": { +"dependsOn": ["./index.css", "../../components/common/index.tsx", "../../hooks/useFetch.ts", "../../hooks/useAuth.ts"] +}, +"src/pages/AccountSettings/index.css": { +"dependsOn": [] +}, +"src/pages/Lyrics/index.tsx": { +"dependsOn": ["./index.css", "../../components/common/index.tsx"] +}, +"src/pages/Lyrics/index.css": { +"dependsOn": [] +}, +"src/utils/constants.ts": { +"dependsOn": [] +}, +"src/utils/helpers.ts": { +"dependsOn": [] +}, +"src/utils/validators.ts": { +"dependsOn": [] +}, +"src/api/auth.ts": { +"dependsOn": [] +}, +"src/api/music.ts": { +"dependsOn": [] +}, +"src/api/user.ts": { +"dependsOn": [] +}, +"src/router.ts": { +"dependsOn": [] +}, +"src/index.tsx": { +"dependsOn": [] +} +} +} diff --git a/backend/src/build-system/__tests__/mock/test_files/File_Structure_Generation.md b/backend/src/build-system/__tests__/mock/test_files/File_Structure_Generation.md new file mode 100644 index 00000000..1510ca5f --- /dev/null +++ b/backend/src/build-system/__tests__/mock/test_files/File_Structure_Generation.md @@ -0,0 +1,242 @@ +{ +"type": "directory", +"name": "spotify-like-music-web", +"children": [ +{ +"type": "directory", +"name": "components", +"children": [ +{ +"type": "directory", +"name": "common", +"children": [ +{ +"type": "file", +"name": "index.tsx" +}, +{ +"type": "file", +"name": "index.css" +} +] +}, +{ +"type": "directory", +"name": "layout", +"children": [ +{ +"type": "file", +"name": "index.tsx" +}, +{ +"type": "file", +"name": "index.css" +} +] +}, +{ +"type": "directory", +"name": "specific", +"children": [ +{ +"type": "file", +"name": "index.tsx" +}, +{ +"type": "file", +"name": "index.css" +} +] +} +] +}, +{ +"type": "directory", +"name": "contexts", +"children": [ +{ +"type": "file", +"name": "AuthContext.tsx" +}, +{ +"type": "file", +"name": "ThemeContext.tsx" +}, +{ +"type": "file", +"name": "PlayerContext.tsx" +} +] +}, +{ +"type": "directory", +"name": "hooks", +"children": [ +{ +"type": "file", +"name": "useAuth.ts" +}, +{ +"type": "file", +"name": "useMusicPlayer.ts" +}, +{ +"type": "file", +"name": "useFetch.ts" +}, +{ +"type": "file", +"name": "usePlaylist.ts" +} +] +}, +{ +"type": "directory", +"name": "pages", +"children": [ +{ +"type": "directory", +"name": "Home", +"children": [ +{ +"type": "file", +"name": "index.tsx" +}, +{ +"type": "file", +"name": "index.css" +} +] +}, +{ +"type": "directory", +"name": "MusicLibrary", +"children": [ +{ +"type": "file", +"name": "index.tsx" +}, +{ +"type": "file", +"name": "index.css" +} +] +}, +{ +"type": "directory", +"name": "Playlists", +"children": [ +{ +"type": "file", +"name": "index.tsx" +}, +{ +"type": "file", +"name": "index.css" +} +] +}, +{ +"type": "directory", +"name": "PlaylistDetails", +"children": [ +{ +"type": "file", +"name": "index.tsx" +}, +{ +"type": "file", +"name": "index.css" +} +] +}, +{ +"type": "directory", +"name": "SearchResults", +"children": [ +{ +"type": "file", +"name": "index.tsx" +}, +{ +"type": "file", +"name": "index.css" +} +] +}, +{ +"type": "directory", +"name": "AccountSettings", +"children": [ +{ +"type": "file", +"name": "index.tsx" +}, +{ +"type": "file", +"name": "index.css" +} +] +}, +{ +"type": "directory", +"name": "Lyrics", +"children": [ +{ +"type": "file", +"name": "index.tsx" +}, +{ +"type": "file", +"name": "index.css" +} +] +} +] +}, +{ +"type": "directory", +"name": "utils", +"children": [ +{ +"type": "file", +"name": "constants.ts" +}, +{ +"type": "file", +"name": "helpers.ts" +}, +{ +"type": "file", +"name": "validators.ts" +} +] +}, +{ +"type": "directory", +"name": "api", +"children": [ +{ +"type": "file", +"name": "auth.ts" +}, +{ +"type": "file", +"name": "music.ts" +}, +{ +"type": "file", +"name": "user.ts" +} +] +}, +{ +"type": "file", +"name": "router.ts" +}, +{ +"type": "file", +"name": "index.tsx" +} +] +} diff --git a/backend/src/build-system/__tests__/mock/test_files/UX_DataMap_Document_Node.md b/backend/src/build-system/__tests__/mock/test_files/UX_DataMap_Document_Node.md new file mode 100644 index 00000000..fce794ab --- /dev/null +++ b/backend/src/build-system/__tests__/mock/test_files/UX_DataMap_Document_Node.md @@ -0,0 +1,229 @@ +### 1. Project Overview + +- **Project Name**: Spotify-like Music Web +- **Platform**: Web +- **General Description**: A web-based music platform allowing users to discover, create, and manage music playlists, access personalized recommendations, and search for music while providing seamless user experience across various interactions. + +### 2. Global Data Elements + +- **User Profile Information**: Name, email, profile picture, playlists, song preferences, and listening history, enabling personalization and user identity across the platform. +- **Navigation States**: Indication of user authentication status, maintaining a seamless transition between different user states, such as logged-in and logged-out. +- **System Status**: Alerts for system statuses like loading data, errors, and success messages during user actions. + +### 3. Page-Specific Data Requirements + +##### Home Page (`/home`) + +**Purpose**: To showcase featured content and provide quick access to music recommendations. + +**User Goals**: + +- Discover new music and playlists. +- Access personalized recommendations (for logged-in users). +- Take immediate action by clicking on offered playlists/albums. + +**Required Data Elements**: + +_Input Data_: + +- No direct input data required on this page apart from user interactions, such as clicking playlists. + +_Display Data_: + +- Featured Playlists Section: Displays curated playlists to attract user engagement. +- Recommended Songs Section (personalized for logged-in users): Shows algorithmically determined music based on user taste. +- Recently Played Section (only for logged-in users): Quick access to previously listened songs, aiding in easy navigation. + +_Feedback & States_: + +- Loading state while fetching personalized music recommendations. +- Success indication when a user interacts with playlists (highlight selected album/playlist). + +**User Interactions**: + +- Users can navigate to specific playlists/album detail pages, requiring instant visual confirmation of their selection. + +--- + +##### Music Library Page (`/library`) + +**Purpose**: Provide users with access to a vast database of music to explore and play. + +**User Goals**: + +- Search for specific songs, albums, or artists. +- Filter results to streamline music browsing. + +**Required Data Elements**: + +_Input Data_: + +- Search bar input: Essential for users to query music, should support typing and dynamic search results. + +_Display Data_: + +- Display grid of songs/albums: This includes thumbnails, titles, and artists, aiding in quick recognition. +- Filters (by genre, artist, or album): Enabling users to navigate musical preferences. + +_Feedback & States_: + +- Loading state while processing search inputs. +- Suggestions provided dynamically as users type. +- Error state for no results found during searches. + +**User Interactions**: + +- Users expect a responsive experience where the filtering and searching constantly update content without page reload. + +--- + +##### Playlists Page (`/playlists`) + +**Purpose**: To provide users, especially logged-in, a means to manage personal music collections through playlists. + +**User Goals**: + +- Create new playlists. +- Edit existing playlists to add or remove songs. + +**Required Data Elements**: + +_Input Data_: + +- Playlist creation form: Requires a name (mandatory input), which contributes to organization. + +_Display Data_: + +- List of User Playlists: Informative display showing thumbnails and number of songs, enhancing visual engagement. +- Create New Playlist Button (visibility conditional for logged-in users): Allows for user expansion of their music collection. + +_Feedback & States_: + +- Confirmation success states when creating/editing a playlist. +- Error alerts for invalid inputs during creation or edits. + +**User Interactions**: + +- Users need timely feedback when manipulating the playlist, including the ability to see changes immediately. + +--- + +##### Playlist Details Page (`/playlists/:playlistId`) + +**Purpose**: Show details of a specific playlist including song options. + +**User Goals**: + +- Play a song or share a playlist. +- Edit their playlist details if they are the owner. + +**Required Data Elements**: + +_Input Data_: + +- Share options: Link to share on platforms should be intuitive to interact with. + +_Display Data_: + +- Playlist title and description: Identifiable information necessary for understanding playlist content. +- List of songs with play buttons: Directly impacts user interaction capabilities. + +_Feedback & States_: + +- Feedback on successful song plays and sharing actions. +- Notices for playlists with no songs available. + +**User Interactions**: + +- Real-time feedback regarding user's actions interacting with songs for an engaging experience. + +--- + +##### Search Results Page (`/search`) + +**Purpose**: Provide comprehensive search functionality across the platform. + +**User Goals**: + +- Quickly find desired tracks, albums, or artists. + +**Required Data Elements**: + +_Input Data_: + +- Dynamic search input field linked to results. + +_Display Data_: + +- Search results dynamically updating as users type, showing song/album/artist names and thumbnails. + +_Feedback & States_: + +- Loading indicators while retrieving search results. +- Error notification when no results are found. + +**User Interactions**: + +- Allow users to click through results, presenting information in a clear and user-friendly manner. + +--- + +##### Account Settings Page (`/settings`) + +**Purpose**: Allow users to manage their account information. + +**User Goals**: + +- Update personal details and account security features. + +**Required Data Elements**: + +_Input Data_: + +- Profile Information: Editable fields for username and email, critical for personalization. +- Change Password: Fields for old and new passwords, necessary for security. + +_Display Data_: + +- Confirmation messages for updates to profile information. +- Error messages for invalid inputs (e.g., weak passwords). + +_Feedback & States_: + +- Immediate feedback on saved changes. +- Loading statuses when processing updates. + +**User Interactions**: + +- Users expect smooth transitions and clear confirmation of saved settings. + +--- + +##### Lyrics Page (`/lyrics/:songId`) + +**Purpose**: Display live lyrics in sync with playback. + +**User Goals**: + +- Engage with song lyrics while listening to music. + +**Required Data Elements**: + +_Input Data_: + +- None directly from the user. + +_Display Data_: + +- Current song lyrics that sync with the music play, promoting user engagement. + +_Feedback & States_: + +- Loading state for lyrics display upon song play. +- Clear visibility of lyrics and playback connection. + +**User Interactions**: + +- Expectation of accurately synced lyrics with music playback, providing enhanced listening experience. + +--- diff --git a/backend/src/build-system/__tests__/mock/test_files/UX_Sitemap_Structure_Node.md b/backend/src/build-system/__tests__/mock/test_files/UX_Sitemap_Structure_Node.md new file mode 100644 index 00000000..20991ec5 --- /dev/null +++ b/backend/src/build-system/__tests__/mock/test_files/UX_Sitemap_Structure_Node.md @@ -0,0 +1,120 @@ + + + + G1. Global Navigation Bar + Authentication Conditions: + - Logged-in users: Access to personalized menu items (e.g., user playlists, account settings). + - Logged-out users: Access to login/sign-in buttons and call-to-action options (e.g., "Sign up" or "Log in"). + Elements: + - Logo + 1. **Type**: Interactive + 2. **Purpose**: Redirect users to the home page. + 3. **States**: Default, Hover + 4. **Interactions**: Clicking + - Search Bar + 1. **Type**: Input + 2. **Purpose**: Allow users to search for songs, albums or artists. + 3. **States**: Default, Focused, Typing, Hover + 4. **Interactions**: Typing, Clicking, Submitting + - Links to Music Library, Playlists, Recommendations + 1. **Type**: Interactive + 2. **Purpose**: Quick navigation to main sections of the app. + 3. **States**: Default, Hover, Selected + 4. **Interactions**: Clicking + + + + G2. Global Footer + Authentication Conditions: + - Accessible to both logged-in and logged-out users. + Elements: + - Links to Terms of Service, Privacy Policy + 1. **Type**: Interactive + 2. **Purpose**: Provide legal and contact information to users. + 3. **States**: Default, Hover + 4. **Interactions**: Clicking + - Social Media Icons + 1. **Type**: Interactive + 2. **Purpose**: Allow users to share content through social platforms. + 3. **States**: Default, Hover + 4. **Interactions**: Clicking + + + + G3. Global Sidebar + Authentication Conditions: + - Logged-in users: Includes "Your Playlists", "Friends' Playlists", "Settings". + - Logged-out users: Limited to "Music Library" and "Sign Up". + Elements: + - Section Links + 1. **Type**: Interactive + 2. **Purpose**: Facilitate quick access to various sections of the site. + 3. **States**: Default, Hover, Selected + 4. **Interactions**: Clicking + + + + +P1. Home Page +URL Path: /home +Parent Page: None +Description: This is the landing page that introduces users to featured playlists and songs. +Authentication Conditions: Accessible to both logged-in and logged-out users. #### Core Components: - C1.1. Featured Playlists Section 1. **Type**: Display 2. **Purpose**: Highlight curated playlists for all users. 3. **States**: Default, Hover 4. **Interactions**: Clicking to view details. - C1.2. Recommended Songs Section 1. **Type**: Display 2. **Purpose**: Suggest songs based on the user's listening preferences (for logged-in users). 3. **States**: Default, Hover 4. **Interactions**: Clicking to listen or view details. - C1.3. Recently Played Section 1. **Type**: Display 2. **Purpose**: Allow logged-in users to easily find their recently played songs. 3. **States**: Default, Hover 4. **Interactions**: Clicking to listen again. #### Features & Functionality: - F1.1. Display Featured Playlists - Description: Showcase curated playlists. - User Stories: Users want to quickly find music they enjoy. - Components Used: C1.1, G1, G2, G3 - F1.2. Display Recommended Songs - Description: Show personalized song recommendations. - User Stories: Users want to discover new music tailored to their preferences. - Components Used: C1.2, G1, G2, G3 #### Page-Specific User Flows: +Flow 1. Discover Music 1. User lands on the home page. 2. User clicks on a featured playlist or recommended song. 3. User listens to the music or further explores playlists. + + + +P2. Music Library +URL Path: /library +Parent Page: None +Description: A comprehensive section where users can browse their music collection. +Authentication Conditions: Full access for logged-in users; logged-out users can browse but cannot personalize. #### Core Components: - C2.1. Search Functionality 1. **Type**: Interactive 2. **Purpose**: Allow users to find specific songs, albums, or artists. 3. **States**: Default, Focused, Typing 4. **Interactions**: Typing, Submitting - C2.2. Filters 1. **Type**: Interactive 2. **Purpose**: Enable users to narrow their search by genre, artist, or album. 3. **States**: Default, Selected 4. **Interactions**: Clicking - C2.3. Display Grid 1. **Type**: Display 2. **Purpose**: Show music items in a visually accessible format. 3. **States**: Default, Hover 4. **Interactions**: Clicking to select. #### Features & Functionality: - F2.1. Search Songs, Albums, or Artists - Description: Users can find any music available in their library. - User Stories: Users want to quickly find specific content within their library. - Components Used: C2.1, C2.2, C2.3, G1, G2, G3 #### Page-Specific User Flows: +Flow 1. Search for Music 1. User enters search terms in the search bar (C2.1). 2. User applies filters (C2.2). 3. User clicks on a song or album from the display grid (C2.3). + + + +P3. Playlists +URL Path: /playlists +Parent Page: None +Description: This page allows users to create, edit, and view playlists. +Authentication Conditions: Create, edit, and share playlists available to logged-in users only. #### Core Components: - C3.1. User Playlists List 1. **Type**: Display 2. **Purpose**: List playlists created by the user. 3. **States**: Default, Hover 4. **Interactions**: Clicking to view or edit. - C3.2. Create New Playlist Button 1. **Type**: Interactive 2. **Purpose**: Allow users to create a new playlist. 3. **States**: Default, Hover 4. **Interactions**: Clicking to open a modal. #### Features & Functionality: - F3.1. Create and Manage Playlists - Description: Users can create new playlists and manage existing ones. - User Stories: Users want to have the ability to curate their music. - Components Used: C3.1, C3.2, G1, G2, G3 #### Page-Specific User Flows: +Flow 1. Create a New Playlist 1. User clicks the "Create New Playlist" button (C3.2). 2. User enters a name and saves the playlist. 3. User adds songs to the newly created playlist. + + + +P4. Playlist Details +URL Path: /playlists/:playlistId +Parent Page: P3. Playlists +Description: Displays the details of a specific playlist, including tracks. +Authentication Conditions: Restricted to logged-in users. Users can only view public playlists if logged out. #### Core Components: - C4.1. Playlist Title and Description 1. **Type**: Display 2. **Purpose**: Communicate the theme and contents of the playlist. 3. **States**: Default 4. **Interactions**: Viewing - C4.2. List of Songs 1. **Type**: Display 2. **Purpose**: Show the songs in the playlist with playback options. 3. **States**: Default, Hover 4. **Interactions**: Clicking play buttons. - C4.3. Share Playlist Button 1. **Type**: Interactive 2. **Purpose**: Allow users to share the playlist on social media. 3. **States**: Default, Hover 4. **Interactions**: Clicking to initiate sharing. #### Features & Functionality: - F4.1. View and Share Playlists - Description: Users can see details and share their playlists. - User Stories: Users want to share their music selections with friends. - Components Used: C4.1, C4.2, C4.3, G1, G2, G3 #### Page-Specific User Flows: +Flow 1. Share a Playlist 1. User views the playlist details (C4.1, C4.2). 2. User clicks on the "Share" button (C4.3). 3. User selects a sharing option and shares it. + + + +P5. Search Results +URL Path: /search +Parent Page: None +Description: Displays results based on user search queries. +Authentication Conditions: Accessible to logged-in and logged-out users. #### Core Components: - C5.1. Dynamic Search Results 1. **Type**: Display 2. **Purpose**: Show results relevant to the user’s search. 3. **States**: Default, Highlighted 4. **Interactions**: Clicking to view details. #### Features & Functionality: - F5.1. View Search Results - Description: Users can view the results of their searches. - User Stories: Users want to find specific songs without difficulty. - Components Used: C5.1, G1, G2, G3 #### Page-Specific User Flows: +Flow 1. View Search Results 1. User types a search term into the search bar (in G1). 2. User views the dynamic results (C5.1). 3. User clicks on a song to play or view details. + + + +P6. Account Settings +URL Path: /settings +Parent Page: None +Description: Allows users to manage their account features and settings. +Authentication Conditions: Accessible to logged-in users only. #### Core Components: - C6.1. Profile Information 1. **Type**: Display 2. **Purpose**: Show user details for updating settings. 3. **States**: Default 4. **Interactions**: Viewing - C6.2. Change Password Option 1. **Type**: Input 2. **Purpose**: Provide an option for users to update their passwords. 3. **States**: Default, Focused 4. **Interactions**: Typing, Submitting - C6.3. Logout Button 1. **Type**: Interactive 2. **Purpose**: Allow users to securely log out. 3. **States**: Default, Hover 4. **Interactions**: Clicking #### Features & Functionality: - F6.1. Manage Account Settings - Description: Users can update their profile and security settings. - User Stories: Users want control over their account security and information. - Components Used: C6.1, C6.2, C6.3, G1, G2 #### Page-Specific User Flows: +Flow 1. Update Profile Information 1. User views their profile information (C6.1). 2. User edits the desired field and submits changes (C6.2). 3. User logs out if needed (C6.3). + + + +P7. Lyrics +URL Path: /lyrics/:songId +Parent Page: None +Description: Displays the lyrics synced with the currently playing song. +Authentication Conditions: Accessible to logged-in and logged-out users. #### Core Components: - C7.1. Current Song Lyrics 1. **Type**: Display 2. **Purpose**: Show the lyrics of the currently playing song. 3. **States**: Default, Highlighted 4. **Interactions**: Viewing #### Features & Functionality: - F7.1. View Song Lyrics - Description: Users can see the lyrics as the song is playing. - User Stories: Users want to sing along or understand the lyrics better. - Components Used: C7.1, G1, G2, G3 #### Page-Specific User Flows: +Flow 1. View Lyrics While Playing 1. User listens to a song using the music player. 2. User navigates to the lyrics page (from the music player interface). 3. User views the lyrics displayed (C7.1) as the song plays. + + + diff --git a/backend/src/build-system/__tests__/test-generate-doc.spec.ts b/backend/src/build-system/__tests__/test-generate-doc.spec.ts index 7b6303f1..43759553 100644 --- a/backend/src/build-system/__tests__/test-generate-doc.spec.ts +++ b/backend/src/build-system/__tests__/test-generate-doc.spec.ts @@ -1,91 +1,91 @@ -import { isIntegrationTest } from 'src/common/utils'; -import { BuildSequence } from '../types'; -import { executeBuildSequence } from './utils'; -import { Logger } from '@nestjs/common'; +// import { isIntegrationTest } from 'src/common/utils'; +// import { BuildSequence } from '../types'; +// import { executeBuildSequence } from './utils'; +// import { Logger } from '@nestjs/common'; -// TODO: adding integration flag -(isIntegrationTest ? describe : describe.skip)( - 'Sequence: PRD -> UXSD -> UXDD -> UXSS', - () => { - it('should execute the full sequence and log results to individual files', async () => { - const sequence: BuildSequence = { - id: 'test-backend-sequence', - version: '1.0.0', - name: 'Spotify-like Music Web', - description: 'Users can play music', - databaseType: 'SQLite', - steps: [ - { - id: 'step-1', - name: 'Generate PRD', - nodes: [ - { - id: 'op:PRD', - name: 'PRD Generation Node', - }, - ], - }, - { - id: 'step-2', - name: 'Generate UX Sitemap Document', - nodes: [ - { - id: 'op:UX:SMD', - name: 'UX Sitemap Document Node', - }, - ], - }, - { - id: 'step-3', - name: 'Generate UX Sitemap Structure', - nodes: [ - { - id: 'op:UX:SMS', - name: 'UX Sitemap Structure Node', - }, - ], - }, - { - id: 'step-4', - name: 'UX Data Map Document', - nodes: [ - { - id: 'op:UX:DATAMAP:DOC', - name: 'UX Data Map Document node', - }, - ], - }, - { - id: 'step-5', - name: 'UX SMD LEVEL 2 Page Details', - nodes: [ - { - id: 'op:UX:SMS:LEVEL2', - name: 'UX SMD LEVEL 2 Page Details Node', - }, - ], - }, - ], - }; +// // TODO: adding integration flag +// (isIntegrationTest ? describe : describe.skip)( +// 'Sequence: PRD -> UXSD -> UXDD -> UXSS', +// () => { +// it('should execute the full sequence and log results to individual files', async () => { +// const sequence: BuildSequence = { +// id: 'test-backend-sequence', +// version: '1.0.0', +// name: 'Spotify-like Music Web', +// description: 'Users can play music', +// databaseType: 'SQLite', +// steps: [ +// { +// id: 'step-1', +// name: 'Generate PRD', +// nodes: [ +// { +// id: 'op:PRD', +// name: 'PRD Generation Node', +// }, +// ], +// }, +// { +// id: 'step-2', +// name: 'Generate UX Sitemap Document', +// nodes: [ +// { +// id: 'op:UX:SMD', +// name: 'UX Sitemap Document Node', +// }, +// ], +// }, +// { +// id: 'step-3', +// name: 'Generate UX Sitemap Structure', +// nodes: [ +// { +// id: 'op:UX:SMS', +// name: 'UX Sitemap Structure Node', +// }, +// ], +// }, +// { +// id: 'step-4', +// name: 'UX Data Map Document', +// nodes: [ +// { +// id: 'op:UX:DATAMAP:DOC', +// name: 'UX Data Map Document node', +// }, +// ], +// }, +// { +// id: 'step-5', +// name: 'UX SMD LEVEL 2 Page Details', +// nodes: [ +// { +// id: 'op:UX:SMS:LEVEL2', +// name: 'UX SMD LEVEL 2 Page Details Node', +// }, +// ], +// }, +// ], +// }; - try { - const result = await executeBuildSequence( - 'test-generate-all-ux-part', - sequence, - ); +// try { +// const result = await executeBuildSequence( +// 'test-generate-all-ux-part', +// sequence, +// ); - Logger.log( - 'Sequence completed successfully. Logs stored in:', - result.logFolderPath, - ); +// Logger.log( +// 'Sequence completed successfully. Logs stored in:', +// result.logFolderPath, +// ); - if (!result.success) { - throw result.error; - } - } catch (error) { - Logger.error('Error during sequence execution:', error); - throw error; - } - }, 600000); - }, -); +// if (!result.success) { +// throw result.error; +// } +// } catch (error) { +// Logger.error('Error during sequence execution:', error); +// throw error; +// } +// }, 600000); +// }, +// ); diff --git a/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts b/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts index a3765b3d..75c43f88 100644 --- a/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts +++ b/backend/src/build-system/__tests__/test.backend-code-generator.spec.ts @@ -1,12 +1,13 @@ -/* eslint-disable no-console */ -import { BuilderContext } from 'src/build-system/context'; import { BuildSequence } from '../types'; import * as fs from 'fs'; -import * as path from 'path'; -import { writeToFile } from './utils'; +import { executeBuildSequence } from './utils'; import { isIntegrationTest } from 'src/common/utils'; -import { Logger } from '@nestjs/common'; - +import { PRDHandler } from '../handlers/product-manager/product-requirements-document/prd'; +import { UXSMDHandler } from '../handlers/ux/sitemap-document'; +import { DBRequirementHandler } from '../handlers/database/requirements-document'; +import { DBSchemaHandler } from '../handlers/database/schemas/schemas'; +import { BackendCodeHandler } from '../handlers/backend/code-generate'; +import { ProjectInitHandler } from '../handlers/project-init'; (isIntegrationTest ? describe : describe.skip)( 'Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGenerator', () => { @@ -25,112 +26,50 @@ import { Logger } from '@nestjs/common'; name: 'Spotify-like Music Web', description: 'Users can play music', databaseType: 'SQLite', - steps: [ + nodes: [ + { + handler: ProjectInitHandler, + name: 'Project Folders Setup', + }, { - id: 'step-1', - name: 'Generate PRD', - nodes: [ - { - id: 'op:PRD', - name: 'PRD Generation Node', - }, - ], + handler: PRDHandler, + name: 'PRD Generation Node', }, + { - id: 'step-2', - name: 'Generate UX Sitemap Document', - nodes: [ - { - id: 'op:UX:SMD', - name: 'UX Sitemap Document Node', - requires: ['op:PRD'], - }, - ], + handler: UXSMDHandler, + name: 'UX Sitemap Document Node', + // requires: ['op:PRD'], }, + { - id: 'step-3', - name: 'Generate UX Data Map Document', - nodes: [ - { - id: 'op:UX:DATAMAP:DOC', - name: 'UX Data Map Document Node', - requires: ['op:UX:SMD'], - }, - ], + handler: UXSMDHandler, + name: 'UX Data Map Document Node', + // requires: ['op:UX:SMD'], }, + { - id: 'step-4', - name: 'Generate Database Requirements', - nodes: [ - { - id: 'op:DATABASE_REQ', - name: 'Database Requirements Node', - requires: ['op:UX:DATAMAP:DOC'], - }, - ], + handler: DBRequirementHandler, + name: 'Database Requirements Node', + // requires: ['op:UX:DATAMAP:DOC'], }, + { - id: 'step-5', - name: 'Generate Database Schemas', - nodes: [ - { - id: 'op:DATABASE:SCHEMAS', - name: 'Database Schemas Node', - requires: ['op:DATABASE_REQ'], - }, - ], + handler: DBSchemaHandler, + name: 'Database Schemas Node', + // requires: ['op:DATABASE_REQ'], }, + { - id: 'step-6', - name: 'Generate Backend Code', - nodes: [ - { - id: 'op:BACKEND:CODE', - name: 'Backend Code Generator Node', - requires: ['op:DATABASE:SCHEMAS', 'op:UX:DATAMAP:DOC'], - }, - ], + handler: BackendCodeHandler, + name: 'Backend Code Generator Node', + // requires: ['op:DATABASE:SCHEMAS', 'op:UX:DATAMAP:DOC'], }, ], }; // Initialize the BuilderContext with the defined sequence and environment - const context = new BuilderContext(sequence, 'test-env'); - - try { - // Execute the build sequence - await context.execute(); - - // Iterate through each step and node to retrieve and log results - for (const step of sequence.steps) { - for (const node of step.nodes) { - const resultData = await context.getNodeData(node.id); - Logger.log(`Result for ${node.name}:`, resultData); - - if (resultData) { - writeToFile(logFolderPath, node.name, resultData); - } else { - Logger.error( - `Handler ${node.name} failed with error:`, - resultData.error, - ); - } - } - } - - Logger.log( - 'Sequence executed successfully. Logs stored in:', - logFolderPath, - ); - } catch (error) { - Logger.error('Error during sequence execution:', error); - fs.writeFileSync( - path.join(logFolderPath, 'error.txt'), - `Error: ${error.message}\n${error.stack}`, - 'utf8', - ); - throw new Error('Sequence execution failed.'); - } + executeBuildSequence('backend code geneerate', sequence); }, 600000, ); // Timeout set to 10 minutes diff --git a/backend/src/build-system/__tests__/test.frontend-code-generate.spec.ts b/backend/src/build-system/__tests__/test.frontend-code-generate.spec.ts new file mode 100644 index 00000000..c7b68576 --- /dev/null +++ b/backend/src/build-system/__tests__/test.frontend-code-generate.spec.ts @@ -0,0 +1,36 @@ +import { MockBuilderContext } from './mock/MockBuilderContext'; +import { FrontendCodeHandler } from '../handlers/frontend-code-generate'; +import { BuildSequence } from '../types'; + +describe('FrontendCodeHandler', () => { + let handler: FrontendCodeHandler; + let context: MockBuilderContext; + + const sequence: BuildSequence = { + id: 'test-backend-sequence', + version: '1.0.0', + name: 'Spotify-like Music Web', + description: 'Users can play music', + databaseType: 'SQLite', + nodes: [ + { + handler: FrontendCodeHandler, + name: 'Frontend Code Handler', + // requires: ['op:FILE:STRUCT', 'op:UX:DATAMAP:DOC'], + }, + ], + }; + + beforeEach(() => { + handler = new FrontendCodeHandler(); + context = new MockBuilderContext(sequence, 'test'); + }); + + //rember to comment requirement in FrontendCodeHandler + + it('should generate frontend code successfully', async () => { + const result = await handler.run(context); + + expect(result.success).toBe(true); + }); +}); diff --git a/backend/src/build-system/__tests__/test.sms-lvl2.spec.ts b/backend/src/build-system/__tests__/test.sms-lvl2.spec.ts index 3c62bcdf..d6c684c6 100644 --- a/backend/src/build-system/__tests__/test.sms-lvl2.spec.ts +++ b/backend/src/build-system/__tests__/test.sms-lvl2.spec.ts @@ -1,7 +1,14 @@ +import { isIntegrationTest } from 'src/common/utils'; +import { PRDHandler } from '../handlers/product-manager/product-requirements-document/prd'; +import { ProjectInitHandler } from '../handlers/project-init'; +import { UXDMDHandler } from '../handlers/ux/datamap'; +import { UXSMDHandler } from '../handlers/ux/sitemap-document'; +import { UXSMSHandler as UXSMSHandler } from '../handlers/ux/sitemap-structure'; +import { UXSMSPageByPageHandler } from '../handlers/ux/sitemap-structure/sms-page'; import { BuildSequence } from '../types'; import { executeBuildSequence } from './utils'; -describe('Build Sequence Test', () => { +(isIntegrationTest ? describe : describe.skip)('Build Sequence Test', () => { it('should execute build sequence successfully', async () => { const sequence: BuildSequence = { id: 'test-backend-sequence', @@ -9,69 +16,34 @@ describe('Build Sequence Test', () => { name: 'Spotify-like Music Web', description: 'Users can play music', databaseType: 'SQLite', - steps: [ + nodes: [ { - id: 'step-0', - name: 'Project Initialization', - parallel: false, - nodes: [ - { - id: 'op:PROJECT::STATE:SETUP', - name: 'Project Folders Setup', - }, - ], + handler: ProjectInitHandler, + name: 'Project Folders Setup', + description: 'Create project folders', }, + { - id: 'step-1', - name: 'Initial Analysis', - parallel: false, - nodes: [ - { - id: 'op:PRD', - name: 'Project Requirements Document Node', - }, - ], + handler: PRDHandler, + name: 'Project Requirements Document Node', }, + + { + handler: UXSMDHandler, + name: 'UX Sitemap Document Node', + }, + { - id: 'step-2', - name: 'UX Base Document Generation', - parallel: false, - nodes: [ - { - id: 'op:UX:SMD', - name: 'UX Sitemap Document Node', - requires: ['op:PRD'], - }, - ], + handler: UXSMSHandler, + name: 'UX Sitemap Structure Node', }, { - id: 'step-3', - name: 'Parallel UX Processing', - parallel: true, - nodes: [ - { - id: 'op:UX:SMS', - name: 'UX Sitemap Structure Node', - requires: ['op:UX:SMD'], - }, - { - id: 'op:UX:DATAMAP:DOC', - name: 'UX DataMap Document Node', - requires: ['op:UX:SMD'], - }, - ], + handler: UXDMDHandler, + name: 'UX DataMap Document Node', }, { - id: 'step-4', - name: 'Parallel Project Structure', - parallel: true, - nodes: [ - { - id: 'op:UX:SMS:LEVEL2', - name: 'Level 2 UX Sitemap Structure Node details', - requires: ['op:UX:SMS'], - }, - ], + handler: UXSMSPageByPageHandler, + name: 'Level 2 UX Sitemap Structure Node details', }, ], }; @@ -79,6 +51,5 @@ describe('Build Sequence Test', () => { const result = await executeBuildSequence('fullstack-code-gen', sequence); expect(result.success).toBe(true); expect(result.metrics).toBeDefined(); - console.log(`Logs saved to: ${result.logFolderPath}`); }, 300000); }); diff --git a/backend/src/build-system/__tests__/utils.ts b/backend/src/build-system/__tests__/utils.ts index 0aacee8a..7e07b947 100644 --- a/backend/src/build-system/__tests__/utils.ts +++ b/backend/src/build-system/__tests__/utils.ts @@ -4,53 +4,10 @@ import * as path from 'path'; import { BuildSequence } from '../types'; import { BuilderContext } from '../context'; import { BuildMonitor } from '../monitor'; -/** - * Utility function to write content to a file in a clean, formatted manner. - * @param handlerName - The name of the handler. - * @param data - The data to be written to the file. - */ -export const writeToFile = ( - rootPath: string, - handlerName: string, - data: string | object, -): void => { - try { - // Sanitize handler name to prevent illegal file names - const sanitizedHandlerName = handlerName.replace(/[^a-zA-Z0-9_-]/g, '_'); - const filePath = path.join(rootPath, `${sanitizedHandlerName}.md`); - - // Generate clean and readable content - const formattedContent = formatContent(data); - - // Write the formatted content to the file - fs.writeFileSync(filePath, formattedContent, 'utf8'); - Logger.log(`Successfully wrote data for ${handlerName} to ${filePath}`); - } catch (error) { - Logger.error(`Failed to write data for ${handlerName}:`, error); - throw error; - } -}; /** - * Formats the content for writing to the file. - * @param data - The content to format (either a string or an object). - * @returns A formatted string. + * Format object to markdown structure */ -export const formatContent = (data: string | object): string => { - if (typeof data === 'string') { - // Remove unnecessary escape characters and normalize newlines - return data - .replace(/\\n/g, '\n') // Handle escaped newlines - .replace(/\\t/g, '\t'); // Handle escaped tabs - } else if (typeof data === 'object') { - // Pretty-print JSON objects with 2-space indentation - return JSON.stringify(data, null, 2); - } else { - // Convert other types to strings - return String(data); - } -}; - export function objectToMarkdown(obj: any, depth = 1): string { if (!obj || typeof obj !== 'object') { return String(obj); @@ -60,9 +17,7 @@ export function objectToMarkdown(obj: any, depth = 1): string { const prefix = '#'.repeat(depth); for (const [key, value] of Object.entries(obj)) { - if (value === null || value === undefined) { - continue; - } + if (value === null || value === undefined) continue; markdown += `${prefix} ${key}\n`; if (typeof value === 'object' && !Array.isArray(value)) { @@ -86,6 +41,33 @@ export function objectToMarkdown(obj: any, depth = 1): string { return markdown; } +/** + * Write content to file + */ +export const writeToFile = ( + rootPath: string, + handlerName: string, + data: string | object, +): void => { + try { + const sanitizedHandlerName = handlerName.replace(/[^a-zA-Z0-9_-]/g, '_'); + const filePath = path.join(rootPath, `${sanitizedHandlerName}.md`); + const formattedContent = + typeof data === 'object' + ? JSON.stringify(data, null, 2) + : String(data).replace(/\\n/g, '\n').replace(/\\t/g, '\t'); + + fs.writeFileSync(filePath, formattedContent, 'utf8'); + Logger.log(`Successfully wrote data for ${handlerName} to ${filePath}`); + } catch (error) { + Logger.error(`Failed to write data for ${handlerName}:`, error); + throw error; + } +}; + +/** + * Test result interface + */ interface TestResult { success: boolean; logFolderPath: string; @@ -93,126 +75,24 @@ interface TestResult { metrics?: any; } +/** + * Execute build sequence and record results + */ export async function executeBuildSequence( name: string, sequence: BuildSequence, ): Promise { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - const logFolderPath = `./logs/${name.toLocaleLowerCase().replaceAll(' ', '-')}-${timestamp}`; + const logFolderPath = `./logs/${name.toLowerCase().replaceAll(' ', '-')}-${timestamp}`; fs.mkdirSync(logFolderPath, { recursive: true }); const context = new BuilderContext(sequence, 'test-env'); const monitor = BuildMonitor.getInstance(); - try { - console.time('Total Execution Time'); - await context.execute(); - console.timeEnd('Total Execution Time'); + await context.execute(); - const monitorReport = monitor.generateTextReport(sequence.id); - fs.writeFileSync( - path.join(logFolderPath, 'execution-metrics.txt'), - monitorReport, - 'utf8', - ); - - const sequenceMetrics = monitor.getSequenceMetrics(sequence.id); - if (sequenceMetrics) { - const metricsJson = { - totalDuration: `${sequenceMetrics.duration}ms`, - successRate: `${sequenceMetrics.successRate.toFixed(2)}%`, - totalSteps: sequenceMetrics.totalSteps, - completedSteps: sequenceMetrics.completedSteps, - failedSteps: sequenceMetrics.failedSteps, - totalNodes: sequenceMetrics.totalNodes, - startTime: new Date(sequenceMetrics.startTime).toISOString(), - endTime: new Date(sequenceMetrics.endTime).toISOString(), - }; - - fs.writeFileSync( - path.join(logFolderPath, 'metrics.json'), - JSON.stringify(metricsJson, null, 2), - 'utf8', - ); - - Logger.log('\nSequence Metrics:'); - console.table(metricsJson); - } - - for (const step of sequence.steps) { - const stepMetrics = sequenceMetrics?.stepMetrics.get(step.id); - for (const node of step.nodes) { - const resultData = await context.getNodeData(node.id); - const nodeMetrics = stepMetrics?.nodeMetrics.get(node.id); - - if (resultData) { - const content = - typeof resultData === 'object' - ? objectToMarkdown(resultData) - : resultData; - writeToFile(logFolderPath, `${node.name}`, content); - } else { - Logger.error( - `Error: Handler ${node.name} failed to produce result data`, - ); - writeToFile( - logFolderPath, - `${node.name}-error`, - objectToMarkdown({ - error: 'No result data', - metrics: nodeMetrics, - }), - ); - } - } - } - - const summary = { - timestamp: new Date().toISOString(), - sequenceId: sequence.id, - sequenceName: sequence.name, - totalExecutionTime: `${sequenceMetrics?.duration}ms`, - successRate: `${sequenceMetrics?.successRate.toFixed(2)}%`, - nodesExecuted: sequenceMetrics?.totalNodes, - completedNodes: sequenceMetrics?.stepMetrics.size, - logFolder: logFolderPath, - }; - - fs.writeFileSync( - path.join(logFolderPath, 'execution-summary.json'), - JSON.stringify(summary, null, 2), - 'utf8', - ); - - return { - success: true, - logFolderPath, - metrics: sequenceMetrics, - }; - } catch (error) { - const errorReport = { - error: { - message: error.message, - stack: error.stack, - }, - metrics: monitor.getSequenceMetrics(sequence.id), - timestamp: new Date().toISOString(), - }; - - fs.writeFileSync( - path.join(logFolderPath, 'error-with-metrics.json'), - JSON.stringify(errorReport, null, 2), - 'utf8', - ); - - Logger.error('\nError during sequence execution:'); - Logger.error(error); - - return { - success: false, - logFolderPath, - error: error as Error, - metrics: monitor.getSequenceMetrics(sequence.id), - }; - } + return { + success: true, + logFolderPath: '', + }; } diff --git a/backend/src/build-system/context.ts b/backend/src/build-system/context.ts index 074ca1ee..2fe2d722 100644 --- a/backend/src/build-system/context.ts +++ b/backend/src/build-system/context.ts @@ -1,21 +1,26 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ import { BuildExecutionState, - BuildNode, BuildResult, BuildSequence, - BuildStep, - NodeOutputMap, -} from './types'; -import { Logger } from '@nestjs/common'; -import { VirtualDirectory } from './virtual-dir'; -import { v4 as uuidv4 } from 'uuid'; -import { BuildMonitor } from './monitor'; -import { BuildHandlerManager } from './hanlder-manager'; -import { OpenAIModelProvider } from 'src/common/model-provider/openai-model-provider'; -import { RetryHandler } from './retry-handler'; - + BuildHandlerConstructor, + ExtractHandlerReturnType, + BuildHandler, + ExtractHandlerType, + BuildNode, +} from './types'; // Importing type definitions related to the build process +import { Logger } from '@nestjs/common'; // Logger class from NestJS for logging +import { VirtualDirectory } from './virtual-dir'; // Virtual directory utility for managing virtual file structures +import { v4 as uuidv4 } from 'uuid'; // UUID generator for unique identifiers +import { BuildMonitor } from './monitor'; // Monitor to track the build process +import { OpenAIModelProvider } from 'src/common/model-provider/openai-model-provider'; // OpenAI model provider for LLM operations +import { RetryHandler } from './retry-handler'; // Retry handler for retrying failed operations +import { BuildHandlerManager } from './hanlder-manager'; // Manager for building handler classes +import path from 'path'; +import * as fs from 'fs'; /** - * Global data keys used throughout the build process + * Global data keys used throughout the build process. + * These keys represent different project-related data that can be accessed globally within the build context. * @type GlobalDataKeys */ export type GlobalDataKeys = @@ -28,22 +33,23 @@ export type GlobalDataKeys = | 'frontendPath'; /** - * Generic context data type mapping keys to any value + * A generic context data type that maps keys to any value. + * It allows storing and retrieving project-specific information in a flexible way. * @type ContextData */ type ContextData = Record; /** - * Core build context class that manages the execution of build sequences + * Core build context class responsible for managing the execution of build sequences. * @class BuilderContext - * @description Responsible for: - * - Managing build execution state - * - Handling node dependencies and execution order - * - Managing global and node-specific context data - * - Coordinating with build handlers and monitors - * - Managing virtual directory operations + * @description This class handles: + * - Managing the execution state of the build (completed, pending, failed, waiting). + * - Handling the dependencies and execution order of nodes (steps in the build process). + * - Managing global and node-specific context data. + * - Coordinating with build handlers, monitors, and virtual directory operations. */ export class BuilderContext { + // Keeps track of the execution state of nodes (completed, pending, failed, waiting) private executionState: BuildExecutionState = { completed: new Set(), pending: new Set(), @@ -51,21 +57,42 @@ export class BuilderContext { waiting: new Set(), }; - private globalPromises: Set> = new Set(); + // Logger instance for logging messages during the build process private logger: Logger; + + // Global context for storing project-related data private globalContext: Map = new Map(); - private nodeData: Map = new Map(); + // Node-specific data storage, keyed by handler constructors + private nodeData: Map = new Map(); + + // Various services and utilities for managing the build process private handlerManager: BuildHandlerManager; private retryHandler: RetryHandler; private monitor: BuildMonitor; public model: OpenAIModelProvider; public virtualDirectory: VirtualDirectory; + // Tracks running node executions, keyed by handler name + private runningNodes: Map>> = new Map(); + + // Polling interval for checking dependencies or waiting for node execution + private readonly POLL_INTERVAL = 500; + + private logFolder: string | null = null; + + /** + * Constructor to initialize the BuilderContext. + * Sets up the handler manager, retry handler, model provider, logger, and virtual directory. + * Initializes the global context with default values. + * @param sequence The build sequence containing nodes to be executed. + * @param id Unique identifier for the builder context. + */ constructor( private sequence: BuildSequence, id: string, ) { + // Initialize service instances this.retryHandler = RetryHandler.getInstance(); this.handlerManager = BuildHandlerManager.getInstance(); this.model = OpenAIModelProvider.getInstance(); @@ -73,319 +100,103 @@ export class BuilderContext { this.logger = new Logger(`builder-context-${id}`); this.virtualDirectory = new VirtualDirectory(); - // Initialize global context with default values + // Initialize global context with default project values this.globalContext.set('projectName', sequence.name); this.globalContext.set('description', sequence.description || ''); - this.globalContext.set('platform', 'web'); + this.globalContext.set('platform', 'web'); // Default platform is 'web' this.globalContext.set('databaseType', sequence.databaseType || 'SQLite'); this.globalContext.set( 'projectUUID', new Date().toISOString().slice(0, 10).replace(/:/g, '-') + '-' + uuidv4(), ); - } - - async execute(): Promise { - this.logger.log(`Starting build sequence: ${this.sequence.id}`); - this.monitor.startSequenceExecution(this.sequence); - - try { - for (const step of this.sequence.steps) { - await this.executeStep(step); - - const incompletedNodes = step.nodes.filter( - (node) => !this.executionState.completed.has(node.id), - ); - - if (incompletedNodes.length > 0) { - this.logger.warn( - `Step ${step.id} failed to complete nodes: ${incompletedNodes - .map((n) => n.id) - .join(', ')}`, - ); - return; - } - } - this.logger.log(`Build sequence completed: ${this.sequence.id}`); - this.logger.log('Final execution state:', this.executionState); - } finally { - this.monitor.endSequenceExecution( - this.sequence.id, - this.globalContext.get('projectUUID'), + if (process.env.DEBUG) { + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + this.logFolder = path.join( + process.cwd(), + 'logs', + `${id}-${timestamp}-${uuidv4().slice(0, 8)}`, ); + fs.mkdirSync(this.logFolder, { recursive: true }); } } - /** - * Executes a build step, handling both parallel and sequential node execution - * @param step The build step to execute - * @private - */ - private async executeStep(step: BuildStep): Promise { - this.logger.log(`Executing build step: ${step.id}`); - this.monitor.setCurrentStep(step); - this.monitor.startStepExecution( - step.id, - this.sequence.id, - step.parallel, - step.nodes.length, - ); + // publig write log method to help all handler to write log + public writeLog(filename: string, content: any): void { + if (!this.logFolder) return; try { - if (step.parallel) { - await this.executeParallelNodes(step); - } else { - await this.executeSequentialNodes(step); - } - } finally { - this.monitor.endStepExecution(step.id, this.sequence.id); - } - } - - /** - * Executes nodes in parallel within a build step - * @param step The build step containing nodes to execute in parallel - * @private - */ - private async executeParallelNodes(step: BuildStep): Promise { - let remainingNodes = [...step.nodes]; - const concurrencyLimit = 20; - - while (remainingNodes.length > 0) { - const executableNodes = remainingNodes.filter((node) => - this.canExecute(node.id), - ); - - if (executableNodes.length > 0) { - for (let i = 0; i < executableNodes.length; i += concurrencyLimit) { - const batch = executableNodes.slice(i, i + concurrencyLimit); - - try { - batch.map(async (node) => { - if (this.executionState.completed.has(node.id)) { - return; - } - - const currentStep = this.monitor.getCurrentStep(); - this.monitor.startNodeExecution( - node.id, - this.sequence.id, - currentStep.id, - ); - - let res; - try { - if (!this.canExecute(node.id)) { - this.logger.log( - `Waiting for dependencies of node ${node.id}: ${node.requires?.join( - ', ', - )}`, - ); - this.monitor.incrementNodeRetry( - node.id, - this.sequence.id, - currentStep.id, - ); - return; - } - - this.logger.log(`Executing node ${node.id} in parallel batch`); - res = this.executeNodeById(node.id); - this.globalPromises.add(res); - - this.monitor.endNodeExecution( - node.id, - this.sequence.id, - currentStep.id, - true, - ); - } catch (error) { - this.monitor.endNodeExecution( - node.id, - this.sequence.id, - currentStep.id, - false, - error instanceof Error ? error : new Error(String(error)), - ); - throw error; - } - }); - - await Promise.all(this.globalPromises); - const activeModelPromises = this.model.getAllActivePromises(); - if (activeModelPromises.length > 0) { - this.logger.debug( - `Waiting for ${activeModelPromises.length} active LLM requests to complete`, - ); - await Promise.all(activeModelPromises); - } - } catch (error) { - this.logger.error( - `Error executing parallel nodes batch: ${error}`, - error instanceof Error ? error.stack : undefined, - ); - throw error; - } - } - - remainingNodes = remainingNodes.filter( - (node) => !this.executionState.completed.has(node.id), - ); - } else { - await new Promise((resolve) => setTimeout(resolve, 100)); - - const activeModelPromises = this.model.getAllActivePromises(); - if (activeModelPromises.length > 0) { - this.logger.debug( - `Waiting for ${activeModelPromises.length} active LLM requests during retry`, - ); - await Promise.all(activeModelPromises); - } - } - } - - const finalActivePromises = this.model.getAllActivePromises(); - if (finalActivePromises.length > 0) { - this.logger.debug( - `Final wait for ${finalActivePromises.length} remaining LLM requests`, - ); - await Promise.all(finalActivePromises); - } - } - - /** - * Executes nodes sequentially within a build step - * @param step The build step containing nodes to execute sequentially - * @private - */ - private async executeSequentialNodes(step: BuildStep): Promise { - for (const node of step.nodes) { - let retryCount = 0; - const maxRetries = 10; - - while ( - !this.executionState.completed.has(node.id) && - retryCount < maxRetries - ) { - await this.executeNode(node); - - if (!this.executionState.completed.has(node.id)) { - await new Promise((resolve) => setTimeout(resolve, 100)); - retryCount++; - } - } - - if (!this.executionState.completed.has(node.id)) { - this.logger.warn( - `Failed to execute node ${node.id} after ${maxRetries} attempts`, - ); - } - } - } - - private async executeNode(node: BuildNode): Promise { - if (this.executionState.completed.has(node.id)) { - return; - } - - const currentStep = this.monitor.getCurrentStep(); - this.monitor.startNodeExecution(node.id, this.sequence.id, currentStep.id); - - try { - if (!this.canExecute(node.id)) { - this.logger.log( - `Waiting for dependencies of node ${node.id}: ${node.requires?.join( - ', ', - )}`, - ); - this.monitor.incrementNodeRetry( - node.id, - this.sequence.id, - currentStep.id, - ); - return; - } - - this.logger.log(`Executing node ${node.id}`); - await this.executeNodeById(node.id); - - this.monitor.endNodeExecution( - node.id, - this.sequence.id, - currentStep.id, - true, - ); + const filePath = path.join(this.logFolder, filename); + const contentStr = + typeof content === 'string' + ? content + : JSON.stringify(content, null, 2); + fs.writeFileSync(filePath, contentStr, 'utf8'); } catch (error) { - this.monitor.endNodeExecution( - node.id, - this.sequence.id, - currentStep.id, - false, - error instanceof Error ? error : new Error(String(error)), - ); - throw error; + this.logger.error(`Failed to write log file: ${filename}`, error); } } /** - * Checks if a node can be executed based on its dependencies - * @param nodeId The ID of the node to check - * @returns boolean indicating if the node can be executed + * Checks if a node can be executed based on its handler's execution state and dependencies. + * @param node The node whose execution eligibility needs to be checked. + * @returns True if the node can be executed, otherwise false. */ - canExecute(nodeId: string): boolean { - const node = this.findNode(nodeId); - - if (!node) return false; + private canExecute(node: BuildNode): boolean { + const handlerName = node.handler.name; if ( - this.executionState.completed.has(nodeId) || - this.executionState.pending.has(nodeId) + this.executionState.completed.has(handlerName) || + this.executionState.pending.has(handlerName) ) { - //this.logger.debug(`Node ${nodeId} is already completed or pending.`); return false; } - return !node.requires?.some( - (dep) => !this.executionState.completed.has(dep), - ); + const canExecute = this.checkNodeDependencies(node); + return canExecute; } - private async executeNodeById( - nodeId: string, - ): Promise> { - const node = this.findNode(nodeId); - if (!node) { - throw new Error(`Node not found: ${nodeId}`); - } - - if (!this.canExecute(nodeId)) { - throw new Error(`Dependencies not met for node: ${nodeId}`); - } + /** + * Invokes a handler for a specific node and returns the result. + * Handles retries in case of failure. + * @param node The node whose handler should be invoked. + * @returns The result of the handler's execution. + */ + private async invokeNodeHandler(node: BuildNode): Promise> { + const handlerClass = node.handler; + const handlerName = handlerClass.name; try { - this.executionState.pending.add(nodeId); - const result = await this.invokeNodeHandler(node); - this.executionState.completed.add(nodeId); - this.logger.log(`${nodeId} is completed`); - this.executionState.pending.delete(nodeId); + const handler = this.handlerManager.getHandler(handlerClass); + const result = await handler.run(this); + + this.writeLog(`${handlerName}.md`, result.data); - this.nodeData.set(node.id, result.data); return result; - } catch (error) { - this.executionState.failed.add(nodeId); - this.executionState.pending.delete(nodeId); - throw error; - } - } + } catch (e) { + this.writeLog(`${handlerName}-error.json`, { + error: e.message, + stack: e.stack, + timestamp: new Date().toISOString(), + }); - getExecutionState(): BuildExecutionState { - return { ...this.executionState }; + const result = await this.retryHandler.retryMethod( + e, + (node) => this.invokeNodeHandler(node), + [node], + ); + if (result === undefined) { + throw e; + } + return result as BuildResult; + } } + // Context management methods for global and node-specific data /** - * Sets data in the global context - * @param key The key to set - * @param value The value to set + * Sets a value in the global context for a specific key. + * @param key The key to identify the context data. + * @param value The value to store in the context. */ setGlobalContext( key: Key, @@ -395,9 +206,9 @@ export class BuilderContext { } /** - * Gets data from the global context - * @param key The key to retrieve - * @returns The value associated with the key, or undefined + * Gets a value from the global context for a specific key. + * @param key The key of the context data. + * @returns The value stored in the context, or undefined if not found. */ getGlobalContext( key: Key, @@ -406,61 +217,203 @@ export class BuilderContext { } /** - * Retrieves node-specific data - * @param nodeId The ID of the node - * @returns The data associated with the node + * Retrieves node-specific data for a given handler class. + * @param handlerClass The handler constructor whose node data should be retrieved. + * @returns The node-specific data or undefined if not found. */ - getNodeData( - nodeId: NodeId, - ): NodeOutputMap[NodeId]; - getNodeData(nodeId: string): any; - getNodeData(nodeId: string) { - return this.nodeData.get(nodeId); + getNodeData( + handlerClass: T, + ): ExtractHandlerReturnType | undefined { + return this.nodeData.get(handlerClass); } /** - * Sets node-specific data - * @param nodeId The ID of the node - * @param data The data to associate with the node + * Sets node-specific data for a given handler class. + * @param handlerClass The handler constructor whose node data should be set. + * @param data The data to store for the node. */ - setNodeData( - nodeId: NodeId, - data: any, + setNodeData( + handlerClass: T, + data: ExtractHandlerReturnType, ): void { - this.nodeData.set(nodeId, data); + this.nodeData.set(handlerClass, data); } + /** + * Gets the current execution state of the build process. + * @returns The current execution state, including completed, pending, failed, and waiting nodes. + */ + getExecutionState(): BuildExecutionState { + return { ...this.executionState }; + } + + /** + * Builds a virtual directory structure from the given JSON content. + * @param jsonContent The JSON string representing the virtual directory structure. + * @returns True if the structure was successfully parsed, false otherwise. + */ buildVirtualDirectory(jsonContent: string): boolean { return this.virtualDirectory.parseJsonStructure(jsonContent); } - private findNode(nodeId: string): BuildNode | null { - for (const step of this.sequence.steps) { - const node = step.nodes.find((n) => n.id === nodeId); - if (node) return node; + /** + * Starts the execution of a specific node in the build sequence. + * The node will be executed if it's eligible (i.e., not completed or pending). + * @param node The node to execute. + * @returns A promise resolving to the result of the node execution. + */ + private startNodeExecution( + node: BuildNode, + ): Promise>> { + const handlerName = node.handler.name; + + // If the node is already completed, pending, or failed, skip execution + if ( + this.executionState.completed.has(handlerName) || + this.executionState.pending.has(handlerName) || + this.executionState.failed.has(handlerName) + ) { + this.logger.debug(`Node ${handlerName} already executed or in progress`); + return; } - return null; + + // Mark the node as pending execution + this.executionState.pending.add(handlerName); + + // Execute the node handler and update the execution state accordingly + const executionPromise = this.invokeNodeHandler>(node) + .then((result) => { + // Mark the node as completed and update the state + this.executionState.completed.add(handlerName); + this.executionState.pending.delete(handlerName); + // Store the result of the node execution + this.setNodeData(node.handler, result.data); + return result; + }) + .catch((error) => { + // Mark the node as failed in case of an error + this.executionState.failed.add(handlerName); + this.executionState.pending.delete(handlerName); + this.logger.error(`[Node Failed] ${handlerName}:`, error); + throw error; + }); + + // Track the running node execution promise + this.runningNodes.set(handlerName, executionPromise); + return executionPromise; } - private async invokeNodeHandler(node: BuildNode): Promise> { - const handler = this.handlerManager.getHandler(node.id); - this.logger.log(`sovling ${node.id}`); - if (!handler) { - throw new Error(`No handler found for node: ${node.id}`); - } + /** + * Executes the entire build sequence by iterating over all nodes in the sequence. + * - The nodes are executed in the given order, respecting dependencies. + * - Each node will only start execution once its dependencies are met. + * - The method waits for all nodes to finish before completing. + * @returns A promise that resolves when the entire build sequence is complete. + */ + + async execute(): Promise { try { - return await handler.run(this, node.options); - } catch (e) { - this.logger.error(`retrying ${node.id}`); - const result = await this.retryHandler.retryMethod( - e, - (node) => this.invokeNodeHandler(node), - [node], - ); - if (result === undefined) { - throw e; + const nodes = this.sequence.nodes; + let currentIndex = 0; + const runningPromises = new Set>(); + + while (currentIndex < nodes.length) { + const currentNode = nodes[currentIndex]; + if (!currentNode?.handler) { + throw new Error(`Invalid node at index ${currentIndex}`); + } + + const handlerName = currentNode.handler.name; + + if (!this.canExecute(currentNode)) { + this.writeLog('execution-state.json', { + timestamp: new Date().toISOString(), + waiting: handlerName, + state: { + completed: Array.from(this.executionState.completed), + pending: Array.from(this.executionState.pending), + failed: Array.from(this.executionState.failed), + }, + }); + await new Promise((resolve) => + setTimeout(resolve, this.POLL_INTERVAL), + ); + continue; + } + + const nodePromise = this.startNodeExecution(currentNode) + .then((result) => { + this.writeLog(`${handlerName}-complete.json`, { + timestamp: new Date().toISOString(), + status: 'complete', + }); + runningPromises.delete(nodePromise); + return result; + }) + .catch((error) => { + this.writeLog(`${handlerName}-error.json`, { + timestamp: new Date().toISOString(), + error: error.message, + stack: error.stack, + }); + runningPromises.delete(nodePromise); + throw error; + }); + + runningPromises.add(nodePromise); + currentIndex++; } - return result as unknown as BuildResult; + + while (runningPromises.size > 0) { + await Promise.all(Array.from(runningPromises)); + await new Promise((resolve) => setTimeout(resolve, this.POLL_INTERVAL)); + } + } catch (error) { + this.writeLog('execution-error.json', { + error: error.message, + stack: error.stack, + timestamp: new Date().toISOString(), + }); + throw error; } } + /** + * Checks if a node's dependencies have been satisfied. + * Each handler may have a set of dependencies that must be completed before the node can run. + * @param node The node whose dependencies need to be checked. + * @returns True if all dependencies are met, otherwise false. + */ + private checkNodeDependencies(node: BuildNode): boolean { + const handlerClass = this.handlerManager.getHandler(node.handler); + const handlerName = handlerClass.name; + + if (!handlerClass.dependencies?.length) { + this.writeLog('dependencies.json', { + handler: handlerName, + dependencies: [], + message: 'No dependencies', + timestamp: new Date().toISOString(), + }); + return true; + } + + const dependencies = handlerClass.dependencies; + const dependencyStatus = dependencies.map((dep) => ({ + dependency: dep.name, + isCompleted: this.executionState.completed.has(dep.name), + })); + + const allDependenciesMet = dependencies.every((dep) => + this.executionState.completed.has(dep.name), + ); + + this.writeLog('dependencies.json', { + handler: handlerName, + dependencies: dependencyStatus, + allDependenciesMet, + timestamp: new Date().toISOString(), + }); + + return allDependenciesMet; + } } diff --git a/backend/src/build-system/errors.ts b/backend/src/build-system/errors.ts index 78a5834f..287c7b45 100644 --- a/backend/src/build-system/errors.ts +++ b/backend/src/build-system/errors.ts @@ -81,7 +81,7 @@ export class ResponseParsingError extends RetryableError { * Indicates that expected tags in the response are missing or invalid during content generation or parsing. * Non-retryable error. */ -export class ResponseTagError extends NonRetryableError { +export class ResponseTagError extends RetryableError { constructor(message: string) { super(message); this.name = 'ResponseTagError'; diff --git a/backend/src/build-system/handlers/backend/code-generate/index.ts b/backend/src/build-system/handlers/backend/code-generate/index.ts index 6fd111c2..ddff2144 100644 --- a/backend/src/build-system/handlers/backend/code-generate/index.ts +++ b/backend/src/build-system/handlers/backend/code-generate/index.ts @@ -11,37 +11,41 @@ import { MissingConfigurationError, ResponseParsingError, } from 'src/build-system/errors'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +import { UXSMDHandler } from '../../ux/sitemap-document'; +import { UXDMDHandler } from '../../ux/datamap'; +import { DBSchemaHandler } from '../../database/schemas/schemas'; +import { BackendRequirementHandler } from '../requirements-document'; /** * BackendCodeHandler is responsible for generating the backend codebase * based on the provided sitemap and data mapping documents. */ -export class BackendCodeHandler implements BuildHandler { - readonly id = 'op:BACKEND:CODE'; - /** - * Executes the handler to generate backend code. - * @param context - The builder context containing configuration and utilities. - * @returns A BuildResult containing the generated code and related data. - */ +@BuildNode() +@BuildNodeRequire([ + UXSMDHandler, + UXDMDHandler, + DBSchemaHandler, + BackendRequirementHandler, +]) +export class BackendCodeHandler implements BuildHandler { async run(context: BuilderContext): Promise> { - // Retrieve project name and database type from context const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; const databaseType = context.getGlobalContext('databaseType') || 'Default database type'; - // Retrieve required documents - const sitemapDoc = context.getNodeData('op:UX:SMD'); - const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC'); - const databaseSchemas = context.getNodeData('op:DATABASE:SCHEMAS'); + const sitemapDoc = context.getNodeData(UXSMDHandler); + const datamapDoc = context.getNodeData(UXDMDHandler); + const databaseSchemas = context.getNodeData(DBSchemaHandler); const backendRequirementDoc = - context.getNodeData('op:BACKEND:REQ')?.overview || ''; + context.getNodeData(BackendRequirementHandler)?.overview || ''; // Validate required data if (!sitemapDoc || !datamapDoc || !databaseSchemas) { throw new MissingConfigurationError( - 'Missing required configuration: sitemapDoc, datamapDoc, or databaseSchemas.', + `Missing required configuration: sitemapDoc, datamapDoc, or databaseSchemas: siteMapDoc: ${!!sitemapDoc}, datamapDoc: ${!!datamapDoc}, databaseSchemas: ${!!databaseSchemas}`, ); } @@ -76,7 +80,7 @@ export class BackendCodeHandler implements BuildHandler { messages: [{ content: backendCodePrompt, role: 'system' }], }, 'generateBackendCode', - this.id, + BackendCodeHandler.name, ); generatedCode = formatResponse(modelResponse); diff --git a/backend/src/build-system/handlers/backend/file-review/file-review.ts b/backend/src/build-system/handlers/backend/file-review/file-review.ts index 458996e6..b01ed099 100644 --- a/backend/src/build-system/handlers/backend/file-review/file-review.ts +++ b/backend/src/build-system/handlers/backend/file-review/file-review.ts @@ -14,14 +14,21 @@ import { ResponseParsingError, ModelUnavailableError, } from 'src/build-system/errors'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +import { BackendRequirementHandler } from '../requirements-document'; +import { BackendCodeHandler } from '../code-generate'; /** * Responsible for reviewing all related source root files and considering modifications * such as package.json, tsconfig.json, .env, etc., in JS/TS projects. - * @requires [op:BACKEND:REQ] - BackendRequirementHandler + * + * Dependencies: + * - BackendCodeHandler: Provides the generated backend code and indirectly provides + * access to backend requirements through its own dependency on BackendRequirementHandler */ +@BuildNode() +@BuildNodeRequire([BackendCodeHandler, BackendRequirementHandler]) export class BackendFileReviewHandler implements BuildHandler { - readonly id = 'op:BACKEND:FILE:REVIEW'; readonly logger: Logger = new Logger('BackendFileModificationHandler'); async run(context: BuilderContext): Promise> { @@ -37,8 +44,10 @@ export class BackendFileReviewHandler implements BuildHandler { project description: ${description}, `; - const backendRequirement = context.getNodeData('op:BACKEND:REQ')?.overview; - const backendCode = [context.getNodeData('op:BACKEND:CODE')]; + const backendRequirement = context.getNodeData( + BackendRequirementHandler, + )?.overview; + const backendCode = [context.getNodeData(BackendCodeHandler)]; if (!backendRequirement) { throw new FileNotFoundError('Backend requirements are missing.'); @@ -75,7 +84,7 @@ export class BackendFileReviewHandler implements BuildHandler { messages, }, 'generateBackendCode', - this.id, + BackendFileReviewHandler.name, ); } catch (error) { throw new ModelUnavailableError('Model Unavailable:' + error); @@ -114,7 +123,7 @@ export class BackendFileReviewHandler implements BuildHandler { messages: [{ content: modificationPrompt, role: 'system' }], }, 'generateBackendFile', - this.id, + BackendFileReviewHandler.name, ); } catch (error) { throw new ModelUnavailableError('Model Unavailable:' + error); diff --git a/backend/src/build-system/handlers/backend/requirements-document/index.ts b/backend/src/build-system/handlers/backend/requirements-document/index.ts index 08d9644f..50a077e8 100644 --- a/backend/src/build-system/handlers/backend/requirements-document/index.ts +++ b/backend/src/build-system/handlers/backend/requirements-document/index.ts @@ -8,6 +8,10 @@ import { ModelUnavailableError, } from 'src/build-system/errors'; import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +import { DBRequirementHandler } from '../../database/requirements-document'; +import { UXDMDHandler } from '../../ux/datamap'; +import { UXSMDHandler } from '../../ux/sitemap-document'; type BackendRequirementResult = { overview: string; @@ -23,10 +27,12 @@ type BackendRequirementResult = { * BackendRequirementHandler is responsible for generating the backend requirements document. * Core Content Generation: API Endpoints, System Overview */ + +@BuildNode() +@BuildNodeRequire([DBRequirementHandler, UXDMDHandler, UXSMDHandler]) export class BackendRequirementHandler implements BuildHandler { - readonly id = 'op:BACKEND:REQ'; private readonly logger: Logger = new Logger('BackendRequirementHandler'); async run( @@ -40,9 +46,9 @@ export class BackendRequirementHandler const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; - const dbRequirements = context.getNodeData('op:DATABASE_REQ'); - const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC'); - const sitemapDoc = context.getNodeData('op:UX:SMD'); + const dbRequirements = context.getNodeData(DBRequirementHandler); + const datamapDoc = context.getNodeData(UXDMDHandler); + const sitemapDoc = context.getNodeData(UXSMDHandler); if (!dbRequirements || !datamapDoc || !sitemapDoc) { this.logger.error( @@ -73,7 +79,7 @@ export class BackendRequirementHandler messages: [{ content: overviewPrompt, role: 'system' }], }, 'generateBackendOverviewPrompt', - this.id, + BackendRequirementHandler.name, ); } catch (error) { throw new ModelUnavailableError('Model is unavailable:' + error); diff --git a/backend/src/build-system/handlers/database/requirements-document/index.ts b/backend/src/build-system/handlers/database/requirements-document/index.ts index d749b6a4..502fd1b8 100644 --- a/backend/src/build-system/handlers/database/requirements-document/index.ts +++ b/backend/src/build-system/handlers/database/requirements-document/index.ts @@ -8,18 +8,20 @@ import { ModelUnavailableError, } from 'src/build-system/errors'; import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; +import { UXDMDHandler } from '../../ux/datamap'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; -export class DatabaseRequirementHandler implements BuildHandler { - readonly id = 'op:DATABASE_REQ'; +@BuildNode() +@BuildNodeRequire([UXDMDHandler]) +export class DBRequirementHandler implements BuildHandler { private readonly logger = new Logger('DatabaseRequirementHandler'); async run(context: BuilderContext): Promise> { - const model = context.model; this.logger.log('Generating Database Requirements Document...'); const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; - const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC'); + const datamapDoc = context.getNodeData(UXDMDHandler); if (!datamapDoc) { this.logger.error('Data mapping document is missing.'); @@ -43,7 +45,7 @@ export class DatabaseRequirementHandler implements BuildHandler { messages: [{ content: prompt, role: 'system' }], }, 'generateDatabaseRequirementPrompt', - this.id, + DBRequirementHandler.name, ); } catch (error) { throw new ModelUnavailableError('Model Unavailable:' + error); diff --git a/backend/src/build-system/handlers/database/schemas/prompt.ts b/backend/src/build-system/handlers/database/schemas/prompt.ts index 4de739d0..524b5006 100644 --- a/backend/src/build-system/handlers/database/schemas/prompt.ts +++ b/backend/src/build-system/handlers/database/schemas/prompt.ts @@ -129,19 +129,41 @@ Your reply must start with "" and end with "".`; schemaContent: string, databaseType: string = 'PostgreSQL', ): string => { - return `You are a Database Expert specializing in ${databaseType}. Validate the following database schema for correctness, syntax, and logical consistency: + return `You are a Database Expert specializing in ${databaseType}. Your task is to analyze and fix any issues in the following database schema: - -${schemaContent} - + ${schemaContent} -Rules for validation: -1. Ensure the schema syntax is valid for ${databaseType}. -2. Check that all necessary tables, fields, indexes, and constraints are present based on standard best practices. -3. Verify that dependencies (e.g., foreign keys) are properly handled and in the correct order. -4. Confirm that all schema operations are idempotent and will not cause issues if executed multiple times. -5. Return 'Validation Passed' if everything is correct. If there are issues, return 'Validation Failed' followed by a detailed explanation of errors. + As an expert database engineer, you should: + 1. Analyze the schema for any syntax errors, logical inconsistencies, or missing elements + 2. Fix any issues including: + - Incorrect syntax or invalid ${databaseType} statements + - Missing or incorrect dependencies + - Improper foreign key relationships + - Missing indexes for commonly queried fields + - Non-idempotent operations + - Incorrect data type usage + - Missing constraints that should exist + - Schema objects created in incorrect order -Your reply must start with "" and end with "".`; + You must provide your response in exactly this format: + 1. First, explain all issues found and fixes applied OUTSIDE of any GENERATE tags + 2. Then provide the complete fixed schema inside GENERATE tags + 3. The GENERATE tags must contain ONLY valid ${databaseType} schema code, no comments or explanations + + For example: + + Found and fixed the following issues: + 1. Added missing IF NOT EXISTS to table creation + 2. Fixed incorrect foreign key reference + 3. Added missing index on commonly queried field + + + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + email VARCHAR(255) NOT NULL + ); + + CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); + `; }, }; diff --git a/backend/src/build-system/handlers/database/schemas/schemas.ts b/backend/src/build-system/handlers/database/schemas/schemas.ts index 69830f5b..fda7b701 100644 --- a/backend/src/build-system/handlers/database/schemas/schemas.ts +++ b/backend/src/build-system/handlers/database/schemas/schemas.ts @@ -12,16 +12,14 @@ import { saveGeneratedCode } from 'src/build-system/utils/files'; import * as path from 'path'; import { formatResponse } from 'src/build-system/utils/strings'; import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; -import { - FileWriteError, - ModelUnavailableError, - ResponseTagError, -} from 'src/build-system/errors'; +import { FileWriteError, ModelUnavailableError } from 'src/build-system/errors'; +import { DBRequirementHandler } from '../requirements-document'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +@BuildNode() +@BuildNodeRequire([DBRequirementHandler]) export class DBSchemaHandler implements BuildHandler { - readonly id = 'op:DATABASE:SCHEMAS'; private readonly logger: Logger = new Logger('DBSchemaHandler'); - async run(context: BuilderContext): Promise { this.logger.log('Generating Database Schemas...'); @@ -30,7 +28,7 @@ export class DBSchemaHandler implements BuildHandler { context.getGlobalContext('projectName') || 'Default Project Name'; const databaseType = context.getGlobalContext('databaseType') || 'PostgreSQL'; - const dbRequirements = context.getNodeData('op:DATABASE_REQ'); + const dbRequirements = context.getNodeData(DBRequirementHandler); const uuid = context.getGlobalContext('projectUUID'); // 2. Validate database type @@ -68,7 +66,7 @@ export class DBSchemaHandler implements BuildHandler { messages: [{ content: analysisPrompt, role: 'system' }], }, 'analyzeDatabaseRequirements', - this.id, + DBSchemaHandler.name, ); dbAnalysis = formatResponse(analysisResponse); } catch (error) { @@ -93,7 +91,7 @@ export class DBSchemaHandler implements BuildHandler { messages: [{ content: schemaPrompt, role: 'system' }], }, 'generateDatabaseSchema', - this.id, + DBSchemaHandler.name, ); schemaContent = formatResponse(schemaResponse); } catch (error) { @@ -108,31 +106,30 @@ export class DBSchemaHandler implements BuildHandler { schemaContent, databaseType, ); - let validationResult: string; try { const validationResponse = await chatSyncWithClocker( context, { model: 'gpt-4o-mini', - messages: [{ content: validationPrompt, role: 'system' }], + messages: [ + { content: validationPrompt, role: 'system' }, + { + role: 'user', + content: + 'help me fix my schema code if there is any failed validation, generate full validated version schemas for me, with xml tag', + }, + ], }, 'validateDatabaseSchema', - this.id, + DBSchemaHandler.name, ); - validationResult = formatResponse(validationResponse); + schemaContent = formatResponse(validationResponse); } catch (error) { throw new ModelUnavailableError( `Model unavailable during validation: ${error}`, ); } - // Check validation result - if (!validationResult.includes('Validation Passed')) { - throw new ResponseTagError( - `Schema validation failed: ${validationResult}`, - ); - } - // Write schema to file const schemaFileName = `schema.${fileExtension}`; try { diff --git a/backend/src/build-system/handlers/file-manager/file-arch/index.ts b/backend/src/build-system/handlers/file-manager/file-arch/index.ts index 21c28f84..f5dcd93d 100644 --- a/backend/src/build-system/handlers/file-manager/file-arch/index.ts +++ b/backend/src/build-system/handlers/file-manager/file-arch/index.ts @@ -13,29 +13,73 @@ import { InvalidParameterError, ModelUnavailableError, } from 'src/build-system/errors'; +import { VirtualDirectory } from 'src/build-system/virtual-dir'; -export class FileArchGenerateHandler implements BuildHandler { - readonly id = 'op:FILE:ARCH'; +import { FileStructureHandler } from '../file-structure'; +import { UXDMDHandler } from '../../ux/datamap'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +import { + buildDependencyGraph, + validateAgainstVirtualDirectory, +} from 'src/build-system/utils/file_generator_util'; + +@BuildNode() +@BuildNodeRequire([FileStructureHandler, UXDMDHandler]) +export class FileFAHandler implements BuildHandler { private readonly logger: Logger = new Logger('FileArchGenerateHandler'); + private virtualDir: VirtualDirectory; async run(context: BuilderContext): Promise> { this.logger.log('Generating File Architecture Document...'); - const fileStructure = context.getNodeData('op:FILE:STRUCT'); - const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC'); + this.virtualDir = context.virtualDirectory; + + const fileStructure = context.getNodeData(FileStructureHandler); + const datamapDoc = context.getNodeData(UXDMDHandler); + this.logger.log('fileStructure:', fileStructure); if (!fileStructure || !datamapDoc) { - Logger.error(fileStructure); - Logger.error(datamapDoc); throw new InvalidParameterError( - 'Missing required parameters: fileStructure or datamapDoc.', + `Missing required parameters: fileStructure or datamapDoc, current fileStructure: ${!!fileStructure}, datamapDoc: ${!!datamapDoc}`, ); } - const prompt = generateFileArchPrompt( - JSON.stringify(fileStructure.jsonFileStructure, null, 2), - JSON.stringify(datamapDoc, null, 2), - ); + const prompt = generateFileArchPrompt(); + + const messages = [ + { + role: 'system' as const, + content: prompt, + }, + { + role: 'user' as const, + content: ` + **Page-by-Page Analysis** + The following is a detailed analysis of each page. Use this information to understand specific roles, interactions, and dependencies. + + ${datamapDoc} + + Next, I will provide the **Directory Structure** to help you understand the full project architecture.`, + }, + { + role: 'user' as const, + content: ` + **Directory Structure**: + The following is the project's directory structure. Use this to identify files and folders. + + ${fileStructure} + + Based on this structure and the analysis provided earlier, please generate the File Architecture JSON object. Ensure the output adheres to all rules and guidelines specified in the system prompt.`, + }, + { + role: 'user' as const, + content: `**Final Check** + Before returning the output, ensure the following: + - The JSON structure is correctly formatted and wrapped in tags. + - File extensions and paths match those in the Directory Structure. + - All files and dependencies are included.`, + }, + ]; let fileArchContent: string; try { @@ -43,28 +87,54 @@ export class FileArchGenerateHandler implements BuildHandler { context, { model: 'gpt-4o-mini', - messages: [{ content: prompt, role: 'system' }], + messages, }, 'generateFileArch', - this.id, + FileFAHandler.name, ); } catch (error) { this.logger.error('Model is unavailable:' + error); throw new ModelUnavailableError('Model is unavailable:' + error); } - const tagContent = parseGenerateTag(fileArchContent); - const jsonData = extractJsonFromText(tagContent); + // Validate the generated file architecture document + try { + const tagContent = parseGenerateTag(fileArchContent); + const jsonData = extractJsonFromText(tagContent); + + if (!jsonData) { + this.logger.error('Failed to extract JSON from text'); + throw new ResponseParsingError('Failed to extract JSON from text.'); + } - if (!jsonData) { - this.logger.error('Failed to extract JSON from text'); - throw new ResponseParsingError('Failed to extract JSON from text.'); - } + if (!this.validateJsonData(jsonData)) { + this.logger.error('File architecture JSON validation failed.'); + throw new ResponseParsingError( + 'File architecture JSON validation failed.', + ); + } + + // validate with virutual dir + const { graph, nodes, fileInfos } = buildDependencyGraph(jsonData); - if (!this.validateJsonData(jsonData)) { - this.logger.error('File architecture JSON validation failed.'); + const invalidFiles = validateAgainstVirtualDirectory( + nodes, + this.virtualDir, + ); + + if (invalidFiles) { + this.logger.error('Validate Against Virtual Directory Fail !!!'); + this.logger.error(`Invalid files detected:\n${invalidFiles}`); + this.logger.error(`${fileArchContent}`); + this.logger.error(`${fileStructure}`); + throw new ResponseParsingError( + 'Failed to validate against virtualDirectory.', + ); + } + } catch (error) { + this.logger.error('File architecture validation failed.'); throw new ResponseParsingError( - 'File architecture JSON validation failed.', + `File architecture JSON validation failed. ${error.message}`, ); } diff --git a/backend/src/build-system/handlers/file-manager/file-arch/prompt.ts b/backend/src/build-system/handlers/file-manager/file-arch/prompt.ts index e985fe90..c19f0c02 100644 --- a/backend/src/build-system/handlers/file-manager/file-arch/prompt.ts +++ b/backend/src/build-system/handlers/file-manager/file-arch/prompt.ts @@ -1,65 +1,55 @@ -export const generateFileArchPrompt = ( - fileStructure: string, - datamapDoc: string, -): string => { - return `You are a File Architecture Analyzer. Your task is to analyze the given project directory structure and the detailed page-by-page analysis, then output a JSON object detailing the file dependencies. The output JSON must be wrapped in tags. - -### Directory Structure Input -The following is the project's directory structure. Use this to identify files and folders. - -\`\`\` -${fileStructure} -\`\`\` - -### Page-by-Page Analysis Input -The following is a detailed analysis of each page. Use this information to understand specific roles, interactions, and dependencies. - -\`\`\` -${datamapDoc} -\`\`\` +export const generateFileArchPrompt = (): string => { + return `Your task is to analyze the given project directory structure and create a detailed JSON object mapping file dependencies. The output JSON must be precisely formatted and wrapped in tags. ### Instructions 1. **Analyze the Inputs**: - Use the directory structure to identify all files and folders. + - Do not assume any additional files or paths. The structure must be based exclusively on the given list. - Leverage the page-by-page analysis to understand the roles and interactions of different components and pages. - Determine the role of each file based on its path and the provided analysis (e.g., page, component, context, hook, styles). - Identify direct dependencies for each file by considering typical imports based on roles, naming conventions, and the provided analysis. - + 2. **Generate File Dependency JSON**: + - Each file must be represented using its full path starting from src/. + - Ensure dependencies are strictly limited to files in the "Paths" array. + - Use absolute file paths from "Paths" for all "dependsOn" values. + Do not use relative paths (./, ../). + Every dependency must match exactly one of the files in "Paths". + - Any file without dependencies should have "dependsOn": []. - For each file, list its direct dependencies as an array of relative paths in the \`dependsOn\` field. - - Use relative paths for dependencies whenever possible. For example: - - If a file \`index.tsx\` references a CSS file \`index.css\` in the same folder, the dependency should be listed as \`"./index.css"\`. - - If a file references another file in its parent folder, use \`"../filename"\`. - - Only use absolute paths (e.g., starting with \`src/\`) when no shorter relative path is available. - - Include CSS/SCSS files as dependencies for any JavaScript or TypeScript files that reference them (e.g., through imports or implied usage). - - Include files that have no dependencies with an empty \`dependsOn\` array. - 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. + - For the src/index.tsx, remember to include router.ts. 3. **Output Requirements**: - The JSON object must strictly follow this structure: \`\`\`json + { "files": { - "path/to/file1": { + "src/path/to/file1": { "dependsOn": ["path/to/dependency1", "path/to/dependency2"] }, - "path/to/file2": { + "src/path/to/file2": { "dependsOn": [] } } } + \`\`\` - - Wrap the JSON output with \`\` tags. + - Keys: Every file must be represented with its full path, starting from src/. + - Dependency Rules: + All dependencies must exist in the "Paths" array. + No inferred or assumed files should be added. + - Wrap the JSON output with \`\` tags. ### Notes -- **CSS Dependencies**: Any file that relies on a CSS/SCSS module file (e.g., \`Header.module.css\`) must list it in the \`dependsOn\` array. -- **Use Relative Paths When Possible**: Ensure dependencies are listed using the shortest possible path (e.g., \`"./filename"\` for files in the same folder). -- **Dependency Inclusion Rule**: All files, except for \`src/index.ts\`, must be depended upon by at least one other file. This means they should appear in the \`dependsOn\` array of at least one other file. - The \`dependsOn\` field should reflect logical dependencies inferred from both the directory structure and the page-by-page analysis. - Use common project patterns to deduce dependencies (e.g., pages depend on components, contexts, hooks, and styles). - Include all files in the output, even if they have no dependencies. -- Ensure the JSON output is properly formatted and wrapped with \`\` tags. ### Output -Return only the JSON object wrapped in \`\` tags.`; +Return only the JSON object wrapped in \`\` tags. +Do not forget tags. +`; }; diff --git a/backend/src/build-system/handlers/file-manager/file-generate/index.ts b/backend/src/build-system/handlers/file-manager/file-generate/index.ts index eb804726..0daad8fa 100644 --- a/backend/src/build-system/handlers/file-manager/file-generate/index.ts +++ b/backend/src/build-system/handlers/file-manager/file-generate/index.ts @@ -1,171 +1,174 @@ -import * as fs from 'fs/promises'; -import * as path from 'path'; -import { Logger } from '@nestjs/common'; -import { VirtualDirectory } from '../../../virtual-dir'; -import { BuilderContext } from 'src/build-system/context'; -import { BuildHandler, BuildResult } from 'src/build-system/types'; -import { extractJsonFromMarkdown } from 'src/build-system/utils/strings'; -import normalizePath from 'normalize-path'; -import toposort from 'toposort'; -import { - InvalidParameterError, - ResponseParsingError, - FileWriteError, -} from 'src/build-system/errors'; -import { getProjectPath } from 'codefox-common'; - -export class FileGeneratorHandler implements BuildHandler { - readonly id = 'op:FILE:GENERATE'; - private readonly logger = new Logger('FileGeneratorHandler'); - private virtualDir: VirtualDirectory; - - async run(context: BuilderContext): Promise> { - this.virtualDir = context.virtualDirectory; - const fileArchDoc = context.getNodeData('op:FILE:ARCH'); - const uuid = context.getGlobalContext('projectUUID'); - - if (!fileArchDoc) { - this.logger.error('File architecture document is missing.'); - throw new InvalidParameterError( - 'Missing required parameter: fileArchDoc.', - ); - } - - const projectSrcPath = getProjectPath(uuid); - - await this.generateFiles(fileArchDoc, projectSrcPath); - this.logger.log('All files generated successfully.'); - return { - success: true, - data: 'Files and dependencies created successfully.', - }; - } - - async generateFiles( - markdownContent: string, - projectSrcPath: string, - ): Promise { - const jsonData = extractJsonFromMarkdown(markdownContent); - - if (!jsonData || !jsonData.files) { - this.logger.error('Invalid or empty file architecture data.'); - throw new ResponseParsingError( - 'Invalid or empty file architecture data.', - ); - } - - const { graph, nodes } = this.buildDependencyGraph(jsonData); - - try { - this.detectCycles(graph); - } catch (error) { - this.logger.error('Circular dependency detected.', error); - throw new InvalidParameterError( - `Circular dependency detected: ${error.message}`, - ); - } - - try { - this.validateAgainstVirtualDirectory(nodes); - } catch (error) { - this.logger.error('Validation against virtual directory failed.', error); - throw new InvalidParameterError(error.message); - } - - const sortedFiles = this.getSortedFiles(graph, nodes); - - for (const file of sortedFiles) { - const fullPath = normalizePath(path.resolve(projectSrcPath, file)); - this.logger.log(`Generating file in dependency order: ${fullPath}`); - try { - await this.createFile(fullPath); - } catch (error) { - throw new FileWriteError(`Failed to create file: ${file}`); - } - } - } - - private buildDependencyGraph(jsonData: { - files: Record; - }): { graph: [string, string][]; nodes: Set } { - const graph: [string, string][] = []; - const nodes = new Set(); - - Object.entries(jsonData.files).forEach(([fileName, details]) => { - nodes.add(fileName); - details.dependsOn.forEach((dep) => { - const resolvedDep = this.resolveDependency(fileName, dep); - graph.push([resolvedDep, fileName]); - nodes.add(resolvedDep); - }); - }); - - return { graph, nodes }; - } - - private detectCycles(graph: [string, string][]): void { - try { - toposort(graph); - } catch (error) { - if (error.message.includes('cycle')) { - throw new InvalidParameterError( - `Circular dependency detected: ${error.message}`, - ); - } - throw error; - } - } - - private getSortedFiles( - graph: [string, string][], - nodes: Set, - ): string[] { - const sortedFiles = toposort(graph).reverse(); - - Array.from(nodes).forEach((node) => { - if (!sortedFiles.includes(node)) { - sortedFiles.unshift(node); - } - }); - - return sortedFiles; - } - - private resolveDependency(currentFile: string, dependency: string): string { - const currentDir = path.dirname(currentFile); - const hasExtension = path.extname(dependency).length > 0; - - if (!hasExtension) { - dependency = path.join(dependency, 'index.ts'); - } - - const resolvedPath = path.join(currentDir, dependency).replace(/\\/g, '/'); - this.logger.log(`Resolved dependency: ${resolvedPath}`); - return resolvedPath; - } - - private validateAgainstVirtualDirectory(nodes: Set): void { - const invalidFiles: string[] = []; - - nodes.forEach((filePath) => { - if (!this.virtualDir.isValidFile(filePath)) { - invalidFiles.push(filePath); - } - }); - - if (invalidFiles.length > 0) { - throw new InvalidParameterError( - `The following files do not exist in the project structure:\n${invalidFiles.join('\n')}`, - ); - } - } - - private async createFile(filePath: string): Promise { - const dir = path.dirname(filePath); - await fs.mkdir(dir, { recursive: true }); - - const content = `// Generated file: ${path.basename(filePath)}`; - await fs.writeFile(filePath, content, 'utf8'); - this.logger.log(`File created: ${filePath}`); - } -} +// import * as fs from 'fs/promises'; +// import * as path from 'path'; +// import { Logger } from '@nestjs/common'; +// import { VirtualDirectory } from '../../../virtual-dir'; +// import { BuilderContext } from 'src/build-system/context'; +// import { BuildHandler, BuildResult } from 'src/build-system/types'; +// import { extractJsonFromMarkdown } from 'src/build-system/utils/strings'; +// import normalizePath from 'normalize-path'; +// import toposort from 'toposort'; +// import { +// InvalidParameterError, +// ResponseParsingError, +// FileWriteError, +// } from 'src/build-system/errors'; +// import { getProjectPath } from 'codefox-common'; +// import { FileFAHandler } from '../file-arch'; +// import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; + +// @BuildNode() +// @BuildNodeRequire([FileFAHandler]) +// export class FileGeneratorHandler implements BuildHandler { +// private readonly logger = new Logger('FileGeneratorHandler'); +// private virtualDir: VirtualDirectory; + +// async run(context: BuilderContext): Promise> { +// this.virtualDir = context.virtualDirectory; +// const fileArchDoc = context.getNodeData(FileFAHandler); +// const uuid = context.getGlobalContext('projectUUID'); + +// if (!fileArchDoc) { +// this.logger.error('File architecture document is missing.'); +// throw new InvalidParameterError( +// 'Missing required parameter: fileArchDoc.', +// ); +// } + +// const projectSrcPath = getProjectPath(uuid); + +// await this.generateFiles(fileArchDoc, projectSrcPath); +// this.logger.log('All files generated successfully.'); +// return { +// success: true, +// data: 'Files and dependencies created successfully.', +// }; +// } + +// async generateFiles( +// markdownContent: string, +// projectSrcPath: string, +// ): Promise { +// const jsonData = extractJsonFromMarkdown(markdownContent); + +// if (!jsonData || !jsonData.files) { +// this.logger.error('Invalid or empty file architecture data.'); +// throw new ResponseParsingError( +// 'Invalid or empty file architecture data.', +// ); +// } + +// const { graph, nodes } = this.buildDependencyGraph(jsonData); + +// try { +// this.detectCycles(graph); +// } catch (error) { +// this.logger.error('Circular dependency detected.', error); +// throw new InvalidParameterError( +// `Circular dependency detected: ${error.message}`, +// ); +// } + +// try { +// this.validateAgainstVirtualDirectory(nodes); +// } catch (error) { +// this.logger.error('Validation against virtual directory failed.', error); +// throw new InvalidParameterError(error.message); +// } + +// const sortedFiles = this.getSortedFiles(graph, nodes); + +// for (const file of sortedFiles) { +// const fullPath = normalizePath(path.resolve(projectSrcPath, file)); +// this.logger.log(`Generating file in dependency order: ${fullPath}`); +// try { +// await this.createFile(fullPath); +// } catch (error) { +// throw new FileWriteError(`Failed to create file: ${file}`); +// } +// } +// } + +// private buildDependencyGraph(jsonData: { +// files: Record; +// }): { graph: [string, string][]; nodes: Set } { +// const graph: [string, string][] = []; +// const nodes = new Set(); + +// Object.entries(jsonData.files).forEach(([fileName, details]) => { +// nodes.add(fileName); +// details.dependsOn.forEach((dep) => { +// const resolvedDep = this.resolveDependency(fileName, dep); +// graph.push([resolvedDep, fileName]); +// nodes.add(resolvedDep); +// }); +// }); + +// return { graph, nodes }; +// } + +// private detectCycles(graph: [string, string][]): void { +// try { +// toposort(graph); +// } catch (error) { +// if (error.message.includes('cycle')) { +// throw new InvalidParameterError( +// `Circular dependency detected: ${error.message}`, +// ); +// } +// throw error; +// } +// } + +// private getSortedFiles( +// graph: [string, string][], +// nodes: Set, +// ): string[] { +// const sortedFiles = toposort(graph).reverse(); + +// Array.from(nodes).forEach((node) => { +// if (!sortedFiles.includes(node)) { +// sortedFiles.unshift(node); +// } +// }); + +// return sortedFiles; +// } + +// private resolveDependency(currentFile: string, dependency: string): string { +// const currentDir = path.dirname(currentFile); +// const hasExtension = path.extname(dependency).length > 0; + +// // if (!hasExtension) { +// // dependency = path.join(dependency, 'index.ts'); +// // } + +// const resolvedPath = path.join(currentDir, dependency).replace(/\\/g, '/'); +// this.logger.log(`Resolved dependency: ${resolvedPath}`); +// return resolvedPath; +// } + +// private validateAgainstVirtualDirectory(nodes: Set): void { +// const invalidFiles: string[] = []; + +// nodes.forEach((filePath) => { +// if (!this.virtualDir.isValidFile(filePath)) { +// invalidFiles.push(filePath); +// } +// }); + +// if (invalidFiles.length > 0) { +// throw new InvalidParameterError( +// `The following files do not exist in the project structure:\n${invalidFiles.join('\n')}`, +// ); +// } +// } + +// private async createFile(filePath: string): Promise { +// const dir = path.dirname(filePath); +// await fs.mkdir(dir, { recursive: true }); + +// const content = `// Generated file: ${path.basename(filePath)}`; +// await fs.writeFile(filePath, content, 'utf8'); +// this.logger.log(`File created: ${filePath}`); +// } +// } diff --git a/backend/src/build-system/handlers/file-manager/file-structure/index.ts b/backend/src/build-system/handlers/file-manager/file-structure/index.ts index 6449bcf2..18721fe3 100644 --- a/backend/src/build-system/handlers/file-manager/file-structure/index.ts +++ b/backend/src/build-system/handlers/file-manager/file-structure/index.ts @@ -1,38 +1,42 @@ -import { - BuildHandler, - BuildOpts, - BuildResult, - FileStructOutput, -} from 'src/build-system/types'; +import { BuildHandler, BuildOpts, BuildResult } from 'src/build-system/types'; import { BuilderContext } from 'src/build-system/context'; import { prompts } from './prompt'; import { Logger } from '@nestjs/common'; -import { removeCodeBlockFences } from 'src/build-system/utils/strings'; +import { + parseGenerateTag, + removeCodeBlockFences, +} from 'src/build-system/utils/strings'; import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; import { ResponseParsingError, MissingConfigurationError, } from 'src/build-system/errors'; +import { UXSMDHandler } from '../../ux/sitemap-document'; +import { UXDMDHandler } from '../../ux/datamap'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; /** * FileStructureHandler is responsible for generating the project's file and folder structure * based on the provided documentation. */ -export class FileStructureHandler implements BuildHandler { +@BuildNode() +@BuildNodeRequire([UXSMDHandler, UXDMDHandler]) +export class FileStructureHandler implements BuildHandler { readonly id = 'op:FILE:STRUCT'; private readonly logger: Logger = new Logger('FileStructureHandler'); async run( context: BuilderContext, opts?: BuildOpts, - ): Promise> { + ): Promise> { this.logger.log('Generating File Structure Document...'); // Retrieve projectName from context const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; - const sitemapDoc = context.getNodeData('op:UX:SMD'); - const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC'); + const sitemapDoc = context.getNodeData(UXSMDHandler); + const datamapDoc = context.getNodeData(UXDMDHandler); + // const projectPart = opts?.projectPart ?? 'frontend'; const projectPart = opts?.projectPart ?? 'frontend'; const framework = context.getGlobalContext('framework') ?? 'react'; @@ -48,13 +52,52 @@ export class FileStructureHandler implements BuildHandler { projectPart, ); + const convertToJsonPrompt = prompts.convertTreeToJsonPrompt(); + + const messages = [ + { + role: 'system' as const, + content: prompt, + }, + { + role: 'user' as const, + content: ` + **Sitemap Documentation** + ${sitemapDoc} + `, + }, + { + role: 'user' as const, + content: ` + **Data map Analysis Documentation:**: + ${datamapDoc} + + Now please generate tree folder structure. + `, + }, + { + role: 'system' as const, + content: convertToJsonPrompt, + }, + { + role: 'user' as const, + content: `**Final Check:** + **Final Check** + Before returning the output, ensure the following: + - The JSON structure is correctly formatted and wrapped in tags. + - File extensions and paths match those in the Directory Structure. + - All files and dependencies are included, with relative paths used wherever possible.`, + }, + ]; + + // Get the generated file structure content let fileStructureContent: string; try { fileStructureContent = await chatSyncWithClocker( context, { model: 'gpt-4o-mini', - messages: [{ content: prompt, role: 'system' }], + messages, }, 'generateCommonFileStructure', this.id, @@ -66,63 +109,60 @@ export class FileStructureHandler implements BuildHandler { ); } } catch (error) { - return { success: false, error }; + this.logger.error( + `Failed to generate file structure: ${error.message}`, + error.stack, + ); + return { + success: false, + error: new ResponseParsingError( + `File structure generation failed. ${error.message}`, + ), + }; } - // Convert the tree structure to JSON - const convertToJsonPrompt = - prompts.convertTreeToJsonPrompt(fileStructureContent); - - let fileStructureJsonContent: string; + // Parse the file structure content + let fileStructureJsonContent = ''; try { - fileStructureJsonContent = await chatSyncWithClocker( - context, - { - model: 'gpt-4o-mini', - messages: [{ content: convertToJsonPrompt, role: 'system' }], - }, - 'convertToJsonPrompt', - this.id, - ); - - if (!fileStructureJsonContent || fileStructureJsonContent.trim() === '') { - throw new ResponseParsingError( - `Generated content is empty during op:FILE:STRUCT 2.`, - ); - } + fileStructureJsonContent = parseGenerateTag(fileStructureContent); } catch (error) { - return { success: false, error }; + return { + success: false, + error: new ResponseParsingError( + `Failed to parse file Structure Json Content. ${error.message}`, + ), + }; } // Build the virtual directory + this.logger.log('start building'); try { const successBuild = context.buildVirtualDirectory( fileStructureJsonContent, ); if (!successBuild) { + this.logger.error( + 'Failed to build virtual directory.' + fileStructureJsonContent, + ); throw new ResponseParsingError('Failed to build virtual directory.'); } } catch (error) { - this.logger.error( - 'Non-retryable error during virtual directory build:', - error, - ); return { success: false, - error: new ResponseParsingError('Failed to build virtual directory.'), + error: new ResponseParsingError( + `Failed to build virtual directory. ${error.message}`, + ), }; } - this.logger.log( - 'File structure JSON content and virtual directory built successfully.', - ); + //debug script print all files + context.virtualDirectory.getAllFiles().forEach((file) => { + this.logger.log(file); + }); return { success: true, - data: { - fileStructure: removeCodeBlockFences(fileStructureContent), - jsonFileStructure: removeCodeBlockFences(fileStructureJsonContent), - }, + data: removeCodeBlockFences(fileStructureContent), }; } diff --git a/backend/src/build-system/handlers/file-manager/file-structure/prompt.ts b/backend/src/build-system/handlers/file-manager/file-structure/prompt.ts index 0e36596f..3bb1ab72 100644 --- a/backend/src/build-system/handlers/file-manager/file-structure/prompt.ts +++ b/backend/src/build-system/handlers/file-manager/file-structure/prompt.ts @@ -13,56 +13,34 @@ export const prompts = { }, */ - convertTreeToJsonPrompt: (treeMarkdown: string): string => { - return `You are a highly skilled developer. Your task is to convert the given file and folder structure, currently represented in an ASCII tree format, into a JSON structure. The JSON structure must: - - - Represent directories and files in a hierarchical manner. - - Use objects with "type" and "name" keys. - - For directories: - - "type": "directory" - - "name": "" - - "children": [ ... ] (an array of files or directories) - - For files: - - "type": "file" - - "name": "" - - Maintain the same nesting as the original ASCII tree. - - **Input Tree:** - \`\`\` - ${treeMarkdown} - \`\`\` - - **Output Format:** - Return a JSON object of the form: - \`\`\`json - { - "type": "directory", - "name": "", - "children": [ - { - "type": "directory", - "name": "subDirName", - "children": [ - { - "type": "file", - "name": "fileName.ext" - } - ] - }, - { - "type": "file", - "name": "anotherFile.ext" - } - ] - } - \`\`\` - - **Additional Rules:** - - Keep directory names and file names exactly as they appear (excluding trailing slashes). - - For directories that appear like "common/", in the JSON just use "common" as the name. - - Do not include comments or extra fields besides "type", "name", and "children". - - The root node should correspond to the top-level directory in the tree. - + convertTreeToJsonPrompt: (): string => { + return `You are a highly skilled developer. Your task is to convert the previous file and folder structure, currently represented in an ASCII tree format, into a JSON structure. The JSON structure must: + + Represent all file paths in a flat list under the "Paths" array. + Maintain the full paths for each file exactly as they appear in the ASCII tree. + Directories should not be included—only file paths. + +Output Format: +Return a JSON object in the following format: +Surround the JSON object with tags. + + +{ + "Paths": [ + "/full/path/to/file1.ext", + "/full/path/to/file2.ext", + "/another/path/to/file3.ext" + ] +} + + +Additional Rules: + + Maintain the original directory structure but only return files in the JSON output. + Keep file names and paths exactly as they appear in the ASCII tree. + Remeber to start with src/ as the root directory (src/...). + The root node should correspond to the top-level directory in the tree. + Do not include comments or extra fields besides "Paths". Return only the JSON structure (no explanations, no additional comments). This JSON will be used directly in the application. `; }, @@ -84,28 +62,30 @@ export const prompts = { roleDescription = 'an expert frontend developer'; includeSections = ` Folder Structure: - components: Reusable UI elements grouped by category (e.g., common, layout, specific). - contexts: Global state management (e.g., auth, theme, player). - hooks: Custom hooks for data fetching and state management. - pages: Route-specific views (e.g., Home, Search, Playlist). - utils: Utility functions (e.g., constants, helpers, validators). - api: Organized API logic (e.g., auth, music, user). - router.ts: Central routing configuration. - index.ts: Application entry point. + src: Main source code folder. + components: Reusable UI elements grouped by category (e.g., common, layout, specific). + contexts: Global state management (e.g., auth, theme, player). + hooks: Custom hooks for data fetching and state management. + pages: Route-specific views (e.g., Home, Search, Playlist). + utils: Utility functions (e.g., constants, helpers, validators). + apis: Organized API logic (e.g., auth, music, user). + router.ts: Central routing configuration. + index.tsx: Application entry point. `; excludeSections = ` Do Not Include: Asset folders (e.g., images, icons, fonts). Test folders or files. Service folders unrelated to API logic. + .css files. `; fileNamingGuidelines = ` - File Naming Guidelines: + File and Folder Naming Guidelines: Use meaningful and descriptive file names. + Do NOT use page_view_* and global_view_* prefixes for folder or file names. For components, include an index.tsx file in each folder to simplify imports. Each component should have its own folder named after the component (e.g., Button/). Use index.tsx as the main file inside the component folder. - Component-specific styles must be in index.css within the same folder as the component. `; break; @@ -149,8 +129,8 @@ export const prompts = { Based on the following input: - Project name: ${projectName} - - Sitemap Documentation: ${sitemapDoc} - - Data Analysis Doc: ${dataAnalysisDoc} + - Sitemap Documentation (provide by user) + - Data Analysis Doc: (provide by user) ### Instructions and Rules: diff --git a/backend/src/build-system/handlers/frontend-code-generate/index.ts b/backend/src/build-system/handlers/frontend-code-generate/index.ts new file mode 100644 index 00000000..6cd92d10 --- /dev/null +++ b/backend/src/build-system/handlers/frontend-code-generate/index.ts @@ -0,0 +1,273 @@ +import { BuildHandler, BuildResult } from 'src/build-system/types'; +import { BuilderContext } from 'src/build-system/context'; +import { Logger } from '@nestjs/common'; +import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; +import { generateFilesDependencyWithLayers } from '../../utils/file_generator_util'; +import { readFileWithRetries, createFileWithRetries } from '../../utils/files'; +import { VirtualDirectory } from '../../virtual-dir'; + +import { UXSMSHandler } from '../ux/sitemap-structure'; +import { UXDMDHandler } from '../ux/datamap'; +import { BackendRequirementHandler } from '../backend/requirements-document'; +import { FileFAHandler } from '../file-manager/file-arch'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +import normalizePath from 'normalize-path'; +import path from 'path'; +import { generateCSSPrompt, generateFrontEndCodePrompt } from './prompt'; +import { formatResponse } from 'src/build-system/utils/strings'; +import { writeFileSync } from 'fs'; +import { MessageInterface } from 'src/common/model-provider/types'; + +/** + * FrontendCodeHandler is responsible for generating the frontend codebase + * based on the provided sitemap, data mapping documents, backend requirement documents, + * frontendDependencyFile, frontendDependenciesContext, . + */ +@BuildNode() +@BuildNodeRequire([ + UXSMSHandler, + UXDMDHandler, + BackendRequirementHandler, + FileFAHandler, +]) +@BuildNode() +@BuildNodeRequire([ + UXSMSHandler, + UXDMDHandler, + BackendRequirementHandler, + FileFAHandler, +]) +export class FrontendCodeHandler implements BuildHandler { + readonly logger: Logger = new Logger('FrontendCodeHandler'); + private virtualDir: VirtualDirectory; + + /** + * Executes the handler to generate frontend code. + * @param context - The builder context containing configuration and utilities. + * @param args - The variadic arguments required for generating the frontend code. + * @returns A BuildResult containing the generated code and related data. + */ + async run(context: BuilderContext): Promise> { + this.logger.log('Generating Frontend Code...'); + + // 1. Retrieve the necessary input from context + const sitemapStruct = context.getNodeData(UXSMSHandler); + const uxDataMapDoc = context.getNodeData(UXDMDHandler); + const backendRequirementDoc = context.getNodeData( + BackendRequirementHandler, + ); + const fileArchDoc = context.getNodeData(FileFAHandler); + + // 2. Grab any globally stored context as needed + this.virtualDir = context.virtualDirectory; + const frontendPath = context.getGlobalContext('frontendPath'); + + if ( + !sitemapStruct || + !uxDataMapDoc || + !backendRequirementDoc || + !fileArchDoc + ) { + this.logger.error(sitemapStruct); + this.logger.error(uxDataMapDoc); + this.logger.error(backendRequirementDoc); + this.logger.error(fileArchDoc); + throw new Error('Missing required parameters.'); + } + + // Dependency + const { concurrencyLayers, fileInfos } = + await generateFilesDependencyWithLayers(fileArchDoc, this.virtualDir); + + // 4. Process each "layer" in sequence; files in a layer in parallel + for (const [layerIndex, layer] of concurrencyLayers.entries()) { + this.logger.log( + `\n==== Concurrency Layer #${layerIndex + 1} ====\nFiles: [${layer.join( + ', ', + )}]\n`, + ); + + const maxRetries = 3; // Maximum retry attempts per file + const delayMs = 200; // Delay between retries for a file + let remainingFiles = [...layer]; // Start with all files in the layer + + for ( + let attempt = 1; + attempt <= maxRetries && remainingFiles.length > 0; + attempt++ + ) { + const failedFiles: any[] = []; + + await Promise.all( + remainingFiles.map(async (file) => { + this.logger.log( + `Layer #${layerIndex + 1}, generating code for file: ${file}`, + ); + + // Resolve the absolute path where this file should be generated + const currentFullFilePath = normalizePath( + path.resolve(frontendPath, file), + ); // src + + // Gather direct dependencies + const directDepsArray = fileInfos[file]?.dependsOn || []; + + // Read each dependency and append to dependenciesContext + let dependenciesText = ''; + for (const dep of directDepsArray) { + this.logger.log( + `Layer #${layerIndex + 1}, file "${file}" → reading dependency "${dep}"`, + ); + try { + // need to check if it really reflect the real path + const resolvedDepPath = normalizePath( + path.resolve(frontendPath, dep), + ); + + // Read the content of the dependency file + const depContent = await readFileWithRetries( + resolvedDepPath, + maxRetries, + delayMs, + ); + + dependenciesText += `\n\n File path: ${dep} \n\`\`\`typescript\n${depContent}\n\`\`\`\n `; + } catch (err) { + this.logger.warn( + `Failed to read dependency "${dep}" for file "${file}": ${err}`, + ); + } + } + + // 5. Build prompt text depending on file extension + const fileExtension = path.extname(file); + let frontendCodePrompt = ''; + if (fileExtension === '.css') { + frontendCodePrompt = generateCSSPrompt( + file, + directDepsArray.join('\n'), + ); + } else { + // default: treat as e.g. .ts, .js, .vue, .jsx, etc. + frontendCodePrompt = generateFrontEndCodePrompt( + file, + directDepsArray.join('\n'), + ); + } + // this.logger.log( + // `Prompt for file "${file}":\n${frontendCodePrompt}\n`, + // ); + + const messages = [ + { + role: 'system' as const, + content: frontendCodePrompt, + }, + { + role: 'user' as const, + content: `**Sitemap Structure** + ${sitemapStruct} + `, + }, + // To DO need to dynamically add the UX Datamap Documentation and Backend Requirement Documentation based on the file generate + // { + // role: 'user' as const, + // content: `This is the UX Datamap Documentation: + // ${uxDataMapDoc} + + // Next will provide UX Datamap Documentation.`, + // }, + // { + // role: 'user' as const, + // content: `This is the Backend Requirement Documentation: + // ${backendRequirementDoc} + + // Next will provide Backend Requirement Documentation.`, + // }, + { + role: 'assistant', + content: + "Good, now provider your dependencies, it's okay dependencies are empty, which means you don't have any dependencies", + }, + { + role: 'user' as const, + content: `Dependencies: + + ${dependenciesText}\n + Now you can provide the code, don't forget the xml tags. +, `, + }, + ] as MessageInterface[]; + + // 6. Call your Chat Model + let generatedCode = ''; + let modelResponse = ''; + try { + modelResponse = await chatSyncWithClocker( + context, + { + model: 'gpt-4o', + messages, + }, + 'generate frontend code', + FrontendCodeHandler.name, + ); + + generatedCode = formatResponse(modelResponse); + + // 7. Write the file to the filesystem + await createFileWithRetries( + currentFullFilePath, + generatedCode, + maxRetries, + delayMs, + ); + } catch (err) { + this.logger.error(`Error generating code for ${file}:`, err); + // FIXME: remove this later + failedFiles.push( + JSON.stringify({ + file: file, + error: err, + modelResponse, + generatedCode, + messages, + }), + ); + } + + this.logger.log( + `Layer #${layerIndex + 1}, completed generation for file: ${file}`, + ); + }), + ); + + // Check if there are still files to retry + if (failedFiles.length > 0) { + writeFileSync( + `./failedFiles-${attempt}.json`, + JSON.stringify(failedFiles), + 'utf-8', + ); + this.logger.warn( + `Retrying failed files: ${failedFiles.join(', ')} (Attempt #${attempt})`, + ); + remainingFiles = failedFiles; // Retry only the failed files + await new Promise((resolve) => setTimeout(resolve, delayMs)); // Add delay between retries + } else { + remainingFiles = []; // All files in this layer succeeded + } + } + + this.logger.log( + `\n==== Finished concurrency layer #${layerIndex + 1} ====\n`, + ); + } + + return { + success: true, + data: frontendPath, + error: new Error('Frontend code generated and parsed successfully.'), + }; + } +} diff --git a/backend/src/build-system/handlers/frontend-code-generate/prompt.ts b/backend/src/build-system/handlers/frontend-code-generate/prompt.ts index 0eb48aec..bd7647bd 100644 --- a/backend/src/build-system/handlers/frontend-code-generate/prompt.ts +++ b/backend/src/build-system/handlers/frontend-code-generate/prompt.ts @@ -1,48 +1,92 @@ export const generateFrontEndCodePrompt = ( - sitemapDoc: string, - uxDatamapDoc: string, currentFile: string, - dependencyFile: string, + dependencyFilePath: string, ): string => { - return `You are an expert frontend developer. - Your task is to generate complete and production-ready React or Next.js frontend code based on the provided inputs. - 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. - - Based on following inputs: - - - Sitemap Documentation: ${sitemapDoc} - - UX Datamap Documentation: ${uxDatamapDoc} - - Current File Context: ${currentFile} - - Dependency File: ${dependencyFile} + return `You are an expert frontend developer specializing in building scalable, maintainable, and production-ready React applications using TypeScript. + Your task: Generate complete and functional React frontend code based on the provided inputs. The output must include all essential files, folders, and logic for UI components, API integration, routing, and state management. + Current File: ${currentFile}. ### Instructions and Rules: - File Requirements: + File Requirements: + Implement all specifications defined in the sitemap and UX datamap documents. + Ensure that the code includes necessary imports, state management, interactions, and API integrations without placeholders. + Incorporate hooks, APIs, or context based on dependencies provide + Use inline Tailwind CSS classes in TSX for all styling requirements. Avoid external .css files unless explicitly required by third-party libraries. + For src/index.tsx, ensure it imports index.css for global styles or third-party integrations. - The generated file must fully implement the requirements defined in the sitemap and UX datamap documents. - Include all necessary imports, state management, and interactions to ensure functionality (no placeholders import). - If applicable, integrate hooks, APIs, or context from the provided dependencies. + Code Standards: + Use React functional components and modern hooks (e.g., useState, useEffect, useContext). + Adhere to styling guidelines (e.g., Tailwind CSS, CSS Modules) as described in dependency files. + Use descriptive and meaningful names for all variables, functions, and components. + Follow accessibility best practices, including proper aria attributes. + When need to import from dependency file, use the user provide dependency file path. + Do not include any unnecessary or redundant code. + Do not assume any specific backend or API implementation. + Do not asume any import or dependencies. + Use only the dependencies provided below for imports. Ensure these imports are included correctly in the generated code wherever applicable. - Code Standards: + ### Dependencies: + Below are the required dependency files to be included in the generated code. - Use functional components and React hooks. - Adhere to any styling guidelines defined in the dependency file (e.g., Tailwind CSS or CSS Modules). - Use descriptive and meaningful names for variables, functions, and components. - Ensure accessibility best practices, including aria attributes. + + File path: (dependency file code path) - Comments: + \`\`\`typescript + dependency file content + \`\`\` + - Add comments describing the purpose of each major code block or function. - Include placeholders for any additional features or data-fetching logic that may need integration later. - Error Handling: + Comments: + Add comments for each major code block or function, describing its purpose and logic. + Mark placeholders or sections where additional future integrations might be needed. - Handle potential edge cases, such as loading, error states, and empty data. + Error Handling: + Handle edge cases such as loading states, error states, and empty data gracefully. + Include fallback UI components or error boundaries where applicable. - Output Completeness: + Output Completeness: + The generated file must be production-ready and include all necessary imports and dependencies. + This final result must be 100% complete. Will be directly use in the production - The generated file must be functional and complete, ready to be added directly to the project. + ### Output Format: + DO NOT include any code fences (no \`\`\`). + Output your final code wrapped in tags ONLY, like: - This final result must be 100% complete. Will be directly use in the production + + ...full code... + `; }; + +export function generateCSSPrompt( + fileName: string, + directDependencies: string, +): string { + return ` + You are an expert CSS developer. Generate valid, production-ready CSS for the file "${fileName}". + + ## Context + - Sitemap Strucutrue: + - UX Datamap Documentation: + + - Direct Dependencies (if any and may include references to other styles or partials): + ${directDependencies} + + - Direct Dependencies Context: + + ## Rules & Guidelines + 1. **Do NOT** include any JavaScript or React code—only plain CSS. + 2. **Do NOT** wrap your output in code fences (\`\`\`). + 3. You may define classes, IDs, or any selectors you need, but **be sure** to keep it purely CSS. + 4. Ensure the output is well-structured, readable, and adheres to best practices (e.g., BEM if you prefer). + 5. Include comments for clarity if needed, but keep them concise. + + ## Output Format + Please produce the complete CSS content in the format described: + + /* Your CSS content here */ + + `; +} diff --git a/backend/src/build-system/handlers/product-manager/product-requirements-document/prd.ts b/backend/src/build-system/handlers/product-manager/product-requirements-document/prd.ts index b562f8f2..3cf7888c 100644 --- a/backend/src/build-system/handlers/product-manager/product-requirements-document/prd.ts +++ b/backend/src/build-system/handlers/product-manager/product-requirements-document/prd.ts @@ -10,12 +10,13 @@ import { ModelUnavailableError, ResponseParsingError, } from 'src/build-system/errors'; +import { BuildNode } from 'src/build-system/hanlder-manager'; -export class PRDHandler implements BuildHandler { - readonly id = 'op:PRD'; +@BuildNode() +export class PRDHandler implements BuildHandler { readonly logger: Logger = new Logger('PRDHandler'); - async run(context: BuilderContext): Promise { + async run(context: BuilderContext): Promise> { this.logger.log('Generating PRD...'); // Extract project data from the context @@ -72,7 +73,7 @@ export class PRDHandler implements BuildHandler { context, { messages, model: 'gpt-4o-mini' }, 'generatePRDFromLLM', - this.id, + PRDHandler.name, ); if (!prdContent || prdContent.trim() === '') { throw new ModelUnavailableError( diff --git a/backend/src/build-system/handlers/project-init.ts b/backend/src/build-system/handlers/project-init.ts index 0562a8a9..f51c2bfe 100644 --- a/backend/src/build-system/handlers/project-init.ts +++ b/backend/src/build-system/handlers/project-init.ts @@ -3,6 +3,8 @@ import { BuildHandler, BuildResult } from '../types'; import { Logger } from '@nestjs/common'; import * as path from 'path'; import { buildProjectPath, copyProjectTemplate } from '../utils/files'; +import { BuildNode } from '../hanlder-manager'; +@BuildNode() export class ProjectInitHandler implements BuildHandler { readonly id = 'op:PROJECT::STATE:SETUP'; private readonly logger = new Logger('ProjectInitHandler'); diff --git a/backend/src/build-system/handlers/ux/datamap/index.ts b/backend/src/build-system/handlers/ux/datamap/index.ts index 6513ebb2..5fefb75f 100644 --- a/backend/src/build-system/handlers/ux/datamap/index.ts +++ b/backend/src/build-system/handlers/ux/datamap/index.ts @@ -8,12 +8,15 @@ import { MissingConfigurationError, ModelUnavailableError, } from 'src/build-system/errors'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +import { UXSMDHandler } from '../sitemap-document'; /** * Handler for generating the UX Data Map document. */ -export class UXDatamapHandler implements BuildHandler { - readonly id = 'op:UX:DATAMAP:DOC'; +@BuildNode() +@BuildNodeRequire([UXSMDHandler]) +export class UXDMDHandler implements BuildHandler { private readonly logger = new Logger('UXDatamapHandler'); async run(context: BuilderContext): Promise> { @@ -22,7 +25,8 @@ export class UXDatamapHandler implements BuildHandler { // Extract relevant data from the context const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; - const sitemapDoc = context.getNodeData('op:UX:SMD'); + const platform = context.getGlobalContext('platform') || 'Default Platform'; + const sitemapDoc = context.getNodeData(UXSMDHandler); // Validate required data if (!projectName || typeof projectName !== 'string') { @@ -36,7 +40,7 @@ export class UXDatamapHandler implements BuildHandler { const prompt = prompts.generateUXDataMapPrompt( projectName, sitemapDoc, - 'web', // TODO: change platform dynamically if needed + platform, // TODO: change platform dynamically if needed ); try { @@ -48,7 +52,7 @@ export class UXDatamapHandler implements BuildHandler { messages: [{ content: prompt, role: 'system' }], }, 'generateUXDataMap', - this.id, + UXDMDHandler.name, ); this.logger.log('Successfully generated UX Data Map content.'); diff --git a/backend/src/build-system/handlers/ux/sitemap-document/index.ts b/backend/src/build-system/handlers/ux/sitemap-document/index.ts new file mode 100644 index 00000000..95fb5eb4 --- /dev/null +++ b/backend/src/build-system/handlers/ux/sitemap-document/index.ts @@ -0,0 +1,104 @@ +import { BuildHandler, BuildResult } from 'src/build-system/types'; +import { BuilderContext } from 'src/build-system/context'; +import { prompts } from './prompt'; +import { Logger } from '@nestjs/common'; +import { removeCodeBlockFences } from 'src/build-system/utils/strings'; +import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; +import { PRDHandler } from '../../product-manager/product-requirements-document/prd'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; +import { ResponseParsingError } from 'src/build-system/errors'; + +@BuildNode() +@BuildNodeRequire([PRDHandler]) +export class UXSMDHandler implements BuildHandler { + readonly logger: Logger = new Logger('UXSMDHandler'); + async run(context: BuilderContext): Promise> { + this.logger.log('Generating UXSMD...'); + + // Extract project data from the context + const projectName = + context.getGlobalContext('projectName') || 'Default Project Name'; + const platform = context.getGlobalContext('platform') || 'Default Platform'; + const prdContent = context.getNodeData(PRDHandler); + this.logger.debug('prd in uxsmd', prdContent); + + // Generate the prompt dynamically + const prompt = prompts.generateUxsmdPrompt(projectName, platform); + + // Send the prompt to the LLM server and process the response + const uxsmdContent = await this.generateUXSMDFromLLM( + context, + prompt, + prdContent, + ); + + if (!uxsmdContent || uxsmdContent === '') { + return { + success: false, + error: new ResponseParsingError('Failed to generate UXSMD'), + }; + } + + // Store the generated document in the context + context.setGlobalContext('uxsmdDocument', uxsmdContent); + + // Return the generated document + return { + success: true, + data: removeCodeBlockFences(uxsmdContent), + }; + } + + private async generateUXSMDFromLLM( + context: BuilderContext, + prompt: string, + prdContent: string, + ): Promise { + const messages = [ + { + role: 'system' as const, + content: prompt, + }, + { + role: 'user' as const, + content: ` + Here is the **Product Requirements Document (PRD)**: + + ${prdContent} + + Please generate the Full UX Sitemap Document now, focusing on MVP features but ensuring each page has enough detail to be functional.`, + }, + { + role: 'user' as const, + content: `**Validation Step:** + - **Review your output** to ensure **100% coverage** of the PRD. + - Make sure you covered all global_view_* and page_view_* in UX Sitemap Document, If any of them is missing add them based on the system prompt. + - If any critical pages, features, or flows are **missing**, **add them**. + - Adjust for **navigation completeness**, making sure all interactions and workflows are **correctly linked**.`, + }, + { + role: 'user' as const, + content: `**Final Refinement:** + - **Expand the Unique UI Pages **, adding page_view_* if needed: + - **Expand the page_views **, adding more details on: + - **Step-by-step user actions** within each page. + - **Alternative user paths** (e.g., different ways a user might complete an action). + - **How components within the page interact** with each other and primary features. + - **Ensure clarity** so developers can implement the structure **without assumptions**.`, + }, + ]; + + const uxsmdContent = await chatSyncWithClocker( + context, + { + model: 'gpt-4o-mini', + messages: messages, + }, + 'generateUXSMDFromLLM', + UXSMDHandler.name, + ); + + this.logger.log('Received full UXSMD content from LLM server.'); + return uxsmdContent; + } +} diff --git a/backend/src/build-system/handlers/ux/sitemap-document/prompt.ts b/backend/src/build-system/handlers/ux/sitemap-document/prompt.ts index ccf8d7f6..8ca4cb2b 100644 --- a/backend/src/build-system/handlers/ux/sitemap-document/prompt.ts +++ b/backend/src/build-system/handlers/ux/sitemap-document/prompt.ts @@ -1,44 +1,82 @@ // Define and export the system prompts object export const prompts = { - generateUxsmdrompt: ( - projectName: string, - prdDocument: string, - platform: string, - ): string => { + generateUxsmdPrompt: (projectName: string, platform: string): string => { return `You are an expert frontend develper and ux designer. Your job is to analyze and expand upon the details provided, generating a Full UX Sitemap Document based on the following inputs: - Project name: ${projectName} - - product requirements document: ${prdDocument} - Platform: ${platform} + - product requirements document - Follow these rules as a guide to ensure clarity and completeness in your UX Sitemap Document. - 1, Write you result in markdown + Your primary goal is to create a fully comprehensive, development-ready UX Sitemap Document that will be directly transferred to the development team for implementation. + This document will be used for an application expected to serve thousands of users, and it must cover all use cases, ensuring a complete and detailed roadmap of all UI components and navigation. + + Formatting & Output Guidelines: + 1, Use Markdown for structuring the document. 2, Your reply should start with : "\`\`\`UXSitemap" and end with "\`\`\`", Use proper markdown syntax for headings, subheadings, and hierarchical lists. - 3. **Comprehensive Analysis**: Thoroughly parse the PRD to identify all core features, functionalities, and user stories. - - Focus on creating a hierarchical sitemap that covers each major section, with relevant sub-sections, pages, and key interactions. - - Ensure all primary and secondary user journeys identified in the PRD are clearly reflected. - - 4. **Page and Navigation Structure**: Break down the sitemap into main sections, secondary sections, and individual screens. - - **Main Sections**: Identify primary sections (e.g., Home, User Account, Product Catalog) based on project requirements. - - **Secondary Sections**: Include sub-sections under each main section (e.g., "Profile" and "Order History" under "User Account"). - - **Screens and Interactions**: List specific screens and interactions that users encounter within each flow. - - 5. **Detailed User Journeys**: - - For every user story described in the PRD, map out the step-by-step navigation path. - - Highlight sequences (e.g., 1. Home > 1.1. Explore > 1.1.1. Product Details). - - 6. **Thorough Coverage**: - - Ensure the sitemap is fully comprehensive. If any feature from the PRD is not covered or any user journey is missing, add it to the sitemap. - - Consider the target audience and validate that all expected navigation flows and screens meet user needs. - -7. Ask Your self: - - Am I cover all the product requirement document? - - Am I Cover all the gloabal UI? - - Am I Cover all unique UI? - - Am I cover all the view that expect by user? - - what is all the details about UI? - - Am I cover all the cases? Is the final result 100% complete? - -Remeber your result will be directly be use in the deveolpment. Make sure is 100% complete. - `; + 3, Ensure proper markdown syntax for headings, subheadings, and hierarchical lists. + 4, Strict Naming Conventions: + Global Shared UI Views → Prefix with global_view_* . + Unique UI Pages → Prefix with page_view_* . + No "Container" Views → Do not create abstract container views . + global_view_* and page_view_* must be independent → page_view_* does not embed global_view_*, but they share screen space. + + UX Sitemap Requirements: + Your UX Sitemap Document should include detailed breakdowns of: + + 1, Global Shared UI Views (global_view_*) + Definition: Shared UI components (e.g., navigation, footers, side menus) used across multiple pages. + Structure: + Each must have a unique ID (global_view_*). + Clearly describe authentication conditions: + Logged-in users (full access, personalized elements). + Logged-out users (restricted access, call-to-actions). + Document all shared elements and their variations: + Example: global_view_top_nav (changes based on authentication state). + + 2, Unique UI Pages (page_view_*) + Definition: Individual, standalone pages (e.g., page_view_home, page_view_settings). + Structure: + Path (URL Route): Clearly define the URL structure of the page. + Example: /home + Covers all unique screens (e.g., page_view_home, page_view_onboarding, page_view_settings). + Describe authentication conditions, permissions, and state dependencies: + What features are available to guest vs. logged-in users? + Are any actions restricted based on user type (e.g., admin, regular user)? + Ensure no duplicate inclusion of global_view_* views (they only share screen space). + Components: + List all UI components that appear on this page. + Describe their functionality and interaction behavior. + Provide detailed descriptions of features, interactions, and user flows. + + 3, Functional & Feature Analysis + Break down each UI page before detailing its components. + Clearly map features to UI views. + Ensure that: + Every feature described in the PRD has a corresponding page (page_view_*). + Features are not missing any expected UI elements. + + 4, Navigation & User Journey Mapping + Map each user journey from the PRD into a step-by-step navigation path. + Example format: + + 1. page_view_home → page_view_explore → page_view_product_details → .... + + Cover both static and dynamic navigation flows. + + Final Instructions: + Self Check Before finalizing, ask yourself: + Did I cover all global shared UI views (global_view_*) separately? + Did I assign unique and expressive IDs (global_view_* for shared views, page_view_* for unique pages)? + Did I avoid embedding global_view_* inside page_view_*? + Did I ensure authentication-based conditions (logged-in vs. guest)? + Did I extensively describe every UI element, interaction, and user flow? + Did I include URL paths for all pages? + Did I include 100% of views required by all features? + Did I avoid unnecessary secondary/tertiary features? + Did I describe inter-app linking and navigation comprehensively? + Strictly follow the naming and formatting conventions. + No extra comments or surrounding text—your reply will be directly used as the final UX Sitemap Analysis Document. + Your response should only contain the markdown-formatted UX Sitemap Document. + Your final document must be exhaustive and 100% complete for development use. + `; }, }; diff --git a/backend/src/build-system/handlers/ux/sitemap-document/uxsmd.ts b/backend/src/build-system/handlers/ux/sitemap-document/uxsmd.ts deleted file mode 100644 index ff8ac1bb..00000000 --- a/backend/src/build-system/handlers/ux/sitemap-document/uxsmd.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { BuildHandler, BuildResult } from 'src/build-system/types'; -import { BuilderContext } from 'src/build-system/context'; -import { prompts } from './prompt'; -import { Logger } from '@nestjs/common'; -import { removeCodeBlockFences } from 'src/build-system/utils/strings'; -import { chatSyncWithClocker } from 'src/build-system/utils/handler-helper'; -import { - MissingConfigurationError, - ModelUnavailableError, - ResponseParsingError, -} from 'src/build-system/errors'; - -export class UXSMDHandler implements BuildHandler { - readonly id = 'op:UX:SMD'; - private readonly logger = new Logger('UXSMDHandler'); - - async run(context: BuilderContext): Promise> { - this.logger.log('Generating UXSMD...'); - - // Extract project data from the context - const projectName = - context.getGlobalContext('projectName') || 'Default Project Name'; - const platform = context.getGlobalContext('platform') || 'Default Platform'; - const prdContent = context.getNodeData('op:PRD'); - - // Validate required data - if (!projectName || typeof projectName !== 'string') { - throw new MissingConfigurationError('Missing or invalid projectName.'); - } - if (!platform || typeof platform !== 'string') { - throw new MissingConfigurationError('Missing or invalid platform.'); - } - if (!prdContent || typeof prdContent !== 'string') { - throw new MissingConfigurationError('Missing or invalid PRD content.'); - } - - // Generate the prompt dynamically - const prompt = prompts.generateUxsmdrompt( - projectName, - prdContent, - platform, - ); - - // Send the prompt to the LLM server and process the response - - try { - // Generate UXSMD content using the language model - const uxsmdContent = await this.generateUXSMDFromLLM(context, prompt); - - if (!uxsmdContent || uxsmdContent.trim() === '') { - this.logger.error('Generated UXSMD content is empty.'); - throw new ResponseParsingError('Generated UXSMD content is empty.'); - } - - // Store the generated document in the context - context.setGlobalContext('uxsmdDocument', uxsmdContent); - - this.logger.log('Successfully generated UXSMD content.'); - return { - success: true, - data: removeCodeBlockFences(uxsmdContent), - }; - } catch (error) { - throw new ResponseParsingError( - 'Failed to generate UXSMD content:' + error, - ); - } - } - - private async generateUXSMDFromLLM( - context: BuilderContext, - prompt: string, - ): Promise { - try { - const uxsmdContent = await chatSyncWithClocker( - context, - { - model: 'gpt-4o-mini', - messages: [{ content: prompt, role: 'system' }], - }, - 'generateUXSMDFromLLM', - this.id, - ); - this.logger.log('Received full UXSMD content from LLM server.'); - return uxsmdContent; - } catch (error) { - throw new ModelUnavailableError( - 'Failed to generate UXSMD content:' + error, - ); - } - } -} diff --git a/backend/src/build-system/handlers/ux/sitemap-structure/index.ts b/backend/src/build-system/handlers/ux/sitemap-structure/index.ts index c6cdef54..f156aa7e 100644 --- a/backend/src/build-system/handlers/ux/sitemap-structure/index.ts +++ b/backend/src/build-system/handlers/ux/sitemap-structure/index.ts @@ -10,10 +10,16 @@ import { ModelUnavailableError, ResponseParsingError, } from 'src/build-system/errors'; +import { UXSMDHandler } from '../sitemap-document'; +import { BuildNode, BuildNodeRequire } from 'src/build-system/hanlder-manager'; -// UXSMS: UX Sitemap Structure -export class UXSitemapStructureHandler implements BuildHandler { - readonly id = 'op:UX:SMS'; +/** + * UXSMS: UX Sitemap Structure + **/ + +@BuildNode() +@BuildNodeRequire([UXSMDHandler]) +export class UXSMSHandler implements BuildHandler { private readonly logger = new Logger('UXSitemapStructureHandler'); async run(context: BuilderContext): Promise> { @@ -22,7 +28,9 @@ export class UXSitemapStructureHandler implements BuildHandler { // Extract relevant data from the context const projectName = context.getGlobalContext('projectName') || 'Default Project Name'; - const sitemapDoc = context.getNodeData('op:UX:SMD'); + const sitemapDoc = context.getNodeData(UXSMDHandler); + + const platform = context.getGlobalContext('platform') || 'Default Platform'; // Validate required parameters if (!projectName || typeof projectName !== 'string') { @@ -34,18 +42,55 @@ export class UXSitemapStructureHandler implements BuildHandler { ); } - // Generate the prompt dynamically - const prompt = prompts.generateUXSiteMapStructrePrompt( + const prompt = prompts.generateUXSiteMapStructurePrompt( projectName, - sitemapDoc, - 'web', // TODO: Change platform dynamically if necessary + platform, // TODO: Change platform dynamically if necessary ); + const messages: MessageInterface[] = [ + { + role: 'system', + content: prompt, + }, + { + role: 'user', + content: ` + Here is the UX Sitemap Documentation (SMD): + + ${sitemapDoc} + + Please generate the Full UX Sitemap Structre now, focusing on MVP features but ensuring each page has enough detail to be functional. You Must Provide all the page_view`, + }, + { + role: 'user', + content: `Check if you covered all major pages, user flows, and any global components mentioned in the SMD. + If anything is missing, please add it now. Also, expand on how each is used across pages.`, + }, + { + role: 'user', + content: `Please add more detail about the Core Components within each . + Specifically: + - Provide a descriptive name for each Core Component (e.g., “C1.1. SearchBar”). + - List possible states (Default, Hover, etc.) and typical user interactions (click, scroll, etc.). + - Clarify how these components support user goals and why they exist on that page.`, + }, + { + role: 'user', + content: ` + Finally, please ensure each page includes at least one user flow under "Page-Specific User Flows", + illustrating step-by-step how a user accomplishes a key goal on that page. + If the SMD mentions additional flows, add them here. + Confirm any login or access restrictions, too. + `, + }, + { + role: 'user', + content: + 'Also, make sure all pages are beginning with and ending with tags. You should follow the rule from system prompt', + }, + ]; + try { - // Generate UX Structure content using the language model - const messages: MessageInterface[] = [ - { content: prompt, role: 'system' }, - ]; const uxStructureContent = await chatSyncWithClocker( context, { @@ -53,7 +98,7 @@ export class UXSitemapStructureHandler implements BuildHandler { messages, }, 'generateUXSiteMapStructre', - this.id, + UXSMSHandler.name, ); if (!uxStructureContent || uxStructureContent.trim() === '') { diff --git a/backend/src/build-system/handlers/ux/sitemap-structure/prompt.ts b/backend/src/build-system/handlers/ux/sitemap-structure/prompt.ts index 933bb8ae..5e8d3d91 100644 --- a/backend/src/build-system/handlers/ux/sitemap-structure/prompt.ts +++ b/backend/src/build-system/handlers/ux/sitemap-structure/prompt.ts @@ -1,131 +1,387 @@ export const prompts = { - generateUXSiteMapStructrePrompt: ( + generateUXSiteMapStructurePrompt: ( projectName: string, - sitemapDoc: string, platform: string, ): string => { - return `You are an expert UX Designer. Your task is to analyze the provided sitemap documentation and generate a Detailed UX Structure Map to support the user experience and frontend implementation. The output should address all aspects of user interaction, content layout, based on the following inputs: - - - Project name: ${projectName} - - Sitemap Documentation: ${sitemapDoc} - - Platform: ${platform} - - Follow these guidelines to analyze requirements from a UX perspective: - - ### Instructions and Rules: - - 1, Your job is to analyzing how the ui element should be layed out and distributed on the page based on the Sitemap Documentation. - 2, You need to ensure all features from the sitemap documentation are addressed. - 3, You need to identify and define every page/screen required for the application. - 4, Detailed Breakdown for Each Page/Screen: - Page name: ## {index}. Name page - Page Purpose: Clearly state the user goal for the page. - Core Elements: - List all components (e.g., headers, buttons, sidebars) and explain their role on the page. - Include specific interactions and behaviors for each element. - Content Display: - Identify the information that needs to be visible on the page and why it's essential for the user. - Navigation and Routes: - Specify all frontend routes required to navigate to this page. - Include links or actions that lead to other pages or states. - Restrictions: - Note any restrictions or conditions for accessing the page (e.g., login required, specific user roles). - - 5, Focus on Detail: - Provide a component-level breakdown for each page, including layouts. - Explain how the design supports user goals and aligns with their journey. - - 6. For each page/screen in the sitemap: - - How the block should be place on the view? - - What information does the user need to see? - - What elements should be on the page? - - What are all the routes require for the frontend? - - What dynamic content needs to be displayed? - - What are the restriction for the page? - - 7. Consider: - - User goals on each page - - User journy - - Element purposes - - Content that needs to be displayed - - Your reply must start with: "\`\`\`UXStructureMap" and end with "\`\`\`". - - Focus on describing the UX Structure from a user experience perspective. For each page: - 1. What element appear on each page and why - 2. What information needs to be displayed and why - 3. How the element supports user goals - - Make sure is 100% completed. It will be directly be use in development. + return `You are an expert frontend develper and ux designer. Your job is to analyze and expand upon the details provided, generating a Full UX Sitemap Structure Document based on the following inputs: + - Project name: ${projectName} + - Platform: ${platform} + - UX Sitemap Documentation: (Provided by the user next) + + Formatting & Output Guidelines: + Output Requirements: + Use plain text (no Markdown). + Begin with and end with . + Within , start with , and generate multiple blocks, one for each page. + + UX Sitemap Structure: + The sitemap should be structured as follows: + + + + + G#. [Component Name] + Authentication Conditions: [Rules for visibility based on user authentication] + Elements: + - [List all UI elements in this component] + 1. **Type** (Layout, Interactive, Display, Input, etc.) + 2. **Purpose** (What does it do for the user or the interface?) + 3. **States** (Possible UI states: Default, Hover, Clicked, Expanded, Loading, etc.) + 4. **Interactions** (User actions or system responses: clicking, hovering, dragging, scrolling, etc.) + + + + + + P#. [Page Name] + URL Path: /[path] + Parent Page: [Parent page if nested, or "None" if top-level] + Description: [Brief description of page purpose] + Authentication Conditions: [Public/Private/Login Required] + #### Core Components: + - C#.1. [Component Name] + - Definition: Core Components are **distinct UI elements** or **functional blocks** on the page that have a clear, identifiable role. Each component must include: + 1. **Type** (Layout, Interactive, Display, Input, etc.) + 2. **Purpose** (What does it do for the user or the interface?) + 3. **States** (Possible UI states: Default, Hover, Clicked, Expanded, Loading, etc.) + 4. **Interactions** (User actions or system responses: clicking, hovering, dragging, scrolling, etc.) + #### Features & Functionality: + - Focus on how these features tie back to user stories, and which **Core Components** are used to achieve them + - F#.1. [Feature Name] + - Description: [Functionality Overview] + - User Stories: [Relevant user stories from PRD] + - Components Used: [Which UI elements power this feature?] + #### Page-Specific User Flows: + Step-by-step sequences describing user interactions and system responses + Flow #. [Flow Name] + [Step 1] + [Step 2] + + + +1. **Goal**: Produce a complete UX Structure Map describing how each page/screen is laid out, including which global components are reused across pages. + +2. **Global Components**: + - Mark all reusable or site-wide elements with \`\` tags and end tag . + - Provide a short but clear definition for each global component (e.g., Navigation Bar, Footer, etc.). + - Explain how/why these components appear on multiple pages (if applicable). + +3. **Page Definitions**: + - Use \`\` tags to define each individual page or screen from the Sitemap Documentation. + - For each \`\`, provide: + - **Page id** a unique page id. + - **Page name** (P#). + - **URL Path**: The route or path used to access this page. + - **Description**: Explain the purpose of the page, the users goal, and how it supports the user journey. + - **Core Elements**: List and describe the UI elements on this page (headers, buttons, sidebars, etc.). + - Explain their states and interactions. + - Reference **global components** (if used) plus any page-specific components. + - **Content Display**: What information is shown, and why is it essential? + - **Navigation and Routes**: Which routes lead here? Which links or buttons lead out? + - **Restrictions**: Note if login is required, or if only certain user roles can access. + +4. **Focus on Detail**: + - Provide a component-level breakdown for each pages layout and user interactions. + - Address all features from the Sitemap Documentation; confirm no item is missed. + - Make sure each pages structure is thorough enough for front-end implementation. + +5. **Consider**: + - User goals on each page. + - The user journey and how each page supports it. + - The purpose of each element (why it exists, how it helps the user). + - The presence of dynamic or personalized content. + + 6. **Output Format**: + - Your reply must begin with: and end with (plain text, no Markdown headings). + - Inside, you must include: + 1. One or more \`\` blocks (if relevant). + 2. Multiple \`\` blocks (one per page). + - Each \`\` or \`\` should include all relevant fields as stated above. + **Number** Goal Component in tag sequentially (G1., G2., etc.). + **Number** pages sequentially (P1., P2., etc.). + **Number** each component and feature sequentially within that page (C1.1, C1.2, F1.1, F1.2, etc.). + Thoroughly parse the PRD to include: + - **All** pages. + - **All** features, functionalities, user stories, and flows. + - **All** major/minor navigation and user journeys. + +Sitemap Coverage + + Comprehensive Analysis: + Capture all features, functionalities, and user stories. + Represent all primary and secondary user flows. + + Page & Navigation Structure: + Identify all main and nested pages. + Ensure clear parent-child relationships. + + Detailed User Journeys: + Provide step-by-step navigational flows unique to each page. + + Thorough Coverage: + Verify every requirement, global UI element, unique UI, and user expectation from the PRD is addressed. + No user flow or screen should be missing. + +Self-Check Before Submitting + + Have you accounted for all sitemap details? + Are all global UI elements correctly categorized? + Have you included all relevant user flows and navigation structures? + Have you detailed each page's components, features, and flows completely? + Is every page from the Sitemap Documentation represented in a \`\` block? + Are all global components defined in \`\` blocks? + Are user flows, interactions, and relevant content needs included? + +Deliver a single XML-like document that strictly follows the structure above. Start with and close with , containing one block per page. + `; }, - generateLevel2UXSiteMapStructrePrompt: ( - projectName: string, - UXStructure: string, - sitemapDoc: string, - platform: string, - ): string => { - return `You are an expert UX Designer. - Your task is to analyze and improve a specific part of a page in the provided UX Structure Document. - The goal is to refine the design, layout, and interaction to ensure a seamless user experience and facilitate frontend implementation. - The output should address all aspects of user interaction, content layout, based on the following inputs: - - - Project name: ${projectName} - - Sitemap Documentation: ${sitemapDoc} - - UX Structure document: ${UXStructure} - - Platform: ${platform} - - Follow these guidelines to analyze requirements from a UX perspective: - - ### Instructions and Rules: - - 1, Analyze the Target Section: - Focus on the specific part of the page provided in the UX Structure document. - Identify ways to enhance its layout, interactions, and functionality. - 2, Improve UX Details: - Add clarity to component placement and hierarchy. - Specify interactions, behaviors, and dynamic content updates. - Ensure the section aligns with user goals and their journey. - 3, You need to identify and define every page/screen required for the application. - 4, Detailed Breakdown for Each Page/Screen: - Page Purpose: Clearly state the user goal for the page. - Core Elements: - List all components (e.g., headers, buttons, sidebars) and explain their role on the page. - Include specific interactions and behaviors for each element. - Content Display: - Identify the information that needs to be visible on the page and why it’s essential for the user. - Navigation and Routes: - Specify all frontend routes required to navigate to this page. - Include links or actions that lead to other pages or states. - Restrictions: - Note any restrictions or conditions for accessing the page (e.g., login required, specific user roles). - - 5, Focus on Detail: - Provide a component-level breakdown for each page, including layouts. - Explain how the design supports user goals and aligns with their journey. - - 6. For each page/screen in the sitemap: - - How the block should be place on the view? - - What information does the user need to see? - - What elements should be on the page? - - What are all the routes require for the frontend? - - What dynamic content needs to be displayed? - - What are the restriction for the page? - - 7. Consider: - - User goals on each page - - User journy - - Element purposes - - Content that needs to be displayed - - Your reply must start with: "\`\`\`UXStructureMap" and end with "\`\`\`". - - Focus on describing the UX Structure from a user experience perspective. For each page: - 1. What element appear on each page and why - 2. What information needs to be displayed and why - 3. How the element supports user goals - - Make sure is 100% completed. It will be directly be use in development. + generatePagebyPageSiteMapStructrePrompt: (): string => { + const guidelines = prompts.HTML_Guidelines_Page_view_Prompt(); + + return ` + You are an expert front-end developer and UX designer. We have two important inputs: + 1, Global_component Sections Contain blocks. + 1. A “UX Sitemap Structure” containing blocks for each main page/screen. + +Your task is to produce a **page-by-page analysis** that enriches each block with additional detail about layout, components, styling, and interactions, based on these two inputs. + +### Instructions +1. **Output Structure** + +Each page **must** follow this format exactly, wrapped in \`\` tags: + + + P#. [Page Name] + URL Path: /[path] + Description: [Brief description of page purpose] + Parent Page: [Parent page if nested, or "None" if top-level] + Access Level: [e.g., Public/Private/Admin] + + #### Core Components + C#.1. [Component Name] + - Definition: Core Components are **distinct UI elements** or **functional blocks** on the page that have a clear, identifiable role. Each component must include: + 1. **Type** (Layout, Interactive, Display, Input, etc.) + 2. **Purpose** (What does it do for the user or the interface?) + 3. **States** (Possible UI states: Default, Hover, Clicked, Expanded, Loading, etc.) + 4. **Interactions** (User actions or system responses: clicking, hovering, dragging, scrolling, etc.) + 5. **Location** (Where on the page this component is placed) + 6. **Component Style**: + - Colors & Theming: + - Fonts & Typography: + - Dimensions & Spacing: + - Iconography: + - Transitions/Animations: + + #### Features & Functionality: + - Focus on how these features tie back to user stories, and which **Core Components** are used to achieve them + - F#.1. [Feature Name] + - Description: [Functionality Overview] + - User Stories: [Relevant user stories from PRD] + - Components Used: [Which UI elements power this feature?] + + #### Page-Specific User Flows + Step-by-step sequences describing user interactions and system responses + Flow #. [Flow Name] + [Step 1] + [Step 2] + + #### Draft HTML Layout (Focused on Page View) + + [Generated HTML Structure Here] + + + +- For **Core Components**, use sequential numbering (C1.1, C1.2, etc.). +- For **Features**, use sequential numbering (F1.1, F1.2, etc.). +- Keep each \`\` labeled with a **unique** page ID and page number (P1, P2, etc.). + +2. Guidelines for Draft HTML Layout: +Your output must emphasize component placement, layout context, and styling directions to ensure developers can implement a responsive and accessible UI effectively. + + ${guidelines} + +3. **Enhance** each page description to include more granular information such as: + - **Layout**: Where each component (e.g. header, cards, sidebars) is placed on the page. + - **Component Details**: + - **Name** (e.g., “Search Bar,” “Recommended Playlists”) + - **Type** (Layout, Interactive, Display, Input, etc.) + - **Purpose** (What does it do for the user or the interface?) + - **States** (Default, Hover, Clicked, Expanded, Loading, etc.) + - **Interactions** (Click, Hover, Drag, etc.) + - **Location** (e.g. “Header bar,” “Left sidebar,” “Main content section”) + - **Component Style**: + - **Color & Theming**: Primary color, background/foreground contrasts, brand highlights, etc. + - **Fonts & Typography**: Font family, size, weight, line-height if relevant. + - **Dimensions & Spacing**: Approximate width/height (e.g., “a 300px-wide sidebar”), margins/paddings. + - **Iconography**: If icons are used, note icon style or library (e.g., Material Icons, FontAwesome). + - **Transitions/Animations**: If any hover or click animations are relevant. + - **Features & Functionality**: Link features to the components implementing them. + - **Page-Specific User Flows**: Step-by-step sequences describing user interactions and system responses. + +4. **Process** + - For each \`\` in the provided UX Sitemap Structure, also consult the “UX Sitemap Document” for higher-level context. + - **Merge** or **add** any missing details, sub-pages, or user journeys not yet reflected in \`\`. + - Provide enough detail that front-end developers have a clear blueprint of layout, styling, and user flows. + +5. **Self-Check** + - Does each \`\` block list **all** relevant components with location, style, and interactions? + - Have you included all user flows from the UX Sitemap Document within the appropriate page? + - Are roles, states, or restrictions noted (if any)? + - Is the final detail sufficient for developers to build each page without ambiguity? + +6. **Final Instructions** +- **Plain text only** (no Markdown). +- Include only one \`\` block per page. +- Do **not** omit any page or feature. The final result must be **100% complete** and consistent with `; }, + + generateGlobalComponentPagebyPageSiteMapStructrePrompt: (): string => { + return ` + You are an expert front-end developer and UX designer. We have one important inputs: + + A "UX Sitemap Structure" containing blocks for each main component. + +Your task is to produce a component-by-component analysis that enriches each block with additional detail about layout, components, styling, and interactions, based on these inputs. +Instructions + + Output Structure: + +Each must follow this format exactly, wrapped in tags: + + + G#. [Component Name] + Authentication Conditions: + - [Condition 1]: [Description of behavior for logged-in/logged-out users] + Elements: + - [Element Name]: + 1. **Type** (e.g., Layout, Interactive, Display, Input, etc.) + 2. **Purpose** (What does it do for the user or the interface?) + 3. **States** (Possible UI states: Default, Hover, Clicked, Expanded, Loading, etc.) + 4. **Interactions** (User actions or system responses: clicking, hovering, dragging, scrolling, etc.) + 5. **Location** (e.g., Header bar, Left sidebar, Main content section) + 6. **Component Style**: + - **Color & Theming**: Primary color, background/foreground contrasts, brand highlights, etc. + - **Fonts & Typography**: Font family, size, weight, line-height if relevant. + - **Dimensions & Spacing**: Approximate width/height (e.g., “a 300px-wide sidebar”), margins/paddings. + - **Iconography**: If icons are used, note icon style or library (e.g., Material Icons, FontAwesome). + - **Transitions/Animations**: Relevant hover or click animations. + + #### Draft HTML Layout (Focused on Component View) + + [Generated HTML Structure Here] + + + + For Elements, use sequential numbering (E1.1, E1.2, etc.). + For Features, use sequential numbering (F1.1, F1.2, etc.). + Each block must be labeled with a unique ID and component number (G1, G2, etc.). + + Enhance Each Component Description + + Include granular details: + Layout: Placement on the page (header, sidebar, main content, footer, etc.). + Component Details: + Name + Type + Purpose + States + Interactions + Location + Component Style (Color, Fonts, Dimensions, Iconography, Transitions/Animations) + + Guidelines for Draft HTML Layout: + ${prompts.HTML_Guidelines_global_component_prompt} + + Self-Check + Does each block include all relevant components with their details? + Are features tied back to the user stories and corresponding components? + Are all roles, states, or restrictions clearly noted? + Is the final output detailed enough for developers to implement the design without ambiguity? + + Final Output + Plain text only. + Include only one block. + Ensure that no html, xml, or other declarations appear in the output. + Do not omit any component or feature. Ensure the final result is 100% complete and consistent. + + `; + }, + + HTML_Guidelines_Page_view_Prompt: (): string => { + return ` + + Structure and Hierarchy: + Use semantic HTML5 tags to represent the structure: +
for the top navigation or page intro. +
for the main content. +
for distinct content groups. +