From 608cc70d2cd5f7e720094e5102fbd4ba53a946a4 Mon Sep 17 00:00:00 2001 From: DJETEJE MOTSEBO VADEX ROMUALD <86348204+DJTJ21@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:14:14 +0100 Subject: [PATCH 1/7] Delete .env.staging --- .env.staging | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 .env.staging diff --git a/.env.staging b/.env.staging deleted file mode 100644 index 2e766dbc0..000000000 --- a/.env.staging +++ /dev/null @@ -1,39 +0,0 @@ -# Firebase Config -FIREBASE_API_KEY=AIzaSyCsUpHmK5-o4hp8_HldvlaLU2gLOUVeHgY -FIREBASE_AUTH_DOMAIN=lexis-ia.firebaseapp.com -FIREBASE_PROJECT_ID=lexis-ia -FIREBASE_STORAGE_BUCKET=lexis-ia.firebasestorage.app -FIREBASE_MESSAGING_SENDER_ID=78825247320 -FIREBASE_APP_ID=1:78825247320:web:2a69ba8ceabad513f3f02d - -# API Service -API_URL=https://api-staging.idem.africa -API_VERSION=v1 -API_LLM_MODEL=gpt-3.5-turbo -PORT=3000 -# Webgen Service -WEBGEN_URL=https://appgen.idem.africa - -# Diagen Service -DIAGEN_URL=http://chart-staging.idem.africa - -DEEPSEEK_API_KEY="sk-1682233fc8b642e78e3a43fdf52723be" - -GEMINI_API_KEY="AIzaSyC09DqsmMLFWBefJFndg6I4QGkk2tyrJb8" - -FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDW6aObLw11aMFM\nD7It/K4425kfIPsBKZlP7s0xQQZKfdOu3XTXHgLVZ2BHpJokisFKUNl5tmrrGAnv\nAg+PTzUdyAu61zw33NhQGIqsS8Kkw/NpYK/Z8CrlD03s2GYOiyyh7yQvkam2BL74\nN/aIlzwVyhMpLaS73RiakcMZvvOJVIi4m8HMSxGdlU4BPpGwWEJmtbgtOUGPFep5\n6GbxzpMZD92bHu1CzmMytxOjPr5FDBmssiTVxaKqigIarHVr3o8YO+Jq4EUWtEyj\nAc/d1MsZSc90289BpFqaV1e21dXlrk/5wSpT2h/jeQ2awyK1bcnUhb/vRmbn0aJV\nUn42MjKjAgMBAAECggEACU2h5HTb+0omm/lN+Emo7RMshLlzxDAdz+UBgvFSqWTb\nBgXWKf3Fl6Fa/J72gUB0b5giYe9wn26x92O6crS2Euz2/QaWN8DaqFT3o8+/xEwr\nPFOQKToJUVMhR3Bysox5ySTGBz0iAJwh/DLH/E+3rSXLQIwWRn+isgY/UN0AQ+CN\nnkW475U6onFi8Vp0JGLIkDJJYHjPC4CcvdItI0BF9jIPriuzbnWQErDc7sGMMS+k\nNBDcDBIbFIw7OWJXUdAvM0OZpPSexLIv4HR8Nnc2GXDDRNEeJLlRa89vE9om7SDd\nWHAlUCV2hMLEekUlPhbM4ye/oi8snyZBmQtLjaZ4gQKBgQDwXd6dtv1AhnXUk3gx\nQs6rvRMEqPk2Mtu8PtMREFmpp14Fjk1r565cpId1vOJg93WWAXEqrTvmub+Pc0PD\n3K2Ba+JfOGWzAWUZ0/fULjj/JnzecWT/4/Sa5ZY2RwTL+erZFFLl21iGIleGauBm\n6daRCAkGrT/ZL+hAQUHyNV6vQQKBgQDk4/URjqgfoBl7439Q6/cMsX/omOXC9njI\nUKg+YXb31vAHIeY0jO+cajAC/6x/tQN8BnoWI0pf7lOJhLUPZBp+P1zE7eAcehtx\nA4YWfZRYqHVZLxqAg4tHxUfdAw+uwdKU08pUr9da7grykJAH7vWcbGsljCB4CFK2\nJsDQf1XM4wKBgQC4+WQRtvqZp2ASIRKDxkfJfg2ernJqNSVIboh7PzvhBT2jxOjr\nuzVBchQUlTEZMhY5RA7Uqs/INPWn7SofFjonuOBSKtKIuPAWujqj/JY8NGAvxs/U\n5JYehcPdLTYRytfiCnPpE63CO7djZ+gdCqLmpWpcywKxnt56ZD3dqRiegQKBgQC8\n3bBxLVJizhtZE64RWrN+oNQXXFpyFigxugQpfQjKlmt2py0p/YUVfrVhNBDlS7q1\nUy7YJ4SORbxeg8dXDNWjiKsGv/Wl6cfM6AhzdGm9AjvaPDjVBDYgIZQbtRPysnIN\nZfjVCkdb+4HDBzAhq7a0vO1ojQiZotyE+tMs93UX2wKBgQDn3+orQf4dk0CTqCsL\nTlvSy7eH5o3xyaeh/J77yOkHM3b9/AAC5wd3NmVvSbeuEh//hWCvQg9HkZ6CvgAn\nLQNW/pavKp5KNf1wOtKOTZOMEAAoghd9/OUVc/+EcQiVelK1wvDR1yMIApuvO1tJ\nF1c7SxxSHUyftu4FXg2nuvKj+g==\n-----END PRIVATE KEY-----\n" - -FIREBASE_CLIENT_EMAIL="firebase-adminsdk-fbsvc@lexis-ia.iam.gserviceaccount.com" - -OPENROUTER_API_KEY="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" - -VITE_LOG_LEVEL=prod - -VITE_API_BASE_URL="http://116.203.108.233:3000" - -CORS_ALLOWED_ORIGINS="https://api-staging.idem.africa,https://appgen.idem.africa,http://chart-staging.idem.africa,https://staging.idem.africa" - -# Redis Configuration - Staging -REDIS_PASSWORD_STAGING=staging_redis_pass_2024 -REDIS_HOST=redis-staging -REDIS_PORT=6379 From 87cad2e0c90603d3be23a3ca8d75f0119ef54b1f Mon Sep 17 00:00:00 2001 From: DJETEJE MOTSEBO VADEX ROMUALD <86348204+DJTJ21@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:14:56 +0100 Subject: [PATCH 2/7] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 763cea741..174b9e332 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,7 @@ Thumbs.db .env.local .env.*.local .env.production - +.env.* # Logs logs *.log From 300dcbef7aca6fd256c27ce2cc9e27d2d2b97af9 Mon Sep 17 00:00:00 2001 From: arolleaguekeng Date: Sun, 25 Jan 2026 19:54:14 +0100 Subject: [PATCH 3/7] add getProjectCode endpoint to retrieve project code files from Firebase Storage and refactor code formatting for consistency Add new getProjectCode controller method and GET /projects/:projectId/code route to fetch project code files from Firebase Storage. Implement getProjectCodeFromFirebase in ProjectService and downloadProjectCodeZip in StorageService to download and extract ZIP files using JSZip. Add corresponding API documentation with OpenAPI spec. Refactor code formatting throughout project --- .../api/api/controllers/project.controller.ts | 65 ++++++- apps/api/api/routes/project.routes.ts | 46 ++++- apps/api/api/services/project.service.ts | 35 ++++ apps/api/api/services/storage.service.ts | 85 +++++++++ .../we-dev-client/src/api/persistence/db.ts | 148 ++++++++------- .../src/components/AiChat/chat/index.tsx | 179 +++++++----------- 6 files changed, 362 insertions(+), 196 deletions(-) diff --git a/apps/api/api/controllers/project.controller.ts b/apps/api/api/controllers/project.controller.ts index 17047730e..782c13ec6 100644 --- a/apps/api/api/controllers/project.controller.ts +++ b/apps/api/api/controllers/project.controller.ts @@ -208,7 +208,11 @@ class ProjectController { } } - async saveProjectGeneration(req: CustomRequest, res: Response, next: NextFunction): Promise { + async saveProjectGeneration( + req: CustomRequest, + res: Response, + next: NextFunction + ): Promise { const userId = req.user?.uid; const { projectId } = req.params; const generationData = req.body; @@ -268,15 +272,15 @@ class ProjectController { return; } if (!req.file) { - logger.warn( - `Save project ZIP failed for projectId ${projectId}: No ZIP file provided.` - ); + logger.warn(`Save project ZIP failed for projectId ${projectId}: No ZIP file provided.`); res.status(400).json({ message: 'ZIP file is required' }); return; } const zipUrl = await projectService.saveProjectZip(userId, projectId as string, req.file); - logger.info(`ZIP saved successfully for project ${projectId} and user ${userId}. URL: ${zipUrl}`); + logger.info( + `ZIP saved successfully for project ${projectId} and user ${userId}. URL: ${zipUrl}` + ); res.status(200).json({ message: 'ZIP saved successfully', url: zipUrl }); } catch (error: any) { logger.error( @@ -315,8 +319,14 @@ class ProjectController { return; } - const repoUrl = await projectService.sendProjectToGitHub(userId, projectId as string, githubData); - logger.info(`Project ${projectId} sent to GitHub successfully for user ${userId}. Repo: ${repoUrl}`); + const repoUrl = await projectService.sendProjectToGitHub( + userId, + projectId as string, + githubData + ); + logger.info( + `Project ${projectId} sent to GitHub successfully for user ${userId}. Repo: ${repoUrl}` + ); res.status(200).json({ message: 'Project sent to GitHub successfully', repoUrl }); } catch (error: any) { logger.error( @@ -326,6 +336,47 @@ class ProjectController { next(error); } } + + async getProjectCode(req: CustomRequest, res: Response, next: NextFunction): Promise { + const userId = req.user?.uid; + const { projectId } = req.params; + logger.info( + `Attempting to get project code from Firebase Storage. ProjectId: ${projectId}, UserId from token: ${userId}` + ); + try { + if (!userId) { + logger.warn( + `Get project code failed for projectId ${projectId}: User ID not found in token.` + ); + res.status(401).json({ message: 'User not authenticated' }); + return; + } + if (!projectId) { + logger.warn('Get project code failed: Project ID missing in params.'); + res.status(400).json({ message: 'Project ID is required' }); + return; + } + + const codeFiles = await projectService.getProjectCodeFromFirebase( + userId, + projectId as string + ); + if (!codeFiles) { + logger.info(`No code found for project ${projectId} and user ${userId}.`); + res.status(404).json({ message: 'No code found for this project' }); + return; + } + + logger.info(`Successfully retrieved code for project ${projectId} and user ${userId}.`); + res.status(200).json({ files: codeFiles }); + } catch (error: any) { + logger.error( + `Error in getProjectCode controller for projectId ${projectId}, userId ${userId}: ${error.message}`, + { stack: error.stack, details: error } + ); + next(error); + } + } } export const projectController = new ProjectController(); diff --git a/apps/api/api/routes/project.routes.ts b/apps/api/api/routes/project.routes.ts index 687c4f166..aa5814c7b 100644 --- a/apps/api/api/routes/project.routes.ts +++ b/apps/api/api/routes/project.routes.ts @@ -9,7 +9,7 @@ const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 50 * 1024 * 1024, // 50MB limit - } + }, }); export const projectRoutes = Router(); @@ -323,7 +323,12 @@ projectRoutes.post('/:projectId/generation', authenticate, projectController.sav * '500': * description: Internal server error. */ -projectRoutes.post('/:projectId/zip', authenticate, upload.single('zip'), projectController.saveProjectZip); +projectRoutes.post( + '/:projectId/zip', + authenticate, + upload.single('zip'), + projectController.saveProjectZip +); // Send project to GitHub /** @@ -384,3 +389,40 @@ projectRoutes.post('/:projectId/zip', authenticate, upload.single('zip'), projec * description: Internal server error. */ projectRoutes.post('/:projectId/github', authenticate, projectController.sendProjectToGitHub); + +// Get project code from Firebase Storage +/** + * @openapi + * /projects/{projectId}/code: + * get: + * tags: + * - Project Generation + * summary: Get project code from Firebase Storage + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: projectId + * required: true + * schema: + * type: string + * description: The ID of the project. + * responses: + * '200': + * description: Project code retrieved successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * files: + * type: object + * description: Project files as key-value pairs (filename -> content). + * '401': + * description: Unauthorized. + * '404': + * description: No code found for this project. + * '500': + * description: Internal server error. + */ +projectRoutes.get('/:projectId/code', authenticate, projectController.getProjectCode); diff --git a/apps/api/api/services/project.service.ts b/apps/api/api/services/project.service.ts index ae146d1a9..77baa8880 100644 --- a/apps/api/api/services/project.service.ts +++ b/apps/api/api/services/project.service.ts @@ -582,6 +582,41 @@ class ProjectService { throw error; } } + + async getProjectCodeFromFirebase( + userId: string, + projectId: string + ): Promise | null> { + if (!userId || !projectId) { + logger.error('User ID and Project ID are required to get project code from Firebase.'); + return null; + } + + try { + logger.info( + `Attempting to retrieve project code from Firebase Storage for project ${projectId} and user ${userId}` + ); + + // Use storage service to download and extract the project code ZIP + const codeFiles = await storageService.downloadProjectCodeZip(projectId, userId); + + if (!codeFiles || Object.keys(codeFiles).length === 0) { + logger.info(`No code files found for project ${projectId} and user ${userId}`); + return null; + } + + logger.info( + `Successfully retrieved ${Object.keys(codeFiles).length} code files for project ${projectId} and user ${userId}` + ); + return codeFiles; + } catch (error: any) { + logger.error( + `Error retrieving project code from Firebase for project ${projectId} and user ${userId}: ${error.message}`, + { stack: error.stack, details: error } + ); + return null; + } + } } export const projectService = new ProjectService(); diff --git a/apps/api/api/services/storage.service.ts b/apps/api/api/services/storage.service.ts index da9ed36d3..c3f487695 100644 --- a/apps/api/api/services/storage.service.ts +++ b/apps/api/api/services/storage.service.ts @@ -389,6 +389,91 @@ export class StorageService { } } + /** + * Download and extract project code ZIP from Firebase Storage + * @param projectId - Project ID for folder structure + * @param userId - User ID for folder structure (optional) + * @returns Extracted files as Record or null if not found + */ + async downloadProjectCodeZip( + projectId: string, + userId?: string + ): Promise | null> { + try { + const folderPath = userId + ? `users/${userId}/projects/${projectId}/code` + : `projects/${projectId}/code`; + + logger.info(`Downloading project code ZIP from Firebase Storage`, { + projectId, + userId, + folderPath, + }); + + // List files in the folder to find the latest ZIP + const [files] = await this.bucket.getFiles({ + prefix: folderPath, + }); + + if (!files || files.length === 0) { + logger.info(`No code ZIP files found for project ${projectId}`); + return null; + } + + // Find the most recent ZIP file + const zipFiles = files.filter((file: any) => file.name.endsWith('.zip')); + if (zipFiles.length === 0) { + logger.info(`No ZIP files found for project ${projectId}`); + return null; + } + + // Sort by creation time and get the latest + zipFiles.sort((a: any, b: any) => { + const aTime = a.metadata.timeCreated || '0'; + const bTime = b.metadata.timeCreated || '0'; + return new Date(bTime).getTime() - new Date(aTime).getTime(); + }); + + const latestZipFile = zipFiles[0]; + logger.info(`Found latest ZIP file: ${latestZipFile.name}`); + + // Download the ZIP file + const [zipBuffer] = await latestZipFile.download(); + + // Extract the ZIP file using JSZip + const JSZip = require('jszip'); + const zip = new JSZip(); + const zipContent = await zip.loadAsync(zipBuffer); + + const extractedFiles: Record = {}; + + // Extract all files from the ZIP + for (const [filePath, file] of Object.entries(zipContent.files)) { + const zipFile = file as any; + if (!zipFile.dir) { + const content = await zipFile.async('string'); + extractedFiles[filePath] = content; + } + } + + logger.info(`Successfully extracted ${Object.keys(extractedFiles).length} files from ZIP`, { + projectId, + userId, + zipFileName: latestZipFile.name, + }); + + return extractedFiles; + } catch (error: any) { + logger.error(`Error downloading project code ZIP`, { + projectId, + userId, + error: error.message, + stack: error.stack, + }); + return null; + } + } + /** * Generate a unique project ID for storage purposes * @returns A unique project ID diff --git a/apps/appgen/apps/we-dev-client/src/api/persistence/db.ts b/apps/appgen/apps/we-dev-client/src/api/persistence/db.ts index da6916f0a..21757973b 100644 --- a/apps/appgen/apps/we-dev-client/src/api/persistence/db.ts +++ b/apps/appgen/apps/we-dev-client/src/api/persistence/db.ts @@ -1,26 +1,25 @@ -import type { ProjectModel } from "./models/project.model"; -import type { UserModel } from "./userModel"; +import type { ProjectModel } from './models/project.model'; +import type { UserModel } from './userModel'; /** * Define the base URL for your API. * It's recommended to use an environment variable for this. */ -const API_BASE_URL = - process.env.REACT_APP_IDEM_API_BASE_URL || "http://localhost:3001"; +const API_BASE_URL = process.env.REACT_APP_IDEM_API_BASE_URL || 'http://localhost:3001'; export async function getCurrentUser(): Promise { try { const response = await fetch(`${API_BASE_URL}/auth/profile`, { - credentials: "include", + credentials: 'include', }); if (!response.ok) { if (response.status === 401) { - console.warn("User not authenticated"); + console.warn('User not authenticated'); return null; } - console.error("Error fetching current user:", response.statusText); + console.error('Error fetching current user:', response.statusText); return null; } @@ -30,7 +29,7 @@ export async function getCurrentUser(): Promise { return user; } catch (error) { - console.error("Error fetching user:", error); + console.error('Error fetching user:', error); return null; } } @@ -38,24 +37,22 @@ export async function getCurrentUser(): Promise { async function checkAuth(): Promise { const currentUser = await getCurrentUser(); if (!currentUser) { - return Promise.reject(new Error("User not authenticated")); + return Promise.reject(new Error('User not authenticated')); } return Promise.resolve(); } -export async function getProjectById( - projectId: string -): Promise { +export async function getProjectById(projectId: string): Promise { try { await checkAuth(); const response = await fetch(`${API_BASE_URL}/projects/${projectId}`, { - credentials: "include", + credentials: 'include', }); if (!response.ok) { - console.warn("Project not found:", projectId); + console.warn('Project not found:', projectId); return null; } @@ -64,7 +61,7 @@ export async function getProjectById( return project; } catch (error) { - console.error("Error fetching project:", error); + console.error('Error fetching project:', error); throw error; } } @@ -74,130 +71,137 @@ export async function getUserProjects(): Promise { await checkAuth(); const response = await fetch(`${API_BASE_URL}/projects`, { - credentials: "include", + credentials: 'include', }); if (!response.ok) { - console.error("Error getting projects from API:", response.statusText); + console.error('Error getting projects from API:', response.statusText); throw new Error(`API Error: ${response.status} ${response.statusText}`); } return (await response.json()) as ProjectModel[]; } catch (error) { - console.error("Error getting projects:", error); + console.error('Error getting projects:', error); throw error; } } // Generation services -export async function getProjectGeneration( - projectId: string -): Promise { +export async function getProjectGeneration(projectId: string): Promise { try { await checkAuth(); - const response = await fetch( - `${API_BASE_URL}/projects/${projectId}/generation`, - { - credentials: "include", - } - ); + const response = await fetch(`${API_BASE_URL}/projects/${projectId}/generation`, { + credentials: 'include', + }); if (!response.ok) { if (response.status === 404) { return null; // No generation exists } - console.error("Error getting project generation:", response.statusText); + console.error('Error getting project generation:', response.statusText); throw new Error(`API Error: ${response.status} ${response.statusText}`); } return await response.json(); } catch (error) { - console.error("Error getting project generation:", error); + console.error('Error getting project generation:', error); throw error; } } -export async function saveProjectGeneration( - projectId: string, - generationData: any -): Promise { +export async function saveProjectGeneration(projectId: string, generationData: any): Promise { try { await checkAuth(); - const response = await fetch( - `${API_BASE_URL}/projects/${projectId}/generation`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - credentials: "include", - body: JSON.stringify(generationData), - } - ); + const response = await fetch(`${API_BASE_URL}/projects/${projectId}/generation`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify(generationData), + }); if (!response.ok) { - console.error("Error saving project generation:", response.statusText); + console.error('Error saving project generation:', response.statusText); throw new Error(`API Error: ${response.status} ${response.statusText}`); } } catch (error) { - console.error("Error saving project generation:", error); + console.error('Error saving project generation:', error); throw error; } } -export async function sendZipToBackend( - projectId: string, - zipFile: Blob -): Promise { +export async function sendZipToBackend(projectId: string, zipFile: Blob): Promise { try { await checkAuth(); const formData = new FormData(); - formData.append("zip", zipFile, `${projectId}-generation.zip`); + formData.append('zip', zipFile, `${projectId}-generation.zip`); const response = await fetch(`${API_BASE_URL}/projects/${projectId}/zip`, { - method: "POST", - credentials: "include", + method: 'POST', + credentials: 'include', body: formData, }); if (!response.ok) { - console.error("Error sending zip to backend:", response.statusText); + console.error('Error sending zip to backend:', response.statusText); throw new Error(`API Error: ${response.status} ${response.statusText}`); } } catch (error) { - console.error("Error sending zip to backend:", error); + console.error('Error sending zip to backend:', error); throw error; } } -export async function sendToGitHub( - projectId: string, - githubData: any -): Promise { +export async function sendToGitHub(projectId: string, githubData: any): Promise { try { await checkAuth(); - const response = await fetch( - `${API_BASE_URL}/projects/${projectId}/github`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - credentials: "include", - body: JSON.stringify(githubData), - } - ); + const response = await fetch(`${API_BASE_URL}/projects/${projectId}/github`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify(githubData), + }); if (!response.ok) { - console.error("Error sending to GitHub:", response.statusText); + console.error('Error sending to GitHub:', response.statusText); throw new Error(`API Error: ${response.status} ${response.statusText}`); } } catch (error) { - console.error("Error sending to GitHub:", error); + console.error('Error sending to GitHub:', error); throw error; } } + +// Fonction pour récupérer le code existant depuis Firebase Storage +export async function getProjectCodeFromFirebase( + projectId: string +): Promise | null> { + try { + await checkAuth(); + + const response = await fetch(`${API_BASE_URL}/projects/${projectId}/code`, { + credentials: 'include', + }); + + if (!response.ok) { + if (response.status === 404) { + return null; // Aucun code n'existe pour ce projet + } + console.error('Error getting project code:', response.statusText); + throw new Error(`API Error: ${response.status} ${response.statusText}`); + } + + const codeData = await response.json(); + return codeData.files || null; + } catch (error) { + console.error('Error getting project code from Firebase:', error); + return null; + } +} diff --git a/apps/appgen/apps/we-dev-client/src/components/AiChat/chat/index.tsx b/apps/appgen/apps/we-dev-client/src/components/AiChat/chat/index.tsx index 0a3ef35d2..c0d3b11b8 100644 --- a/apps/appgen/apps/we-dev-client/src/components/AiChat/chat/index.tsx +++ b/apps/appgen/apps/we-dev-client/src/components/AiChat/chat/index.tsx @@ -27,6 +27,7 @@ import { saveProjectGeneration, sendZipToBackend, sendToGitHub, + getProjectCodeFromFirebase, } from '@/api/persistence/db'; import { createZipFromFiles, @@ -426,7 +427,6 @@ export const BaseChat = ({ uuid: propUuid }: { uuid?: string }) => { const [showStartButton, setShowStartButton] = useState(false); const [hasGeneration, setHasGeneration] = useState(false); const [isGenerationComplete, setIsGenerationComplete] = useState(false); - const [showGitHubButton, setShowGitHubButton] = useState(false); const [showTutorial, setShowTutorial] = useState(false); const [projectLoadError, setProjectLoadError] = useState(null); @@ -598,7 +598,7 @@ export const BaseChat = ({ uuid: propUuid }: { uuid?: string }) => { } setIsGenerationComplete(true); - setShowGitHubButton(true); + // Ne plus afficher les boutons GitHub - le code est automatiquement sauvé sur Firebase } catch (error) { console.error('Error saving generation:', error); } @@ -670,12 +670,59 @@ export const BaseChat = ({ uuid: propUuid }: { uuid?: string }) => { if (existingGeneration) { setHasGeneration(true); setIsGenerationComplete(true); - setShowGitHubButton(true); console.log('Existing generation found for project:', project.name); } else { setShowStartButton(true); console.log('No generation found, showing start button for:', project.name); } + + // Vérifier et charger le code existant depuis Firebase Storage + try { + console.log('Checking for existing code in Firebase Storage for project:', projectId); + const existingCode = await getProjectCodeFromFirebase(projectId); + + if (existingCode && Object.keys(existingCode).length > 0) { + console.log( + 'Found existing code in Firebase Storage, loading into workspace:', + Object.keys(existingCode).length, + 'files' + ); + + // Charger le code dans l'espace de travail + setFiles(existingCode); + + // Marquer les fichiers comme étant déjà envoyés (réinitialiser d'abord) + setIsFirstSend(); + + // Créer un message initial avec le code existant si on est en mode Builder + if (mode === ChatMode.Builder && messages.length === 0) { + const boltAction = convertToBoltAction(existingCode); + setMessages([ + { + id: '1', + role: 'user', + content: `\n${boltAction}\n\n\n`, + }, + ]); + setMessagesa([ + { + id: '1', + role: 'user', + content: `\n${boltAction}\n\n\n`, + }, + ]); + } + + toast.success( + `Code existant chargé depuis Firebase Storage (${Object.keys(existingCode).length} fichiers)` + ); + } else { + console.log('No existing code found in Firebase Storage for project:', projectId); + } + } catch (error) { + console.error('Error loading existing code from Firebase Storage:', error); + // Ne pas afficher d'erreur à l'utilisateur car ce n'est pas critique + } } else { console.warn('Project not found with ID:', projectId); setProjectLoadError('Project not found. Please check the project ID and try again.'); @@ -721,47 +768,26 @@ export const BaseChat = ({ uuid: propUuid }: { uuid?: string }) => { } }; - // Handle sending zip to backend - const handleSendZip = async () => { - if (!projectId || !files) return; + // Fonction pour sauvegarder automatiquement le code sur Firebase Storage + const saveCodeToFirebase = async (generatedFiles: Record) => { + if (!projectId || !generatedFiles || Object.keys(generatedFiles).length === 0) return; try { - // Create zip from files - const JSZip = (await import('jszip')).default; - const zip = new JSZip(); - - // Add files to zip - Object.entries(files).forEach(([filePath, content]) => { - zip.file(filePath, content); - }); + console.log('Saving code to Firebase Storage for project:', projectId); - const zipBlob = await zip.generateAsync({ type: 'blob' }); + // Create ZIP from generated files + const zipBlob = await createZipFromFiles(generatedFiles); + // Upload ZIP to backend (Firebase Storage) await sendZipToBackend(projectId, zipBlob); - toast.success('Generation saved successfully!'); - } catch (error) { - console.error('Error sending zip:', error); - toast.error('Error saving generation'); - } - }; - - // Handle sending to GitHub - const handleSendToGitHub = async () => { - if (!projectId || !projectData) return; - - try { - const githubData = { - projectName: projectData.name, - description: projectData.description || 'Generated project', - files: files, - isPublic: true, // You can make this configurable - }; - await sendToGitHub(projectId, githubData); - toast.success('Project sent to GitHub successfully!'); + console.log('Code successfully saved to Firebase Storage'); + toast.success( + `Code sauvegardé sur Firebase Storage (${Object.keys(generatedFiles).length} fichiers)` + ); } catch (error) { - console.error('Error sending to GitHub:', error); - toast.error('Error sending to GitHub'); + console.error('Error saving code to Firebase Storage:', error); + toast.error('Erreur lors de la sauvegarde sur Firebase Storage'); } }; @@ -1326,83 +1352,7 @@ export const BaseChat = ({ uuid: propUuid }: { uuid?: string }) => { )} - {/* Export Actions */} - {isGenerationComplete && showGitHubButton && ( -
-
-
-

- Export Your Application -

-

- Choose how you'd like to export your generated code -

-
- -
- - - -
-
-
- )} + {/* Les boutons d'export ont été supprimés - le code est automatiquement sauvé sur Firebase Storage */}
@@ -1416,7 +1366,6 @@ export const BaseChat = ({ uuid: propUuid }: { uuid?: string }) => { showStartButton, hasGeneration, isGenerationComplete, - showGitHubButton, projectData, projectLoadError, retryLoadProject, From f227803d1b38c4330e95c2249c8b8e1ef501ed6c Mon Sep 17 00:00:00 2001 From: arolleaguekeng Date: Sun, 25 Jan 2026 20:04:08 +0100 Subject: [PATCH 4/7] refactor deploy endpoint to create Netlify site before deployment and update deployment URL to use site-specific endpoint Add two-step deployment process: first create new Netlify site with unique timestamped name, then deploy to created site using site-specific deploy URL. Update logging messages to distinguish between site creation and deployment steps. Add error handling for site creation failures with detailed error responses. --- .../we-dev-next/src/app/api/deploy/route.ts | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/apps/appgen/apps/we-dev-next/src/app/api/deploy/route.ts b/apps/appgen/apps/we-dev-next/src/app/api/deploy/route.ts index 2955ba0b7..b6207e352 100644 --- a/apps/appgen/apps/we-dev-next/src/app/api/deploy/route.ts +++ b/apps/appgen/apps/we-dev-next/src/app/api/deploy/route.ts @@ -42,13 +42,42 @@ export async function POST(request: Request) { }); } + // Create a new site first + console.log('Creating new site on Netlify...'); + const createSiteResponse = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify({ + name: `idem-app-${Date.now()}`, // Generate unique site name + }), + }); + + if (!createSiteResponse.ok) { + const errorText = await createSiteResponse.text(); + console.error('Failed to create site:', createSiteResponse.status, errorText); + return NextResponse.json({ + success: false, + message: `Failed to create site: ${createSiteResponse.status} - ${errorText}`, + }); + } + + const siteData = await createSiteResponse.json(); + console.log('Site created:', siteData); + + // Now deploy to the created site + const deployUrl = `${url}/${siteData.id}/deploys`; + console.log('Deploying to site:', deployUrl); + const headers = { 'Content-Type': 'application/zip', Authorization: `Bearer ${accessToken}`, }; - console.log('Sending request to Netlify...'); - const response = await fetch(url, { + console.log('Sending deployment request to Netlify...'); + const response = await fetch(deployUrl, { method: 'POST', headers: headers, body: file, From a967cbd74e1dcbc1e627635c5f29096e797c29eb Mon Sep 17 00:00:00 2001 From: arolleaguekeng Date: Sun, 25 Jan 2026 20:13:14 +0100 Subject: [PATCH 5/7] refactor deployment modal to French localization and disable custom deployment option with coming soon state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update HeaderActions deployment modal text from English to French. Disable custom deployment (Idem) option by removing button interaction and adding opacity/cursor-not-allowed styling with "Disponible bientôt" label. Replace Netlify branding with generic "Déploiement rapide" (Quick Deployment) and update icon from Netlify logo to lightning bolt SVG. Reorder deployment options to show custom deployment first. --- .../src/components/Header/HeaderActions.tsx | 99 +++++++++++-------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/apps/appgen/apps/we-dev-client/src/components/Header/HeaderActions.tsx b/apps/appgen/apps/we-dev-client/src/components/Header/HeaderActions.tsx index e7a58a96e..79876cb98 100644 --- a/apps/appgen/apps/we-dev-client/src/components/Header/HeaderActions.tsx +++ b/apps/appgen/apps/we-dev-client/src/components/Header/HeaderActions.tsx @@ -449,94 +449,107 @@ export function HeaderActions() { /> -

Deploy Your Project

-

Choose how you want to deploy your application

+

Déployer votre projet

+

Choisissez votre méthode de déploiement

- {/* Idem Option - Custom Deployment */} - - - {/* Netlify Option - Quick Deployment */} + {/* Quick Deployment */}
@@ -545,7 +558,7 @@ export function HeaderActions() { onClick={() => setShowDeployChoiceModal(false)} className="px-6 py-2 text-gray-400 hover:text-gray-200 transition-colors font-medium" > - Cancel + Annuler From 22e511753b2f0d794530623e375f30b195b96c99 Mon Sep 17 00:00:00 2001 From: arolleaguekeng Date: Sun, 25 Jan 2026 21:37:55 +0100 Subject: [PATCH 6/7] refactor deployment modal UI to use consistent blue gradient theme and improve visual hierarchy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace green color scheme with blue gradient throughout deployment modal. Update success modal header with blue gradient icon container and improved dark theme styling. Enhance deployment URL input and buttons with darker backgrounds and blue accent colors. Change "Disponible bientôt" label from plain text to prominent orange-to-red gradient badge with shadow. Update quick deployment option from green --- .../src/components/Header/HeaderActions.tsx | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/apps/appgen/apps/we-dev-client/src/components/Header/HeaderActions.tsx b/apps/appgen/apps/we-dev-client/src/components/Header/HeaderActions.tsx index 79876cb98..8aa7b99b8 100644 --- a/apps/appgen/apps/we-dev-client/src/components/Header/HeaderActions.tsx +++ b/apps/appgen/apps/we-dev-client/src/components/Header/HeaderActions.tsx @@ -320,27 +320,37 @@ export function HeaderActions() { }} >
-
🚀
-

- {t('header.deploySuccess')} -

-

{t('header.deployToCloud')}

+
+ + + +
+

{t('header.deploySuccess')}

+

{t('header.deployToCloud')}

-
-

- {t('header.accessLink')} -

+
+

{t('header.accessLink')}

Déploiement personnalisé

-

+ Disponible bientôt -

+
@@ -502,11 +512,11 @@ export function HeaderActions() { {/* Quick Deployment */}
From aa709805d52b63eb99f92ee9639b215c1477a7f1 Mon Sep 17 00:00:00 2001 From: arolleaguekeng Date: Sun, 25 Jan 2026 21:41:02 +0100 Subject: [PATCH 7/7] refactor deployment success modal icon from blue gradient lightning bolt to green checkmark circle Replace blue gradient container with lightning bolt icon with simpler green checkmark circle icon in deployment success modal header. Remove wrapper div and update SVG path to use checkmark-in-circle design instead of lightning bolt. --- .../src/components/Header/HeaderActions.tsx | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/apps/appgen/apps/we-dev-client/src/components/Header/HeaderActions.tsx b/apps/appgen/apps/we-dev-client/src/components/Header/HeaderActions.tsx index 8aa7b99b8..293d4ba17 100644 --- a/apps/appgen/apps/we-dev-client/src/components/Header/HeaderActions.tsx +++ b/apps/appgen/apps/we-dev-client/src/components/Header/HeaderActions.tsx @@ -320,21 +320,19 @@ export function HeaderActions() { }} >
-
- - - -
+ + +

{t('header.deploySuccess')}

{t('header.deployToCloud')}