From 7136304c63b16e0ac26017eeab8cd3d3815dd6a9 Mon Sep 17 00:00:00 2001
From: Jackson Chen <90215880+Sma1lboy@users.noreply.github.com>
Date: Mon, 3 Mar 2025 14:28:09 -0600
Subject: [PATCH 01/13] feat: add build and start scripts for frontend and
 backend (#143)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added dedicated commands for building and starting both the frontend
and backend, offering improved operational clarity and separation
between environments.

- **Chores**
- Refined task dependencies and command configurations to enhance
performance, streamline development workflows, and simplify maintenance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
---
 backend/package.json        |  2 ++
 codefox-common/package.json |  2 ++
 frontend/package.json       |  3 ++-
 llm-server/package.json     |  6 ++++--
 package.json                |  6 +++++-
 turbo.json                  | 16 ++++++++++++++++
 6 files changed, 31 insertions(+), 4 deletions(-)

diff --git a/backend/package.json b/backend/package.json
index aaa90edb..a74cf3ff 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -8,10 +8,12 @@
   "packageManager": "pnpm@9.1.0",
   "scripts": {
     "build": "nest build",
+    "build:backend": "pnpm build",
     "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
     "lint": "ts-prune \"{src,apps,libs,test}/**/*.ts\" && eslint \"{src,apps,libs,test}/**/*.ts\" --fix ",
     "start": "nest start",
     "start:dev": "NODE_OPTIONS=\"--experimental-specifier-resolution=node\" nest start --watch",
+    "start:backend": "pnpm start",
     "dev": "pnpm start:dev",
     "dev:backend": "pnpm start:dev",
     "start:debug": "nest start --debug --watch",
diff --git a/codefox-common/package.json b/codefox-common/package.json
index 8123a03b..d67eef64 100644
--- a/codefox-common/package.json
+++ b/codefox-common/package.json
@@ -19,6 +19,8 @@
   ],
   "scripts": {
     "build": "pnpm run build:cjs && pnpm run build:esm && pnpm run build:types",
+    "build:frontend": "pnpm run build",
+    "build:backend": "pnpm run build",
     "build:cjs": "tsc -p tsconfig.cjs.json",
     "build:esm": "tsc -p tsconfig.esm.json",
     "build:types": "tsc -p tsconfig.types.json",
diff --git a/frontend/package.json b/frontend/package.json
index e7c58a36..09b55b32 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -4,8 +4,9 @@
   "private": true,
   "scripts": {
     "build": "next build",
+    "build:frontend": "next build",
     "dev": "next dev",
-    "start:dev": "next dev",
+    "start:frontend": "next start",
     "start:dev-watch": "tmuxinator start -p .tmuxinator/build.yml",
     "dev:watch": "tmuxinator start -p .tmuxinator/dev.yml",
     "start": "next start",
diff --git a/llm-server/package.json b/llm-server/package.json
index dc217305..2acccfee 100644
--- a/llm-server/package.json
+++ b/llm-server/package.json
@@ -5,11 +5,13 @@
   "type": "module",
   "scripts": {
     "start": "NODE_OPTIONS='--enable-source-maps' nodemon --watch \"src/**/*.ts\" --exec \"tsx\" src/main.ts",
+    "start:backend": "pnpm start",
     "dev": "NODE_OPTIONS='--enable-source-maps' nodemon --watch \"src/**/*.ts\" --exec \"tsx\" src/main.ts",
     "dev:backend": "pnpm dev",
-    "build": " tsc",
+    "build": "tsc",
+    "build:backend": "pnpm build",
     "serve": "node --enable-source-maps dist/main.js",
-    "format": "prettier --write \"src/**/*.ts\" ",
+    "format": "prettier --write \"src/**/*.ts\"",
     "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
     "test": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.config.js",
     "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.config.js --watch",
diff --git a/package.json b/package.json
index 2ae4201b..982461ca 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,8 @@
   "scripts": {
     "build": "turbo build",
     "build:common": "pnpm --filter codefox-common run build",
+    "build:frontend": "turbo build:frontend",
+    "build:backend": "turbo build:backend",
     "dev:turbo": "turbo dev",
     "dev": "tmuxinator start -p .tmuxinator/dev.yml",
     "lint": "eslint . --ext .js,.ts,.tsx",
@@ -14,7 +16,9 @@
     "dev:backend": "turbo dev:backend",
     "test": "turbo test",
     "fix": "eslint . --ext .js,.ts,.tsx --fix",
-    "start": "turbo start"
+    "start": "turbo start",
+    "start:frontend": "turbo start:frontend",
+    "start:backend": "turbo start:backend"
   },
   "postinstall": "pnpm --filter codefox-common run build",
   "keywords": [],
diff --git a/turbo.json b/turbo.json
index dd42c2db..54bc2361 100644
--- a/turbo.json
+++ b/turbo.json
@@ -21,6 +21,22 @@
     "start": {
       "dependsOn": ["^build"],
       "cache": false
+    },
+    "start:frontend": {
+      "dependsOn": ["^build"],
+      "cache": false
+    },
+    "start:backend": {
+      "dependsOn": ["^build"],
+      "cache": false
+    },
+    "build:frontend": {
+      "dependsOn": ["^build:frontend"],
+      "cache": true
+    },
+    "build:backend": {
+      "dependsOn": ["^build:backend"],
+      "cache": true
     }
   }
 }

From b6162854e06aaea88a630764718285439e8ee943 Mon Sep 17 00:00:00 2001
From: ZHallen122 <106571949+ZHallen122@users.noreply.github.com>
Date: Tue, 4 Mar 2025 18:22:31 -0500
Subject: [PATCH 02/13] Project create limit (#148)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a daily limit for project creation, capping the number of
new projects a user can create per day.
- Added a new query endpoint that lets users check how many projects
they can still create today.
- Enhanced error notifications to clearly inform users when their daily
project creation limit has been reached.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---
 backend/src/project/project-limits.ts   | 26 ++++++++++
 backend/src/project/project.resolver.ts |  9 ++++
 backend/src/project/project.service.ts  | 65 ++++++++++++++++++++++++-
 3 files changed, 98 insertions(+), 2 deletions(-)
 create mode 100644 backend/src/project/project-limits.ts

diff --git a/backend/src/project/project-limits.ts b/backend/src/project/project-limits.ts
new file mode 100644
index 00000000..018c1b88
--- /dev/null
+++ b/backend/src/project/project-limits.ts
@@ -0,0 +1,26 @@
+import { ForbiddenException, HttpStatus } from '@nestjs/common';
+import { GraphQLError } from 'graphql';
+
+export const PROJECT_DAILY_LIMIT = 3; // Maximum number of projects a user can create per day
+
+export enum ProjectErrorCode {
+  DAILY_LIMIT_EXCEEDED = 'DAILY_LIMIT_EXCEEDED',
+}
+
+export class ProjectRateLimitException extends ForbiddenException {
+  constructor(limit: number) {
+    super(
+      `Daily project creation limit of ${limit} reached. Please try again tomorrow.`,
+    );
+  }
+
+  getGraphQLError(): GraphQLError {
+    return new GraphQLError(this.message, {
+      extensions: {
+        code: ProjectErrorCode.DAILY_LIMIT_EXCEEDED,
+        limit: PROJECT_DAILY_LIMIT,
+        status: HttpStatus.TOO_MANY_REQUESTS,
+      },
+    });
+  }
+}
diff --git a/backend/src/project/project.resolver.ts b/backend/src/project/project.resolver.ts
index 944d8174..69686ee5 100644
--- a/backend/src/project/project.resolver.ts
+++ b/backend/src/project/project.resolver.ts
@@ -7,6 +7,7 @@ import {
   ResolveField,
   Parent,
   ID,
+  Int,
 } from '@nestjs/graphql';
 import { ProjectService } from './project.service';
 import { Project } from './project.model';
@@ -147,4 +148,12 @@ export class ProjectsResolver {
   ): Promise<Project[]> {
     return this.projectService.fetchPublicProjects(input);
   }
+
+  // In ProjectsResolver:
+  @Query(() => Int)
+  async getRemainingProjectLimit(
+    @GetUserIdFromToken() userId: string,
+  ): Promise<number> {
+    return this.projectService.getRemainingProjectLimit(userId);
+  }
 }
diff --git a/backend/src/project/project.service.ts b/backend/src/project/project.service.ts
index c30fa75e..61d92f51 100644
--- a/backend/src/project/project.service.ts
+++ b/backend/src/project/project.service.ts
@@ -7,7 +7,7 @@ import {
   ForbiddenException,
 } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
-import { In, Not, Repository } from 'typeorm';
+import { Between, In, Not, Repository } from 'typeorm';
 import { Project } from './project.model';
 import { ProjectPackages } from './project-packages.model';
 import {
@@ -26,6 +26,11 @@ import { BuilderContext } from 'src/build-system/context';
 import { ChatService } from 'src/chat/chat.service';
 import { Chat } from 'src/chat/chat.model';
 import { v4 as uuidv4 } from 'uuid';
+import {
+  PROJECT_DAILY_LIMIT,
+  ProjectRateLimitException,
+} from './project-limits';
+
 @Injectable()
 export class ProjectService {
   private readonly model: OpenAIModelProvider =
@@ -119,12 +124,41 @@ export class ProjectService {
     }
   }
 
+  /**
+   * Checks if a user has exceeded their daily project creation limit
+   * @param userId The user ID to check
+   * @returns A boolean indicating whether the user can create more projects today
+   */
+  async canCreateProject(userId: string): Promise<boolean> {
+    const today = new Date();
+    today.setHours(0, 0, 0, 0); // Start of today
+
+    const tomorrow = new Date(today);
+    tomorrow.setDate(tomorrow.getDate() + 1); // Start of tomorrow
+
+    // Count projects created by user today
+    const todayProjectCount = await this.projectsRepository.count({
+      where: {
+        userId: userId,
+        createdAt: Between(today, tomorrow),
+      },
+    });
+
+    return todayProjectCount < PROJECT_DAILY_LIMIT;
+  }
+
   async createProject(
     input: CreateProjectInput,
     userId: string,
   ): Promise<Chat> {
     try {
-      // First, handle project name generation if needed (this is the only sync operation we need)
+      //First check if user have reach the create project limit
+      const canCreate = await this.canCreateProject(userId);
+      if (!canCreate) {
+        throw new ProjectRateLimitException(PROJECT_DAILY_LIMIT);
+      }
+
+      // handle project name generation if needed (this is the only sync operation we need)
       let projectName = input.projectName;
       if (!projectName || projectName === '') {
         this.logger.debug(
@@ -164,6 +198,10 @@ export class ProjectService {
       // Return chat immediately so user can start interacting
       return defaultChat;
     } catch (error) {
+      if (error instanceof ProjectRateLimitException) {
+        throw error.getGraphQLError(); // Throw as a GraphQL error for the client
+      }
+
       this.logger.error(
         `Error in createProject: ${error.message}`,
         error.stack,
@@ -602,4 +640,27 @@ export class ProjectService {
 
     return [];
   }
+
+  /**
+   * Gets the number of projects a user can still create today
+   * @param userId The user ID to check
+   * @returns The number of remaining projects the user can create today
+   */
+  async getRemainingProjectLimit(userId: string): Promise<number> {
+    const today = new Date();
+    today.setHours(0, 0, 0, 0); // Start of today
+
+    const tomorrow = new Date(today);
+    tomorrow.setDate(tomorrow.getDate() + 1); // Start of tomorrow
+
+    // Count projects created by this user today
+    const todayProjectCount = await this.projectsRepository.count({
+      where: {
+        userId: userId,
+        createdAt: Between(today, tomorrow),
+      },
+    });
+
+    return Math.max(0, PROJECT_DAILY_LIMIT - todayProjectCount);
+  }
 }

From 1173bef6d5ceea52a540727280bbb448c9a2f4ab Mon Sep 17 00:00:00 2001
From: Jackson Chen <90215880+Sma1lboy@users.noreply.github.com>
Date: Tue, 4 Mar 2025 18:39:26 -0600
Subject: [PATCH 03/13] feat(backend): adding s3 supports, and also expose
 uploading project images (#154)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- **Enhanced Project Photo Upload:** Users can now update project photos
directly through file uploads for a smoother, more reliable experience.
- **Expanded Image Flexibility:** The application now supports loading
images from any domain, broadening your content sourcing options.
- **Improved Upload Performance:** Upgraded file handling ensures
consistent and efficient processing for a better overall experience.
- **New Configuration Options:** A new example configuration file has
been added to guide users on setting up environment variables.

- **Bug Fixes**
- **Updated Project Photo Mutation:** The mutation for updating project
photos has been streamlined, enhancing functionality and usability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: NarwhalChen <125920907+NarwhalChen@users.noreply.github.com>
---
 .gitignore                               |    4 +-
 backend/.env                             |    5 -
 backend/.env.development                 |    5 -
 backend/.env.example                     |   23 +
 backend/.gitignore                       |    3 +-
 backend/package.json                     |    3 +
 backend/src/config/config.module.ts      |   34 +
 backend/src/config/config.service.ts     |   71 ++
 backend/src/config/env.validation.ts     |   51 +
 backend/src/guard/project.guard.ts       |    3 +
 backend/src/main.ts                      |    7 +
 backend/src/project/dto/project.input.ts |   12 +
 backend/src/project/project.module.ts    |    6 +-
 backend/src/project/project.resolver.ts  |   28 +-
 backend/src/project/project.service.ts   |   30 +-
 backend/src/upload/upload.module.ts      |   10 +
 backend/src/upload/upload.service.ts     |  193 +++
 frontend/next.config.mjs                 |    2 +-
 frontend/package.json                    |    4 +-
 frontend/src/graphql/request.ts          |   11 -
 frontend/src/graphql/schema.gql          |   16 +-
 frontend/src/graphql/type.tsx            |   28 +-
 frontend/src/lib/client.ts               |   22 +-
 llm-server/.env                          |    9 -
 pnpm-lock.yaml                           | 1471 +++++++++++++++++++---
 25 files changed, 1826 insertions(+), 225 deletions(-)
 delete mode 100644 backend/.env
 delete mode 100644 backend/.env.development
 create mode 100644 backend/.env.example
 create mode 100644 backend/src/config/config.module.ts
 create mode 100644 backend/src/config/config.service.ts
 create mode 100644 backend/src/config/env.validation.ts
 create mode 100644 backend/src/upload/upload.module.ts
 create mode 100644 backend/src/upload/upload.service.ts
 delete mode 100644 llm-server/.env

diff --git a/.gitignore b/.gitignore
index f5c6219c..7e3c534e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,4 +16,6 @@ models/
 
 */**/database.sqlite
 ./backend/src/database.sqlite
-.codefox
\ No newline at end of file
+.codefox
+
+.env
\ No newline at end of file
diff --git a/backend/.env b/backend/.env
deleted file mode 100644
index bec5018a..00000000
--- a/backend/.env
+++ /dev/null
@@ -1,5 +0,0 @@
-PORT=8080
-JWT_SECRET="JACKSONCHENNAHEULALLENPENGYU"
-JWT_REFRESH_SECRET="JACKSONCHENNAHEULALLENPENGYUREFRESH"
-SALT_ROUNDS=123
-NODE_ENV="DEV"
\ No newline at end of file
diff --git a/backend/.env.development b/backend/.env.development
deleted file mode 100644
index d375f0f8..00000000
--- a/backend/.env.development
+++ /dev/null
@@ -1,5 +0,0 @@
-PORT=8080
-JWT_SECRET="JACKSONCHENNAHEULALLENPENGYU"
-JWT_REFRESH="JACKSONCHENNAHEULALLENPENGYUREFRESH"
-SALT_ROUNDS=123
-OPENAI_BASE_URI="http://localhost:3001"
diff --git a/backend/.env.example b/backend/.env.example
new file mode 100644
index 00000000..7f8afe80
--- /dev/null
+++ b/backend/.env.example
@@ -0,0 +1,23 @@
+# Server Configuration
+PORT=8080
+
+# DEV PROD OR TEST
+NODE_ENV="DEV" 
+# JWT Configuration
+JWT_SECRET="your_jwt_secret_here"
+JWT_REFRESH="your_jwt_refresh_secret_here"
+SALT_ROUNDS=10
+
+# OpenAI Configuration
+OPENAI_BASE_URI="http://localhost:3001"
+
+# S3/Cloudflare R2 Configuration (Optional)
+# If not provided, local file storage will be used
+S3_ACCESS_KEY_ID="your_s3_access_key_id"      # Must be 32 characters for Cloudflare R2
+S3_SECRET_ACCESS_KEY="your_s3_secret_access_key"
+S3_REGION="auto"                              # Use 'auto' for Cloudflare R2
+S3_BUCKET_NAME="your_bucket_name"
+S3_ENDPOINT="https://<account_id>.r2.cloudflarestorage.com"    # Cloudflare R2 endpoint
+S3_ACCOUNT_ID="your_cloudflare_account_id"    # Your Cloudflare account ID
+S3_PUBLIC_URL="https://pub-xxx.r2.dev"        # Your R2 public bucket URL
+
diff --git a/backend/.gitignore b/backend/.gitignore
index c493aef4..3ff469a1 100644
--- a/backend/.gitignore
+++ b/backend/.gitignore
@@ -55,4 +55,5 @@ log-*/
 
 
 # Backend
-/backend/package-lock.json
\ No newline at end of file
+/backend/package-lock.json
+.env
\ No newline at end of file
diff --git a/backend/package.json b/backend/package.json
index a74cf3ff..d084f34d 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -28,6 +28,7 @@
   },
   "dependencies": {
     "@apollo/server": "^4.11.0",
+    "@aws-sdk/client-s3": "^3.758.0",
     "@huggingface/hub": "latest",
     "@huggingface/transformers": "latest",
     "@nestjs/apollo": "^12.2.0",
@@ -45,6 +46,7 @@
     "@types/toposort": "^2.0.7",
     "axios": "^1.7.7",
     "bcrypt": "^5.1.1",
+    "class-transformer": "^0.5.1",
     "class-validator": "^0.14.1",
     "dotenv": "^16.4.7",
     "eslint-plugin-unused-imports": "^4.1.4",
@@ -53,6 +55,7 @@
     "gpt-3-encoder": "^1.1.4",
     "graphql": "^16.9.0",
     "graphql-subscriptions": "^2.0.0",
+    "graphql-upload-minimal": "^1.6.1",
     "graphql-ws": "^5.16.0",
     "lodash": "^4.17.21",
     "markdown-to-txt": "^2.0.1",
diff --git a/backend/src/config/config.module.ts b/backend/src/config/config.module.ts
new file mode 100644
index 00000000..37864ce0
--- /dev/null
+++ b/backend/src/config/config.module.ts
@@ -0,0 +1,34 @@
+import { Module } from '@nestjs/common';
+import { ConfigModule as NestConfigModule } from '@nestjs/config';
+import { AppConfigService } from './config.service';
+import { EnvironmentVariables } from './env.validation';
+import { plainToInstance } from 'class-transformer';
+import { validateSync } from 'class-validator';
+
+const validate = (config: Record<string, unknown>) => {
+  const validatedConfig = plainToInstance(EnvironmentVariables, config, {
+    enableImplicitConversion: true,
+  });
+
+  const errors = validateSync(validatedConfig, {
+    skipMissingProperties: false,
+  });
+
+  if (errors.length > 0) {
+    throw new Error(errors.toString());
+  }
+
+  return validatedConfig;
+};
+
+@Module({
+  imports: [
+    NestConfigModule.forRoot({
+      validate,
+      isGlobal: true,
+    }),
+  ],
+  providers: [AppConfigService],
+  exports: [AppConfigService],
+})
+export class AppConfigModule {}
diff --git a/backend/src/config/config.service.ts b/backend/src/config/config.service.ts
new file mode 100644
index 00000000..686450f0
--- /dev/null
+++ b/backend/src/config/config.service.ts
@@ -0,0 +1,71 @@
+import { Injectable } from '@nestjs/common';
+import { ConfigService as NestConfigService } from '@nestjs/config';
+import { EnvironmentVariables } from './env.validation';
+
+@Injectable()
+export class AppConfigService {
+  constructor(private configService: NestConfigService<EnvironmentVariables>) {}
+
+  /**
+   * Get server port from environment
+   */
+  get port(): number {
+    return this.configService.get('PORT');
+  }
+
+  /**
+   * Get JWT secret key for token generation
+   */
+  get jwtSecret(): string {
+    return this.configService.get('JWT_SECRET');
+  }
+
+  /**
+   * Get JWT refresh token secret
+   */
+  get jwtRefresh(): string {
+    return this.configService.get('JWT_REFRESH');
+  }
+
+  /**
+   * Get password hashing salt rounds
+   */
+  get saltRounds(): number {
+    return this.configService.get('SALT_ROUNDS');
+  }
+
+  /**
+   * Get OpenAI API base URI
+   */
+  get openaiBaseUri(): string {
+    return this.configService.get('OPENAI_BASE_URI');
+  }
+
+  /**
+   * Get S3/Cloudflare R2 configuration object
+   */
+  get s3Config() {
+    return {
+      accessKeyId: this.configService.get('S3_ACCESS_KEY_ID'),
+      secretAccessKey: this.configService.get('S3_SECRET_ACCESS_KEY'),
+      region: this.configService.get('S3_REGION'),
+      bucketName: this.configService.get('S3_BUCKET_NAME'),
+      endpoint: this.configService.get('S3_ENDPOINT'),
+      accountId: this.configService.get('S3_ACCOUNT_ID'),
+      publicUrl: this.configService.get('S3_PUBLIC_URL'),
+    };
+  }
+
+  /**
+   * Check if S3 storage is properly configured
+   */
+  get hasS3Configured(): boolean {
+    const config = this.s3Config;
+    return !!(
+      config.accessKeyId &&
+      config.secretAccessKey &&
+      config.region &&
+      (config.endpoint || config.accountId)
+    );
+  }
+}
diff --git a/backend/src/config/env.validation.ts b/backend/src/config/env.validation.ts
new file mode 100644
index 00000000..0e81f5f3
--- /dev/null
+++ b/backend/src/config/env.validation.ts
@@ -0,0 +1,51 @@
+import { IsOptional, IsString, IsNumber, IsIn } from 'class-validator';
+
+export class EnvironmentVariables {
+  @IsNumber()
+  PORT: number;
+
+  @IsString()
+  @IsIn(['DEV', 'PROD', 'TEST'])
+  NODE_ENV: string;
+
+  @IsString()
+  JWT_SECRET: string;
+
+  @IsString()
+  JWT_REFRESH: string;
+
+  @IsNumber()
+  SALT_ROUNDS: number;
+
+  @IsString()
+  OPENAI_BASE_URI: string;
+
+  // S3/Cloudflare R2 Configuration - all optional
+  @IsOptional()
+  @IsString()
+  S3_ACCESS_KEY_ID?: string;
+
+  @IsOptional()
+  @IsString()
+  S3_SECRET_ACCESS_KEY?: string;
+
+  @IsOptional()
+  @IsString()
+  S3_REGION?: string;
+
+  @IsOptional()
+  @IsString()
+  S3_BUCKET_NAME?: string;
+
+  @IsOptional()
+  @IsString()
+  S3_ENDPOINT?: string;
+
+  @IsOptional()
+  @IsString()
+  S3_ACCOUNT_ID?: string;
+
+  @IsOptional()
+  @IsString()
+  S3_PUBLIC_URL?: string;
+}
diff --git a/backend/src/guard/project.guard.ts b/backend/src/guard/project.guard.ts
index a6dac748..9d3ee0d5 100644
--- a/backend/src/guard/project.guard.ts
+++ b/backend/src/guard/project.guard.ts
@@ -10,6 +10,9 @@ import { JwtService } from '@nestjs/jwt';
 
 import { ProjectService } from '../project/project.service';
 
+/**
+ * This guard checks if the user is authorized to access a project.
+ */
 @Injectable()
 export class ProjectGuard implements CanActivate {
   private readonly logger = new Logger('ProjectGuard');
diff --git a/backend/src/main.ts b/backend/src/main.ts
index f36dff02..b084baaa 100644
--- a/backend/src/main.ts
+++ b/backend/src/main.ts
@@ -3,6 +3,7 @@ import { AppModule } from './app.module';
 import 'reflect-metadata';
 import * as dotenv from 'dotenv';
 import { Logger } from '@nestjs/common';
+import { graphqlUploadExpress } from 'graphql-upload-minimal';
 
 async function bootstrap() {
   const logger = new Logger('Bootstrap');
@@ -18,10 +19,16 @@ async function bootstrap() {
       'Authorization',
       'Access-Control-Allow-Origin',
       'Access-Control-Allow-Credentials',
+      'Apollo-Require-Preflight',
       'x-refresh-token',
     ],
   });
 
+  app.use(
+    '/graphql',
+    graphqlUploadExpress({ maxFileSize: 50000000, maxFiles: 10 }),
+  );
+
   console.log('process.env.PORT:', process.env.PORT);
   const server = await app.listen(process.env.PORT ?? 8080);
   logger.log(`Application is running on port ${process.env.PORT ?? 8080}`);
diff --git a/backend/src/project/dto/project.input.ts b/backend/src/project/dto/project.input.ts
index 26c05551..f6715316 100644
--- a/backend/src/project/dto/project.input.ts
+++ b/backend/src/project/dto/project.input.ts
@@ -2,6 +2,7 @@
 import { InputType, Field, ID, ObjectType } from '@nestjs/graphql';
 import { IsNotEmpty, IsString, IsUUID, IsOptional } from 'class-validator';
 import { Project } from '../project.model';
+import { FileUpload, GraphQLUpload } from 'graphql-upload-minimal';
 
 /**
  * @deprecated We don't need project upsert
@@ -118,3 +119,14 @@ export class FetchPublicProjectsInputs {
   @Field()
   size: number;
 }
+
+@InputType()
+export class UpdateProjectPhotoInput {
+  @IsString()
+  @Field(() => ID)
+  projectId: string;
+
+  @IsOptional()
+  @Field(() => GraphQLUpload)
+  file: FileUpload;
+}
diff --git a/backend/src/project/project.module.ts b/backend/src/project/project.module.ts
index c0be9389..9145f7d2 100644
--- a/backend/src/project/project.module.ts
+++ b/backend/src/project/project.module.ts
@@ -9,11 +9,15 @@ import { ProjectGuard } from '../guard/project.guard';
 import { ChatService } from 'src/chat/chat.service';
 import { User } from 'src/user/user.model';
 import { Chat } from 'src/chat/chat.model';
+import { AppConfigModule } from 'src/config/config.module';
+import { UploadModule } from 'src/upload/upload.module';
 
 @Module({
   imports: [
     TypeOrmModule.forFeature([Project, Chat, User, ProjectPackages]),
-    AuthModule, // Import AuthModule to provide JwtService to the ProjectGuard
+    AuthModule,
+    AppConfigModule,
+    UploadModule,
   ],
   providers: [ChatService, ProjectService, ProjectsResolver, ProjectGuard],
   exports: [ProjectService, ProjectGuard],
diff --git a/backend/src/project/project.resolver.ts b/backend/src/project/project.resolver.ts
index 69686ee5..ea1ccd34 100644
--- a/backend/src/project/project.resolver.ts
+++ b/backend/src/project/project.resolver.ts
@@ -15,6 +15,7 @@ import {
   CreateProjectInput,
   FetchPublicProjectsInputs,
   IsValidProjectInput,
+  UpdateProjectPhotoInput,
 } from './dto/project.input';
 import { Logger, UseGuards } from '@nestjs/common';
 import { ProjectGuard } from '../guard/project.guard';
@@ -87,22 +88,33 @@ export class ProjectsResolver {
     return this.projectService.subscribeToProject(userId, projectId);
   }
 
+  @UseGuards(ProjectGuard)
   @Mutation(() => Project)
-  async updateProjectPhotoUrl(
+  async updateProjectPhoto(
     @GetUserIdFromToken() userId: string,
-    @Args('projectId', { type: () => ID }) projectId: string,
-    @Args('photoUrl') photoUrl: string,
+    @Args('input') input: UpdateProjectPhotoInput,
   ): Promise<Project> {
-    this.logger.log(
-      `User ${userId} updating photo URL for project ${projectId}`,
-    );
+    const { projectId, file } = input;
+    this.logger.log(`User ${userId} uploading photo for project ${projectId}`);
+
+    // Extract the file data
+    const { createReadStream, mimetype } = await file;
+
+    // Buffer the file content
+    const chunks = [];
+    for await (const chunk of createReadStream()) {
+      chunks.push(chunk);
+    }
+    const buffer = Buffer.concat(chunks);
+
+    // Call the service with the extracted buffer and mimetype
     return this.projectService.updateProjectPhotoUrl(
       userId,
       projectId,
-      photoUrl,
+      buffer,
+      mimetype,
     );
   }
-
   @Mutation(() => Project)
   async updateProjectPublicStatus(
     @GetUserIdFromToken() userId: string,
diff --git a/backend/src/project/project.service.ts b/backend/src/project/project.service.ts
index 61d92f51..5a057bbe 100644
--- a/backend/src/project/project.service.ts
+++ b/backend/src/project/project.service.ts
@@ -26,16 +26,17 @@ import { BuilderContext } from 'src/build-system/context';
 import { ChatService } from 'src/chat/chat.service';
 import { Chat } from 'src/chat/chat.model';
 import { v4 as uuidv4 } from 'uuid';
+import { UploadService } from 'src/upload/upload.service';
 import {
   PROJECT_DAILY_LIMIT,
   ProjectRateLimitException,
 } from './project-limits';
-
 @Injectable()
 export class ProjectService {
   private readonly model: OpenAIModelProvider =
     OpenAIModelProvider.getInstance();
   private readonly logger = new Logger('ProjectService');
+
   constructor(
     @InjectRepository(Project)
     private projectsRepository: Repository<Project>,
@@ -44,6 +45,7 @@ export class ProjectService {
     @InjectRepository(ProjectPackages)
     private projectPackagesRepository: Repository<ProjectPackages>,
     private chatService: ChatService,
+    private uploadService: UploadService,
   ) {}
 
   async getProjectsByUser(userId: string): Promise<Project[]> {
@@ -468,17 +470,35 @@ export class ProjectService {
   async updateProjectPhotoUrl(
     userId: string,
     projectId: string,
-    photoUrl: string,
+    file: Buffer,
+    mimeType: string,
   ): Promise<Project> {
     const project = await this.getProjectById(projectId);
 
     // Check ownership permission
     this.checkProjectOwnership(project, userId);
 
-    // Update photo URL
-    project.photoUrl = photoUrl;
+    try {
+      // Use the upload service to handle the file upload
+      const subdirectory = `projects/${projectId}/images`;
+      const uploadResult = await this.uploadService.upload(
+        file,
+        mimeType,
+        subdirectory,
+      );
 
-    return this.projectsRepository.save(project);
+      // Update the project with the new URL
+      project.photoUrl = uploadResult.url;
+
+      this.logger.debug(
+        `Updated photo URL for project ${projectId} to ${uploadResult.url}`,
+      );
+
+      return this.projectsRepository.save(project);
+    } catch (error) {
+      this.logger.error('Error uploading image:', error);
+      throw new InternalServerErrorException('Failed to upload image:', error);
+    }
   }
 
   /**
diff --git a/backend/src/upload/upload.module.ts b/backend/src/upload/upload.module.ts
new file mode 100644
index 00000000..555013bd
--- /dev/null
+++ b/backend/src/upload/upload.module.ts
@@ -0,0 +1,10 @@
+import { Module } from '@nestjs/common';
+import { UploadService } from './upload.service';
+import { AppConfigModule } from '../config/config.module';
+
+@Module({
+  imports: [AppConfigModule],
+  providers: [UploadService],
+  exports: [UploadService],
+})
+export class UploadModule {}
diff --git a/backend/src/upload/upload.service.ts b/backend/src/upload/upload.service.ts
new file mode 100644
index 00000000..b78e89b7
--- /dev/null
+++ b/backend/src/upload/upload.service.ts
@@ -0,0 +1,193 @@
+import { Injectable } from '@nestjs/common';
+import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
+import { v4 as uuidv4 } from 'uuid';
+import path from 'path';
+import { existsSync, mkdirSync, createWriteStream } from 'fs-extra';
+import { finished } from 'stream/promises';
+import { getRootDir } from 'codefox-common';
+import { FileUpload } from 'graphql-upload-minimal';
+import * as fs from 'fs';
+import { AppConfigService } from 'src/config/config.service';
+export interface UploadResult {
+  url: string;
+  key: string;
+}
+
+@Injectable()
+export class UploadService {
+  private s3Client: S3Client | null = null;
+  private readonly mediaDir: string;
+
+  constructor(private configService: AppConfigService) {
+    // Initialize S3 client if configurations are available
+    if (this.configService.hasS3Configured) {
+      const s3Config = this.configService.s3Config;
+      this.s3Client = new S3Client({
+        region: s3Config.region,
+        endpoint: this.getEndpoint(),
+        credentials: {
+          accessKeyId: s3Config.accessKeyId,
+          secretAccessKey: s3Config.secretAccessKey,
+        },
+      });
+    }
+
+    // Initialize media directory for local storage
+    this.mediaDir = path.join(getRootDir(), 'media');
+    if (!existsSync(this.mediaDir)) {
+      mkdirSync(this.mediaDir, { recursive: true });
+    }
+  }
+
+  /**
+   * Get the S3 endpoint URL based on configuration
+   * Constructs the endpoint from account ID if not explicitly provided
+   * @returns The S3 endpoint URL string
+   */
+  private getEndpoint(): string {
+    const s3Config = this.configService.s3Config;
+
+    // Use explicit endpoint if provided
+    if (s3Config.endpoint) {
+      return s3Config.endpoint;
+    }
+
+    // Construct endpoint from account ID for Cloudflare R2
+    if (s3Config.accountId) {
+      return `https://${s3Config.accountId}.r2.cloudflarestorage.com`;
+    }
+
+    // Fallback to default Cloudflare endpoint (should not reach here if hasS3Configured is properly checked)
+    return 'https://r2.cloudflarestorage.com';
+  }
+
+  /**
+   * Upload a file to either S3/Cloudflare R2 or local storage based on configuration
+   * @param file File to upload (can be a GraphQL FileUpload or a Buffer)
+   * @param mimetype File mimetype (required when uploading a Buffer)
+   * @param subdirectory Directory path to store the file in
+   * @returns Promise with the upload result
+   */
+  async upload(
+    file: FileUpload | Buffer,
+    mimetype?: string,
+    subdirectory: string = 'uploads',
+  ): Promise<UploadResult> {
+    // Generate filename
+    const fileExtension = mimetype?.split('/')[1] || 'jpg';
+    const filename = `${uuidv4()}.${fileExtension}`;
+    const key = `${subdirectory}/${filename}`;
+
+    // Handle different file input types
+    if (Buffer.isBuffer(file)) {
+      // Direct buffer upload
+      if (!mimetype) {
+        throw new Error('Mimetype is required when uploading a buffer');
+      }
+
+      if (this.s3Client) {
+        // Upload to S3/Cloudflare R2
+        await this.s3Client.send(
+          new PutObjectCommand({
+            Bucket: this.configService.s3Config.bucketName,
+            Key: key,
+            Body: file,
+            ContentType: mimetype,
+          }),
+        );
+
+        // Get the appropriate URL for the uploaded file
+        const bucketUrl = this.getBucketUrl();
+
+        return { url: `${bucketUrl}/${key}`, key };
+      } else {
+        // Upload to local storage from buffer
+        const directory = path.join(this.mediaDir, subdirectory);
+        if (!existsSync(directory)) {
+          mkdirSync(directory, { recursive: true });
+        }
+
+        const filePath = path.join(directory, filename);
+
+        try {
+          await fs.promises.writeFile(filePath, file);
+          return { url: `/media/${key}`, key };
+        } catch (error) {
+          throw new Error(`Failed to upload file: ${error.message}`);
+        }
+      }
+    } else {
+      // GraphQL FileUpload
+      const { createReadStream, mimetype: fileMimetype } = await file;
+
+      if (this.s3Client) {
+        // Convert stream to buffer and upload to S3/Cloudflare R2
+        const buffer = await this.streamToBuffer(createReadStream());
+
+        await this.s3Client.send(
+          new PutObjectCommand({
+            Bucket: this.configService.s3Config.bucketName,
+            Key: key,
+            Body: buffer,
+            ContentType: fileMimetype,
+          }),
+        );
+
+        // Get the appropriate URL for the uploaded file
+        const bucketUrl = this.getBucketUrl();
+
+        return { url: `${bucketUrl}/${key}`, key };
+      } else {
+        // Upload to local storage using stream
+        const directory = path.join(this.mediaDir, subdirectory);
+        if (!existsSync(directory)) {
+          mkdirSync(directory, { recursive: true });
+        }
+
+        const filePath = path.join(directory, filename);
+        const writeStream = createWriteStream(filePath);
+
+        createReadStream().pipe(writeStream);
+
+        try {
+          await finished(writeStream);
+          return { url: `/media/${key}`, key };
+        } catch (error) {
+          throw new Error(`Failed to upload file: ${error.message}`);
+        }
+      }
+    }
+  }
+
+  /**
+   * Convert a readable stream to a buffer
+   * @param stream Readable stream
+   * @returns Promise with buffer
+   */
+  private async streamToBuffer(stream: NodeJS.ReadableStream): Promise<Buffer> {
+    const chunks: Buffer[] = [];
+
+    return new Promise((resolve, reject) => {
+      stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
+      stream.on('error', (err) => reject(err));
+      stream.on('end', () => resolve(Buffer.concat(chunks)));
+    });
+  }
+
+  /**
+   * Get the bucket URL to use in the returned file URL
+   * @returns The bucket URL string
+   */
+  private getBucketUrl(): string {
+    const s3Config = this.configService.s3Config;
+
+    // If a public URL is configured, use it (e.g., CDN or custom domain)
+    if (s3Config.publicUrl) {
+      return s3Config.publicUrl;
+    }
+
+    // Use the constructed endpoint + bucket name
+    const endpoint = this.getEndpoint();
+    return `${endpoint}/${s3Config.bucketName}`;
+  }
+}
diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs
index 0374b496..c68c442e 100644
--- a/frontend/next.config.mjs
+++ b/frontend/next.config.mjs
@@ -28,7 +28,7 @@ const nextConfig = {
     remotePatterns: [
       {
         protocol: 'https',
-        hostname: 'picsum.photos',
+        hostname: '*',
         pathname: '/**',
       },
     ],
diff --git a/frontend/package.json b/frontend/package.json
index 09b55b32..ffb7364b 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -16,7 +16,6 @@
     "generate:watch": "graphql-codegen --watch"
   },
   "dependencies": {
-    "codefox-common": "workspace:*",
     "@apollo/client": "^3.11.8",
     "@emoji-mart/data": "^1.2.1",
     "@emoji-mart/react": "^1.1.1",
@@ -38,9 +37,12 @@
     "@radix-ui/react-tabs": "^1.1.2",
     "@radix-ui/react-tooltip": "^1.1.6",
     "@radix-ui/react-visually-hidden": "^1.1.1",
+    "@types/apollo-upload-client": "^18.0.0",
     "@types/dom-speech-recognition": "^0.0.4",
+    "apollo-upload-client": "^18.0.0",
     "class-variance-authority": "^0.7.1",
     "clsx": "^2.1.1",
+    "codefox-common": "workspace:*",
     "emoji-mart": "^5.6.0",
     "framer-motion": "^11.18.2",
     "graphql": "^16.9.0",
diff --git a/frontend/src/graphql/request.ts b/frontend/src/graphql/request.ts
index 7b099dd6..3a0d5fef 100644
--- a/frontend/src/graphql/request.ts
+++ b/frontend/src/graphql/request.ts
@@ -209,17 +209,6 @@ export const UPDATE_PROJECT_PUBLIC_STATUS = gql`
   }
 `;
 
-// Mutation to update project photo URL
-export const UPDATE_PROJECT_PHOTO_URL = gql`
-  mutation UpdateProjectPhotoUrl($projectId: ID!, $photoUrl: String!) {
-    updateProjectPhotoUrl(projectId: $projectId, photoUrl: $photoUrl) {
-      id
-      projectName
-      photoUrl
-    }
-  }
-`;
-
 // Query to get subscribed/forked projects
 export const GET_SUBSCRIBED_PROJECTS = gql`
   query GetSubscribedProjects {
diff --git a/frontend/src/graphql/schema.gql b/frontend/src/graphql/schema.gql
index 7a256c75..c637f81d 100644
--- a/frontend/src/graphql/schema.gql
+++ b/frontend/src/graphql/schema.gql
@@ -54,9 +54,7 @@ input CreateProjectInput {
   public: Boolean
 }
 
-"""
-Date custom scalar type
-"""
+"""Date custom scalar type"""
 scalar Date
 
 input FetchPublicProjectsInputs {
@@ -115,7 +113,7 @@ type Mutation {
   subscribeToProject(projectId: ID!): Project!
   triggerChatStream(input: ChatInputType!): Boolean!
   updateChatTitle(updateChatTitleInput: UpdateChatTitleInput!): Chat
-  updateProjectPhotoUrl(photoUrl: String!, projectId: ID!): Project!
+  updateProjectPhoto(input: UpdateProjectPhotoInput!): Project!
   updateProjectPublicStatus(isPublic: Boolean!, projectId: ID!): Project!
 }
 
@@ -210,6 +208,14 @@ input UpdateChatTitleInput {
   title: String
 }
 
+input UpdateProjectPhotoInput {
+  file: Upload!
+  projectId: ID!
+}
+
+"""The `Upload` scalar type represents a file upload."""
+scalar Upload
+
 type User {
   chats: [Chat!]!
   createdAt: Date!
@@ -221,4 +227,4 @@ type User {
   subscribedProjects: [Project!] @deprecated(reason: "Use projects with forkedFromId instead")
   updatedAt: Date!
   username: String!
-}
+}
\ No newline at end of file
diff --git a/frontend/src/graphql/type.tsx b/frontend/src/graphql/type.tsx
index 554833ce..9f8b8b8f 100644
--- a/frontend/src/graphql/type.tsx
+++ b/frontend/src/graphql/type.tsx
@@ -36,6 +36,8 @@ export type Scalars = {
   Float: { input: number; output: number };
   /** Date custom scalar type */
   Date: { input: Date; output: Date };
+  /** The `Upload` scalar type represents a file upload. */
+  Upload: { input: any; output: any };
 };
 
 export type Chat = {
@@ -154,7 +156,7 @@ export type Mutation = {
   subscribeToProject: Project;
   triggerChatStream: Scalars['Boolean']['output'];
   updateChatTitle?: Maybe<Chat>;
-  updateProjectPhotoUrl: Project;
+  updateProjectPhoto: Project;
   updateProjectPublicStatus: Project;
 };
 
@@ -210,9 +212,8 @@ export type MutationUpdateChatTitleArgs = {
   updateChatTitleInput: UpdateChatTitleInput;
 };
 
-export type MutationUpdateProjectPhotoUrlArgs = {
-  photoUrl: Scalars['String']['input'];
-  projectId: Scalars['ID']['input'];
+export type MutationUpdateProjectPhotoArgs = {
+  input: UpdateProjectPhotoInput;
 };
 
 export type MutationUpdateProjectPublicStatusArgs = {
@@ -336,6 +337,11 @@ export type UpdateChatTitleInput = {
   title?: InputMaybe<Scalars['String']['input']>;
 };
 
+export type UpdateProjectPhotoInput = {
+  file: Scalars['Upload']['input'];
+  projectId: Scalars['ID']['input'];
+};
+
 export type User = {
   __typename: 'User';
   chats: Array<Chat>;
@@ -491,6 +497,8 @@ export type ResolversTypes = ResolversObject<{
   String: ResolverTypeWrapper<Scalars['String']['output']>;
   Subscription: ResolverTypeWrapper<{}>;
   UpdateChatTitleInput: UpdateChatTitleInput;
+  UpdateProjectPhotoInput: UpdateProjectPhotoInput;
+  Upload: ResolverTypeWrapper<Scalars['Upload']['output']>;
   User: ResolverTypeWrapper<User>;
 }>;
 
@@ -524,6 +532,8 @@ export type ResolversParentTypes = ResolversObject<{
   String: Scalars['String']['output'];
   Subscription: {};
   UpdateChatTitleInput: UpdateChatTitleInput;
+  UpdateProjectPhotoInput: UpdateProjectPhotoInput;
+  Upload: Scalars['Upload']['output'];
   User: User;
 }>;
 
@@ -730,11 +740,11 @@ export type MutationResolvers<
     ContextType,
     RequireFields<MutationUpdateChatTitleArgs, 'updateChatTitleInput'>
   >;
-  updateProjectPhotoUrl?: Resolver<
+  updateProjectPhoto?: Resolver<
     ResolversTypes['Project'],
     ParentType,
     ContextType,
-    RequireFields<MutationUpdateProjectPhotoUrlArgs, 'photoUrl' | 'projectId'>
+    RequireFields<MutationUpdateProjectPhotoArgs, 'input'>
   >;
   updateProjectPublicStatus?: Resolver<
     ResolversTypes['Project'],
@@ -900,6 +910,11 @@ export type SubscriptionResolvers<
   >;
 }>;
 
+export interface UploadScalarConfig
+  extends GraphQLScalarTypeConfig<ResolversTypes['Upload'], any> {
+  name: 'Upload';
+}
+
 export type UserResolvers<
   ContextType = any,
   ParentType extends
@@ -941,5 +956,6 @@ export type Resolvers<ContextType = any> = ResolversObject<{
   Query?: QueryResolvers<ContextType>;
   RefreshTokenResponse?: RefreshTokenResponseResolvers<ContextType>;
   Subscription?: SubscriptionResolvers<ContextType>;
+  Upload?: GraphQLScalarType;
   User?: UserResolvers<ContextType>;
 }>;
diff --git a/frontend/src/lib/client.ts b/frontend/src/lib/client.ts
index d9fe6586..9f8aa7d2 100644
--- a/frontend/src/lib/client.ts
+++ b/frontend/src/lib/client.ts
@@ -3,7 +3,6 @@
 import {
   ApolloClient,
   InMemoryCache,
-  HttpLink,
   ApolloLink,
   from,
   split,
@@ -12,13 +11,14 @@ import { onError } from '@apollo/client/link/error';
 import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
 import { createClient } from 'graphql-ws';
 import { getMainDefinition } from '@apollo/client/utilities';
+import createUploadLink from 'apollo-upload-client/createUploadLink.mjs';
 import { LocalStore } from '@/lib/storage';
 
-// HTTP Link
-const httpLink = new HttpLink({
+// Create the upload link as the terminating link
+const uploadLink = createUploadLink({
   uri: process.env.NEXT_PUBLIC_GRAPHQL_URL || 'http://localhost:8080/graphql',
   headers: {
-    'Content-Type': 'application/json',
+    'Apollo-Require-Preflight': 'true',
     'Access-Control-Allow-Credentials': 'true',
     'Access-Control-Allow-Origin': '*',
   },
@@ -85,7 +85,15 @@ const errorLink = onError(({ graphQLErrors, networkError }) => {
   }
 });
 
-// Split traffic based on operation type
+// Build the HTTP link chain
+const httpLinkWithMiddleware = from([
+  errorLink,
+  requestLoggingMiddleware,
+  authMiddleware,
+  uploadLink as unknown as ApolloLink, // Cast to ApolloLink to satisfy TypeScript
+]);
+
+// Split traffic between WebSocket and HTTP
 const splitLink = wsLink
   ? split(
       ({ query }) => {
@@ -96,9 +104,9 @@ const splitLink = wsLink
         );
       },
       wsLink,
-      from([errorLink, requestLoggingMiddleware, authMiddleware, httpLink])
+      httpLinkWithMiddleware
     )
-  : from([errorLink, requestLoggingMiddleware, authMiddleware, httpLink]);
+  : httpLinkWithMiddleware;
 
 // Create Apollo Client
 export const client = new ApolloClient({
diff --git a/llm-server/.env b/llm-server/.env
deleted file mode 100644
index e30ddc6f..00000000
--- a/llm-server/.env
+++ /dev/null
@@ -1,9 +0,0 @@
-# Server Configuration
-PORT=8001
-
-# Model Configuration
-MODEL_PATH=./models
-MODEL_TYPE=llama
-
-# Other Configuration
-NODE_ENV=development
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 553258ac..814f243e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -36,6 +36,9 @@ importers:
       '@apollo/server':
         specifier: ^4.11.0
         version: 4.11.3(graphql@16.10.0)
+      '@aws-sdk/client-s3':
+        specifier: ^3.758.0
+        version: 3.758.0
       '@huggingface/hub':
         specifier: latest
         version: 1.0.1
@@ -50,7 +53,7 @@ importers:
         version: 3.1.3(@nestjs/common@10.4.15)(axios@1.8.1)(rxjs@7.8.2)
       '@nestjs/common':
         specifier: ^10.0.0
-        version: 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+        version: 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@nestjs/config':
         specifier: ^3.2.3
         version: 3.3.0(@nestjs/common@10.4.15)(rxjs@7.8.2)
@@ -59,7 +62,7 @@ importers:
         version: 10.4.15(@nestjs/common@10.4.15)(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@nestjs/graphql':
         specifier: ^12.2.0
-        version: 12.2.2(@nestjs/common@10.4.15)(@nestjs/core@10.4.15)(class-validator@0.14.1)(graphql@16.10.0)(reflect-metadata@0.2.2)
+        version: 12.2.2(@nestjs/common@10.4.15)(@nestjs/core@10.4.15)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.10.0)(reflect-metadata@0.2.2)
       '@nestjs/jwt':
         specifier: ^10.2.0
         version: 10.2.0(@nestjs/common@10.4.15)
@@ -87,6 +90,9 @@ importers:
       bcrypt:
         specifier: ^5.1.1
         version: 5.1.1
+      class-transformer:
+        specifier: ^0.5.1
+        version: 0.5.1
       class-validator:
         specifier: ^0.14.1
         version: 0.14.1
@@ -111,6 +117,9 @@ importers:
       graphql-subscriptions:
         specifier: ^2.0.0
         version: 2.0.0(graphql@16.10.0)
+      graphql-upload-minimal:
+        specifier: ^1.6.1
+        version: 1.6.1(graphql@16.10.0)
       graphql-ws:
         specifier: ^5.16.0
         version: 5.16.2(graphql@16.10.0)
@@ -233,7 +242,7 @@ importers:
     devDependencies:
       '@nestjs/common':
         specifier: 10.4.15
-        version: 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+        version: 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@types/fs-extra':
         specifier: ^11.0.4
         version: 11.0.4
@@ -327,7 +336,7 @@ importers:
         version: 3.10.0(react-hook-form@7.54.2)
       '@langchain/community':
         specifier: ^0.3.1
-        version: 0.3.33(@browserbasehq/stagehand@1.13.1)(@ibm-cloud/watsonx-ai@1.5.1)(@langchain/core@0.3.42)(axios@1.7.9)(ibm-cloud-sdk-core@5.1.3)(openai@4.86.1)(ws@8.18.1)
+        version: 0.3.34(@browserbasehq/stagehand@1.13.1)(@ibm-cloud/watsonx-ai@1.5.1)(@langchain/core@0.3.42)(axios@1.7.9)(ibm-cloud-sdk-core@5.1.3)(openai@4.86.1)(ws@8.18.1)
       '@langchain/core':
         specifier: ^0.3.3
         version: 0.3.42(openai@4.86.1)
@@ -336,7 +345,7 @@ importers:
         version: 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1)(react@18.3.1)
       '@nestjs/common':
         specifier: ^10.4.6
-        version: 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+        version: 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@radix-ui/react-avatar':
         specifier: ^1.1.0
         version: 1.1.3(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
@@ -376,9 +385,15 @@ importers:
       '@radix-ui/react-visually-hidden':
         specifier: ^1.1.1
         version: 1.1.2(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
+      '@types/apollo-upload-client':
+        specifier: ^18.0.0
+        version: 18.0.0(@types/react@18.3.18)(graphql-ws@5.16.2)(react-dom@18.3.1)(react@18.3.1)(subscriptions-transport-ws@0.11.0)
       '@types/dom-speech-recognition':
         specifier: ^0.0.4
         version: 0.0.4
+      apollo-upload-client:
+        specifier: ^18.0.0
+        version: 18.0.1(@apollo/client@3.13.1)(graphql@16.10.0)
       class-variance-authority:
         specifier: ^0.7.1
         version: 0.7.1
@@ -405,7 +420,7 @@ importers:
         version: 0.445.0(react@18.3.1)
       motion:
         specifier: ^12.4.7
-        version: 12.4.9(react-dom@18.3.1)(react@18.3.1)
+        version: 12.4.10(react-dom@18.3.1)(react@18.3.1)
       next:
         specifier: ^14.2.13
         version: 14.2.24(@babel/core@7.26.9)(@playwright/test@1.50.1)(react-dom@18.3.1)(react@18.3.1)
@@ -417,7 +432,7 @@ importers:
         version: 18.3.1
       react-activity-calendar:
         specifier: ^2.7.8
-        version: 2.7.8(react@18.3.1)
+        version: 2.7.9(react@18.3.1)
       react-code-blocks:
         specifier: ^0.1.6
         version: 0.1.6(react-dom@18.3.1)(react@18.3.1)
@@ -556,7 +571,7 @@ importers:
         version: 3.3.3
       '@nestjs/common':
         specifier: ^10.4.5
-        version: 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+        version: 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@types/axios':
         specifier: ^0.14.4
         version: 0.14.4
@@ -660,8 +675,8 @@ importers:
 
 packages:
 
-  /@0no-co/graphql.web@1.1.1(graphql@16.10.0):
-    resolution: {integrity: sha512-F2i3xdycesw78QCOBHmpTn7eaD2iNXGwB2gkfwxcOfBbeauYpr8RBSyJOkDrFtKtVRMclg8Sg3n1ip0ACyUuag==}
+  /@0no-co/graphql.web@1.1.2(graphql@16.10.0):
+    resolution: {integrity: sha512-N2NGsU5FLBhT8NZ+3l2YrzZSHITjNXNuDhC4iDiikv0IujaJ0Xc6xIxQZ/Ek3Cb+rgPjnLHYyJm11tInuJn+cw==}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0
     peerDependenciesMeta:
@@ -1347,6 +1362,577 @@ packages:
       - encoding
     dev: true
 
+  /@aws-crypto/crc32@5.2.0:
+    resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
+    engines: {node: '>=16.0.0'}
+    dependencies:
+      '@aws-crypto/util': 5.2.0
+      '@aws-sdk/types': 3.734.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-crypto/crc32c@5.2.0:
+    resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==}
+    dependencies:
+      '@aws-crypto/util': 5.2.0
+      '@aws-sdk/types': 3.734.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-crypto/sha1-browser@5.2.0:
+    resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==}
+    dependencies:
+      '@aws-crypto/supports-web-crypto': 5.2.0
+      '@aws-crypto/util': 5.2.0
+      '@aws-sdk/types': 3.734.0
+      '@aws-sdk/util-locate-window': 3.723.0
+      '@smithy/util-utf8': 2.3.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-crypto/sha256-browser@5.2.0:
+    resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==}
+    dependencies:
+      '@aws-crypto/sha256-js': 5.2.0
+      '@aws-crypto/supports-web-crypto': 5.2.0
+      '@aws-crypto/util': 5.2.0
+      '@aws-sdk/types': 3.734.0
+      '@aws-sdk/util-locate-window': 3.723.0
+      '@smithy/util-utf8': 2.3.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-crypto/sha256-js@5.2.0:
+    resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==}
+    engines: {node: '>=16.0.0'}
+    dependencies:
+      '@aws-crypto/util': 5.2.0
+      '@aws-sdk/types': 3.734.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-crypto/supports-web-crypto@5.2.0:
+    resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==}
+    dependencies:
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-crypto/util@5.2.0:
+    resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
+    dependencies:
+      '@aws-sdk/types': 3.734.0
+      '@smithy/util-utf8': 2.3.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/client-s3@3.758.0:
+    resolution: {integrity: sha512-f8SlhU9/93OC/WEI6xVJf/x/GoQFj9a/xXK6QCtr5fvCjfSLgMVFmKTiIl/tgtDRzxUDc8YS6EGtbHjJ3Y/atg==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-crypto/sha1-browser': 5.2.0
+      '@aws-crypto/sha256-browser': 5.2.0
+      '@aws-crypto/sha256-js': 5.2.0
+      '@aws-sdk/core': 3.758.0
+      '@aws-sdk/credential-provider-node': 3.758.0
+      '@aws-sdk/middleware-bucket-endpoint': 3.734.0
+      '@aws-sdk/middleware-expect-continue': 3.734.0
+      '@aws-sdk/middleware-flexible-checksums': 3.758.0
+      '@aws-sdk/middleware-host-header': 3.734.0
+      '@aws-sdk/middleware-location-constraint': 3.734.0
+      '@aws-sdk/middleware-logger': 3.734.0
+      '@aws-sdk/middleware-recursion-detection': 3.734.0
+      '@aws-sdk/middleware-sdk-s3': 3.758.0
+      '@aws-sdk/middleware-ssec': 3.734.0
+      '@aws-sdk/middleware-user-agent': 3.758.0
+      '@aws-sdk/region-config-resolver': 3.734.0
+      '@aws-sdk/signature-v4-multi-region': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@aws-sdk/util-endpoints': 3.743.0
+      '@aws-sdk/util-user-agent-browser': 3.734.0
+      '@aws-sdk/util-user-agent-node': 3.758.0
+      '@aws-sdk/xml-builder': 3.734.0
+      '@smithy/config-resolver': 4.0.1
+      '@smithy/core': 3.1.5
+      '@smithy/eventstream-serde-browser': 4.0.1
+      '@smithy/eventstream-serde-config-resolver': 4.0.1
+      '@smithy/eventstream-serde-node': 4.0.1
+      '@smithy/fetch-http-handler': 5.0.1
+      '@smithy/hash-blob-browser': 4.0.1
+      '@smithy/hash-node': 4.0.1
+      '@smithy/hash-stream-node': 4.0.1
+      '@smithy/invalid-dependency': 4.0.1
+      '@smithy/md5-js': 4.0.1
+      '@smithy/middleware-content-length': 4.0.1
+      '@smithy/middleware-endpoint': 4.0.6
+      '@smithy/middleware-retry': 4.0.7
+      '@smithy/middleware-serde': 4.0.2
+      '@smithy/middleware-stack': 4.0.1
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/node-http-handler': 4.0.3
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/smithy-client': 4.1.6
+      '@smithy/types': 4.1.0
+      '@smithy/url-parser': 4.0.1
+      '@smithy/util-base64': 4.0.0
+      '@smithy/util-body-length-browser': 4.0.0
+      '@smithy/util-body-length-node': 4.0.0
+      '@smithy/util-defaults-mode-browser': 4.0.7
+      '@smithy/util-defaults-mode-node': 4.0.7
+      '@smithy/util-endpoints': 3.0.1
+      '@smithy/util-middleware': 4.0.1
+      '@smithy/util-retry': 4.0.1
+      '@smithy/util-stream': 4.1.2
+      '@smithy/util-utf8': 4.0.0
+      '@smithy/util-waiter': 4.0.2
+      tslib: 2.8.1
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/client-sso@3.758.0:
+    resolution: {integrity: sha512-BoGO6IIWrLyLxQG6txJw6RT2urmbtlwfggapNCrNPyYjlXpzTSJhBYjndg7TpDATFd0SXL0zm8y/tXsUXNkdYQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-crypto/sha256-browser': 5.2.0
+      '@aws-crypto/sha256-js': 5.2.0
+      '@aws-sdk/core': 3.758.0
+      '@aws-sdk/middleware-host-header': 3.734.0
+      '@aws-sdk/middleware-logger': 3.734.0
+      '@aws-sdk/middleware-recursion-detection': 3.734.0
+      '@aws-sdk/middleware-user-agent': 3.758.0
+      '@aws-sdk/region-config-resolver': 3.734.0
+      '@aws-sdk/types': 3.734.0
+      '@aws-sdk/util-endpoints': 3.743.0
+      '@aws-sdk/util-user-agent-browser': 3.734.0
+      '@aws-sdk/util-user-agent-node': 3.758.0
+      '@smithy/config-resolver': 4.0.1
+      '@smithy/core': 3.1.5
+      '@smithy/fetch-http-handler': 5.0.1
+      '@smithy/hash-node': 4.0.1
+      '@smithy/invalid-dependency': 4.0.1
+      '@smithy/middleware-content-length': 4.0.1
+      '@smithy/middleware-endpoint': 4.0.6
+      '@smithy/middleware-retry': 4.0.7
+      '@smithy/middleware-serde': 4.0.2
+      '@smithy/middleware-stack': 4.0.1
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/node-http-handler': 4.0.3
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/smithy-client': 4.1.6
+      '@smithy/types': 4.1.0
+      '@smithy/url-parser': 4.0.1
+      '@smithy/util-base64': 4.0.0
+      '@smithy/util-body-length-browser': 4.0.0
+      '@smithy/util-body-length-node': 4.0.0
+      '@smithy/util-defaults-mode-browser': 4.0.7
+      '@smithy/util-defaults-mode-node': 4.0.7
+      '@smithy/util-endpoints': 3.0.1
+      '@smithy/util-middleware': 4.0.1
+      '@smithy/util-retry': 4.0.1
+      '@smithy/util-utf8': 4.0.0
+      tslib: 2.8.1
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/core@3.758.0:
+    resolution: {integrity: sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.734.0
+      '@smithy/core': 3.1.5
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/property-provider': 4.0.1
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/signature-v4': 5.0.1
+      '@smithy/smithy-client': 4.1.6
+      '@smithy/types': 4.1.0
+      '@smithy/util-middleware': 4.0.1
+      fast-xml-parser: 4.4.1
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/credential-provider-env@3.758.0:
+    resolution: {integrity: sha512-N27eFoRrO6MeUNumtNHDW9WOiwfd59LPXPqDrIa3kWL/s+fOKFHb9xIcF++bAwtcZnAxKkgpDCUP+INNZskE+w==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/core': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@smithy/property-provider': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/credential-provider-http@3.758.0:
+    resolution: {integrity: sha512-Xt9/U8qUCiw1hihztWkNeIR+arg6P+yda10OuCHX6kFVx3auTlU7+hCqs3UxqniGU4dguHuftf3mRpi5/GJ33Q==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/core': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@smithy/fetch-http-handler': 5.0.1
+      '@smithy/node-http-handler': 4.0.3
+      '@smithy/property-provider': 4.0.1
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/smithy-client': 4.1.6
+      '@smithy/types': 4.1.0
+      '@smithy/util-stream': 4.1.2
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/credential-provider-ini@3.758.0:
+    resolution: {integrity: sha512-cymSKMcP5d+OsgetoIZ5QCe1wnp2Q/tq+uIxVdh9MbfdBBEnl9Ecq6dH6VlYS89sp4QKuxHxkWXVnbXU3Q19Aw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/core': 3.758.0
+      '@aws-sdk/credential-provider-env': 3.758.0
+      '@aws-sdk/credential-provider-http': 3.758.0
+      '@aws-sdk/credential-provider-process': 3.758.0
+      '@aws-sdk/credential-provider-sso': 3.758.0
+      '@aws-sdk/credential-provider-web-identity': 3.758.0
+      '@aws-sdk/nested-clients': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@smithy/credential-provider-imds': 4.0.1
+      '@smithy/property-provider': 4.0.1
+      '@smithy/shared-ini-file-loader': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/credential-provider-node@3.758.0:
+    resolution: {integrity: sha512-+DaMv63wiq7pJrhIQzZYMn4hSarKiizDoJRvyR7WGhnn0oQ/getX9Z0VNCV3i7lIFoLNTb7WMmQ9k7+z/uD5EQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/credential-provider-env': 3.758.0
+      '@aws-sdk/credential-provider-http': 3.758.0
+      '@aws-sdk/credential-provider-ini': 3.758.0
+      '@aws-sdk/credential-provider-process': 3.758.0
+      '@aws-sdk/credential-provider-sso': 3.758.0
+      '@aws-sdk/credential-provider-web-identity': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@smithy/credential-provider-imds': 4.0.1
+      '@smithy/property-provider': 4.0.1
+      '@smithy/shared-ini-file-loader': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/credential-provider-process@3.758.0:
+    resolution: {integrity: sha512-AzcY74QTPqcbXWVgjpPZ3HOmxQZYPROIBz2YINF0OQk0MhezDWV/O7Xec+K1+MPGQO3qS6EDrUUlnPLjsqieHA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/core': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@smithy/property-provider': 4.0.1
+      '@smithy/shared-ini-file-loader': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/credential-provider-sso@3.758.0:
+    resolution: {integrity: sha512-x0FYJqcOLUCv8GLLFDYMXRAQKGjoM+L0BG4BiHYZRDf24yQWFCAZsCQAYKo6XZYh2qznbsW6f//qpyJ5b0QVKQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/client-sso': 3.758.0
+      '@aws-sdk/core': 3.758.0
+      '@aws-sdk/token-providers': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@smithy/property-provider': 4.0.1
+      '@smithy/shared-ini-file-loader': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/credential-provider-web-identity@3.758.0:
+    resolution: {integrity: sha512-XGguXhBqiCXMXRxcfCAVPlMbm3VyJTou79r/3mxWddHWF0XbhaQiBIbUz6vobVTD25YQRbWSmSch7VA8kI5Lrw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/core': 3.758.0
+      '@aws-sdk/nested-clients': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@smithy/property-provider': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/middleware-bucket-endpoint@3.734.0:
+    resolution: {integrity: sha512-etC7G18aF7KdZguW27GE/wpbrNmYLVT755EsFc8kXpZj8D6AFKxc7OuveinJmiy0bYXAMspJUWsF6CrGpOw6CQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.734.0
+      '@aws-sdk/util-arn-parser': 3.723.0
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/types': 4.1.0
+      '@smithy/util-config-provider': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/middleware-expect-continue@3.734.0:
+    resolution: {integrity: sha512-P38/v1l6HjuB2aFUewt7ueAW5IvKkFcv5dalPtbMGRhLeyivBOHwbCyuRKgVs7z7ClTpu9EaViEGki2jEQqEsQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.734.0
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/middleware-flexible-checksums@3.758.0:
+    resolution: {integrity: sha512-o8Rk71S08YTKLoSobucjnbj97OCGaXgpEDNKXpXaavUM5xLNoHCLSUPRCiEN86Ivqxg1n17Y2nSRhfbsveOXXA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-crypto/crc32': 5.2.0
+      '@aws-crypto/crc32c': 5.2.0
+      '@aws-crypto/util': 5.2.0
+      '@aws-sdk/core': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@smithy/is-array-buffer': 4.0.0
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/types': 4.1.0
+      '@smithy/util-middleware': 4.0.1
+      '@smithy/util-stream': 4.1.2
+      '@smithy/util-utf8': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/middleware-host-header@3.734.0:
+    resolution: {integrity: sha512-LW7RRgSOHHBzWZnigNsDIzu3AiwtjeI2X66v+Wn1P1u+eXssy1+up4ZY/h+t2sU4LU36UvEf+jrZti9c6vRnFw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.734.0
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/middleware-location-constraint@3.734.0:
+    resolution: {integrity: sha512-EJEIXwCQhto/cBfHdm3ZOeLxd2NlJD+X2F+ZTOxzokuhBtY0IONfC/91hOo5tWQweerojwshSMHRCKzRv1tlwg==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.734.0
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/middleware-logger@3.734.0:
+    resolution: {integrity: sha512-mUMFITpJUW3LcKvFok176eI5zXAUomVtahb9IQBwLzkqFYOrMJvWAvoV4yuxrJ8TlQBG8gyEnkb9SnhZvjg67w==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.734.0
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/middleware-recursion-detection@3.734.0:
+    resolution: {integrity: sha512-CUat2d9ITsFc2XsmeiRQO96iWpxSKYFjxvj27Hc7vo87YUHRnfMfnc8jw1EpxEwMcvBD7LsRa6vDNky6AjcrFA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.734.0
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/middleware-sdk-s3@3.758.0:
+    resolution: {integrity: sha512-6mJ2zyyHPYSV6bAcaFpsdoXZJeQlR1QgBnZZ6juY/+dcYiuyWCdyLUbGzSZSE7GTfx6i+9+QWFeoIMlWKgU63A==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/core': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@aws-sdk/util-arn-parser': 3.723.0
+      '@smithy/core': 3.1.5
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/signature-v4': 5.0.1
+      '@smithy/smithy-client': 4.1.6
+      '@smithy/types': 4.1.0
+      '@smithy/util-config-provider': 4.0.0
+      '@smithy/util-middleware': 4.0.1
+      '@smithy/util-stream': 4.1.2
+      '@smithy/util-utf8': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/middleware-ssec@3.734.0:
+    resolution: {integrity: sha512-d4yd1RrPW/sspEXizq2NSOUivnheac6LPeLSLnaeTbBG9g1KqIqvCzP1TfXEqv2CrWfHEsWtJpX7oyjySSPvDQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.734.0
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/middleware-user-agent@3.758.0:
+    resolution: {integrity: sha512-iNyehQXtQlj69JCgfaOssgZD4HeYGOwxcaKeG6F+40cwBjTAi0+Ph1yfDwqk2qiBPIRWJ/9l2LodZbxiBqgrwg==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/core': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@aws-sdk/util-endpoints': 3.743.0
+      '@smithy/core': 3.1.5
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/nested-clients@3.758.0:
+    resolution: {integrity: sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-crypto/sha256-browser': 5.2.0
+      '@aws-crypto/sha256-js': 5.2.0
+      '@aws-sdk/core': 3.758.0
+      '@aws-sdk/middleware-host-header': 3.734.0
+      '@aws-sdk/middleware-logger': 3.734.0
+      '@aws-sdk/middleware-recursion-detection': 3.734.0
+      '@aws-sdk/middleware-user-agent': 3.758.0
+      '@aws-sdk/region-config-resolver': 3.734.0
+      '@aws-sdk/types': 3.734.0
+      '@aws-sdk/util-endpoints': 3.743.0
+      '@aws-sdk/util-user-agent-browser': 3.734.0
+      '@aws-sdk/util-user-agent-node': 3.758.0
+      '@smithy/config-resolver': 4.0.1
+      '@smithy/core': 3.1.5
+      '@smithy/fetch-http-handler': 5.0.1
+      '@smithy/hash-node': 4.0.1
+      '@smithy/invalid-dependency': 4.0.1
+      '@smithy/middleware-content-length': 4.0.1
+      '@smithy/middleware-endpoint': 4.0.6
+      '@smithy/middleware-retry': 4.0.7
+      '@smithy/middleware-serde': 4.0.2
+      '@smithy/middleware-stack': 4.0.1
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/node-http-handler': 4.0.3
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/smithy-client': 4.1.6
+      '@smithy/types': 4.1.0
+      '@smithy/url-parser': 4.0.1
+      '@smithy/util-base64': 4.0.0
+      '@smithy/util-body-length-browser': 4.0.0
+      '@smithy/util-body-length-node': 4.0.0
+      '@smithy/util-defaults-mode-browser': 4.0.7
+      '@smithy/util-defaults-mode-node': 4.0.7
+      '@smithy/util-endpoints': 3.0.1
+      '@smithy/util-middleware': 4.0.1
+      '@smithy/util-retry': 4.0.1
+      '@smithy/util-utf8': 4.0.0
+      tslib: 2.8.1
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/region-config-resolver@3.734.0:
+    resolution: {integrity: sha512-Lvj1kPRC5IuJBr9DyJ9T9/plkh+EfKLy+12s/mykOy1JaKHDpvj+XGy2YO6YgYVOb8JFtaqloid+5COtje4JTQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.734.0
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/types': 4.1.0
+      '@smithy/util-config-provider': 4.0.0
+      '@smithy/util-middleware': 4.0.1
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/signature-v4-multi-region@3.758.0:
+    resolution: {integrity: sha512-0RPCo8fYJcrenJ6bRtiUbFOSgQ1CX/GpvwtLU2Fam1tS9h2klKK8d74caeV6A1mIUvBU7bhyQ0wMGlwMtn3EYw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/middleware-sdk-s3': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/signature-v4': 5.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/token-providers@3.758.0:
+    resolution: {integrity: sha512-ckptN1tNrIfQUaGWm/ayW1ddG+imbKN7HHhjFdS4VfItsP0QQOB0+Ov+tpgb4MoNR4JaUghMIVStjIeHN2ks1w==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/nested-clients': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@smithy/property-provider': 4.0.1
+      '@smithy/shared-ini-file-loader': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    transitivePeerDependencies:
+      - aws-crt
+    dev: false
+
+  /@aws-sdk/types@3.734.0:
+    resolution: {integrity: sha512-o11tSPTT70nAkGV1fN9wm/hAIiLPyWX6SuGf+9JyTp7S/rC2cFWhR26MvA69nplcjNaXVzB0f+QFrLXXjOqCrg==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/util-arn-parser@3.723.0:
+    resolution: {integrity: sha512-ZhEfvUwNliOQROcAk34WJWVYTlTa4694kSVhDSjW6lE1bMataPnIN8A0ycukEzBXmd8ZSoBcQLn6lKGl7XIJ5w==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/util-endpoints@3.743.0:
+    resolution: {integrity: sha512-sN1l559zrixeh5x+pttrnd0A3+r34r0tmPkJ/eaaMaAzXqsmKU/xYre9K3FNnsSS1J1k4PEfk/nHDTVUgFYjnw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-sdk/types': 3.734.0
+      '@smithy/types': 4.1.0
+      '@smithy/util-endpoints': 3.0.1
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/util-locate-window@3.723.0:
+    resolution: {integrity: sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/util-user-agent-browser@3.734.0:
+    resolution: {integrity: sha512-xQTCus6Q9LwUuALW+S76OL0jcWtMOVu14q+GoLnWPUM7QeUw963oQcLhF7oq0CtaLLKyl4GOUfcwc773Zmwwng==}
+    dependencies:
+      '@aws-sdk/types': 3.734.0
+      '@smithy/types': 4.1.0
+      bowser: 2.11.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/util-user-agent-node@3.758.0:
+    resolution: {integrity: sha512-A5EZw85V6WhoKMV2hbuFRvb9NPlxEErb4HPO6/SPXYY4QrjprIzScHxikqcWv1w4J3apB1wto9LPU3IMsYtfrw==}
+    engines: {node: '>=18.0.0'}
+    peerDependencies:
+      aws-crt: '>=1.0.0'
+    peerDependenciesMeta:
+      aws-crt:
+        optional: true
+    dependencies:
+      '@aws-sdk/middleware-user-agent': 3.758.0
+      '@aws-sdk/types': 3.734.0
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@aws-sdk/xml-builder@3.734.0:
+    resolution: {integrity: sha512-Zrjxi5qwGEcUsJ0ru7fRtW74WcTS0rbLcehoFB+rN1GRi2hbLcFaYs4PwVA5diLeAJH0gszv3x4Hr/S87MfbKQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
   /@babel/code-frame@7.26.2:
     resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
     engines: {node: '>=6.9.0'}
@@ -3961,7 +4547,7 @@ packages:
       '@docusaurus/utils': 3.6.3(acorn@8.14.0)(react-dom@18.3.1)(react@18.3.1)(typescript@5.6.3)
       '@docusaurus/utils-validation': 3.6.3(acorn@8.14.0)(react-dom@18.3.1)(react@18.3.1)(typescript@5.6.3)
       algoliasearch: 4.24.0
-      algoliasearch-helper: 3.24.1(algoliasearch@4.24.0)
+      algoliasearch-helper: 3.24.2(algoliasearch@4.24.0)
       clsx: 2.1.1
       eta: 2.2.0
       fs-extra: 11.3.0
@@ -4146,17 +4732,26 @@ packages:
     resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
     dev: false
 
-  /@envelop/core@5.1.1:
-    resolution: {integrity: sha512-6+OukzuNsm33DtLnOats3e7VnnHndqINJbp/vlIyIlSGBc/wtgQiTAijNWwHhnozHc7WmCKzTsPSrGObvkJazg==}
+  /@envelop/core@5.2.0:
+    resolution: {integrity: sha512-JMrISsiZ4XQXMtJhs5K4MtM64zFfPHAmpWrkLt+d7d3UC4W39rQu9+4ba3kTV9MyohNr39+wfjBdTa13rKTbjA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@envelop/instruments': 1.0.0
+      '@envelop/types': 5.2.0
+      '@whatwg-node/promise-helpers': 1.2.2
+      tslib: 2.8.1
+    dev: true
+
+  /@envelop/instruments@1.0.0:
+    resolution: {integrity: sha512-f4lHoti7QgUIluIGTM0mG9Wf9/w6zc1mosYmyFkrApeHSP2PIUC6a8fMoqkdk6pgVOps39kLdIhOPF8pIKS8/A==}
     engines: {node: '>=18.0.0'}
     dependencies:
-      '@envelop/types': 5.1.1
       '@whatwg-node/promise-helpers': 1.2.2
       tslib: 2.8.1
     dev: true
 
-  /@envelop/types@5.1.1:
-    resolution: {integrity: sha512-uJyCPQRSqxH/4q8/TTTY2fMYIK/Tgv1IhOm6aFUUxuE/EI7muJM/UI85iv9Qo1OCpaafthwRLWzufRp20FyXaA==}
+  /@envelop/types@5.2.0:
+    resolution: {integrity: sha512-vCJY6URc8bK1O6p4zVRFpv/ASdyXvLM+Iqn2HP44UfTgEUQLyN4buwLawlkAv/KtzAL7VOeefpF2eKPWk7rHjg==}
     engines: {node: '>=18.0.0'}
     dependencies:
       '@whatwg-node/promise-helpers': 1.2.2
@@ -4478,7 +5073,7 @@ packages:
       graphql: ^15.5.0 || ^16.0.0 || ^17.0.0
       typescript: ^5.0.0
     dependencies:
-      '@0no-co/graphql.web': 1.1.1(graphql@16.10.0)
+      '@0no-co/graphql.web': 1.1.2(graphql@16.10.0)
       graphql: 16.10.0
       typescript: 5.6.3
     dev: true
@@ -4510,16 +5105,16 @@ packages:
       '@graphql-codegen/client-preset': 4.6.4(graphql@16.10.0)
       '@graphql-codegen/core': 4.0.2(graphql@16.10.0)
       '@graphql-codegen/plugin-helpers': 5.1.0(graphql@16.10.0)
-      '@graphql-tools/apollo-engine-loader': 8.0.17(graphql@16.10.0)
-      '@graphql-tools/code-file-loader': 8.1.17(graphql@16.10.0)
-      '@graphql-tools/git-loader': 8.0.21(graphql@16.10.0)
-      '@graphql-tools/github-loader': 8.0.17(@types/node@22.13.9)(graphql@16.10.0)
-      '@graphql-tools/graphql-file-loader': 8.0.16(graphql@16.10.0)
-      '@graphql-tools/json-file-loader': 8.0.15(graphql@16.10.0)
-      '@graphql-tools/load': 8.0.16(graphql@16.10.0)
+      '@graphql-tools/apollo-engine-loader': 8.0.18(graphql@16.10.0)
+      '@graphql-tools/code-file-loader': 8.1.18(graphql@16.10.0)
+      '@graphql-tools/git-loader': 8.0.22(graphql@16.10.0)
+      '@graphql-tools/github-loader': 8.0.18(@types/node@22.13.9)(graphql@16.10.0)
+      '@graphql-tools/graphql-file-loader': 8.0.17(graphql@16.10.0)
+      '@graphql-tools/json-file-loader': 8.0.16(graphql@16.10.0)
+      '@graphql-tools/load': 8.0.17(graphql@16.10.0)
       '@graphql-tools/prisma-loader': 8.0.17(@types/node@22.13.9)(graphql@16.10.0)
-      '@graphql-tools/url-loader': 8.0.28(@types/node@22.13.9)(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/url-loader': 8.0.29(@types/node@22.13.9)(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       '@parcel/watcher': 2.5.1
       '@whatwg-node/fetch': 0.10.5
       chalk: 4.1.2
@@ -4570,7 +5165,7 @@ packages:
       '@graphql-codegen/typescript-operations': 4.5.1(graphql@16.10.0)
       '@graphql-codegen/visitor-plugin-common': 5.7.1(graphql@16.10.0)
       '@graphql-tools/documents': 1.0.1(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       '@graphql-typed-document-node/core': 3.2.0(graphql@16.10.0)
       graphql: 16.10.0
       tslib: 2.6.3
@@ -4584,8 +5179,8 @@ packages:
       graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
     dependencies:
       '@graphql-codegen/plugin-helpers': 5.1.0(graphql@16.10.0)
-      '@graphql-tools/schema': 10.0.20(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/schema': 10.0.21(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       graphql: 16.10.0
       tslib: 2.6.3
     dev: true
@@ -4598,7 +5193,7 @@ packages:
     dependencies:
       '@graphql-codegen/plugin-helpers': 5.1.0(graphql@16.10.0)
       '@graphql-codegen/visitor-plugin-common': 5.7.1(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       auto-bind: 4.0.0
       graphql: 16.10.0
       tslib: 2.6.3
@@ -4640,7 +5235,7 @@ packages:
     peerDependencies:
       graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
     dependencies:
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       change-case-all: 1.0.15
       common-tags: 1.8.2
       graphql: 16.10.0
@@ -4655,7 +5250,7 @@ packages:
       graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
     dependencies:
       '@graphql-codegen/plugin-helpers': 5.1.0(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       graphql: 16.10.0
       tslib: 2.6.3
     dev: true
@@ -4718,7 +5313,7 @@ packages:
       '@graphql-codegen/plugin-helpers': 5.1.0(graphql@16.10.0)
       '@graphql-codegen/typescript': 4.1.5(graphql@16.10.0)
       '@graphql-codegen/visitor-plugin-common': 5.7.1(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       auto-bind: 4.0.0
       graphql: 16.10.0
       tslib: 2.6.3
@@ -4771,8 +5366,8 @@ packages:
     dependencies:
       '@graphql-codegen/plugin-helpers': 5.1.0(graphql@16.10.0)
       '@graphql-tools/optimize': 2.0.0(graphql@16.10.0)
-      '@graphql-tools/relay-operation-optimizer': 7.0.16(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/relay-operation-optimizer': 7.0.17(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       auto-bind: 4.0.0
       change-case-all: 1.0.15
       dependency-graph: 0.11.0
@@ -4784,13 +5379,13 @@ packages:
       - encoding
     dev: true
 
-  /@graphql-tools/apollo-engine-loader@8.0.17(graphql@16.10.0):
-    resolution: {integrity: sha512-2DwndS4GurK7VB8LD1paWZPdaYIwqMkMg2a3GU5nNpkL401QAAr2Mw3zYZ6XNe+nNHv4EyhywJ/ahuKKBcmJIA==}
+  /@graphql-tools/apollo-engine-loader@8.0.18(graphql@16.10.0):
+    resolution: {integrity: sha512-PSN5YEA3AheVkGlD85w/ukFVXN4e0y6gCNj0vwr3sTaL/Z5eTrqZCmalbEDs5PeZRZBo39tYBDKygcVceh3OQQ==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       '@whatwg-node/fetch': 0.10.5
       graphql: 16.10.0
       sync-fetch: 0.6.0-2
@@ -4803,20 +5398,20 @@ packages:
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       dataloader: 2.2.3
       graphql: 16.10.0
       tslib: 2.8.1
     dev: true
 
-  /@graphql-tools/code-file-loader@8.1.17(graphql@16.10.0):
-    resolution: {integrity: sha512-KQ+6n0HJQcBZ4b2HVV9rFJezyps6QLxRDPeGah3JX+MOLjjOtkpueE4br4x3+byVIm31fwWTA05wQjUx469DkA==}
+  /@graphql-tools/code-file-loader@8.1.18(graphql@16.10.0):
+    resolution: {integrity: sha512-/7oFP5EoMc5KcogOnLIxSeSstkxETry9JUvlV4Dw4e0XQv3n2aT1emqAqGznb8zdPsE5ZLwVQ7dEh0CGuYCCNw==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@graphql-tools/graphql-tag-pluck': 8.3.16(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/graphql-tag-pluck': 8.3.17(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       globby: 11.1.0
       graphql: 16.10.0
       tslib: 2.8.1
@@ -4832,9 +5427,9 @@ packages:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
       '@graphql-tools/batch-execute': 9.0.12(graphql@16.10.0)
-      '@graphql-tools/executor': 1.4.2(graphql@16.10.0)
-      '@graphql-tools/schema': 10.0.20(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/executor': 1.4.3(graphql@16.10.0)
+      '@graphql-tools/schema': 10.0.21(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       '@repeaterjs/repeater': 3.0.6
       dataloader: 2.2.3
       dset: 3.1.4
@@ -4859,8 +5454,8 @@ packages:
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@envelop/core': 5.1.1
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@envelop/core': 5.2.0
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       graphql: 16.10.0
     dev: true
 
@@ -4871,7 +5466,7 @@ packages:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
       '@graphql-tools/executor-common': 0.0.3(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       '@whatwg-node/disposablestack': 0.0.5
       graphql: 16.10.0
       graphql-ws: 6.0.4(graphql@16.10.0)(ws@8.18.1)
@@ -4892,7 +5487,7 @@ packages:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
       '@graphql-tools/executor-common': 0.0.3(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       '@repeaterjs/repeater': 3.0.6
       '@whatwg-node/disposablestack': 0.0.5
       '@whatwg-node/fetch': 0.10.5
@@ -4905,14 +5500,14 @@ packages:
       - '@types/node'
     dev: true
 
-  /@graphql-tools/executor-legacy-ws@1.1.14(graphql@16.10.0):
-    resolution: {integrity: sha512-8xyIy0uiT5PkmIcOiNJg+Kg9pLwrs9MblxucKmBez8lUCL+0nKpx8o9ntXzmbLcVBA+4hV3wO3E9Bm7gkxiTUA==}
+  /@graphql-tools/executor-legacy-ws@1.1.15(graphql@16.10.0):
+    resolution: {integrity: sha512-5VM5m/WQWoIj2GKXuOUvhtzkm11g/rbKYOiLvur6AxD59FdLwVwDisWvarj8rsZ1NUedK312fD22vpKjc2m+dw==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
-      '@types/ws': 8.5.14
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
+      '@types/ws': 8.18.0
       graphql: 16.10.0
       isomorphic-ws: 5.0.0(ws@8.18.1)
       tslib: 2.8.1
@@ -4922,13 +5517,13 @@ packages:
       - utf-8-validate
     dev: true
 
-  /@graphql-tools/executor@1.4.2(graphql@16.10.0):
-    resolution: {integrity: sha512-TzXh4SIfkOp969xeX3Z2dArzLXisAuj+YOnlhqphX+rC/OzZ1m4cZxsxaqosp/hTwlt5xXJFCoOPYjHEAU42Rw==}
+  /@graphql-tools/executor@1.4.3(graphql@16.10.0):
+    resolution: {integrity: sha512-QBefKv3h8gxXC/THqFPBF+ZKRVg4PoX/Tpczlv/mOffw6sp0w+pJ1ZeWYIr/Jh+r4kcxgqTd3/1MzYC4cl1EGA==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       '@graphql-typed-document-node/core': 3.2.0(graphql@16.10.0)
       '@repeaterjs/repeater': 3.0.6
       '@whatwg-node/disposablestack': 0.0.6
@@ -4937,14 +5532,14 @@ packages:
       tslib: 2.8.1
     dev: true
 
-  /@graphql-tools/git-loader@8.0.21(graphql@16.10.0):
-    resolution: {integrity: sha512-93c7aG/BBsu44kOh1d50rZtqfa1TRym4su+VLyC8SS7fmzeP9JuysHchsbtEQexJXPNXM9C5BHnVrdzgO9TmZg==}
+  /@graphql-tools/git-loader@8.0.22(graphql@16.10.0):
+    resolution: {integrity: sha512-O9TJqhqdouRgIAr2DeqchWq50mUN2OS1dzfrDEJ/k1Rx42gAenOuLft7QO6us90bFdK5BDzgD+5TEhE5a87O0g==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@graphql-tools/graphql-tag-pluck': 8.3.16(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/graphql-tag-pluck': 8.3.17(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       graphql: 16.10.0
       is-glob: 4.0.3
       micromatch: 4.0.8
@@ -4954,15 +5549,15 @@ packages:
       - supports-color
     dev: true
 
-  /@graphql-tools/github-loader@8.0.17(@types/node@22.13.9)(graphql@16.10.0):
-    resolution: {integrity: sha512-igUUqGGV8b5dnhNZBSTweYKIhBNby8fvNe0fv2JyQjDBysnFNAAOFAR6Bnr+8K9QbhW6aHkZInOQrOWxMQ77xg==}
+  /@graphql-tools/github-loader@8.0.18(@types/node@22.13.9)(graphql@16.10.0):
+    resolution: {integrity: sha512-st/T8W4ADVA71X2FLJLUciHT0LdYOo08DPuPKIGO0x+aRB8uxgDC8raDWWA8D928Y0OECxJi40+SNX/n07ww+g==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
       '@graphql-tools/executor-http': 1.2.8(@types/node@22.13.9)(graphql@16.10.0)
-      '@graphql-tools/graphql-tag-pluck': 8.3.16(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/graphql-tag-pluck': 8.3.17(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       '@whatwg-node/fetch': 0.10.5
       '@whatwg-node/promise-helpers': 1.2.2
       graphql: 16.10.0
@@ -4973,22 +5568,22 @@ packages:
       - supports-color
     dev: true
 
-  /@graphql-tools/graphql-file-loader@8.0.16(graphql@16.10.0):
-    resolution: {integrity: sha512-/L77iJ0CMbJMm+xgi9m8u3KCcbQ6e//MissdYXOBax2wFH3pkPXJLClSlkoN5GqRd4rGgNrenDhkkqWhVFDQHg==}
+  /@graphql-tools/graphql-file-loader@8.0.17(graphql@16.10.0):
+    resolution: {integrity: sha512-N3bjg+XSBUGydWWh7w5FUxgwjXGdXP0OPRDgyPUT1nqKZhfGZmqc0nKJEXMReXsFMwAcFF95mLtkj7gMeKMkpw==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@graphql-tools/import': 7.0.15(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/import': 7.0.16(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       globby: 11.1.0
       graphql: 16.10.0
       tslib: 2.8.1
       unixify: 1.0.0
     dev: true
 
-  /@graphql-tools/graphql-tag-pluck@8.3.16(graphql@16.10.0):
-    resolution: {integrity: sha512-zs9bhnqA+7UzSDCIsZHI5Cz9RsbpyVVVDjAUn1ToEFsrAtxvqqvfXnjFS6nZSBTJ7PQK2Jf6spzB0cBOBAGNRQ==}
+  /@graphql-tools/graphql-tag-pluck@8.3.17(graphql@16.10.0):
+    resolution: {integrity: sha512-x1ocLp4CWecQ/pwU4jP9YgcVd1fRu5VgDYiddNY4otAQk3Z44ip5Lep1unimce6xBU9FMSNgh6mKIgwmYGpUpQ==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
@@ -4998,46 +5593,46 @@ packages:
       '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.9)
       '@babel/traverse': 7.26.9
       '@babel/types': 7.26.9
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       graphql: 16.10.0
       tslib: 2.8.1
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@graphql-tools/import@7.0.15(graphql@16.10.0):
-    resolution: {integrity: sha512-g8PLNIBdhiVH52PbbpJXuWZqZb9oF2xqQaTYu31ssqlxlqAyBQJb/PNnCl3aL6Rl607Pmvvor0+lBbh26Gvn0Q==}
+  /@graphql-tools/import@7.0.16(graphql@16.10.0):
+    resolution: {integrity: sha512-YtE0qQbZEe/GlMfN6UO9DKspOndQzyVxG4kzCq2LoWLRiQsAE1z9maCT+62TDEUTHsljGeUY/ekzvSHkTH2Nqg==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       graphql: 16.10.0
       resolve-from: 5.0.0
       tslib: 2.8.1
     dev: true
 
-  /@graphql-tools/json-file-loader@8.0.15(graphql@16.10.0):
-    resolution: {integrity: sha512-e9ehBKNa6LKKqGYjq23qOIbvaYwKsVMRO8p9q1qzdF1izWVIHN9fE9dRb6y78rCwMu/tq1a0bq1KpAH5W6Sz0w==}
+  /@graphql-tools/json-file-loader@8.0.16(graphql@16.10.0):
+    resolution: {integrity: sha512-l7LVJMdsphmRcjEx7SezEXg1E24eyjQwQHn04uk41WbvhNfbB3X2fUdDsHzH8dbRXUp+wWABoAIgVOiE1qXpSw==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       globby: 11.1.0
       graphql: 16.10.0
       tslib: 2.8.1
       unixify: 1.0.0
     dev: true
 
-  /@graphql-tools/load@8.0.16(graphql@16.10.0):
-    resolution: {integrity: sha512-gD++qJvQYpbRLxBJvWEVKfb8IQZ3YyDkDFyuXVn7A/Fjoi2o6vsij/s6xfimNFyreYZL42MHjC5pWJEGQisDjg==}
+  /@graphql-tools/load@8.0.17(graphql@16.10.0):
+    resolution: {integrity: sha512-oFXpXSghoi+qdghBtkJY6VlQqR/BdLG5JVEbSSJcyh1U2cXILTPiO42zWnzjCI+5jxzFDDdBSqTgfGBL33gTLQ==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@graphql-tools/schema': 10.0.20(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/schema': 10.0.21(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       graphql: 16.10.0
       p-limit: 3.1.0
       tslib: 2.8.1
@@ -5064,13 +5659,13 @@ packages:
       tslib: 2.8.1
     dev: false
 
-  /@graphql-tools/merge@9.0.21(graphql@16.10.0):
-    resolution: {integrity: sha512-5EiVL2InZeBlsZXlXjqyNMD697QP44j/dipXEogHlZcZzWEP/JTDwx9hTfFbmrePVR8+p89gFg1tE25iEgSong==}
+  /@graphql-tools/merge@9.0.22(graphql@16.10.0):
+    resolution: {integrity: sha512-bjOs9DlTbo1Yz2UzQcJ78Dn9/pKyY2zNaoqNLfRTTSkO56QFkvqhfjQuqJcqu+V3rtaB2o0VMpWaY6JT8ZTvQA==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       graphql: 16.10.0
       tslib: 2.8.1
     dev: true
@@ -5100,8 +5695,8 @@ packages:
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@graphql-tools/url-loader': 8.0.28(@types/node@22.13.9)(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/url-loader': 8.0.29(@types/node@22.13.9)(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       '@types/js-yaml': 4.0.9
       '@whatwg-node/fetch': 0.10.5
       chalk: 4.1.2
@@ -5141,14 +5736,14 @@ packages:
       - supports-color
     dev: true
 
-  /@graphql-tools/relay-operation-optimizer@7.0.16(graphql@16.10.0):
-    resolution: {integrity: sha512-uE17qf/uhXAFTmDe5ghHC4Y9N51aCNgyPrSwFpgWxfckZvW1idi5MR5cCl8jC1w9129+XDI5WGfFXx1b2GR1Ow==}
+  /@graphql-tools/relay-operation-optimizer@7.0.17(graphql@16.10.0):
+    resolution: {integrity: sha512-zEdEIYmDsEtGhP9sl06N8aNFIo3mLrDzSlzIgfc7jKWpOY1H/a8b5MFNQd22kmaiCWlxOjDe3K0cCwWX4ygM+g==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
       '@ardatan/relay-compiler': 12.0.2(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       graphql: 16.10.0
       tslib: 2.6.3
     transitivePeerDependencies:
@@ -5168,14 +5763,14 @@ packages:
       value-or-promise: 1.0.12
     dev: false
 
-  /@graphql-tools/schema@10.0.20(graphql@16.10.0):
-    resolution: {integrity: sha512-BmDqXS9gHJF2Cl1k+IOiPCYWApBU6LhqSEPc8WmAxn08HtmhKoCizwiUuWtt8SOV67yoMzC1zJFkBdm3wZX9Fw==}
+  /@graphql-tools/schema@10.0.21(graphql@16.10.0):
+    resolution: {integrity: sha512-AECSlNnD0WNxICwfJs93gYn2oHxPmztn1MYBETIQXrJJcymfD6BoUrDlYPa6F27RzRc+gbPZPHMWL26uujfKBg==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
-      '@graphql-tools/merge': 9.0.21(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/merge': 9.0.22(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       graphql: 16.10.0
       tslib: 2.8.1
     dev: true
@@ -5192,18 +5787,18 @@ packages:
       value-or-promise: 1.0.12
     dev: false
 
-  /@graphql-tools/url-loader@8.0.28(@types/node@22.13.9)(graphql@16.10.0):
-    resolution: {integrity: sha512-zeshp2c0AFKIatLAhm0BtD0Om4Wr5Cu775rFpk369CA1nA8ZQV25EZ/TIrYwoUkg+b0ERC9H5EZrB2hqTJfaxQ==}
+  /@graphql-tools/url-loader@8.0.29(@types/node@22.13.9)(graphql@16.10.0):
+    resolution: {integrity: sha512-xCWmAL20DUzb9inrnrGEAL6PP9Exg8zfM/zkPHtPGNydKGpOXRFXvDoC6DJpwdN3B9HABUjamw38vj1uN5I1Uw==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
       '@graphql-tools/executor-graphql-ws': 2.0.3(graphql@16.10.0)
       '@graphql-tools/executor-http': 1.2.8(@types/node@22.13.9)(graphql@16.10.0)
-      '@graphql-tools/executor-legacy-ws': 1.1.14(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/executor-legacy-ws': 1.1.15(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       '@graphql-tools/wrap': 10.0.31(graphql@16.10.0)
-      '@types/ws': 8.5.14
+      '@types/ws': 8.18.0
       '@whatwg-node/fetch': 0.10.5
       '@whatwg-node/promise-helpers': 1.2.2
       graphql: 16.10.0
@@ -5232,8 +5827,8 @@ packages:
       tslib: 2.8.1
     dev: false
 
-  /@graphql-tools/utils@10.8.3(graphql@16.10.0):
-    resolution: {integrity: sha512-4QCvx3SWRsbH7wnktl51mBek+zE9hsjsv796XVlJlOUdWpAghJmA3ID2P7/Vwuy7BivVNfuAKe4ucUdE1fG7vA==}
+  /@graphql-tools/utils@10.8.4(graphql@16.10.0):
+    resolution: {integrity: sha512-HpHBgcmLIE79jWk1v5Bm0Eb8MaPiwSJT/Iy5xIJ+GMe7yAKpCYrbjf7wb+UMDMkLkfEryvo3syCx8k+TMAZ9bA==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
@@ -5271,8 +5866,8 @@ packages:
       graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
     dependencies:
       '@graphql-tools/delegate': 10.2.13(graphql@16.10.0)
-      '@graphql-tools/schema': 10.0.20(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/schema': 10.0.21(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       graphql: 16.10.0
       tslib: 2.8.1
     dev: true
@@ -5832,8 +6427,8 @@ packages:
     resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==}
     dev: false
 
-  /@langchain/community@0.3.33(@browserbasehq/stagehand@1.13.1)(@ibm-cloud/watsonx-ai@1.5.1)(@langchain/core@0.3.42)(axios@1.7.9)(ibm-cloud-sdk-core@5.1.3)(openai@4.86.1)(ws@8.18.1):
-    resolution: {integrity: sha512-BtgfvyPvb/HYUWLa5YXoDVMY+8pkZvaZzwp5NSebstVKsitsjuG/pwzZ7gDQO1c8LJZlxAeYyAwwQBI87ibRRg==}
+  /@langchain/community@0.3.34(@browserbasehq/stagehand@1.13.1)(@ibm-cloud/watsonx-ai@1.5.1)(@langchain/core@0.3.42)(axios@1.7.9)(ibm-cloud-sdk-core@5.1.3)(openai@4.86.1)(ws@8.18.1):
+    resolution: {integrity: sha512-s0KVulgVIPd90s3m6XZtWrCRGQPWsY93uY62seFMmNhzcyF+I65kKnN04Nbiouthrn/YJ9HB4hW8MJAFuX6RRg==}
     engines: {node: '>=18'}
     peerDependencies:
       '@arcjet/redact': ^v1.0.0-alpha.23
@@ -6216,7 +6811,7 @@ packages:
       ibm-cloud-sdk-core: 5.1.3
       js-yaml: 4.1.0
       langchain: 0.3.19(@langchain/core@0.3.42)(axios@1.7.9)(openai@4.86.1)(ws@8.18.1)
-      langsmith: 0.3.11(openai@4.86.1)
+      langsmith: 0.3.12(openai@4.86.1)
       openai: 4.86.1(ws@8.18.1)(zod@3.24.2)
       uuid: 10.0.0
       ws: 8.18.1
@@ -6250,7 +6845,7 @@ packages:
       camelcase: 6.3.0
       decamelize: 1.2.0
       js-tiktoken: 1.0.19
-      langsmith: 0.3.11(openai@4.86.1)
+      langsmith: 0.3.12(openai@4.86.1)
       mustache: 4.2.0
       p-queue: 6.6.2
       p-retry: 4.6.2
@@ -6402,9 +6997,9 @@ packages:
     dependencies:
       '@apollo/server': 4.11.3(graphql@16.10.0)
       '@apollo/server-plugin-landing-page-graphql-playground': 4.0.0(@apollo/server@4.11.3)
-      '@nestjs/common': 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@nestjs/core': 10.4.15(@nestjs/common@10.4.15)(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
-      '@nestjs/graphql': 12.2.2(@nestjs/common@10.4.15)(@nestjs/core@10.4.15)(class-validator@0.14.1)(graphql@16.10.0)(reflect-metadata@0.2.2)
+      '@nestjs/graphql': 12.2.2(@nestjs/common@10.4.15)(@nestjs/core@10.4.15)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.10.0)(reflect-metadata@0.2.2)
       graphql: 16.10.0
       iterall: 1.3.0
       lodash.omit: 4.5.0
@@ -6418,7 +7013,7 @@ packages:
       axios: ^1.3.1
       rxjs: ^6.0.0 || ^7.0.0
     dependencies:
-      '@nestjs/common': 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       axios: 1.8.1(debug@4.4.0)
       rxjs: 7.8.2
     dev: false
@@ -6461,7 +7056,7 @@ packages:
       - webpack-cli
     dev: true
 
-  /@nestjs/common@10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2):
+  /@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2):
     resolution: {integrity: sha512-vaLg1ZgwhG29BuLDxPA9OAcIlgqzp9/N8iG0wGapyUNTf4IY4O6zAHgN6QalwLhFxq7nOI021vdRojR1oF3bqg==}
     peerDependencies:
       class-transformer: '*'
@@ -6474,6 +7069,7 @@ packages:
       class-validator:
         optional: true
     dependencies:
+      class-transformer: 0.5.1
       class-validator: 0.14.1
       iterare: 1.2.1
       reflect-metadata: 0.2.2
@@ -6487,7 +7083,7 @@ packages:
       '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0
       rxjs: ^7.1.0
     dependencies:
-      '@nestjs/common': 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       dotenv: 16.4.5
       dotenv-expand: 10.0.0
       lodash: 4.17.21
@@ -6512,7 +7108,7 @@ packages:
       '@nestjs/websockets':
         optional: true
     dependencies:
-      '@nestjs/common': 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@nestjs/platform-express': 10.4.15(@nestjs/common@10.4.15)(@nestjs/core@10.4.15)
       '@nuxtjs/opencollective': 0.3.2
       fast-safe-stringify: 2.1.1
@@ -6525,7 +7121,7 @@ packages:
     transitivePeerDependencies:
       - encoding
 
-  /@nestjs/graphql@12.2.2(@nestjs/common@10.4.15)(@nestjs/core@10.4.15)(class-validator@0.14.1)(graphql@16.10.0)(reflect-metadata@0.2.2):
+  /@nestjs/graphql@12.2.2(@nestjs/common@10.4.15)(@nestjs/core@10.4.15)(class-transformer@0.5.1)(class-validator@0.14.1)(graphql@16.10.0)(reflect-metadata@0.2.2):
     resolution: {integrity: sha512-lUDy/1uqbRA1kBKpXcmY0aHhcPbfeG52Wg5+9Jzd1d57dwSjCAmuO+mWy5jz9ugopVCZeK0S/kdAMvA+r9fNdA==}
     peerDependencies:
       '@apollo/subgraph': ^2.0.0
@@ -6549,10 +7145,11 @@ packages:
       '@graphql-tools/merge': 9.0.11(graphql@16.10.0)
       '@graphql-tools/schema': 10.0.10(graphql@16.10.0)
       '@graphql-tools/utils': 10.6.1(graphql@16.10.0)
-      '@nestjs/common': 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@nestjs/core': 10.4.15(@nestjs/common@10.4.15)(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
-      '@nestjs/mapped-types': 2.0.6(@nestjs/common@10.4.15)(class-validator@0.14.1)(reflect-metadata@0.2.2)
+      '@nestjs/mapped-types': 2.0.6(@nestjs/common@10.4.15)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)
       chokidar: 4.0.1
+      class-transformer: 0.5.1
       class-validator: 0.14.1
       fast-glob: 3.3.2
       graphql: 16.10.0
@@ -6575,12 +7172,12 @@ packages:
     peerDependencies:
       '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0
     dependencies:
-      '@nestjs/common': 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@types/jsonwebtoken': 9.0.5
       jsonwebtoken: 9.0.2
     dev: false
 
-  /@nestjs/mapped-types@2.0.6(@nestjs/common@10.4.15)(class-validator@0.14.1)(reflect-metadata@0.2.2):
+  /@nestjs/mapped-types@2.0.6(@nestjs/common@10.4.15)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2):
     resolution: {integrity: sha512-84ze+CPfp1OWdpRi1/lOu59hOhTz38eVzJvRKrg9ykRFwDz+XleKfMsG0gUqNZYFa6v53XYzeD+xItt8uDW7NQ==}
     peerDependencies:
       '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0
@@ -6593,7 +7190,8 @@ packages:
       class-validator:
         optional: true
     dependencies:
-      '@nestjs/common': 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      class-transformer: 0.5.1
       class-validator: 0.14.1
       reflect-metadata: 0.2.2
     dev: false
@@ -6604,7 +7202,7 @@ packages:
       '@nestjs/common': ^10.0.0
       '@nestjs/core': ^10.0.0
     dependencies:
-      '@nestjs/common': 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@nestjs/core': 10.4.15(@nestjs/common@10.4.15)(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       body-parser: 1.20.3
       cors: 2.8.5
@@ -6657,7 +7255,7 @@ packages:
       '@nestjs/platform-express':
         optional: true
     dependencies:
-      '@nestjs/common': 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@nestjs/core': 10.4.15(@nestjs/common@10.4.15)(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@nestjs/platform-express': 10.4.15(@nestjs/common@10.4.15)(@nestjs/core@10.4.15)
       tslib: 2.8.1
@@ -6672,7 +7270,7 @@ packages:
       rxjs: ^7.2.0
       typeorm: ^0.3.0
     dependencies:
-      '@nestjs/common': 10.4.15(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@nestjs/core': 10.4.15(@nestjs/common@10.4.15)(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       reflect-metadata: 0.2.2
       rxjs: 7.8.2
@@ -8268,6 +8866,496 @@ packages:
       micromark-util-symbol: 1.1.0
     dev: false
 
+  /@smithy/abort-controller@4.0.1:
+    resolution: {integrity: sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/chunked-blob-reader-native@4.0.0:
+    resolution: {integrity: sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/util-base64': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/chunked-blob-reader@5.0.0:
+    resolution: {integrity: sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/config-resolver@4.0.1:
+    resolution: {integrity: sha512-Igfg8lKu3dRVkTSEm98QpZUvKEOa71jDX4vKRcvJVyRc3UgN3j7vFMf0s7xLQhYmKa8kyJGQgUJDOV5V3neVlQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/types': 4.1.0
+      '@smithy/util-config-provider': 4.0.0
+      '@smithy/util-middleware': 4.0.1
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/core@3.1.5:
+    resolution: {integrity: sha512-HLclGWPkCsekQgsyzxLhCQLa8THWXtB5PxyYN+2O6nkyLt550KQKTlbV2D1/j5dNIQapAZM1+qFnpBFxZQkgCA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/middleware-serde': 4.0.2
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/types': 4.1.0
+      '@smithy/util-body-length-browser': 4.0.0
+      '@smithy/util-middleware': 4.0.1
+      '@smithy/util-stream': 4.1.2
+      '@smithy/util-utf8': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/credential-provider-imds@4.0.1:
+    resolution: {integrity: sha512-l/qdInaDq1Zpznpmev/+52QomsJNZ3JkTl5yrTl02V6NBgJOQ4LY0SFw/8zsMwj3tLe8vqiIuwF6nxaEwgf6mg==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/property-provider': 4.0.1
+      '@smithy/types': 4.1.0
+      '@smithy/url-parser': 4.0.1
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/eventstream-codec@4.0.1:
+    resolution: {integrity: sha512-Q2bCAAR6zXNVtJgifsU16ZjKGqdw/DyecKNgIgi7dlqw04fqDu0mnq+JmGphqheypVc64CYq3azSuCpAdFk2+A==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@aws-crypto/crc32': 5.2.0
+      '@smithy/types': 4.1.0
+      '@smithy/util-hex-encoding': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/eventstream-serde-browser@4.0.1:
+    resolution: {integrity: sha512-HbIybmz5rhNg+zxKiyVAnvdM3vkzjE6ccrJ620iPL8IXcJEntd3hnBl+ktMwIy12Te/kyrSbUb8UCdnUT4QEdA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/eventstream-serde-universal': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/eventstream-serde-config-resolver@4.0.1:
+    resolution: {integrity: sha512-lSipaiq3rmHguHa3QFF4YcCM3VJOrY9oq2sow3qlhFY+nBSTF/nrO82MUQRPrxHQXA58J5G1UnU2WuJfi465BA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/eventstream-serde-node@4.0.1:
+    resolution: {integrity: sha512-o4CoOI6oYGYJ4zXo34U8X9szDe3oGjmHgsMGiZM0j4vtNoT+h80TLnkUcrLZR3+E6HIxqW+G+9WHAVfl0GXK0Q==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/eventstream-serde-universal': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/eventstream-serde-universal@4.0.1:
+    resolution: {integrity: sha512-Z94uZp0tGJuxds3iEAZBqGU2QiaBHP4YytLUjwZWx+oUeohCsLyUm33yp4MMBmhkuPqSbQCXq5hDet6JGUgHWA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/eventstream-codec': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/fetch-http-handler@5.0.1:
+    resolution: {integrity: sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/querystring-builder': 4.0.1
+      '@smithy/types': 4.1.0
+      '@smithy/util-base64': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/hash-blob-browser@4.0.1:
+    resolution: {integrity: sha512-rkFIrQOKZGS6i1D3gKJ8skJ0RlXqDvb1IyAphksaFOMzkn3v3I1eJ8m7OkLj0jf1McP63rcCEoLlkAn/HjcTRw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/chunked-blob-reader': 5.0.0
+      '@smithy/chunked-blob-reader-native': 4.0.0
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/hash-node@4.0.1:
+    resolution: {integrity: sha512-TJ6oZS+3r2Xu4emVse1YPB3Dq3d8RkZDKcPr71Nj/lJsdAP1c7oFzYqEn1IBc915TsgLl2xIJNuxCz+gLbLE0w==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      '@smithy/util-buffer-from': 4.0.0
+      '@smithy/util-utf8': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/hash-stream-node@4.0.1:
+    resolution: {integrity: sha512-U1rAE1fxmReCIr6D2o/4ROqAQX+GffZpyMt3d7njtGDr2pUNmAKRWa49gsNVhCh2vVAuf3wXzWwNr2YN8PAXIw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      '@smithy/util-utf8': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/invalid-dependency@4.0.1:
+    resolution: {integrity: sha512-gdudFPf4QRQ5pzj7HEnu6FhKRi61BfH/Gk5Yf6O0KiSbr1LlVhgjThcvjdu658VE6Nve8vaIWB8/fodmS1rBPQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/is-array-buffer@2.2.0:
+    resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/is-array-buffer@4.0.0:
+    resolution: {integrity: sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/md5-js@4.0.1:
+    resolution: {integrity: sha512-HLZ647L27APi6zXkZlzSFZIjpo8po45YiyjMGJZM3gyDY8n7dPGdmxIIljLm4gPt/7rRvutLTTkYJpZVfG5r+A==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      '@smithy/util-utf8': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/middleware-content-length@4.0.1:
+    resolution: {integrity: sha512-OGXo7w5EkB5pPiac7KNzVtfCW2vKBTZNuCctn++TTSOMpe6RZO/n6WEC1AxJINn3+vWLKW49uad3lo/u0WJ9oQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/middleware-endpoint@4.0.6:
+    resolution: {integrity: sha512-ftpmkTHIFqgaFugcjzLZv3kzPEFsBFSnq1JsIkr2mwFzCraZVhQk2gqN51OOeRxqhbPTkRFj39Qd2V91E/mQxg==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/core': 3.1.5
+      '@smithy/middleware-serde': 4.0.2
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/shared-ini-file-loader': 4.0.1
+      '@smithy/types': 4.1.0
+      '@smithy/url-parser': 4.0.1
+      '@smithy/util-middleware': 4.0.1
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/middleware-retry@4.0.7:
+    resolution: {integrity: sha512-58j9XbUPLkqAcV1kHzVX/kAR16GT+j7DUZJqwzsxh1jtz7G82caZiGyyFgUvogVfNTg3TeAOIJepGc8TXF4AVQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/service-error-classification': 4.0.1
+      '@smithy/smithy-client': 4.1.6
+      '@smithy/types': 4.1.0
+      '@smithy/util-middleware': 4.0.1
+      '@smithy/util-retry': 4.0.1
+      tslib: 2.8.1
+      uuid: 9.0.1
+    dev: false
+
+  /@smithy/middleware-serde@4.0.2:
+    resolution: {integrity: sha512-Sdr5lOagCn5tt+zKsaW+U2/iwr6bI9p08wOkCp6/eL6iMbgdtc2R5Ety66rf87PeohR0ExI84Txz9GYv5ou3iQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/middleware-stack@4.0.1:
+    resolution: {integrity: sha512-dHwDmrtR/ln8UTHpaIavRSzeIk5+YZTBtLnKwDW3G2t6nAupCiQUvNzNoHBpik63fwUaJPtlnMzXbQrNFWssIA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/node-config-provider@4.0.1:
+    resolution: {integrity: sha512-8mRTjvCtVET8+rxvmzRNRR0hH2JjV0DFOmwXPrISmTIJEfnCBugpYYGAsCj8t41qd+RB5gbheSQ/6aKZCQvFLQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/property-provider': 4.0.1
+      '@smithy/shared-ini-file-loader': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/node-http-handler@4.0.3:
+    resolution: {integrity: sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/abort-controller': 4.0.1
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/querystring-builder': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/property-provider@4.0.1:
+    resolution: {integrity: sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/protocol-http@5.0.1:
+    resolution: {integrity: sha512-TE4cpj49jJNB/oHyh/cRVEgNZaoPaxd4vteJNB0yGidOCVR0jCw/hjPVsT8Q8FRmj8Bd3bFZt8Dh7xGCT+xMBQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/querystring-builder@4.0.1:
+    resolution: {integrity: sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      '@smithy/util-uri-escape': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/querystring-parser@4.0.1:
+    resolution: {integrity: sha512-Ma2XC7VS9aV77+clSFylVUnPZRindhB7BbmYiNOdr+CHt/kZNJoPP0cd3QxCnCFyPXC4eybmyE98phEHkqZ5Jw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/service-error-classification@4.0.1:
+    resolution: {integrity: sha512-3JNjBfOWpj/mYfjXJHB4Txc/7E4LVq32bwzE7m28GN79+M1f76XHflUaSUkhOriprPDzev9cX/M+dEB80DNDKA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+    dev: false
+
+  /@smithy/shared-ini-file-loader@4.0.1:
+    resolution: {integrity: sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/signature-v4@5.0.1:
+    resolution: {integrity: sha512-nCe6fQ+ppm1bQuw5iKoeJ0MJfz2os7Ic3GBjOkLOPtavbD1ONoyE3ygjBfz2ythFWm4YnRm6OxW+8p/m9uCoIA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/is-array-buffer': 4.0.0
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/types': 4.1.0
+      '@smithy/util-hex-encoding': 4.0.0
+      '@smithy/util-middleware': 4.0.1
+      '@smithy/util-uri-escape': 4.0.0
+      '@smithy/util-utf8': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/smithy-client@4.1.6:
+    resolution: {integrity: sha512-UYDolNg6h2O0L+cJjtgSyKKvEKCOa/8FHYJnBobyeoeWDmNpXjwOAtw16ezyeu1ETuuLEOZbrynK0ZY1Lx9Jbw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/core': 3.1.5
+      '@smithy/middleware-endpoint': 4.0.6
+      '@smithy/middleware-stack': 4.0.1
+      '@smithy/protocol-http': 5.0.1
+      '@smithy/types': 4.1.0
+      '@smithy/util-stream': 4.1.2
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/types@4.1.0:
+    resolution: {integrity: sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/url-parser@4.0.1:
+    resolution: {integrity: sha512-gPXcIEUtw7VlK8f/QcruNXm7q+T5hhvGu9tl63LsJPZ27exB6dtNwvh2HIi0v7JcXJ5emBxB+CJxwaLEdJfA+g==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/querystring-parser': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-base64@4.0.0:
+    resolution: {integrity: sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/util-buffer-from': 4.0.0
+      '@smithy/util-utf8': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-body-length-browser@4.0.0:
+    resolution: {integrity: sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-body-length-node@4.0.0:
+    resolution: {integrity: sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-buffer-from@2.2.0:
+    resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@smithy/is-array-buffer': 2.2.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-buffer-from@4.0.0:
+    resolution: {integrity: sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/is-array-buffer': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-config-provider@4.0.0:
+    resolution: {integrity: sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-defaults-mode-browser@4.0.7:
+    resolution: {integrity: sha512-CZgDDrYHLv0RUElOsmZtAnp1pIjwDVCSuZWOPhIOBvG36RDfX1Q9+6lS61xBf+qqvHoqRjHxgINeQz47cYFC2Q==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/property-provider': 4.0.1
+      '@smithy/smithy-client': 4.1.6
+      '@smithy/types': 4.1.0
+      bowser: 2.11.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-defaults-mode-node@4.0.7:
+    resolution: {integrity: sha512-79fQW3hnfCdrfIi1soPbK3zmooRFnLpSx3Vxi6nUlqaaQeC5dm8plt4OTNDNqEEEDkvKghZSaoti684dQFVrGQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/config-resolver': 4.0.1
+      '@smithy/credential-provider-imds': 4.0.1
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/property-provider': 4.0.1
+      '@smithy/smithy-client': 4.1.6
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-endpoints@3.0.1:
+    resolution: {integrity: sha512-zVdUENQpdtn9jbpD9SCFK4+aSiavRb9BxEtw9ZGUR1TYo6bBHbIoi7VkrFQ0/RwZlzx0wRBaRmPclj8iAoJCLA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/node-config-provider': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-hex-encoding@4.0.0:
+    resolution: {integrity: sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-middleware@4.0.1:
+    resolution: {integrity: sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-retry@4.0.1:
+    resolution: {integrity: sha512-WmRHqNVwn3kI3rKk1LsKcVgPBG6iLTBGC1iYOV3GQegwJ3E8yjzHytPt26VNzOWr1qu0xE03nK0Ug8S7T7oufw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/service-error-classification': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-stream@4.1.2:
+    resolution: {integrity: sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/fetch-http-handler': 5.0.1
+      '@smithy/node-http-handler': 4.0.3
+      '@smithy/types': 4.1.0
+      '@smithy/util-base64': 4.0.0
+      '@smithy/util-buffer-from': 4.0.0
+      '@smithy/util-hex-encoding': 4.0.0
+      '@smithy/util-utf8': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-uri-escape@4.0.0:
+    resolution: {integrity: sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-utf8@2.3.0:
+    resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
+    engines: {node: '>=14.0.0'}
+    dependencies:
+      '@smithy/util-buffer-from': 2.2.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-utf8@4.0.0:
+    resolution: {integrity: sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/util-buffer-from': 4.0.0
+      tslib: 2.8.1
+    dev: false
+
+  /@smithy/util-waiter@4.0.2:
+    resolution: {integrity: sha512-piUTHyp2Axx3p/kc2CIJkYSv0BAaheBQmbACZgQSSfWUumWNW+R1lL+H9PDBxKJkvOeEX+hKYEFiwO8xagL8AQ==}
+    engines: {node: '>=18.0.0'}
+    dependencies:
+      '@smithy/abort-controller': 4.0.1
+      '@smithy/types': 4.1.0
+      tslib: 2.8.1
+    dev: false
+
   /@sqltools/formatter@1.2.5:
     resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==}
     dev: false
@@ -8549,6 +9637,20 @@ packages:
     dependencies:
       '@types/estree': 1.0.6
 
+  /@types/apollo-upload-client@18.0.0(@types/react@18.3.18)(graphql-ws@5.16.2)(react-dom@18.3.1)(react@18.3.1)(subscriptions-transport-ws@0.11.0):
+    resolution: {integrity: sha512-cMgITNemktxasqvp6jiPj15dv84n3FTMvMoYBP1+xonDS+0l6JygIJrj2LJh85rShRzTOOkrElrAsCXXARa3KA==}
+    dependencies:
+      '@apollo/client': 3.13.1(@types/react@18.3.18)(graphql-ws@5.16.2)(graphql@16.10.0)(react-dom@18.3.1)(react@18.3.1)(subscriptions-transport-ws@0.11.0)
+      '@types/extract-files': 13.0.1
+      graphql: 16.10.0
+    transitivePeerDependencies:
+      - '@types/react'
+      - graphql-ws
+      - react
+      - react-dom
+      - subscriptions-transport-ws
+    dev: false
+
   /@types/aria-query@5.0.4:
     resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
     dev: true
@@ -8691,6 +9793,10 @@ packages:
       '@types/serve-static': 1.15.7
     dev: true
 
+  /@types/extract-files@13.0.1:
+    resolution: {integrity: sha512-/fRbzc2lAd7jDJSSnxWiUyXWjdUZZ4HbISLJzVgt1AvrdOa7U49YRPcvuCUywkmURZ7uwJOheDjx19itbQ5KvA==}
+    dev: false
+
   /@types/fs-extra@11.0.4:
     resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==}
     dependencies:
@@ -8988,8 +10094,8 @@ packages:
   /@types/validator@13.12.2:
     resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==}
 
-  /@types/ws@8.5.14:
-    resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==}
+  /@types/ws@8.18.0:
+    resolution: {integrity: sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==}
     dependencies:
       '@types/node': 20.17.23
 
@@ -9677,8 +10783,8 @@ packages:
       json-schema-traverse: 1.0.0
       require-from-string: 2.0.2
 
-  /algoliasearch-helper@3.24.1(algoliasearch@4.24.0):
-    resolution: {integrity: sha512-knYRACqLH9UpeR+WRUrBzBFR2ulGuOjI2b525k4PNeqZxeFMHJE7YcL7s6Jh12Qza0rtHqZdgHMfeuaaAkf4wA==}
+  /algoliasearch-helper@3.24.2(algoliasearch@4.24.0):
+    resolution: {integrity: sha512-vBw/INZDfyh/THbVeDy8On8lZqd2qiUAHde5N4N1ygL4SoeLqLGJ4GHneHrDAYsjikRwTTtodEP0fiXl5MxHFQ==}
     peerDependencies:
       algoliasearch: '>= 3.1 < 6'
     dependencies:
@@ -9790,6 +10896,18 @@ packages:
       normalize-path: 3.0.0
       picomatch: 2.3.1
 
+  /apollo-upload-client@18.0.1(@apollo/client@3.13.1)(graphql@16.10.0):
+    resolution: {integrity: sha512-OQvZg1rK05VNI79D658FUmMdoI2oB/KJKb6QGMa2Si25QXOaAvLMBFUEwJct7wf+19U8vk9ILhidBOU1ZWv6QA==}
+    engines: {node: ^18.15.0 || >=20.4.0}
+    peerDependencies:
+      '@apollo/client': ^3.8.0
+      graphql: 14 - 16
+    dependencies:
+      '@apollo/client': 3.13.1(@types/react@18.3.18)(graphql-ws@5.16.2)(graphql@16.10.0)(react-dom@18.3.1)(react@18.3.1)(subscriptions-transport-ws@0.11.0)
+      extract-files: 13.0.0
+      graphql: 16.10.0
+    dev: false
+
   /app-root-path@3.1.0:
     resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==}
     engines: {node: '>= 6.0.0'}
@@ -10009,7 +11127,7 @@ packages:
       postcss: ^8.1.0
     dependencies:
       browserslist: 4.24.4
-      caniuse-lite: 1.0.30001701
+      caniuse-lite: 1.0.30001702
       fraction.js: 4.3.7
       normalize-range: 0.1.2
       picocolors: 1.1.1
@@ -10324,6 +11442,10 @@ packages:
     resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
     dev: false
 
+  /bowser@2.11.0:
+    resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==}
+    dev: false
+
   /boxen@6.2.1:
     resolution: {integrity: sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==}
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -10374,8 +11496,8 @@ packages:
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
     hasBin: true
     dependencies:
-      caniuse-lite: 1.0.30001701
-      electron-to-chromium: 1.5.110
+      caniuse-lite: 1.0.30001702
+      electron-to-chromium: 1.5.112
       node-releases: 2.0.19
       update-browserslist-db: 1.1.3(browserslist@4.24.4)
 
@@ -10532,13 +11654,13 @@ packages:
     resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
     dependencies:
       browserslist: 4.24.4
-      caniuse-lite: 1.0.30001701
+      caniuse-lite: 1.0.30001702
       lodash.memoize: 4.1.2
       lodash.uniq: 4.5.0
     dev: false
 
-  /caniuse-lite@1.0.30001701:
-    resolution: {integrity: sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==}
+  /caniuse-lite@1.0.30001702:
+    resolution: {integrity: sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==}
 
   /capital-case@1.0.4:
     resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
@@ -10729,6 +11851,9 @@ packages:
     resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}
     dev: true
 
+  /class-transformer@0.5.1:
+    resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==}
+
   /class-validator@0.14.1:
     resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==}
     dependencies:
@@ -12018,8 +13143,8 @@ packages:
       jake: 10.9.2
     dev: true
 
-  /electron-to-chromium@1.5.110:
-    resolution: {integrity: sha512-/p/OvOm6AfLtQteAHTUWwf+Vhh76PlluagzQlSnxMoOJ4R6SmAScWBrVev6rExJoUhP9zudN9+lBxoYUEmC1HQ==}
+  /electron-to-chromium@1.5.112:
+    resolution: {integrity: sha512-oen93kVyqSb3l+ziUgzIOlWt/oOuy4zRmpwestMn4rhFWAoFJeFuCVte9F2fASjeZZo7l/Cif9TiyrdW4CwEMA==}
 
   /emittery@0.13.1:
     resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
@@ -12862,6 +13987,13 @@ packages:
     engines: {node: ^12.20 || >= 14.13}
     dev: true
 
+  /extract-files@13.0.0:
+    resolution: {integrity: sha512-FXD+2Tsr8Iqtm3QZy1Zmwscca7Jx3mMC5Crr+sEP1I303Jy1CYMuYCm7hRTplFNg3XdUavErkxnTzpaqdSoi6g==}
+    engines: {node: ^14.17.0 || ^16.0.0 || >= 18.0.0}
+    dependencies:
+      is-plain-obj: 4.1.0
+    dev: false
+
   /fast-content-type-parse@2.0.1:
     resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==}
     dev: false
@@ -12906,6 +14038,13 @@ packages:
   /fast-uri@3.0.6:
     resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==}
 
+  /fast-xml-parser@4.4.1:
+    resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==}
+    hasBin: true
+    dependencies:
+      strnum: 1.1.2
+    dev: false
+
   /fastembed@1.14.1:
     resolution: {integrity: sha512-Y14v+FWZwjNUpQ7mRGYu4N5yF+hZkF7zqzPWzzLbwdIEtYsHy0DSpiVJ+Fg6Oi1fQjrBKASQt0hdSMSjw1/Wtw==}
     dependencies:
@@ -13295,8 +14434,8 @@ packages:
       tslib: 2.8.1
     dev: false
 
-  /framer-motion@12.4.9(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-c+nDhfiNUwi8G4BrhrP2hjPsDHzIKRbUhDlcK7oC5kXY4QK1IrT/kuhY4BgK6h2ujDrZ8ocvFrG2X8+b1m/MkQ==}
+  /framer-motion@12.4.10(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-3Msuyjcr1Pb5hjkn4EJcRe1HumaveP0Gbv4DBMKTPKcV/1GSMkQXj+Uqgneys+9DPcZM18Hac9qY9iUEF5LZtg==}
     peerDependencies:
       '@emotion/is-prop-valid': '*'
       react: ^18.0.0 || ^19.0.0
@@ -13309,8 +14448,8 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      motion-dom: 12.4.5
-      motion-utils: 12.0.0
+      motion-dom: 12.4.10
+      motion-utils: 12.4.10
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
       tslib: 2.8.1
@@ -13667,12 +14806,12 @@ packages:
       cosmiconfig-toml-loader:
         optional: true
     dependencies:
-      '@graphql-tools/graphql-file-loader': 8.0.16(graphql@16.10.0)
-      '@graphql-tools/json-file-loader': 8.0.15(graphql@16.10.0)
-      '@graphql-tools/load': 8.0.16(graphql@16.10.0)
-      '@graphql-tools/merge': 9.0.21(graphql@16.10.0)
-      '@graphql-tools/url-loader': 8.0.28(@types/node@22.13.9)(graphql@16.10.0)
-      '@graphql-tools/utils': 10.8.3(graphql@16.10.0)
+      '@graphql-tools/graphql-file-loader': 8.0.17(graphql@16.10.0)
+      '@graphql-tools/json-file-loader': 8.0.16(graphql@16.10.0)
+      '@graphql-tools/load': 8.0.17(graphql@16.10.0)
+      '@graphql-tools/merge': 9.0.22(graphql@16.10.0)
+      '@graphql-tools/url-loader': 8.0.29(@types/node@22.13.9)(graphql@16.10.0)
+      '@graphql-tools/utils': 10.8.4(graphql@16.10.0)
       cosmiconfig: 8.3.6(typescript@5.6.3)
       graphql: 16.10.0
       jiti: 2.4.2
@@ -13718,6 +14857,16 @@ packages:
       graphql: 16.10.0
       tslib: 2.8.1
 
+  /graphql-upload-minimal@1.6.1(graphql@16.10.0):
+    resolution: {integrity: sha512-wNUf/KqA0B/OguL1k6qWa4AmAduLUAhXzovh9i14SKbpBa8HX2vc7f+fR67S0rG7fSpGdM/aivxzC329/+9xXw==}
+    engines: {node: '>=12'}
+    peerDependencies:
+      graphql: 0.13.1 - 16
+    dependencies:
+      busboy: 1.6.0
+      graphql: 16.10.0
+    dev: false
+
   /graphql-ws@5.16.0(graphql@16.10.0):
     resolution: {integrity: sha512-Ju2RCU2dQMgSKtArPbEtsK5gNLnsQyTNIo/T7cZNp96niC1x0KdJNZV0TIoilceBPQwfb5itrGl8pkFeOUMl4A==}
     engines: {node: '>=10'}
@@ -15992,7 +17141,7 @@ packages:
       js-tiktoken: 1.0.19
       js-yaml: 4.1.0
       jsonpointer: 5.0.1
-      langsmith: 0.3.11(openai@4.86.1)
+      langsmith: 0.3.12(openai@4.86.1)
       openapi-types: 12.1.3
       p-retry: 4.6.2
       uuid: 10.0.0
@@ -16005,8 +17154,8 @@ packages:
       - ws
     dev: false
 
-  /langsmith@0.3.11(openai@4.86.1):
-    resolution: {integrity: sha512-pzA7wemfMjqCiaNY3AtUkQJ7jubIBmKRTl0dMNEUz8A4ewIqCEpB2caiTeeAwVkugEylny80cDk3u16WqL25Sw==}
+  /langsmith@0.3.12(openai@4.86.1):
+    resolution: {integrity: sha512-e4qWM27hxEr8GfO6dgXrc3W8La+wxkX1zEtMhxhqS/Th2ujTt5OH7x0uXfXFDqCv9WaC3nquo1Y2s4vpYmLLtg==}
     peerDependencies:
       openai: '*'
     peerDependenciesMeta:
@@ -17257,22 +18406,22 @@ packages:
       motion-utils: 11.18.1
     dev: false
 
-  /motion-dom@12.4.5:
-    resolution: {integrity: sha512-Q2xmhuyYug1CGTo0jdsL05EQ4RhIYXlggFS/yPhQQRNzbrhjKQ1tbjThx5Plv68aX31LsUQRq4uIkuDxdO5vRQ==}
+  /motion-dom@12.4.10:
+    resolution: {integrity: sha512-ISP5u6FTceoD6qKdLupIPU/LyXBrxGox+P2e3mBbm1+pLdlBbwv01YENJr7+1WZnW5ucVKzFScYsV1eXTCG4Xg==}
     dependencies:
-      motion-utils: 12.0.0
+      motion-utils: 12.4.10
     dev: false
 
   /motion-utils@11.18.1:
     resolution: {integrity: sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==}
     dev: false
 
-  /motion-utils@12.0.0:
-    resolution: {integrity: sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA==}
+  /motion-utils@12.4.10:
+    resolution: {integrity: sha512-NPwZd94V013SwRf++jMrk2+HEBgPkeIE2RiOzhAuuQlqxMJPkKt/LXVh6Upl+iN8oarSGD2dlY5/bqgsYXDABA==}
     dev: false
 
-  /motion@12.4.9(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-lOT2+X9b3yvDEC+pAClTzLSW/D5T/zZweO+UN1lMe86WtGFQIbHU/VjEhwGREW0QryG9KECB1uK3QJo8G3NGag==}
+  /motion@12.4.10(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-AM21Lyfn7ZHO+nBuHJEA2REFgS3kUM83CLZnzM0ZY1/sVeKGkCtV4LF4O/YsQXyZ9mrUrrnTaUkKquS4eaIYjg==}
     peerDependencies:
       '@emotion/is-prop-valid': '*'
       react: ^18.0.0 || ^19.0.0
@@ -17285,7 +18434,7 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      framer-motion: 12.4.9(react-dom@18.3.1)(react@18.3.1)
+      framer-motion: 12.4.10(react-dom@18.3.1)(react@18.3.1)
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
       tslib: 2.8.1
@@ -17405,7 +18554,7 @@ packages:
       '@playwright/test': 1.50.1
       '@swc/helpers': 0.5.5
       busboy: 1.6.0
-      caniuse-lite: 1.0.30001701
+      caniuse-lite: 1.0.30001702
       graceful-fs: 4.2.11
       postcss: 8.4.31
       react: 18.3.1
@@ -19454,8 +20603,8 @@ packages:
       strip-json-comments: 2.0.1
     dev: false
 
-  /react-activity-calendar@2.7.8(react@18.3.1):
-    resolution: {integrity: sha512-lj9IIMrRAoMsXSf6wWo7AcMNXie61Y5EuNApm6rVdJswngyw8LS2Ja50yHrGBFu9GaL1HMutGIoSbr5ifEi9xw==}
+  /react-activity-calendar@2.7.9(react@18.3.1):
+    resolution: {integrity: sha512-35B65SiJ18aTEHzK6MQcvPVLFSBr95L/QATM9WaPGSli6hHPV2LlGuoOflYG/TuMlmmcUaBCHMjG6cHwuRzwXQ==}
     peerDependencies:
       react: ^18.0.0 || ^19.0.0
     dependencies:
@@ -21184,6 +22333,10 @@ packages:
     engines: {node: '>=14.16'}
     dev: false
 
+  /strnum@1.1.2:
+    resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==}
+    dev: false
+
   /strtok3@6.3.0:
     resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==}
     engines: {node: '>=10'}
@@ -22711,7 +23864,7 @@ packages:
       '@types/serve-index': 1.9.4
       '@types/serve-static': 1.15.7
       '@types/sockjs': 0.3.36
-      '@types/ws': 8.5.14
+      '@types/ws': 8.18.0
       ansi-html-community: 0.0.8
       bonjour-service: 1.3.0
       chokidar: 3.6.0

From 90ebd1f98889e6b6b4f4930d3a9138ea7593aa57 Mon Sep 17 00:00:00 2001
From: NarwhalChen <125920907+NarwhalChen@users.noreply.github.com>
Date: Tue, 4 Mar 2025 18:40:08 -0600
Subject: [PATCH 04/13] fix: cannot redirect to project page for the first time
 (#155)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Refactor**
- Improved chat session navigation for smoother transitions and a more
integrated user experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---
 frontend/src/components/sidebar.tsx | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx
index 04939509..7f5c4311 100644
--- a/frontend/src/components/sidebar.tsx
+++ b/frontend/src/components/sidebar.tsx
@@ -20,6 +20,7 @@ import {
 } from './ui/sidebar';
 import { cn } from '@/lib/utils';
 import { ProjectContext } from './chat/code-engine/project-context';
+import { useRouter } from 'next/navigation';
 
 interface SidebarProps {
   setIsModalOpen: (value: boolean) => void; // Parent setter to update collapse state
@@ -57,6 +58,7 @@ export function ChatSideBar({
     const event = new Event(EventEnum.NEW_CHAT);
     window.dispatchEvent(event);
   }, []);
+  const router = useRouter();
 
   if (loading) return <SidebarSkeleton />;
   if (error) {
@@ -151,11 +153,7 @@ export function ChatSideBar({
                         pollChatProject(chat.id).then((p) => {
                           setCurProject(p);
                         });
-                        window.history.replaceState(
-                          {},
-                          '',
-                          `/chat?id=${chat.id}`
-                        );
+                        router.push(`/chat?id=${chat.id}`);
                         setCurrentChatid(chat.id);
                       }}
                       refetchChats={onRefetch}

From f92c7888dd030d17bf975667a1c17a1bd29dfa2c Mon Sep 17 00:00:00 2001
From: NarwhalChen <125920907+NarwhalChen@users.noreply.github.com>
Date: Tue, 4 Mar 2025 20:29:54 -0600
Subject: [PATCH 05/13] =?UTF-8?q?fix:=20fixing=20bug=20of=20cannot=20runni?=
 =?UTF-8?q?ng=20docker=20container=20after=20container=20is=E2=80=A6=20(#1?=
 =?UTF-8?q?47)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

… shut down
---
 frontend/src/app/api/file/route.ts            |  2 +
 frontend/src/app/api/runProject/route.ts      | 95 ++++++++++++++++---
 frontend/src/components/chat/chat-layout.tsx  | 15 ++-
 .../chat/code-engine/code-engine.tsx          | 12 ++-
 .../components/chat/code-engine/web-view.tsx  | 88 ++++++++++++++++-
 frontend/src/components/root/root-layout.tsx  | 28 +++---
 6 files changed, 195 insertions(+), 45 deletions(-)

diff --git a/frontend/src/app/api/file/route.ts b/frontend/src/app/api/file/route.ts
index 7205aaa2..9892ac00 100644
--- a/frontend/src/app/api/file/route.ts
+++ b/frontend/src/app/api/file/route.ts
@@ -59,6 +59,8 @@ function getFileType(filePath: string): string {
   const extension = filePath.split('.').pop()?.toLowerCase() || '';
 
   const typeMap: { [key: string]: string } = {
+    //TODO: Add more file types
+    tsx: 'typescriptreact',
     txt: 'text',
     md: 'markdown',
     json: 'json',
diff --git a/frontend/src/app/api/runProject/route.ts b/frontend/src/app/api/runProject/route.ts
index 45b5d909..cb7269b1 100644
--- a/frontend/src/app/api/runProject/route.ts
+++ b/frontend/src/app/api/runProject/route.ts
@@ -128,6 +128,9 @@ async function buildAndRunDocker(
 
   return new Promise((resolve, reject) => {
     // 2. Build the Docker image
+    console.log(
+      `Starting Docker build for image: ${imageName} in directory: ${directory}`
+    );
     exec(
       `docker build -t ${imageName} ${directory}`,
       (buildErr, buildStdout, buildStderr) => {
@@ -141,19 +144,61 @@ async function buildAndRunDocker(
 
         // 3. Run the Docker container
         const runCommand = `docker run -d --name ${containerName} -l "traefik.enable=true" \
-            -l "traefik.http.routers.${subdomain}.rule=Host(\\"${domain}\\")" \
-            -l "traefik.http.services.${subdomain}.loadbalancer.server.port=5173" \
-            --network=codefox_traefik_network -p ${exposedPort}:5173 \
-            -v "${directory}:/app" \
-            ${imageName}`;
+          -l "traefik.http.routers.${subdomain}.rule=Host(\\"${domain}\\")" \
+          -l "traefik.http.services.${subdomain}.loadbalancer.server.port=5173" \
+          --network=codefox_traefik_network -p ${exposedPort}:5173 \
+          -v "${directory}:/app" \
+          ${imageName}`;
 
-        console.log(runCommand);
+        console.log(`Executing run command: ${runCommand}`);
 
         exec(runCommand, (runErr, runStdout, runStderr) => {
           if (runErr) {
             // If the container name already exists
+            console.error(`Error during Docker run: ${runStderr}`);
             if (runStderr.includes('Conflict. The container name')) {
-              resolve({ domain, containerId: containerName });
+              console.log(
+                `Container name conflict detected. Removing existing container ${containerName}.`
+              );
+              // Remove the existing container
+              exec(
+                `docker rm -f ${containerName}`,
+                (removeErr, removeStdout, removeStderr) => {
+                  if (removeErr) {
+                    console.error(
+                      `Error removing existing container: ${removeStderr}`
+                    );
+                    return reject(removeErr);
+                  }
+                  console.log(
+                    `Existing container ${containerName} removed. Retrying to run the container.`
+                  );
+
+                  // Retry running the Docker container
+                  exec(
+                    runCommand,
+                    (retryRunErr, retryRunStdout, retryRunStderr) => {
+                      if (retryRunErr) {
+                        console.error(
+                          `Error during Docker run: ${retryRunStderr}`
+                        );
+                        return reject(retryRunErr);
+                      }
+
+                      const containerActualId = retryRunStdout.trim();
+                      runningContainers.set(projectPath, {
+                        domain,
+                        containerId: containerActualId,
+                      });
+
+                      console.log(
+                        `Container ${containerName} is now running at http://${domain}`
+                      );
+                      resolve({ domain, containerId: containerActualId });
+                    }
+                  );
+                }
+              );
               return;
             }
             console.error(`Error during Docker run: ${runStderr}`);
@@ -169,7 +214,6 @@ async function buildAndRunDocker(
           console.log(
             `Container ${containerName} is now running at http://${domain}`
           );
-
           resolve({ domain, containerId: containerActualId });
         });
       }
@@ -204,11 +248,38 @@ export async function GET(req: Request) {
   // Check if a container is already running
   const existingContainer = runningContainers.get(projectPath);
   if (existingContainer) {
-    return NextResponse.json({
-      message: 'Docker container already running',
-      domain: existingContainer.domain,
-      containerId: existingContainer.containerId,
+    // Check if the container is running
+    const containerStatus = await new Promise<string>((resolve) => {
+      exec(
+        `docker inspect -f "{{.State.Running}}" ${existingContainer.containerId}`,
+        (err, stdout) => {
+          if (err) {
+            resolve('not found');
+          } else {
+            resolve(stdout.trim());
+          }
+        }
+      );
     });
+
+    if (containerStatus === 'true') {
+      return NextResponse.json({
+        message: 'Docker container already running',
+        domain: existingContainer.domain,
+        containerId: existingContainer.containerId,
+      });
+    } else {
+      // Remove the existing container if it's not running
+      exec(`docker rm -f ${existingContainer.containerId}`, (removeErr) => {
+        if (removeErr) {
+          console.error(`Error removing existing container: ${removeErr}`);
+        } else {
+          console.log(
+            `Removed existing container: ${existingContainer.containerId}`
+          );
+        }
+      });
+    }
   }
 
   // Prevent duplicate builds
diff --git a/frontend/src/components/chat/chat-layout.tsx b/frontend/src/components/chat/chat-layout.tsx
index 55663614..0f13d951 100644
--- a/frontend/src/components/chat/chat-layout.tsx
+++ b/frontend/src/components/chat/chat-layout.tsx
@@ -5,7 +5,6 @@ import ProjectModal from '@/components/chat/project-modal';
 import { useQuery } from '@apollo/client';
 import { GET_USER_PROJECTS } from '@/graphql/request';
 import { useAuthContext } from '@/providers/AuthProvider';
-import { ProjectProvider } from './code-engine/project-context';
 
 export default function ChatLayout({
   children,
@@ -30,14 +29,12 @@ export default function ChatLayout({
 
   return (
     <main className="flex h-[calc(100dvh)] flex-col items-center">
-      <ProjectProvider>
-        <ProjectModal
-          isOpen={isModalOpen}
-          onClose={() => setIsModalOpen(false)}
-          refetchProjects={refetch}
-        />
-        <div className="w-full h-full">{children}</div>
-      </ProjectProvider>
+      <ProjectModal
+        isOpen={isModalOpen}
+        onClose={() => setIsModalOpen(false)}
+        refetchProjects={refetch}
+      />
+      <div className="w-full h-full">{children}</div>
     </main>
   );
 }
diff --git a/frontend/src/components/chat/code-engine/code-engine.tsx b/frontend/src/components/chat/code-engine/code-engine.tsx
index 0e0ab493..64f732e7 100644
--- a/frontend/src/components/chat/code-engine/code-engine.tsx
+++ b/frontend/src/components/chat/code-engine/code-engine.tsx
@@ -82,12 +82,16 @@ export function CodeEngine({
   // Effect: Fetch file structure when projectId changes
   useEffect(() => {
     async function fetchFiles() {
-      if (!curProject?.projectPath) return;
+      if (!curProject?.projectPath) {
+        console.log('no project path found');
+        return;
+      }
 
       try {
         const response = await fetch(
           `/api/project?path=${curProject.projectPath}`
         );
+        console.log('loading file structure');
         const data = await response.json();
         setFileStructureData(data.res || {});
       } catch (error) {
@@ -270,12 +274,12 @@ export function CodeEngine({
 
   // Render the CodeEngine layout
   return (
-    <div className="rounded-lg border shadow-sm overflow-hidden h-full">
+    <div className="rounded-lg border shadow-sm overflow-scroll h-full">
       {/* Header Bar */}
       <ResponsiveToolbar isLoading={!isProjectReady} />
 
       {/* Main Content Area with Loading */}
-      <div className="relative h-[calc(100vh-48px-2rem)]">
+      <div className="relative h-[calc(100vh-48px-4rem)]">
         <AnimatePresence>
           {!isProjectReady && (
             <motion.div
@@ -311,7 +315,7 @@ export function CodeEngine({
                 <Editor
                   height="100%"
                   width="100%"
-                  defaultLanguage="typescript"
+                  defaultLanguage="typescriptreact"
                   value={newCode}
                   language={type}
                   loading={isLoading}
diff --git a/frontend/src/components/chat/code-engine/web-view.tsx b/frontend/src/components/chat/code-engine/web-view.tsx
index 91562ed9..86d3d890 100644
--- a/frontend/src/components/chat/code-engine/web-view.tsx
+++ b/frontend/src/components/chat/code-engine/web-view.tsx
@@ -7,6 +7,9 @@ import {
   ChevronRight,
   Maximize,
   ExternalLink,
+  RefreshCcw,
+  ZoomIn,
+  ZoomOut,
 } from 'lucide-react';
 
 export default function WebPreview() {
@@ -15,6 +18,7 @@ export default function WebPreview() {
   const [displayPath, setDisplayPath] = useState('/');
   const [history, setHistory] = useState<string[]>(['/']);
   const [currentIndex, setCurrentIndex] = useState(0);
+  const [scale, setScale] = useState(0.7);
   const iframeRef = useRef(null);
   const containerRef = useRef<{ projectPath: string; domain: string } | null>(
     null
@@ -49,13 +53,34 @@ export default function WebPreview() {
         );
         const json = await response.json();
 
-        await new Promise((resolve) => setTimeout(resolve, 200));
+        await new Promise((resolve) => setTimeout(resolve, 100));
 
         containerRef.current = {
           projectPath,
           domain: json.domain,
         };
-        setBaseUrl(`http://${json.domain}`);
+
+        const checkUrlStatus = async (url: string) => {
+          let status = 0;
+          while (status !== 200) {
+            try {
+              const res = await fetch(url, { method: 'HEAD' });
+              status = res.status;
+              if (status !== 200) {
+                console.log(`URL status: ${status}. Retrying...`);
+                await new Promise((resolve) => setTimeout(resolve, 1000));
+              }
+            } catch (err) {
+              console.error('Error checking URL status:', err);
+              await new Promise((resolve) => setTimeout(resolve, 1000));
+            }
+          }
+        };
+
+        const baseUrl = `http://${json.domain}`;
+        await checkUrlStatus(baseUrl);
+
+        setBaseUrl(baseUrl);
         setDisplayPath('/');
       } catch (error) {
         console.error('fetching url error:', error);
@@ -109,6 +134,25 @@ export default function WebPreview() {
       setDisplayPath(history[currentIndex + 1]);
     }
   };
+  const reloadIframe = () => {
+    const iframe = document.getElementById('myIframe') as HTMLIFrameElement;
+    if (iframe) {
+      const src = iframe.src;
+      iframe.src = 'about:blank';
+      setTimeout(() => {
+        iframe.src = src;
+        setScale(0.7);
+      }, 50);
+    }
+  };
+
+  const zoomIn = () => {
+    setScale((prevScale) => Math.min(prevScale + 0.1, 2)); // 最大缩放比例为 2
+  };
+
+  const zoomOut = () => {
+    setScale((prevScale) => Math.max(prevScale - 0.1, 0.5)); // 最小缩放比例为 0.5
+  };
 
   return (
     <div className="flex flex-col w-full h-full">
@@ -119,7 +163,7 @@ export default function WebPreview() {
           <Button
             variant="ghost"
             size="icon"
-            className="h-8 w-8"
+            className="h-6 w-6"
             onClick={goBack}
             disabled={!baseUrl || currentIndex === 0}
           >
@@ -128,12 +172,20 @@ export default function WebPreview() {
           <Button
             variant="ghost"
             size="icon"
-            className="h-8 w-8"
+            className="h-6 w-6"
             onClick={goForward}
             disabled={!baseUrl || currentIndex >= history.length - 1}
           >
             <ChevronRight className="h-4 w-4" />
           </Button>
+          <Button
+            variant="ghost"
+            size="icon"
+            className="h-6 w-6"
+            onClick={reloadIframe}
+          >
+            <RefreshCcw />
+          </Button>
         </div>
 
         {/* URL Input */}
@@ -150,6 +202,24 @@ export default function WebPreview() {
 
         {/* Actions */}
         <div className="flex items-center gap-1">
+          <Button
+            variant="ghost"
+            size="icon"
+            onClick={zoomOut}
+            className="h-8 w-8"
+            disabled={!baseUrl}
+          >
+            <ZoomOut className="h-4 w-4" />
+          </Button>
+          <Button
+            variant="ghost"
+            size="icon"
+            onClick={zoomIn}
+            className="h-8 w-8"
+            disabled={!baseUrl}
+          >
+            <ZoomIn className="h-4 w-4" />
+          </Button>
           <Button
             variant="ghost"
             size="icon"
@@ -175,9 +245,17 @@ export default function WebPreview() {
       <div className="relative flex-1 w-full h-full">
         {baseUrl ? (
           <iframe
+            id="myIframe"
             ref={iframeRef}
             src={`${baseUrl}${displayPath}`}
-            className="absolute inset-0 w-full h-full border-none bg-background"
+            className="absolute inset-0 w-full h-80% border-none bg-background"
+            style={{
+              transform: `scale(${scale})`,
+              transformOrigin: 'top left',
+              width: `calc(100% / ${scale})`,
+              height: `calc(100% / ${scale})`,
+              border: 'none',
+            }}
           />
         ) : (
           <div className="absolute inset-0 w-full h-full flex items-center justify-center bg-background">
diff --git a/frontend/src/components/root/root-layout.tsx b/frontend/src/components/root/root-layout.tsx
index 3074b7e1..7fc8ff05 100644
--- a/frontend/src/components/root/root-layout.tsx
+++ b/frontend/src/components/root/root-layout.tsx
@@ -42,21 +42,19 @@ export default function RootLayout({ children }: RootLayoutProps) {
             className="fixed left-0 top-0 h-full z-50"
             style={{ width: isCollapsed ? '80px' : '250px' }}
           >
-            <ProjectProvider>
-              <ChatSideBar
-                setIsModalOpen={() => {}}
-                isCollapsed={isCollapsed}
-                setIsCollapsed={setIsCollapsed}
-                isMobile={false}
-                currentChatId={''}
-                chatListUpdated={chatListUpdated}
-                setChatListUpdated={setChatListUpdated}
-                chats={chats}
-                loading={loading}
-                error={error}
-                onRefetch={refetchChats}
-              />
-            </ProjectProvider>
+            <ChatSideBar
+              setIsModalOpen={() => {}}
+              isCollapsed={isCollapsed}
+              setIsCollapsed={setIsCollapsed}
+              isMobile={false}
+              currentChatId={''}
+              chatListUpdated={chatListUpdated}
+              setChatListUpdated={setChatListUpdated}
+              chats={chats}
+              loading={loading}
+              error={error}
+              onRefetch={refetchChats}
+            />
           </motion.div>
         )}
 

From af3af8ee065b115645fe6d8de3ae2c5fa1401a60 Mon Sep 17 00:00:00 2001
From: ZHallen122 <106571949+ZHallen122@users.noreply.github.com>
Date: Tue, 4 Mar 2025 21:34:44 -0500
Subject: [PATCH 06/13] feat: implement SMTP  email service, adding env also
 adding email verification (#153)

This creates backend email service support.
1, send verification email
2, confirm token for User email
3, resend email
4, template for verification email

Frontend:
1, register to tell user check email
2, after user click confirm email link. Frontend should send request to
backend to do check

Vedio:
https://jam.dev/c/1e63139a-e24f-4baf-968c-a9d2e771fef0

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a robust email verification workflow that requires users to
confirm their email addresses upon registration.
- Added the ability to resend confirmation emails and a dedicated
confirmation page that provides real-time success or error feedback.
- **Chores**
- Expanded configuration options to support customizable email settings
via environment variables.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
---
 backend/.env.example                         |   11 +
 backend/package.json                         |    2 +
 backend/src/app.module.ts                    |    2 +
 backend/src/auth/auth.module.ts              |    2 +
 backend/src/auth/auth.resolver.ts            |   16 +
 backend/src/auth/auth.service.ts             |  143 +-
 backend/src/mail/mail.module.ts              |   47 +
 backend/src/mail/mail.service.ts             |   59 +
 backend/src/mail/templates/confirmation.hbs  |   26 +
 backend/src/mail/templates/passwordReset.hbs |   41 +
 backend/src/user/dto/resend-email.input.ts   |   11 +
 backend/src/user/user.model.ts               |    9 +
 backend/src/user/user.module.ts              |    9 +-
 backend/src/user/user.resolver.ts            |   16 +
 frontend/src/app/auth/confirm/page.tsx       |  102 ++
 frontend/src/components/sign-up-modal.tsx    |  264 +++-
 frontend/src/graphql/mutations/auth.ts       |   19 +
 pnpm-lock.yaml                               | 1473 +++++++++++++++++-
 18 files changed, 2149 insertions(+), 103 deletions(-)
 create mode 100644 backend/src/mail/mail.module.ts
 create mode 100644 backend/src/mail/mail.service.ts
 create mode 100644 backend/src/mail/templates/confirmation.hbs
 create mode 100644 backend/src/mail/templates/passwordReset.hbs
 create mode 100644 backend/src/user/dto/resend-email.input.ts
 create mode 100644 frontend/src/app/auth/confirm/page.tsx

diff --git a/backend/.env.example b/backend/.env.example
index 7f8afe80..c3c50c60 100644
--- a/backend/.env.example
+++ b/backend/.env.example
@@ -21,3 +21,14 @@ S3_ENDPOINT="https://<account_id>.r2.cloudflarestorage.com"    # Cloudflare R2 e
 S3_ACCOUNT_ID="your_cloudflare_account_id"    # Your Cloudflare account ID
 S3_PUBLIC_URL="https://pub-xxx.r2.dev"        # Your R2 public bucket URL
 
+# mail
+# Set to false to disable all email functionality
+MAIL_ENABLED=false
+
+MAIL_HOST=smtp.example.com
+MAIL_USER=user@example.com
+MAIL_PASSWORD=topsecret
+MAIL_FROM=noreply@example.com
+MAIL_PORT=587
+MAIL_DOMAIN=your_net
+
diff --git a/backend/package.json b/backend/package.json
index d084f34d..aa09d147 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -31,6 +31,7 @@
     "@aws-sdk/client-s3": "^3.758.0",
     "@huggingface/hub": "latest",
     "@huggingface/transformers": "latest",
+    "@nestjs-modules/mailer": "^2.0.2",
     "@nestjs/apollo": "^12.2.0",
     "@nestjs/axios": "^3.0.3",
     "@nestjs/common": "^10.0.0",
@@ -59,6 +60,7 @@
     "graphql-ws": "^5.16.0",
     "lodash": "^4.17.21",
     "markdown-to-txt": "^2.0.1",
+    "nodemailer": "^6.10.0",
     "normalize-path": "^3.0.0",
     "openai": "^4.77.0",
     "p-queue-es5": "^6.0.2",
diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts
index 041abc60..8a5c4a26 100644
--- a/backend/src/app.module.ts
+++ b/backend/src/app.module.ts
@@ -15,6 +15,7 @@ import { AppResolver } from './app.resolver';
 import { APP_INTERCEPTOR } from '@nestjs/core';
 import { LoggingInterceptor } from 'src/interceptor/LoggingInterceptor';
 import { PromptToolModule } from './prompt-tool/prompt-tool.module';
+import { MailModule } from './mail/mail.module';
 
 // TODO(Sma1lboy): move to a separate file
 function isProduction(): boolean {
@@ -47,6 +48,7 @@ function isProduction(): boolean {
     TokenModule,
     ChatModule,
     PromptToolModule,
+    MailModule,
     TypeOrmModule.forFeature([User]),
   ],
   providers: [
diff --git a/backend/src/auth/auth.module.ts b/backend/src/auth/auth.module.ts
index 91ea39b8..1065fcf5 100644
--- a/backend/src/auth/auth.module.ts
+++ b/backend/src/auth/auth.module.ts
@@ -9,6 +9,7 @@ import { User } from 'src/user/user.model';
 import { AuthResolver } from './auth.resolver';
 import { RefreshToken } from './refresh-token/refresh-token.model';
 import { JwtCacheModule } from 'src/jwt-cache/jwt-cache.module';
+import { MailModule } from 'src/mail/mail.module';
 
 @Module({
   imports: [
@@ -23,6 +24,7 @@ import { JwtCacheModule } from 'src/jwt-cache/jwt-cache.module';
       inject: [ConfigService],
     }),
     JwtCacheModule,
+    MailModule,
   ],
   providers: [AuthService, AuthResolver],
   exports: [AuthService, JwtModule],
diff --git a/backend/src/auth/auth.resolver.ts b/backend/src/auth/auth.resolver.ts
index b0d3946e..316f2042 100644
--- a/backend/src/auth/auth.resolver.ts
+++ b/backend/src/auth/auth.resolver.ts
@@ -18,6 +18,15 @@ export class RefreshTokenResponse {
   refreshToken: string;
 }
 
+@ObjectType()
+export class EmailConfirmationResponse {
+  @Field()
+  message: string;
+
+  @Field({ nullable: true })
+  success?: boolean;
+}
+
 @Resolver()
 export class AuthResolver {
   constructor(private readonly authService: AuthService) {}
@@ -33,4 +42,11 @@ export class AuthResolver {
   ): Promise<RefreshTokenResponse> {
     return this.authService.refreshToken(refreshToken);
   }
+
+  @Mutation(() => EmailConfirmationResponse)
+  async confirmEmail(
+    @Args('token') token: string,
+  ): Promise<EmailConfirmationResponse> {
+    return this.authService.confirmEmail(token);
+  }
 }
diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts
index 8c94a22c..413261ad 100644
--- a/backend/src/auth/auth.service.ts
+++ b/backend/src/auth/auth.service.ts
@@ -19,23 +19,127 @@ import { Role } from './role/role.model';
 import { RefreshToken } from './refresh-token/refresh-token.model';
 import { randomUUID } from 'crypto';
 import { compare, hash } from 'bcrypt';
-import { RefreshTokenResponse } from './auth.resolver';
+import {
+  EmailConfirmationResponse,
+  RefreshTokenResponse,
+} from './auth.resolver';
+import { MailService } from 'src/mail/mail.service';
 
 @Injectable()
 export class AuthService {
+  private readonly isMailEnabled: boolean;
+
   constructor(
     @InjectRepository(User)
     private userRepository: Repository<User>,
     private jwtService: JwtService,
     private jwtCacheService: JwtCacheService,
     private configService: ConfigService,
+    private mailService: MailService,
     @InjectRepository(Menu)
     private menuRepository: Repository<Menu>,
     @InjectRepository(Role)
     private roleRepository: Repository<Role>,
     @InjectRepository(RefreshToken)
     private refreshTokenRepository: Repository<RefreshToken>,
-  ) {}
+  ) {
+    // Read the MAIL_ENABLED environment variable, default to 'true'
+    this.isMailEnabled =
+      this.configService.get<string>('MAIL_ENABLED', 'true').toLowerCase() ===
+      'true';
+  }
+
+  async confirmEmail(token: string): Promise<EmailConfirmationResponse> {
+    try {
+      const payload = await this.jwtService.verifyAsync(token);
+
+      // Check if payload has the required email field
+      if (!payload || !payload.email) {
+        return {
+          message: 'Invalid token format',
+          success: false,
+        };
+      }
+
+      // Find user and update
+      const user = await this.userRepository.findOne({
+        where: { email: payload.email },
+      });
+
+      if (user && !user.isEmailConfirmed) {
+        user.isEmailConfirmed = true;
+        await this.userRepository.save(user);
+
+        return {
+          message: 'Email confirmed successfully!',
+          success: true,
+        };
+      }
+
+      return {
+        message: 'Email already confirmed or user not found.',
+        success: false,
+      };
+    } catch (error) {
+      return {
+        message: 'Invalid or expired token',
+        success: false,
+      };
+    }
+  }
+
+  async sendVerificationEmail(user: User): Promise<EmailConfirmationResponse> {
+    // Generate confirmation token
+    const verifyToken = this.jwtService.sign(
+      { email: user.email },
+      { expiresIn: '30m' },
+    );
+
+    // Send confirmation email
+    await this.mailService.sendConfirmationEmail(user.email, verifyToken);
+
+    // update user last time send email time
+    user.lastEmailSendTime = new Date();
+    await this.userRepository.save(user);
+
+    return {
+      message: 'Verification email sent successfully!',
+      success: true,
+    };
+  }
+
+  async resendVerificationEmail(email: string) {
+    const user = await this.userRepository.findOne({
+      where: { email },
+    });
+
+    if (!user) {
+      throw new Error('User not found');
+    }
+
+    if (user.isEmailConfirmed) {
+      return { message: 'Email already confirmed!' };
+    }
+
+    // Check if a cooldown period has passed (e.g., 1 minute)
+    const cooldownPeriod = 1 * 60 * 1000; // 1 minute in milliseconds
+    if (
+      user.lastEmailSendTime &&
+      new Date().getTime() - user.lastEmailSendTime.getTime() < cooldownPeriod
+    ) {
+      const timeLeft = Math.ceil(
+        (cooldownPeriod -
+          (new Date().getTime() - user.lastEmailSendTime.getTime())) /
+          1000,
+      );
+      return {
+        message: `Please wait ${timeLeft} seconds before requesting another email`,
+        success: false,
+      };
+    }
+
+    return this.sendVerificationEmail(user);
+  }
 
   async register(registerUserInput: RegisterUserInput): Promise<User> {
     const { username, email, password } = registerUserInput;
@@ -50,13 +154,31 @@ export class AuthService {
     }
 
     const hashedPassword = await hash(password, 10);
-    const newUser = this.userRepository.create({
-      username,
-      email,
-      password: hashedPassword,
-    });
 
-    return this.userRepository.save(newUser);
+    let newUser;
+    if (this.isMailEnabled) {
+      newUser = this.userRepository.create({
+        username,
+        email,
+        password: hashedPassword,
+        isEmailConfirmed: false,
+      });
+    } else {
+      newUser = this.userRepository.create({
+        username,
+        email,
+        password: hashedPassword,
+        isEmailConfirmed: true,
+      });
+    }
+
+    await this.userRepository.save(newUser);
+
+    if (this.isMailEnabled) {
+      await this.sendVerificationEmail(newUser);
+    }
+
+    return newUser;
   }
 
   async login(loginUserInput: LoginUserInput): Promise<RefreshTokenResponse> {
@@ -70,6 +192,10 @@ export class AuthService {
       throw new UnauthorizedException('Invalid credentials');
     }
 
+    if (!user.isEmailConfirmed) {
+      throw new Error('Email not confirmed. Please check your inbox.');
+    }
+
     const isPasswordValid = await compare(password, user.password);
 
     if (!isPasswordValid) {
@@ -113,6 +239,7 @@ export class AuthService {
       return false;
     }
   }
+
   async logout(token: string): Promise<boolean> {
     try {
       await this.jwtService.verifyAsync(token);
diff --git a/backend/src/mail/mail.module.ts b/backend/src/mail/mail.module.ts
new file mode 100644
index 00000000..8767bfd8
--- /dev/null
+++ b/backend/src/mail/mail.module.ts
@@ -0,0 +1,47 @@
+import { MailerModule } from '@nestjs-modules/mailer';
+import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
+import { Module } from '@nestjs/common';
+import { MailService } from './mail.service';
+import { join } from 'path';
+import { ConfigModule, ConfigService } from '@nestjs/config';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { User } from 'src/user/user.model';
+import { JwtModule } from '@nestjs/jwt';
+
+@Module({
+  imports: [
+    ConfigModule,
+    TypeOrmModule.forFeature([User]),
+    MailerModule.forRootAsync({
+      // imports: [ConfigModule], // import module if not enabled globally
+      useFactory: async (config: ConfigService) => ({
+        // transport: config.get("MAIL_TRANSPORT"),
+        // or
+        transport: {
+          host: config.get('MAIL_HOST'),
+          port: config.get<number>('MAIL_PORT'),
+          secure: false,
+          auth: {
+            user: config.get('MAIL_USER'),
+            pass: config.get('MAIL_PASSWORD'),
+          },
+        },
+        defaults: {
+          from: `"Your App" <${config.get<string>('MAIL_FROM')}>`,
+        },
+        template: {
+          dir: join(__dirname, 'templates'),
+          adapter: new HandlebarsAdapter(),
+          options: {
+            strict: true,
+          },
+        },
+      }),
+      inject: [ConfigService],
+    }),
+    JwtModule,
+  ],
+  providers: [MailService],
+  exports: [MailService],
+})
+export class MailModule {}
diff --git a/backend/src/mail/mail.service.ts b/backend/src/mail/mail.service.ts
new file mode 100644
index 00000000..9b0d4dae
--- /dev/null
+++ b/backend/src/mail/mail.service.ts
@@ -0,0 +1,59 @@
+import { Injectable } from '@nestjs/common';
+import { MailerService } from '@nestjs-modules/mailer';
+import { InjectRepository } from '@nestjs/typeorm';
+import { User } from 'src/user/user.model';
+import { Repository } from 'typeorm';
+import { ConfigService } from '@nestjs/config';
+
+@Injectable()
+export class MailService {
+  constructor(
+    @InjectRepository(User)
+    private userRepository: Repository<User>,
+    private readonly mailerService: MailerService,
+    private configService: ConfigService,
+  ) {}
+
+  async sendConfirmationEmail(email: string, token: string) {
+    const confirmUrl = `https://${this.configService.get('MAIL_DOMAIN')}/auth/confirm?token=${token}`;
+
+    await this.mailerService.sendMail({
+      to: email,
+      subject: 'Confirm Your Email',
+      template: './confirmation',
+      context: { confirmUrl }, // Data for template
+    });
+  }
+
+  async sendPasswordResetEmail(user: User, token: string) {
+    const frontendUrl = this.configService.get('FRONTEND_URL');
+    const url = `${frontendUrl}/reset-password?token=${token}`;
+
+    await this.mailerService.sendMail({
+      to: user.email,
+      subject: 'Password Reset Request',
+      template: './passwordReset',
+      context: {
+        name: user.username,
+        firstName: user.username,
+        url,
+      },
+    });
+  }
+
+  // async sendConfirmationEmail(user: User, token: string) {
+  //   const frontendUrl = this.configService.get('FRONTEND_URL');
+  //   const url = `${frontendUrl}/confirm-email?token=${token}`;
+
+  //   await this.mailerService.sendMail({
+  //     to: user.email,
+  //     subject: 'Welcome! Confirm Your Email',
+  //     template: './confirmation', // This will use the confirmation.hbs template
+  //     context: { // Data to be sent to the template
+  //       name: user.username,
+  //       firstName: user.firstName || user.username,
+  //       url,
+  //     },
+  //   });
+  // }
+}
diff --git a/backend/src/mail/templates/confirmation.hbs b/backend/src/mail/templates/confirmation.hbs
new file mode 100644
index 00000000..603300b7
--- /dev/null
+++ b/backend/src/mail/templates/confirmation.hbs
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>Confirm Your Email</title>
+</head>
+<body style="font-family: Arial, sans-serif; background-color: #f4f4f4; padding: 20px; text-align: center;">
+    <div style="max-width: 600px; background-color: #ffffff; padding: 30px; border-radius: 8px; margin: auto;">
+        <h2 style="color: #333;">Welcome to CodeFox! 🎉</h2>
+        <p style="font-size: 16px; color: #555;">
+            Hi there! Thanks for signing up. Please confirm your email address to activate your account.
+        </p>
+        <p>
+            <a href="{{confirmUrl}}" style="display: inline-block; padding: 12px 24px; font-size: 16px; color: #fff; background-color: #007bff; text-decoration: none; border-radius: 5px;">
+                Confirm Email
+            </a>
+        </p>
+        <p style="font-size: 14px; color: #888;">
+            If you didn't create an account, you can ignore this email. The confirmation link will expire in 24 hours.
+        </p>
+        <p style="font-size: 14px; color: #888;">
+            Need help? Contact our support team at <a href="mailto:support@codefox.net">support@codefox.net</a>.
+        </p>
+    </div>
+</body>
+</html>
diff --git a/backend/src/mail/templates/passwordReset.hbs b/backend/src/mail/templates/passwordReset.hbs
new file mode 100644
index 00000000..e32326e2
--- /dev/null
+++ b/backend/src/mail/templates/passwordReset.hbs
@@ -0,0 +1,41 @@
+<!-- src/mail/templates/confirmation.hbs -->
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Email Confirmation</title>
+  <style>
+    body {
+      font-family: Arial, sans-serif;
+      line-height: 1.6;
+      color: #333;
+    }
+    .container {
+      max-width: 600px;
+      margin: 0 auto;
+      padding: 20px;
+    }
+    .button {
+      display: inline-block;
+      padding: 10px 20px;
+      background-color: #3498db;
+      color: white;
+      text-decoration: none;
+      border-radius: 4px;
+    }
+  </style>
+</head>
+<body>
+  <div class="container">
+    <h1>Welcome to Our App!</h1>
+    <p>Hello test user,</p>
+    <p>Thank you for registering. Please confirm your email address by clicking the button below:</p>
+    <p>
+      
+    </p>
+    <p>If you did not request this email, please ignore it.</p>
+    <p>This link will expire in 24 hours.</p>
+    <p>Regards,<br>The App Team</p>
+  </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/backend/src/user/dto/resend-email.input.ts b/backend/src/user/dto/resend-email.input.ts
new file mode 100644
index 00000000..05b9b964
--- /dev/null
+++ b/backend/src/user/dto/resend-email.input.ts
@@ -0,0 +1,11 @@
+// src/auth/dto/resend-email.input.ts
+import { InputType, Field } from '@nestjs/graphql';
+import { IsEmail, IsNotEmpty } from 'class-validator';
+
+@InputType()
+export class ResendEmailInput {
+  @Field()
+  @IsEmail()
+  @IsNotEmpty()
+  email: string;
+}
diff --git a/backend/src/user/user.model.ts b/backend/src/user/user.model.ts
index ba46b489..5cd35dfd 100644
--- a/backend/src/user/user.model.ts
+++ b/backend/src/user/user.model.ts
@@ -11,6 +11,7 @@ import {
   ManyToMany,
   JoinTable,
   OneToMany,
+  UpdateDateColumn,
 } from 'typeorm';
 
 @Entity()
@@ -32,6 +33,14 @@ export class User extends SystemBaseModel {
   @IsEmail()
   email: string;
 
+  @Field(() => Boolean)
+  @Column({ default: false })
+  isEmailConfirmed: boolean;
+
+  @Field()
+  @UpdateDateColumn({ type: 'datetime' })
+  lastEmailSendTime: Date;
+
   @Field(() => [Chat])
   @OneToMany(() => Chat, (chat) => chat.user, {
     cascade: true,
diff --git a/backend/src/user/user.module.ts b/backend/src/user/user.module.ts
index c443575c..9f01e50e 100644
--- a/backend/src/user/user.module.ts
+++ b/backend/src/user/user.module.ts
@@ -6,8 +6,15 @@ import { User } from './user.model';
 import { TypeOrmModule } from '@nestjs/typeorm';
 import { JwtModule } from '@nestjs/jwt';
 import { AuthModule } from 'src/auth/auth.module';
+import { MailModule } from 'src/mail/mail.module';
+
 @Module({
-  imports: [TypeOrmModule.forFeature([User]), JwtModule, AuthModule],
+  imports: [
+    TypeOrmModule.forFeature([User]),
+    JwtModule,
+    AuthModule,
+    MailModule,
+  ],
   providers: [UserResolver, UserService, DateScalar],
   exports: [UserService],
 })
diff --git a/backend/src/user/user.resolver.ts b/backend/src/user/user.resolver.ts
index fe6cd5d6..37617be9 100644
--- a/backend/src/user/user.resolver.ts
+++ b/backend/src/user/user.resolver.ts
@@ -16,6 +16,8 @@ import {
   GetUserIdFromToken,
 } from 'src/decorator/get-auth-token.decorator';
 import { Logger } from '@nestjs/common';
+import { EmailConfirmationResponse } from 'src/auth/auth.resolver';
+import { ResendEmailInput } from './dto/resend-email.input';
 
 @ObjectType()
 class LoginResponse {
@@ -33,6 +35,20 @@ export class UserResolver {
     private readonly authService: AuthService,
   ) {}
 
+  // @Mutation(() => EmailConfirmationResponse)
+  // async resendConfirmationEmail(
+  //   @Args('input') resendInput: ResendConfirmationInput,
+  // ): Promise<EmailConfirmationResponse> {
+  //   return this.authService.resendVerificationEmail(resendInput.email);
+  // }
+
+  @Mutation(() => EmailConfirmationResponse)
+  async resendConfirmationEmail(
+    @Args('input') input: ResendEmailInput,
+  ): Promise<EmailConfirmationResponse> {
+    return this.authService.resendVerificationEmail(input.email);
+  }
+
   @Mutation(() => User)
   async registerUser(
     @Args('input') registerUserInput: RegisterUserInput,
diff --git a/frontend/src/app/auth/confirm/page.tsx b/frontend/src/app/auth/confirm/page.tsx
new file mode 100644
index 00000000..e8c3258d
--- /dev/null
+++ b/frontend/src/app/auth/confirm/page.tsx
@@ -0,0 +1,102 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import { useMutation } from '@apollo/client';
+import { CONFIRM_EMAIL_MUTATION } from '@/graphql/mutations/auth';
+import { useRouter, useSearchParams } from 'next/navigation';
+import { BackgroundGradient } from '@/components/ui/background-gradient';
+import {
+  TextureCardHeader,
+  TextureCardTitle,
+  TextureCardContent,
+} from '@/components/ui/texture-card';
+import { AlertCircle, CheckCircle } from 'lucide-react';
+import { Button } from '@/components/ui/button';
+
+export default function ConfirmEmailPage() {
+  const searchParams = useSearchParams();
+  const router = useRouter();
+  const [status, setStatus] = useState<'loading' | 'success' | 'error'>(
+    'loading'
+  );
+  const [message, setMessage] = useState('Verifying your email...');
+
+  const [confirmEmail] = useMutation(CONFIRM_EMAIL_MUTATION, {
+    onCompleted: (data) => {
+      if (data.confirmEmail.success) {
+        setStatus('success');
+        setMessage(data.confirmEmail.message || 'Email verified successfully!');
+        // Redirect to home page after a short delay
+        setTimeout(() => {
+          router.push('/');
+        }, 3000);
+      } else {
+        setStatus('error');
+        setMessage(data.confirmEmail.message || 'Failed to verify email.');
+      }
+    },
+    onError: (error) => {
+      setStatus('error');
+      setMessage(
+        error.message || 'An error occurred while verifying your email.'
+      );
+    },
+  });
+
+  useEffect(() => {
+    const token = searchParams.get('token');
+
+    if (!token) {
+      setStatus('error');
+      setMessage('Invalid verification link. No token provided.');
+      return;
+    }
+
+    // Call the mutation to confirm the email
+    confirmEmail({
+      variables: {
+        token,
+      },
+    });
+  }, [confirmEmail, searchParams]);
+
+  return (
+    <div className="flex items-center justify-center min-h-screen p-4">
+      <BackgroundGradient className="rounded-[22px] p-4 bg-white dark:bg-zinc-900 max-w-md w-full">
+        <TextureCardHeader className="flex flex-col gap-2 items-center justify-center p-4">
+          <TextureCardTitle className="text-center text-2xl">
+            Email Verification
+          </TextureCardTitle>
+
+          <div className="mt-4 flex items-center justify-center">
+            {status === 'loading' && (
+              <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-500" />
+            )}
+            {status === 'success' && (
+              <CheckCircle className="h-16 w-16 text-green-500" />
+            )}
+            {status === 'error' && (
+              <AlertCircle className="h-16 w-16 text-red-500" />
+            )}
+          </div>
+        </TextureCardHeader>
+
+        <TextureCardContent className="text-center">
+          <p className="mb-6">{message}</p>
+
+          {status === 'success' && (
+            <p className="text-sm text-neutral-600 dark:text-neutral-400">
+              You will be redirected to the home page shortly...
+            </p>
+          )}
+
+          {status === 'error' && (
+            <Button onClick={() => router.push('/')} className="mt-4">
+              Go to Home Page
+            </Button>
+          )}
+        </TextureCardContent>
+      </BackgroundGradient>
+    </div>
+  );
+}
diff --git a/frontend/src/components/sign-up-modal.tsx b/frontend/src/components/sign-up-modal.tsx
index 1b0833f6..c7537fff 100644
--- a/frontend/src/components/sign-up-modal.tsx
+++ b/frontend/src/components/sign-up-modal.tsx
@@ -18,10 +18,14 @@ import {
   TextureSeparator,
 } from '@/components/ui/texture-card';
 import { useMutation } from '@apollo/client';
-import { REGISTER_USER } from '@/graphql/mutations/auth';
+import {
+  REGISTER_USER,
+  RESEND_CONFIRMATION_EMAIL_MUTATION,
+} from '@/graphql/mutations/auth';
 import { useRouter } from 'next/navigation';
 import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
-import { AlertCircle } from 'lucide-react';
+import { AlertCircle, CheckCircle, Mail, Clock } from 'lucide-react';
+import { useEffect } from 'react';
 
 export function SignUpModal({
   isOpen,
@@ -35,6 +39,9 @@ export function SignUpModal({
   const [email, setEmail] = useState('');
   const [password, setPassword] = useState('');
   const [errorMessage, setErrorMessage] = useState<string | null>(null);
+  const [registrationSuccess, setRegistrationSuccess] = useState(false);
+  const [resendCooldown, setResendCooldown] = useState(0);
+  const [resendMessage, setResendMessage] = useState<string | null>(null);
 
   const [registerUser, { loading }] = useMutation(REGISTER_USER, {
     onError: (error) => {
@@ -45,7 +52,7 @@ export function SignUpModal({
       }
     },
     onCompleted: () => {
-      onClose();
+      setRegistrationSuccess(true);
     },
   });
 
@@ -73,6 +80,56 @@ export function SignUpModal({
     }
   };
 
+  const [resendConfirmationEmail, { loading: resendLoading }] = useMutation(
+    RESEND_CONFIRMATION_EMAIL_MUTATION,
+    {
+      onCompleted: (data) => {
+        if (data.resendConfirmationEmail.success) {
+          setResendMessage(
+            'Verification email has been resent. Please check your inbox.'
+          );
+          setResendCooldown(60); // Start 60 second cooldown
+        } else {
+          setResendMessage(
+            data.resendConfirmationEmail.message ||
+              'Failed to resend. Please try again later.'
+          );
+        }
+      },
+      onError: (error) => {
+        setResendMessage(`Error: ${error.message}`);
+      },
+    }
+  );
+
+  useEffect(() => {
+    let timer: NodeJS.Timeout;
+    if (resendCooldown > 0) {
+      timer = setTimeout(() => {
+        setResendCooldown(resendCooldown - 1);
+      }, 1000);
+    } else if (resendCooldown === 0) {
+      // Clear the resend message once cooldown is complete
+      if (resendMessage && resendMessage.includes('has been resent')) {
+        setResendMessage(null);
+      }
+    }
+    return () => clearTimeout(timer);
+  }, [resendCooldown, resendMessage]);
+
+  const handleResendConfirmation = () => {
+    if (resendCooldown > 0) return;
+
+    setResendMessage(null);
+    resendConfirmationEmail({
+      variables: {
+        input: {
+          email,
+        },
+      },
+    });
+  };
+
   return (
     <Dialog open={isOpen} onOpenChange={onClose}>
       <DialogContent className="sm:max-w-[425px] fixed top-[50%] left-[50%] transform -translate-x-[50%] -translate-y-[50%] p-0">
@@ -85,73 +142,144 @@ export function SignUpModal({
 
         <BackgroundGradient className="rounded-[22px] p-4 bg-white dark:bg-zinc-900">
           <div className="w-full">
-            <TextureCardHeader className="flex flex-col gap-1 items-center justify-center p-4">
-              <TextureCardTitle>Create your account</TextureCardTitle>
-              <p className="text-center text-neutral-600 dark:text-neutral-400">
-                Welcome! Please fill in the details to get started.
-              </p>
-            </TextureCardHeader>
-            <TextureSeparator />
-            <TextureCardContent>
-              <form onSubmit={handleSubmit} className="space-y-2">
-                <div className="space-y-1">
-                  <Label htmlFor="name">Name</Label>
-                  <Input
-                    id="name"
-                    placeholder="Name"
-                    type="text"
-                    value={name}
-                    onChange={(e) => {
-                      setName(e.target.value);
-                      setErrorMessage(null);
-                    }}
-                    required
-                    className="w-full"
-                  />
-                </div>
-                <div className="space-y-1">
-                  <Label htmlFor="email">Email</Label>
-                  <Input
-                    id="email"
-                    placeholder="Email"
-                    type="email"
-                    value={email}
-                    onChange={(e) => {
-                      setEmail(e.target.value);
-                      setErrorMessage(null);
-                    }}
-                    required
-                    className="w-full"
-                  />
-                </div>
-                <div className="space-y-1">
-                  <Label htmlFor="password">Password</Label>
-                  <Input
-                    id="password"
-                    placeholder="Password"
-                    type="password"
-                    value={password}
-                    onChange={(e) => {
-                      setPassword(e.target.value);
-                      setErrorMessage(null);
-                    }}
-                    required
-                    className="w-full"
-                  />
-                </div>
-
-                {errorMessage && (
-                  <div className="flex items-center gap-2 text-primary-700 dark:text-primary-400 text-sm p-2 rounded-md bg-primary-50 dark:bg-zinc-800 border border-primary-200 dark:border-primary-800">
-                    <AlertCircle className="h-4 w-4" />
-                    <span>{errorMessage}</span>
+            {registrationSuccess ? (
+              <>
+                <TextureCardHeader className="flex flex-col gap-1 items-center justify-center p-4">
+                  <CheckCircle className="h-12 w-12 text-green-500 mb-2" />
+                  <TextureCardTitle>Verification Email Sent</TextureCardTitle>
+                  <p className="text-center text-neutral-600 dark:text-neutral-400">
+                    Please check your email to complete registration. We have
+                    sent a verification link to{' '}
+                    <span className="font-medium">{email}</span>.
+                  </p>
+                </TextureCardHeader>
+                <TextureSeparator />
+                <TextureCardContent className="space-y-4">
+                  <div className="flex flex-col gap-2 p-4 rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-100 dark:border-blue-800">
+                    <div className="flex items-center gap-2">
+                      <Mail className="h-5 w-5 text-blue-500" />
+                      <span className="font-medium text-blue-700 dark:text-blue-300">
+                        Email Verification Required
+                      </span>
+                    </div>
+                    <p className="text-sm text-blue-600 dark:text-blue-400">
+                      To complete your registration, please click the
+                      verification link sent to your email address.
+                    </p>
                   </div>
-                )}
 
-                <Button type="submit" className="w-full" disabled={loading}>
-                  {loading ? 'Signing up...' : 'Sign up'}
-                </Button>
-              </form>
-            </TextureCardContent>
+                  {resendMessage && (
+                    <div
+                      className={`flex items-center gap-2 text-sm p-2 rounded-md ${
+                        resendMessage.includes('has been resent')
+                          ? 'bg-green-50 dark:bg-green-900/20 border border-green-100 dark:border-green-800 text-green-700 dark:text-green-400'
+                          : 'bg-primary-50 dark:bg-zinc-800 border border-primary-200 dark:border-primary-800 text-primary-700 dark:text-primary-400'
+                      }`}
+                    >
+                      {resendMessage.includes('has been resent') ? (
+                        <CheckCircle className="h-4 w-4" />
+                      ) : (
+                        <AlertCircle className="h-4 w-4" />
+                      )}
+                      <span>{resendMessage}</span>
+                    </div>
+                  )}
+
+                  <div className="flex flex-col gap-2">
+                    <Button onClick={onClose} className="w-full">
+                      Got it
+                    </Button>
+                    <Button
+                      onClick={handleResendConfirmation}
+                      variant="outline"
+                      className="w-full"
+                      disabled={resendCooldown > 0 || resendLoading}
+                    >
+                      {resendLoading ? (
+                        'Sending...'
+                      ) : resendCooldown > 0 ? (
+                        <span className="flex items-center gap-2">
+                          <Clock className="h-4 w-4" />
+                          Resend available in {resendCooldown}s
+                        </span>
+                      ) : (
+                        'Resend verification email'
+                      )}
+                    </Button>
+                  </div>
+                </TextureCardContent>
+              </>
+            ) : (
+              <>
+                <TextureCardHeader className="flex flex-col gap-1 items-center justify-center p-4">
+                  <TextureCardTitle>Create your account</TextureCardTitle>
+                  <p className="text-center text-neutral-600 dark:text-neutral-400">
+                    Welcome! Please fill in the details to get started.
+                  </p>
+                </TextureCardHeader>
+                <TextureSeparator />
+                <TextureCardContent>
+                  <form onSubmit={handleSubmit} className="space-y-2">
+                    <div className="space-y-1">
+                      <Label htmlFor="name">Name</Label>
+                      <Input
+                        id="name"
+                        placeholder="Name"
+                        type="text"
+                        value={name}
+                        onChange={(e) => {
+                          setName(e.target.value);
+                          setErrorMessage(null);
+                        }}
+                        required
+                        className="w-full"
+                      />
+                    </div>
+                    <div className="space-y-1">
+                      <Label htmlFor="email">Email</Label>
+                      <Input
+                        id="email"
+                        placeholder="Email"
+                        type="email"
+                        value={email}
+                        onChange={(e) => {
+                          setEmail(e.target.value);
+                          setErrorMessage(null);
+                        }}
+                        required
+                        className="w-full"
+                      />
+                    </div>
+                    <div className="space-y-1">
+                      <Label htmlFor="password">Password</Label>
+                      <Input
+                        id="password"
+                        placeholder="Password"
+                        type="password"
+                        value={password}
+                        onChange={(e) => {
+                          setPassword(e.target.value);
+                          setErrorMessage(null);
+                        }}
+                        required
+                        className="w-full"
+                      />
+                    </div>
+
+                    {errorMessage && (
+                      <div className="flex items-center gap-2 text-primary-700 dark:text-primary-400 text-sm p-2 rounded-md bg-primary-50 dark:bg-zinc-800 border border-primary-200 dark:border-primary-800">
+                        <AlertCircle className="h-4 w-4" />
+                        <span>{errorMessage}</span>
+                      </div>
+                    )}
+
+                    <Button type="submit" className="w-full" disabled={loading}>
+                      {loading ? 'Signing up...' : 'Sign up'}
+                    </Button>
+                  </form>
+                </TextureCardContent>
+              </>
+            )}
           </div>
         </BackgroundGradient>
       </DialogContent>
diff --git a/frontend/src/graphql/mutations/auth.ts b/frontend/src/graphql/mutations/auth.ts
index 9c00ec93..694b26ab 100644
--- a/frontend/src/graphql/mutations/auth.ts
+++ b/frontend/src/graphql/mutations/auth.ts
@@ -18,6 +18,7 @@ export const LOGIN_USER = gql`
     }
   }
 `;
+
 export const REFRESH_TOKEN_MUTATION = gql`
   mutation RefreshToken($refreshToken: String!) {
     refreshToken(refreshToken: $refreshToken) {
@@ -26,3 +27,21 @@ export const REFRESH_TOKEN_MUTATION = gql`
     }
   }
 `;
+
+export const CONFIRM_EMAIL_MUTATION = gql`
+  mutation ConfirmEmail($token: String!) {
+    confirmEmail(token: $token) {
+      message
+      success
+    }
+  }
+`;
+
+export const RESEND_CONFIRMATION_EMAIL_MUTATION = gql`
+  mutation ResendConfirmationEmail($input: ResendEmailInput!) {
+    resendConfirmationEmail(input: $input) {
+      message
+      success
+    }
+  }
+`;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 814f243e..2b20fb9e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -45,6 +45,9 @@ importers:
       '@huggingface/transformers':
         specifier: latest
         version: 3.3.3
+      '@nestjs-modules/mailer':
+        specifier: ^2.0.2
+        version: 2.0.2(@nestjs/common@10.4.15)(@nestjs/core@10.4.15)(nodemailer@6.10.0)
       '@nestjs/apollo':
         specifier: ^12.2.0
         version: 12.2.2(@apollo/server@4.11.3)(@nestjs/common@10.4.15)(@nestjs/core@10.4.15)(@nestjs/graphql@12.2.2)(graphql@16.10.0)
@@ -129,6 +132,9 @@ importers:
       markdown-to-txt:
         specifier: ^2.0.1
         version: 2.0.1
+      nodemailer:
+        specifier: ^6.10.0
+        version: 6.10.0
       normalize-path:
         specifier: ^3.0.0
         version: 3.0.0
@@ -3322,6 +3328,112 @@ packages:
     dependencies:
       '@jridgewell/trace-mapping': 0.3.9
 
+  /@css-inline/css-inline-android-arm-eabi@0.14.1:
+    resolution: {integrity: sha512-LNUR8TY4ldfYi0mi/d4UNuHJ+3o8yLQH9r2Nt6i4qeg1i7xswfL3n/LDLRXvGjBYqeEYNlhlBQzbPwMX1qrU6A==}
+    engines: {node: '>= 10'}
+    cpu: [arm]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@css-inline/css-inline-android-arm64@0.14.1:
+    resolution: {integrity: sha512-tH5us0NYGoTNBHOUHVV7j9KfJ4DtFOeTLA3cM0XNoMtArNu2pmaaBMFJPqECzavfXkLc7x5Z22UPZYjoyHfvCA==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@css-inline/css-inline-darwin-arm64@0.14.1:
+    resolution: {integrity: sha512-QE5W1YRIfRayFrtrcK/wqEaxNaqLULPI0gZB4ArbFRd3d56IycvgBasDTHPre5qL2cXCO3VyPx+80XyHOaVkag==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@css-inline/css-inline-darwin-x64@0.14.1:
+    resolution: {integrity: sha512-mAvv2sN8awNFsbvBzlFkZPbCNZ6GCWY5/YcIz7V5dPYw+bHHRbjnlkNTEZq5BsDxErVrMIGvz05PGgzuNvZvdQ==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@css-inline/css-inline-linux-arm-gnueabihf@0.14.1:
+    resolution: {integrity: sha512-AWC44xL0X7BgKvrWEqfSqkT2tJA5kwSGrAGT+m0gt11wnTYySvQ6YpX0fTY9i3ppYGu4bEdXFjyK2uY1DTQMHA==}
+    engines: {node: '>= 10'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@css-inline/css-inline-linux-arm64-gnu@0.14.1:
+    resolution: {integrity: sha512-drj0ciiJgdP3xKXvNAt4W+FH4KKMs8vB5iKLJ3HcH07sNZj58Sx++2GxFRS1el3p+GFp9OoYA6dgouJsGEqt0Q==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@css-inline/css-inline-linux-arm64-musl@0.14.1:
+    resolution: {integrity: sha512-FzknI+st8eA8YQSdEJU9ykcM0LZjjigBuynVF5/p7hiMm9OMP8aNhWbhZ8LKJpKbZrQsxSGS4g9Vnr6n6FiSdQ==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@css-inline/css-inline-linux-x64-gnu@0.14.1:
+    resolution: {integrity: sha512-yubbEye+daDY/4vXnyASAxH88s256pPati1DfVoZpU1V0+KP0BZ1dByZOU1ktExurbPH3gZOWisAnBE9xon0Uw==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@css-inline/css-inline-linux-x64-musl@0.14.1:
+    resolution: {integrity: sha512-6CRAZzoy1dMLPC/tns2rTt1ZwPo0nL/jYBEIAsYTCWhfAnNnpoLKVh5Nm+fSU3OOwTTqU87UkGrFJhObD/wobQ==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@css-inline/css-inline-win32-x64-msvc@0.14.1:
+    resolution: {integrity: sha512-nzotGiaiuiQW78EzsiwsHZXbxEt6DiMUFcDJ6dhiliomXxnlaPyBfZb6/FMBgRJOf6sknDt/5695OttNmbMYzg==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@css-inline/css-inline@0.14.1:
+    resolution: {integrity: sha512-u4eku+hnPqqHIGq/ZUQcaP0TrCbYeLIYBaK7qClNRGZbnh8RC4gVxLEIo8Pceo1nOK9E5G4Lxzlw5KnXcvflfA==}
+    engines: {node: '>= 10'}
+    optionalDependencies:
+      '@css-inline/css-inline-android-arm-eabi': 0.14.1
+      '@css-inline/css-inline-android-arm64': 0.14.1
+      '@css-inline/css-inline-darwin-arm64': 0.14.1
+      '@css-inline/css-inline-darwin-x64': 0.14.1
+      '@css-inline/css-inline-linux-arm-gnueabihf': 0.14.1
+      '@css-inline/css-inline-linux-arm64-gnu': 0.14.1
+      '@css-inline/css-inline-linux-arm64-musl': 0.14.1
+      '@css-inline/css-inline-linux-x64-gnu': 0.14.1
+      '@css-inline/css-inline-linux-x64-musl': 0.14.1
+      '@css-inline/css-inline-win32-x64-msvc': 0.14.1
+    dev: false
+
   /@csstools/cascade-layer-name-parser@2.0.4(@csstools/css-parser-algorithms@3.0.4)(@csstools/css-tokenizer@3.0.3):
     resolution: {integrity: sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==}
     engines: {node: '>=18'}
@@ -6976,6 +7088,32 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
+  /@nestjs-modules/mailer@2.0.2(@nestjs/common@10.4.15)(@nestjs/core@10.4.15)(nodemailer@6.10.0):
+    resolution: {integrity: sha512-+z4mADQasg0H1ZaGu4zZTuKv2pu+XdErqx99PLFPzCDNTN/q9U59WPgkxVaHnsvKHNopLj5Xap7G4ZpptduoYw==}
+    peerDependencies:
+      '@nestjs/common': '>=7.0.9'
+      '@nestjs/core': '>=7.0.9'
+      nodemailer: '>=6.4.6'
+    dependencies:
+      '@css-inline/css-inline': 0.14.1
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/core': 10.4.15(@nestjs/common@10.4.15)(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      glob: 10.3.12
+      nodemailer: 6.10.0
+    optionalDependencies:
+      '@types/ejs': 3.1.5
+      '@types/mjml': 4.7.4
+      '@types/pug': 2.0.10
+      ejs: 3.1.10
+      handlebars: 4.7.8
+      liquidjs: 10.21.0
+      mjml: 4.15.3
+      preview-email: 3.1.0
+      pug: 3.0.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+
   /@nestjs/apollo@12.2.2(@apollo/server@4.11.3)(@nestjs/common@10.4.15)(@nestjs/core@10.4.15)(@nestjs/graphql@12.2.2)(graphql@16.10.0):
     resolution: {integrity: sha512-gsDqSfsmTSvF0k3XaRESRgM3uE/YFO+59txCsq7T1EadDOVOuoF3zVQiFmi6D50Rlnqohqs63qjjf46mgiiXgQ==}
     peerDependencies:
@@ -7751,6 +7889,12 @@ packages:
       '@octokit/webhooks-methods': 5.1.1
     dev: false
 
+  /@one-ini/wasm@0.1.1:
+    resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@parcel/watcher-android-arm64@2.5.1:
     resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
     engines: {node: '>= 10.0.0'}
@@ -8822,6 +8966,15 @@ packages:
     resolution: {integrity: sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==}
     dev: true
 
+  /@selderee/plugin-htmlparser2@0.11.0:
+    resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
+    requiresBuild: true
+    dependencies:
+      domhandler: 5.0.3
+      selderee: 0.11.0
+    dev: false
+    optional: true
+
   /@sideway/address@4.1.5:
     resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==}
     dependencies:
@@ -9740,6 +9893,11 @@ packages:
     resolution: {integrity: sha512-zf2GwV/G6TdaLwpLDcGTIkHnXf8JEf/viMux+khqKQKDa8/8BAUtXXZS563GnvJ4Fg0PBLGAaFf2GekEVSZ6GQ==}
     dev: false
 
+  /@types/ejs@3.1.5:
+    resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==}
+    dev: false
+    optional: true
+
   /@types/eslint-scope@3.7.7:
     resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
     dependencies:
@@ -9917,6 +10075,19 @@ packages:
   /@types/mime@1.3.5:
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
 
+  /@types/mjml-core@4.15.1:
+    resolution: {integrity: sha512-qu8dUksU8yXX18qMTFINkM4uoz7WQYC5F14lcWeSNmWbulaGG0KG19yeZwpx75b9RJXr8WI/FRHH0LyQTU9JbA==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@types/mjml@4.7.4:
+    resolution: {integrity: sha512-vyi1vzWgMzFMwZY7GSZYX0GU0dmtC8vLHwpgk+NWmwbwRSrlieVyJ9sn5elodwUfklJM7yGl0zQeet1brKTWaQ==}
+    dependencies:
+      '@types/mjml-core': 4.15.1
+    dev: false
+    optional: true
+
   /@types/ms@2.1.0:
     resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
 
@@ -9973,6 +10144,11 @@ packages:
   /@types/prop-types@15.7.14:
     resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
 
+  /@types/pug@2.0.10:
+    resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
+    dev: false
+    optional: true
+
   /@types/qs@6.9.18:
     resolution: {integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==}
 
@@ -10653,6 +10829,13 @@ packages:
     resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
     dev: false
 
+  /abbrev@2.0.0:
+    resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==}
+    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /abort-controller@3.0.0:
     resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
     engines: {node: '>=6.5'}
@@ -10686,6 +10869,14 @@ packages:
     dependencies:
       acorn: 8.14.0
 
+  /acorn@7.4.1:
+    resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /acorn@8.14.0:
     resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
     engines: {node: '>=0.4.0'}
@@ -10783,6 +10974,16 @@ packages:
       json-schema-traverse: 1.0.0
       require-from-string: 2.0.2
 
+  /alce@1.2.0:
+    resolution: {integrity: sha512-XppPf2S42nO2WhvKzlwzlfcApcXHzjlod30pKmcWjRgLOtqoe5DMuqdiYoM6AgyXksc6A6pV4v1L/WW217e57w==}
+    engines: {node: '>=0.8.0'}
+    requiresBuild: true
+    dependencies:
+      esprima: 1.2.5
+      estraverse: 1.9.3
+    dev: false
+    optional: true
+
   /algoliasearch-helper@3.24.2(algoliasearch@4.24.0):
     resolution: {integrity: sha512-vBw/INZDfyh/THbVeDy8On8lZqd2qiUAHde5N4N1ygL4SoeLqLGJ4GHneHrDAYsjikRwTTtodEP0fiXl5MxHFQ==}
     peerDependencies:
@@ -10840,7 +11041,6 @@ packages:
   /ansi-colors@4.1.3:
     resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
     engines: {node: '>=6'}
-    dev: true
 
   /ansi-escapes@4.3.2:
     resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
@@ -11071,7 +11271,12 @@ packages:
 
   /asap@2.0.6:
     resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
-    dev: true
+
+  /assert-never@1.4.0:
+    resolution: {integrity: sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==}
+    requiresBuild: true
+    dev: false
+    optional: true
 
   /ast-types-flow@0.0.8:
     resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
@@ -11099,7 +11304,6 @@ packages:
 
   /async@3.2.6:
     resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
-    dev: true
 
   /asynckit@0.4.0:
     resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
@@ -11354,6 +11558,15 @@ packages:
       babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.9)
     dev: true
 
+  /babel-walk@3.0.0-canary-5:
+    resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==}
+    engines: {node: '>= 10.0.0'}
+    requiresBuild: true
+    dependencies:
+      '@babel/types': 7.26.9
+    dev: false
+    optional: true
+
   /backo2@1.0.2:
     resolution: {integrity: sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==}
     dev: false
@@ -11622,6 +11835,15 @@ packages:
     resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
     engines: {node: '>=6'}
 
+  /camel-case@3.0.0:
+    resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==}
+    requiresBuild: true
+    dependencies:
+      no-case: 2.3.2
+      upper-case: 1.1.3
+    dev: false
+    optional: true
+
   /camel-case@4.1.2:
     resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
     dependencies:
@@ -11679,7 +11901,6 @@ packages:
     dependencies:
       ansi-styles: 4.3.0
       supports-color: 7.2.0
-    dev: true
 
   /chalk@4.1.2:
     resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
@@ -11760,6 +11981,14 @@ packages:
   /character-entities@2.0.2:
     resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
 
+  /character-parser@2.2.0:
+    resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==}
+    requiresBuild: true
+    dependencies:
+      is-regex: 1.2.1
+    dev: false
+    optional: true
+
   /character-reference-invalid@1.1.4:
     resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==}
     dev: false
@@ -11867,6 +12096,15 @@ packages:
       clsx: 2.1.1
     dev: false
 
+  /clean-css@4.2.4:
+    resolution: {integrity: sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==}
+    engines: {node: '>= 4.0'}
+    requiresBuild: true
+    dependencies:
+      source-map: 0.6.1
+    dev: false
+    optional: true
+
   /clean-css@5.3.3:
     resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==}
     engines: {node: '>= 10.0'}
@@ -12075,7 +12313,6 @@ packages:
   /commander@6.2.1:
     resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==}
     engines: {node: '>= 6'}
-    dev: true
 
   /commander@7.2.0:
     resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
@@ -12194,6 +12431,15 @@ packages:
       upper-case: 2.0.2
     dev: true
 
+  /constantinople@4.0.1:
+    resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==}
+    requiresBuild: true
+    dependencies:
+      '@babel/parser': 7.26.9
+      '@babel/types': 7.26.9
+    dev: false
+    optional: true
+
   /content-disposition@0.5.2:
     resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==}
     engines: {node: '>= 0.6'}
@@ -12404,6 +12650,19 @@ packages:
     dependencies:
       tslib: 2.8.1
 
+  /cross-spawn@6.0.6:
+    resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==}
+    engines: {node: '>=4.8'}
+    requiresBuild: true
+    dependencies:
+      nice-try: 1.0.5
+      path-key: 2.0.1
+      semver: 5.7.2
+      shebang-command: 1.2.0
+      which: 1.3.1
+    dev: false
+    optional: true
+
   /cross-spawn@7.0.6:
     resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
     engines: {node: '>= 8'}
@@ -12907,7 +13166,6 @@ packages:
   /detect-indent@6.1.0:
     resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
     engines: {node: '>=8'}
-    dev: true
 
   /detect-libc@1.0.3:
     resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
@@ -12923,7 +13181,6 @@ packages:
   /detect-newline@3.1.0:
     resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
     engines: {node: '>=8'}
-    dev: true
 
   /detect-node-es@1.1.0:
     resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
@@ -12985,6 +13242,16 @@ packages:
     dependencies:
       path-type: 4.0.0
 
+  /display-notification@2.0.0:
+    resolution: {integrity: sha512-TdmtlAcdqy1NU+j7zlkDdMnCL878zriLaBmoD9quOoq1ySSSGv03l0hXK5CvIFZlIfFI/hizqdQuW+Num7xuhw==}
+    engines: {node: '>=4'}
+    requiresBuild: true
+    dependencies:
+      escape-string-applescript: 1.0.0
+      run-applescript: 3.2.0
+    dev: false
+    optional: true
+
   /dlv@1.1.3:
     resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
 
@@ -13008,6 +13275,12 @@ packages:
     dependencies:
       esutils: 2.0.3
 
+  /doctypes@1.1.0:
+    resolution: {integrity: sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /dom-accessibility-api@0.5.16:
     resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
     dev: true
@@ -13050,6 +13323,15 @@ packages:
       webidl-conversions: 7.0.0
     dev: true
 
+  /domhandler@3.3.0:
+    resolution: {integrity: sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==}
+    engines: {node: '>= 4'}
+    requiresBuild: true
+    dependencies:
+      domelementtype: 2.3.0
+    dev: false
+    optional: true
+
   /domhandler@4.3.1:
     resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
     engines: {node: '>= 4'}
@@ -13132,6 +13414,19 @@ packages:
       safe-buffer: 5.2.1
     dev: false
 
+  /editorconfig@1.0.4:
+    resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==}
+    engines: {node: '>=14'}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      '@one-ini/wasm': 0.1.1
+      commander: 10.0.1
+      minimatch: 9.0.1
+      semver: 7.7.1
+    dev: false
+    optional: true
+
   /ee-first@1.1.1:
     resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
 
@@ -13141,7 +13436,6 @@ packages:
     hasBin: true
     dependencies:
       jake: 10.9.2
-    dev: true
 
   /electron-to-chromium@1.5.112:
     resolution: {integrity: sha512-oen93kVyqSb3l+ziUgzIOlWt/oOuy4zRmpwestMn4rhFWAoFJeFuCVte9F2fASjeZZo7l/Cif9TiyrdW4CwEMA==}
@@ -13186,6 +13480,13 @@ packages:
     resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
     engines: {node: '>= 0.8'}
 
+  /encoding-japanese@2.2.0:
+    resolution: {integrity: sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==}
+    engines: {node: '>=8.10.0'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /encoding@0.1.13:
     resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
     requiresBuild: true
@@ -13417,6 +13718,13 @@ packages:
     resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
     engines: {node: '>=6'}
 
+  /escape-goat@3.0.0:
+    resolution: {integrity: sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==}
+    engines: {node: '>=10'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /escape-goat@4.0.0:
     resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==}
     engines: {node: '>=12'}
@@ -13425,6 +13733,13 @@ packages:
   /escape-html@1.0.3:
     resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
 
+  /escape-string-applescript@1.0.0:
+    resolution: {integrity: sha512-4/hFwoYaC6TkpDn9A3pTC52zQPArFeXuIfhUtCGYdauTzXVP9H3BDr3oO/QzQehMpLDC7srvYgfwvImPFGfvBA==}
+    engines: {node: '>=0.10.0'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /escape-string-regexp@1.0.5:
     resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
     engines: {node: '>=0.8.0'}
@@ -13774,6 +14089,14 @@ packages:
       acorn-jsx: 5.3.2(acorn@8.14.0)
       eslint-visitor-keys: 3.4.3
 
+  /esprima@1.2.5:
+    resolution: {integrity: sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /esprima@4.0.1:
     resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
     engines: {node: '>=4'}
@@ -13791,6 +14114,13 @@ packages:
     dependencies:
       estraverse: 5.3.0
 
+  /estraverse@1.9.3:
+    resolution: {integrity: sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==}
+    engines: {node: '>=0.10.0'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /estraverse@4.3.0:
     resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
     engines: {node: '>=4.0'}
@@ -13886,6 +14216,21 @@ packages:
     resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
     engines: {node: '>=0.8.x'}
 
+  /execa@0.10.0:
+    resolution: {integrity: sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==}
+    engines: {node: '>=4'}
+    requiresBuild: true
+    dependencies:
+      cross-spawn: 6.0.6
+      get-stream: 3.0.0
+      is-stream: 1.1.0
+      npm-run-path: 2.0.2
+      p-finally: 1.0.0
+      signal-exit: 3.0.7
+      strip-eof: 1.0.0
+    dev: false
+    optional: true
+
   /execa@5.1.1:
     resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
     engines: {node: '>=10'}
@@ -13963,6 +14308,12 @@ packages:
     transitivePeerDependencies:
       - supports-color
 
+  /extend-object@1.0.0:
+    resolution: {integrity: sha512-0dHDIXC7y7LDmCh/lp1oYkmv73K25AMugQI07r8eFopkW6f7Ufn1q+ETMsJjnV9Am14SlElkqy3O92r6xEaxPw==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /extend-shallow@2.0.1:
     resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
     engines: {node: '>=0.10.0'}
@@ -14178,7 +14529,6 @@ packages:
     resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
     dependencies:
       minimatch: 5.1.6
-    dev: true
 
   /filename-reserved-regex@3.0.0:
     resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==}
@@ -14254,6 +14604,20 @@ packages:
       path-exists: 5.0.0
     dev: false
 
+  /fixpack@4.0.0:
+    resolution: {integrity: sha512-5SM1+H2CcuJ3gGEwTiVo/+nd/hYpNj9Ch3iMDOQ58ndY+VGQ2QdvaUTkd3otjZvYnd/8LF/HkJ5cx7PBq0orCQ==}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      alce: 1.2.0
+      chalk: 3.0.0
+      detect-indent: 6.1.0
+      detect-newline: 3.1.0
+      extend-object: 1.0.0
+      rc: 1.2.8
+    dev: false
+    optional: true
+
   /flat-cache@3.2.0:
     resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
     engines: {node: ^10.12.0 || >=12.0.0}
@@ -14610,6 +14974,13 @@ packages:
     engines: {node: '>=8.0.0'}
     dev: true
 
+  /get-port@5.1.1:
+    resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==}
+    engines: {node: '>=8'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /get-proto@1.0.1:
     resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
     engines: {node: '>= 0.4'}
@@ -14617,7 +14988,14 @@ packages:
       dunder-proto: 1.0.1
       es-object-atoms: 1.1.1
 
-  /get-stream@6.0.1:
+  /get-stream@3.0.0:
+    resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==}
+    engines: {node: '>=4'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /get-stream@6.0.1:
     resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
     engines: {node: '>=10'}
 
@@ -14671,6 +15049,18 @@ packages:
       path-scurry: 1.11.1
     dev: true
 
+  /glob@10.3.12:
+    resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==}
+    engines: {node: '>=16 || 14 >=14.17'}
+    hasBin: true
+    dependencies:
+      foreground-child: 3.3.1
+      jackspeak: 2.3.6
+      minimatch: 9.0.5
+      minipass: 7.1.2
+      path-scurry: 1.11.1
+    dev: false
+
   /glob@10.4.5:
     resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
     hasBin: true
@@ -14934,6 +15324,20 @@ packages:
     resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==}
     dev: false
 
+  /handlebars@4.7.8:
+    resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
+    engines: {node: '>=0.4.7'}
+    hasBin: true
+    dependencies:
+      minimist: 1.2.8
+      neo-async: 2.6.2
+      source-map: 0.6.1
+      wordwrap: 1.0.0
+    optionalDependencies:
+      uglify-js: 3.19.3
+    dev: false
+    optional: true
+
   /has-bigints@1.1.0:
     resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
     engines: {node: '>= 0.4'}
@@ -15203,11 +15607,40 @@ packages:
       terser: 5.39.0
     dev: false
 
+  /html-minifier@4.0.0:
+    resolution: {integrity: sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==}
+    engines: {node: '>=6'}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      camel-case: 3.0.0
+      clean-css: 4.2.4
+      commander: 2.20.3
+      he: 1.2.0
+      param-case: 2.1.1
+      relateurl: 0.2.7
+      uglify-js: 3.19.3
+    dev: false
+    optional: true
+
   /html-tags@3.3.1:
     resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
     engines: {node: '>=8'}
     dev: false
 
+  /html-to-text@9.0.5:
+    resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
+    engines: {node: '>=14'}
+    requiresBuild: true
+    dependencies:
+      '@selderee/plugin-htmlparser2': 0.11.0
+      deepmerge: 4.3.1
+      dom-serializer: 2.0.0
+      htmlparser2: 8.0.2
+      selderee: 0.11.0
+    dev: false
+    optional: true
+
   /html-url-attributes@3.0.1:
     resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
     dev: false
@@ -15236,6 +15669,17 @@ packages:
       webpack: 5.98.0(webpack-cli@5.1.4)
     dev: false
 
+  /htmlparser2@5.0.1:
+    resolution: {integrity: sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==}
+    requiresBuild: true
+    dependencies:
+      domelementtype: 2.3.0
+      domhandler: 3.3.0
+      domutils: 2.8.0
+      entities: 2.2.0
+    dev: false
+    optional: true
+
   /htmlparser2@6.1.0:
     resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
     dependencies:
@@ -15254,6 +15698,17 @@ packages:
       entities: 4.5.0
     dev: false
 
+  /htmlparser2@9.1.0:
+    resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
+    requiresBuild: true
+    dependencies:
+      domelementtype: 2.3.0
+      domhandler: 5.0.3
+      domutils: 3.2.2
+      entities: 4.5.0
+    dev: false
+    optional: true
+
   /http-cache-semantics@4.1.1:
     resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
     dev: false
@@ -15775,6 +16230,15 @@ packages:
     hasBin: true
     dev: false
 
+  /is-expression@4.0.0:
+    resolution: {integrity: sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==}
+    requiresBuild: true
+    dependencies:
+      acorn: 7.4.1
+      object-assign: 4.1.1
+    dev: false
+    optional: true
+
   /is-extendable@0.1.1:
     resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
     engines: {node: '>=0.10.0'}
@@ -15920,6 +16384,12 @@ packages:
     resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
     dev: true
 
+  /is-promise@2.2.2:
+    resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /is-regex@1.2.1:
     resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
     engines: {node: '>= 0.4'}
@@ -15928,7 +16398,6 @@ packages:
       gopd: 1.2.0
       has-tostringtag: 1.0.2
       hasown: 2.0.2
-    dev: true
 
   /is-regexp@1.0.0:
     resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==}
@@ -15959,6 +16428,13 @@ packages:
       call-bound: 1.0.4
     dev: true
 
+  /is-stream@1.1.0:
+    resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==}
+    engines: {node: '>=0.10.0'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /is-stream@2.0.1:
     resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
     engines: {node: '>=8'}
@@ -16177,7 +16653,6 @@ packages:
       '@isaacs/cliui': 8.0.2
     optionalDependencies:
       '@pkgjs/parseargs': 0.11.0
-    dev: true
 
   /jackspeak@3.4.3:
     resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
@@ -16195,7 +16670,6 @@ packages:
       chalk: 4.1.2
       filelist: 1.0.4
       minimatch: 3.1.2
-    dev: true
 
   /jest-changed-files@29.7.0:
     resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==}
@@ -16887,6 +17361,33 @@ packages:
     resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==}
     dev: true
 
+  /js-beautify@1.15.4:
+    resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==}
+    engines: {node: '>=14'}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      config-chain: 1.1.13
+      editorconfig: 1.0.4
+      glob: 10.4.5
+      js-cookie: 3.0.5
+      nopt: 7.2.1
+    dev: false
+    optional: true
+
+  /js-cookie@3.0.5:
+    resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
+    engines: {node: '>=14'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /js-stringify@1.0.2:
+    resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /js-tiktoken@1.0.19:
     resolution: {integrity: sha512-XC63YQeEcS47Y53gg950xiZ4IWmkfMe4p2V9OSaBt26q+p47WHn18izuXzSclCI73B7yGqtfRsT6jcZQI0y08g==}
     dependencies:
@@ -17038,6 +17539,15 @@ packages:
       semver: 7.7.1
     dev: false
 
+  /jstransformer@1.0.0:
+    resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==}
+    requiresBuild: true
+    dependencies:
+      is-promise: 2.2.2
+      promise: 7.3.1
+    dev: false
+    optional: true
+
   /jsx-ast-utils@3.3.5:
     resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
     engines: {node: '>=4.0'}
@@ -17048,6 +17558,22 @@ packages:
       object.values: 1.2.1
     dev: true
 
+  /juice@10.0.1:
+    resolution: {integrity: sha512-ZhJT1soxJCkOiO55/mz8yeBKTAJhRzX9WBO+16ZTqNTONnnVlUPyVBIzQ7lDRjaBdTbid+bAnyIon/GM3yp4cA==}
+    engines: {node: '>=10.0.0'}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      cheerio: 1.0.0-rc.12
+      commander: 6.2.1
+      mensch: 0.3.4
+      slick: 1.12.2
+      web-resource-inliner: 6.0.1
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
   /jwa@1.4.1:
     resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==}
     dependencies:
@@ -17197,6 +17723,12 @@ packages:
       shell-quote: 1.8.2
     dev: false
 
+  /leac@0.6.0:
+    resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /leven@3.1.0:
     resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
     engines: {node: '>=6'}
@@ -17208,9 +17740,32 @@ packages:
       prelude-ls: 1.2.1
       type-check: 0.4.0
 
+  /libbase64@1.3.0:
+    resolution: {integrity: sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /libmime@5.3.6:
+    resolution: {integrity: sha512-j9mBC7eiqi6fgBPAGvKCXJKJSIASanYF4EeA4iBzSG0HxQxmXnR3KbyWqTn4CwsKSebqCv2f5XZfAO6sKzgvwA==}
+    requiresBuild: true
+    dependencies:
+      encoding-japanese: 2.2.0
+      iconv-lite: 0.6.3
+      libbase64: 1.3.0
+      libqp: 2.1.1
+    dev: false
+    optional: true
+
   /libphonenumber-js@1.12.4:
     resolution: {integrity: sha512-vLmhg7Gan7idyAKfc6pvCtNzvar4/eIzrVVk3hjNFH5+fGqyjD0gQRovdTrDl20wsmZhBtmZpcsR0tOfquwb8g==}
 
+  /libqp@2.1.1:
+    resolution: {integrity: sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /lifecycle-utils@1.7.3:
     resolution: {integrity: sha512-T7zs7J6/sgsqwVyG34Sfo5LTQmlPmmqaUe3yBhdF8nq24RtR/HtbkNZRhNbr9BEaKySdSgH+P9H5U9X+p0WjXw==}
     dev: false
@@ -17226,6 +17781,23 @@ packages:
   /lines-and-columns@1.2.4:
     resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
 
+  /linkify-it@5.0.0:
+    resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
+    requiresBuild: true
+    dependencies:
+      uc.micro: 2.1.0
+    dev: false
+    optional: true
+
+  /liquidjs@10.21.0:
+    resolution: {integrity: sha512-DouqxNU2jfoZzb1LinVjOc/f6ssitGIxiDJT+kEKyYqPSSSd+WmGOAhtWbVm1/n75svu4aQ+FyQ3ctd3wh1bbw==}
+    engines: {node: '>=14'}
+    hasBin: true
+    dependencies:
+      commander: 10.0.1
+    dev: false
+    optional: true
+
   /listr2@4.0.5:
     resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==}
     engines: {node: '>=12'}
@@ -17420,6 +17992,12 @@ packages:
       tslib: 2.6.3
     dev: true
 
+  /lower-case@1.1.4:
+    resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /lower-case@2.0.2:
     resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
     dependencies:
@@ -17479,6 +18057,33 @@ packages:
       '@jridgewell/sourcemap-codec': 1.5.0
     dev: true
 
+  /mailparser@3.7.2:
+    resolution: {integrity: sha512-iI0p2TCcIodR1qGiRoDBBwboSSff50vQAWytM5JRggLfABa4hHYCf3YVujtuzV454xrOP352VsAPIzviqMTo4Q==}
+    requiresBuild: true
+    dependencies:
+      encoding-japanese: 2.2.0
+      he: 1.2.0
+      html-to-text: 9.0.5
+      iconv-lite: 0.6.3
+      libmime: 5.3.6
+      linkify-it: 5.0.0
+      mailsplit: 5.4.2
+      nodemailer: 6.9.16
+      punycode.js: 2.3.1
+      tlds: 1.255.0
+    dev: false
+    optional: true
+
+  /mailsplit@5.4.2:
+    resolution: {integrity: sha512-4cczG/3Iu3pyl8JgQ76dKkisurZTmxMrA4dj/e8d2jKYcFTZ7MxOzg1gTioTDMPuFXwTrVuN/gxhkrO7wLg7qA==}
+    requiresBuild: true
+    dependencies:
+      libbase64: 1.3.0
+      libmime: 5.3.6
+      libqp: 2.1.1
+    dev: false
+    optional: true
+
   /make-dir@3.1.0:
     resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
     engines: {node: '>=8'}
@@ -17805,6 +18410,12 @@ packages:
       readable-stream: 3.6.2
     dev: false
 
+  /mensch@0.3.4:
+    resolution: {integrity: sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /merge-descriptors@1.0.3:
     resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
 
@@ -18225,7 +18836,6 @@ packages:
     resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
     engines: {node: '>=4.0.0'}
     hasBin: true
-    dev: true
 
   /mimic-fn@2.1.0:
     resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
@@ -18276,7 +18886,15 @@ packages:
     engines: {node: '>=10'}
     dependencies:
       brace-expansion: 2.0.1
-    dev: true
+
+  /minimatch@9.0.1:
+    resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==}
+    engines: {node: '>=16 || 14 >=14.17'}
+    requiresBuild: true
+    dependencies:
+      brace-expansion: 2.0.1
+    dev: false
+    optional: true
 
   /minimatch@9.0.3:
     resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
@@ -18359,21 +18977,448 @@ packages:
     resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
     engines: {node: '>=16 || 14 >=14.17'}
 
-  /minizlib@2.1.2:
-    resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
-    engines: {node: '>= 8'}
+  /minizlib@2.1.2:
+    resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
+    engines: {node: '>= 8'}
+    dependencies:
+      minipass: 3.3.6
+      yallist: 4.0.0
+    dev: false
+
+  /minizlib@3.0.1:
+    resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==}
+    engines: {node: '>= 18'}
+    dependencies:
+      minipass: 7.1.2
+      rimraf: 5.0.10
+    dev: false
+
+  /mjml-accordion@4.15.3:
+    resolution: {integrity: sha512-LPNVSj1LyUVYT9G1gWwSw3GSuDzDsQCu0tPB2uDsq4VesYNnU6v3iLCQidMiR6azmIt13OEozG700ygAUuA6Ng==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-body@4.15.3:
+    resolution: {integrity: sha512-7pfUOVPtmb0wC+oUOn4xBsAw4eT5DyD6xqaxj/kssu6RrFXOXgJaVnDPAI9AzIvXJ/5as9QrqRGYAddehwWpHQ==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-button@4.15.3:
+    resolution: {integrity: sha512-79qwn9AgdGjJR1vLnrcm2rq2AsAZkKC5JPwffTMG+Nja6zGYpTDZFZ56ekHWr/r1b5WxkukcPj2PdevUug8c+Q==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-carousel@4.15.3:
+    resolution: {integrity: sha512-3ju6I4l7uUhPRrJfN3yK9AMsfHvrYbRkcJ1GRphFHzUj37B2J6qJOQUpzA547Y4aeh69TSb7HFVf1t12ejQxVw==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-cli@4.15.3:
+    resolution: {integrity: sha512-+V2TDw3tXUVEptFvLSerz125C2ogYl8klIBRY1m5BHd4JvGVf3yhx8N3PngByCzA6PGcv/eydGQN+wy34SHf0Q==}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      chokidar: 3.6.0
+      glob: 10.3.12
+      html-minifier: 4.0.0
+      js-beautify: 1.15.4
+      lodash: 4.17.21
+      minimatch: 9.0.5
+      mjml-core: 4.15.3
+      mjml-migrate: 4.15.3
+      mjml-parser-xml: 4.15.3
+      mjml-validator: 4.15.3
+      yargs: 17.7.2
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-column@4.15.3:
+    resolution: {integrity: sha512-hYdEFdJGHPbZJSEysykrevEbB07yhJGSwfDZEYDSbhQQFjV2tXrEgYcFD5EneMaowjb55e3divSJxU4c5q4Qgw==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-core@4.15.3:
+    resolution: {integrity: sha512-Dmwk+2cgSD9L9GmTbEUNd8QxkTZtW9P7FN/ROZW/fGZD6Hq6/4TB0zEspg2Ow9eYjZXO2ofOJ3PaQEEShKV0kQ==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      cheerio: 1.0.0-rc.12
+      detect-node: 2.1.0
+      html-minifier: 4.0.0
+      js-beautify: 1.15.4
+      juice: 10.0.1
+      lodash: 4.17.21
+      mjml-migrate: 4.15.3
+      mjml-parser-xml: 4.15.3
+      mjml-validator: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-divider@4.15.3:
+    resolution: {integrity: sha512-vh27LQ9FG/01y0b9ntfqm+GT5AjJnDSDY9hilss2ixIUh0FemvfGRfsGVeV5UBVPBKK7Ffhvfqc7Rciob9Spzw==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-group@4.15.3:
+    resolution: {integrity: sha512-HSu/rKnGZVKFq3ciT46vi1EOy+9mkB0HewO4+P6dP/Y0UerWkN6S3UK11Cxsj0cAp0vFwkPDCdOeEzRdpFEkzA==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-head-attributes@4.15.3:
+    resolution: {integrity: sha512-2ISo0r5ZKwkrvJgDou9xVPxxtXMaETe2AsAA02L89LnbB2KC0N5myNsHV0sEysTw9+CfCmgjAb0GAI5QGpxKkQ==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-head-breakpoint@4.15.3:
+    resolution: {integrity: sha512-Eo56FA5C2v6ucmWQL/JBJ2z641pLOom4k0wP6CMZI2utfyiJ+e2Uuinj1KTrgDcEvW4EtU9HrfAqLK9UosLZlg==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-head-font@4.15.3:
+    resolution: {integrity: sha512-CzV2aDPpiNIIgGPHNcBhgyedKY4SX3BJoTwOobSwZVIlEA6TAWB4Z9WwFUmQqZOgo1AkkiTHPZQvGcEhFFXH6g==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-head-html-attributes@4.15.3:
+    resolution: {integrity: sha512-MDNDPMBOgXUZYdxhosyrA2kudiGO8aogT0/cODyi2Ed9o/1S7W+je11JUYskQbncqhWKGxNyaP4VWa+6+vUC/g==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-head-preview@4.15.3:
+    resolution: {integrity: sha512-J2PxCefUVeFwsAExhrKo4lwxDevc5aKj888HBl/wN4EuWOoOg06iOGCxz4Omd8dqyFsrqvbBuPqRzQ+VycGmaA==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-head-style@4.15.3:
+    resolution: {integrity: sha512-9J+JuH+mKrQU65CaJ4KZegACUgNIlYmWQYx3VOBR/tyz+8kDYX7xBhKJCjQ1I4wj2Tvga3bykd89Oc2kFZ5WOw==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-head-title@4.15.3:
+    resolution: {integrity: sha512-IM59xRtsxID4DubQ0iLmoCGXguEe+9BFG4z6y2xQDrscIa4QY3KlfqgKGT69ojW+AVbXXJPEVqrAi4/eCsLItQ==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-head@4.15.3:
+    resolution: {integrity: sha512-o3mRuuP/MB5fZycjD3KH/uXsnaPl7Oo8GtdbJTKtH1+O/3pz8GzGMkscTKa97l03DAG2EhGrzzLcU2A6eshwFw==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-hero@4.15.3:
+    resolution: {integrity: sha512-9cLAPuc69yiuzNrMZIN58j+HMK1UWPaq2i3/Fg2ZpimfcGFKRcPGCbEVh0v+Pb6/J0+kf8yIO0leH20opu3AyQ==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-image@4.15.3:
+    resolution: {integrity: sha512-g1OhSdofIytE9qaOGdTPmRIp7JsCtgO0zbsn1Fk6wQh2gEL55Z40j/VoghslWAWTgT2OHFdBKnMvWtN6U5+d2Q==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-migrate@4.15.3:
+    resolution: {integrity: sha512-sr/+35RdxZroNQVegjpfRHJ5hda9XCgaS4mK2FGO+Mb1IUevKfeEPII3F/cHDpNwFeYH3kAgyqQ22ClhGLWNBA==}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      js-beautify: 1.15.4
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+      mjml-parser-xml: 4.15.3
+      yargs: 17.7.2
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-navbar@4.15.3:
+    resolution: {integrity: sha512-VsKH/Jdlf8Yu3y7GpzQV5n7JMdpqvZvTSpF6UQXL0PWOm7k6+LX+sCZimOfpHJ+wCaaybpxokjWZ71mxOoCWoA==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-parser-xml@4.15.3:
+    resolution: {integrity: sha512-Tz0UX8/JVYICLjT+U8J1f/TFxIYVYjzZHeh4/Oyta0pLpRLeZlxEd71f3u3kdnulCKMP4i37pFRDmyLXAlEuLw==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      detect-node: 2.1.0
+      htmlparser2: 9.1.0
+      lodash: 4.17.21
+    dev: false
+    optional: true
+
+  /mjml-preset-core@4.15.3:
+    resolution: {integrity: sha512-1zZS8P4O0KweWUqNS655+oNnVMPQ1Rq1GaZq5S9JfwT1Vh/m516lSmiTW9oko6gGHytt5s6Yj6oOeu5Zm8FoLw==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      mjml-accordion: 4.15.3
+      mjml-body: 4.15.3
+      mjml-button: 4.15.3
+      mjml-carousel: 4.15.3
+      mjml-column: 4.15.3
+      mjml-divider: 4.15.3
+      mjml-group: 4.15.3
+      mjml-head: 4.15.3
+      mjml-head-attributes: 4.15.3
+      mjml-head-breakpoint: 4.15.3
+      mjml-head-font: 4.15.3
+      mjml-head-html-attributes: 4.15.3
+      mjml-head-preview: 4.15.3
+      mjml-head-style: 4.15.3
+      mjml-head-title: 4.15.3
+      mjml-hero: 4.15.3
+      mjml-image: 4.15.3
+      mjml-navbar: 4.15.3
+      mjml-raw: 4.15.3
+      mjml-section: 4.15.3
+      mjml-social: 4.15.3
+      mjml-spacer: 4.15.3
+      mjml-table: 4.15.3
+      mjml-text: 4.15.3
+      mjml-wrapper: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-raw@4.15.3:
+    resolution: {integrity: sha512-IGyHheOYyRchBLiAEgw3UM11kFNmBSMupu2BDdejC6ZiDhEAdG+tyERlsCwDPYtXanvFpGWULIu3XlsUPc+RZw==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-section@4.15.3:
+    resolution: {integrity: sha512-JfVPRXH++Hd933gmQfG8JXXCBCR6fIzC3DwiYycvanL/aW1cEQ2EnebUfQkt5QzlYjOkJEH+JpccAsq3ln6FZQ==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-social@4.15.3:
+    resolution: {integrity: sha512-7sD5FXrESOxpT9Z4Oh36bS6u/geuUrMP1aCg2sjyAwbPcF1aWa2k9OcatQfpRf6pJEhUZ18y6/WBBXmMVmSzXg==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-spacer@4.15.3:
+    resolution: {integrity: sha512-3B7Qj+17EgDdAtZ3NAdMyOwLTX1jfmJuY7gjyhS2HtcZAmppW+cxqHUBwCKfvSRgTQiccmEvtNxaQK+tfyrZqA==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-table@4.15.3:
+    resolution: {integrity: sha512-FLx7DcRKTdKdcOCbMyBaeudeHaHpwPveRrBm6WyQe3LXx6FfdmOh59i71/16LFQMgBOD3N4/UJkzxLzlTJzMqQ==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-text@4.15.3:
+    resolution: {integrity: sha512-+C0hxCmw9kg0XzT6vhE5mFkK6y225nC8UEQcN94K0fBCjPKkM+HqZMwGX205fzdGRi+Bxa55b/VhrIVwdv+8vw==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
+  /mjml-validator@4.15.3:
+    resolution: {integrity: sha512-Xb72KdqRwjv/qM2rJpV22syyP2N3cRQ9VVDrN6u2FSzLq02buFNxmSPJ7CKhat3PrUNdVHU75KZwOf/tz4UEhA==}
+    requiresBuild: true
+    dependencies:
+      '@babel/runtime': 7.26.9
+    dev: false
+    optional: true
+
+  /mjml-wrapper@4.15.3:
+    resolution: {integrity: sha512-ditsCijeHJrmBmObtJmQ18ddLxv5oPyMTdPU8Di8APOnD2zPk7Z4UAuJSl7HXB45oFiivr3MJf4koFzMUSZ6Gg==}
+    requiresBuild: true
     dependencies:
-      minipass: 3.3.6
-      yallist: 4.0.0
+      '@babel/runtime': 7.26.9
+      lodash: 4.17.21
+      mjml-core: 4.15.3
+      mjml-section: 4.15.3
+    transitivePeerDependencies:
+      - encoding
     dev: false
+    optional: true
 
-  /minizlib@3.0.1:
-    resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==}
-    engines: {node: '>= 18'}
+  /mjml@4.15.3:
+    resolution: {integrity: sha512-bW2WpJxm6HS+S3Yu6tq1DUPFoTxU9sPviUSmnL7Ua+oVO3WA5ILFWqvujUlz+oeuM+HCwEyMiP5xvKNPENVjYA==}
+    hasBin: true
     dependencies:
-      minipass: 7.1.2
-      rimraf: 5.0.10
+      '@babel/runtime': 7.26.9
+      mjml-cli: 4.15.3
+      mjml-core: 4.15.3
+      mjml-migrate: 4.15.3
+      mjml-preset-core: 4.15.3
+      mjml-validator: 4.15.3
+    transitivePeerDependencies:
+      - encoding
     dev: false
+    optional: true
 
   /mkdirp-classic@0.5.3:
     resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
@@ -18575,6 +19620,20 @@ packages:
       - babel-plugin-macros
     dev: false
 
+  /nice-try@1.0.5:
+    resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /no-case@2.3.2:
+    resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==}
+    requiresBuild: true
+    dependencies:
+      lower-case: 1.1.4
+    dev: false
+    optional: true
+
   /no-case@3.0.4:
     resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
     dependencies:
@@ -18738,6 +19797,18 @@ packages:
   /node-releases@2.0.19:
     resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
 
+  /nodemailer@6.10.0:
+    resolution: {integrity: sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==}
+    engines: {node: '>=6.0.0'}
+    dev: false
+
+  /nodemailer@6.9.16:
+    resolution: {integrity: sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==}
+    engines: {node: '>=6.0.0'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /nodemon@3.1.9:
     resolution: {integrity: sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==}
     engines: {node: '>=10'}
@@ -18763,6 +19834,16 @@ packages:
       abbrev: 1.1.1
     dev: false
 
+  /nopt@7.2.1:
+    resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==}
+    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      abbrev: 2.0.0
+    dev: false
+    optional: true
+
   /normalize-path@2.1.1:
     resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==}
     engines: {node: '>=0.10.0'}
@@ -18783,6 +19864,15 @@ packages:
     engines: {node: '>=14.16'}
     dev: false
 
+  /npm-run-path@2.0.2:
+    resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==}
+    engines: {node: '>=4'}
+    requiresBuild: true
+    dependencies:
+      path-key: 2.0.1
+    dev: false
+    optional: true
+
   /npm-run-path@4.0.1:
     resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
     engines: {node: '>=8'}
@@ -18992,6 +20082,16 @@ packages:
       protobufjs: 7.4.0
     dev: false
 
+  /open@7.4.2:
+    resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==}
+    engines: {node: '>=8'}
+    requiresBuild: true
+    dependencies:
+      is-docker: 2.2.1
+      is-wsl: 2.2.0
+    dev: false
+    optional: true
+
   /open@8.4.2:
     resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
     engines: {node: '>=12'}
@@ -19107,6 +20207,15 @@ packages:
     engines: {node: '>=12.20'}
     dev: false
 
+  /p-event@4.2.0:
+    resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==}
+    engines: {node: '>=8'}
+    requiresBuild: true
+    dependencies:
+      p-timeout: 3.2.0
+    dev: false
+    optional: true
+
   /p-finally@1.0.0:
     resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
     engines: {node: '>=4'}
@@ -19211,6 +20320,15 @@ packages:
     resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
     engines: {node: '>=6'}
 
+  /p-wait-for@3.2.0:
+    resolution: {integrity: sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==}
+    engines: {node: '>=8'}
+    requiresBuild: true
+    dependencies:
+      p-timeout: 3.2.0
+    dev: false
+    optional: true
+
   /package-json-from-dist@1.0.1:
     resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
 
@@ -19224,6 +20342,14 @@ packages:
       semver: 7.7.1
     dev: false
 
+  /param-case@2.1.1:
+    resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==}
+    requiresBuild: true
+    dependencies:
+      no-case: 2.3.2
+    dev: false
+    optional: true
+
   /param-case@3.0.4:
     resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
     dependencies:
@@ -19302,6 +20428,15 @@ packages:
     dependencies:
       entities: 4.5.0
 
+  /parseley@0.12.1:
+    resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
+    requiresBuild: true
+    dependencies:
+      leac: 0.6.0
+      peberminta: 0.9.0
+    dev: false
+    optional: true
+
   /parseurl@1.3.3:
     resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
     engines: {node: '>= 0.8'}
@@ -19345,6 +20480,13 @@ packages:
     resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
     dev: false
 
+  /path-key@2.0.1:
+    resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
+    engines: {node: '>=4'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /path-key@3.1.1:
     resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
     engines: {node: '>=8'}
@@ -19387,6 +20529,12 @@ packages:
     resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
     engines: {node: '>=8'}
 
+  /peberminta@0.9.0:
+    resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /peek-readable@4.1.0:
     resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==}
     engines: {node: '>=8'}
@@ -20374,6 +21522,24 @@ packages:
     engines: {node: '>=4'}
     dev: false
 
+  /preview-email@3.1.0:
+    resolution: {integrity: sha512-ZtV1YrwscEjlrUzYrTSs6Nwo49JM3pXLM4fFOBSC3wSni+bxaWlw9/Qgk75PZO8M7cX2EybmL2iwvaV3vkAttw==}
+    engines: {node: '>=14'}
+    dependencies:
+      ci-info: 3.9.0
+      display-notification: 2.0.0
+      fixpack: 4.0.0
+      get-port: 5.1.1
+      mailparser: 3.7.2
+      nodemailer: 6.10.0
+      open: 7.4.2
+      p-event: 4.2.0
+      p-wait-for: 3.2.0
+      pug: 3.0.3
+      uuid: 9.0.1
+    dev: false
+    optional: true
+
   /prism-react-renderer@2.4.1(react@18.3.1):
     resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==}
     peerDependencies:
@@ -20432,7 +21598,6 @@ packages:
     resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
     dependencies:
       asap: 2.0.6
-    dev: true
 
   /prompts@2.4.2:
     resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
@@ -20471,6 +21636,7 @@ packages:
 
   /proto-list@1.2.4:
     resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
+    requiresBuild: true
     dev: false
 
   /protobufjs@7.4.0:
@@ -20512,6 +21678,120 @@ packages:
     resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
     dev: false
 
+  /pug-attrs@3.0.0:
+    resolution: {integrity: sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==}
+    requiresBuild: true
+    dependencies:
+      constantinople: 4.0.1
+      js-stringify: 1.0.2
+      pug-runtime: 3.0.1
+    dev: false
+    optional: true
+
+  /pug-code-gen@3.0.3:
+    resolution: {integrity: sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==}
+    requiresBuild: true
+    dependencies:
+      constantinople: 4.0.1
+      doctypes: 1.1.0
+      js-stringify: 1.0.2
+      pug-attrs: 3.0.0
+      pug-error: 2.1.0
+      pug-runtime: 3.0.1
+      void-elements: 3.1.0
+      with: 7.0.2
+    dev: false
+    optional: true
+
+  /pug-error@2.1.0:
+    resolution: {integrity: sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /pug-filters@4.0.0:
+    resolution: {integrity: sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==}
+    requiresBuild: true
+    dependencies:
+      constantinople: 4.0.1
+      jstransformer: 1.0.0
+      pug-error: 2.1.0
+      pug-walk: 2.0.0
+      resolve: 1.22.10
+    dev: false
+    optional: true
+
+  /pug-lexer@5.0.1:
+    resolution: {integrity: sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==}
+    requiresBuild: true
+    dependencies:
+      character-parser: 2.2.0
+      is-expression: 4.0.0
+      pug-error: 2.1.0
+    dev: false
+    optional: true
+
+  /pug-linker@4.0.0:
+    resolution: {integrity: sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==}
+    requiresBuild: true
+    dependencies:
+      pug-error: 2.1.0
+      pug-walk: 2.0.0
+    dev: false
+    optional: true
+
+  /pug-load@3.0.0:
+    resolution: {integrity: sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==}
+    requiresBuild: true
+    dependencies:
+      object-assign: 4.1.1
+      pug-walk: 2.0.0
+    dev: false
+    optional: true
+
+  /pug-parser@6.0.0:
+    resolution: {integrity: sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==}
+    requiresBuild: true
+    dependencies:
+      pug-error: 2.1.0
+      token-stream: 1.0.0
+    dev: false
+    optional: true
+
+  /pug-runtime@3.0.1:
+    resolution: {integrity: sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /pug-strip-comments@2.0.0:
+    resolution: {integrity: sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==}
+    requiresBuild: true
+    dependencies:
+      pug-error: 2.1.0
+    dev: false
+    optional: true
+
+  /pug-walk@2.0.0:
+    resolution: {integrity: sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /pug@3.0.3:
+    resolution: {integrity: sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==}
+    dependencies:
+      pug-code-gen: 3.0.3
+      pug-filters: 4.0.0
+      pug-lexer: 5.0.1
+      pug-linker: 4.0.0
+      pug-load: 3.0.0
+      pug-parser: 6.0.0
+      pug-runtime: 3.0.1
+      pug-strip-comments: 2.0.0
+    dev: false
+    optional: true
+
   /pump@3.0.2:
     resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==}
     dependencies:
@@ -20519,6 +21799,13 @@ packages:
       once: 1.4.0
     dev: false
 
+  /punycode.js@2.3.1:
+    resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
+    engines: {node: '>=6'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /punycode@2.3.1:
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     engines: {node: '>=6'}
@@ -21444,6 +22731,15 @@ packages:
       strip-json-comments: 3.1.1
     dev: false
 
+  /run-applescript@3.2.0:
+    resolution: {integrity: sha512-Ep0RsvAjnRcBX1p5vogbaBdAGu/8j/ewpvGqnQYunnLd9SM0vWcPJewPKNnWFggf0hF0pwIgwV5XK7qQ7UZ8Qg==}
+    engines: {node: '>=4'}
+    requiresBuild: true
+    dependencies:
+      execa: 0.10.0
+    dev: false
+    optional: true
+
   /run-async@2.4.1:
     resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
     engines: {node: '>=0.12.0'}
@@ -21565,6 +22861,14 @@ packages:
       kind-of: 6.0.3
     dev: false
 
+  /selderee@0.11.0:
+    resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==}
+    requiresBuild: true
+    dependencies:
+      parseley: 0.12.1
+    dev: false
+    optional: true
+
   /select-hose@2.0.0:
     resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==}
     dev: false
@@ -21584,6 +22888,13 @@ packages:
       semver: 7.7.1
     dev: false
 
+  /semver@5.7.2:
+    resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
+    hasBin: true
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /semver@6.3.1:
     resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
     hasBin: true
@@ -21755,12 +23066,28 @@ packages:
       '@img/sharp-win32-x64': 0.33.5
     dev: false
 
+  /shebang-command@1.2.0:
+    resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
+    engines: {node: '>=0.10.0'}
+    requiresBuild: true
+    dependencies:
+      shebang-regex: 1.0.0
+    dev: false
+    optional: true
+
   /shebang-command@2.0.0:
     resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
     engines: {node: '>=8'}
     dependencies:
       shebang-regex: 3.0.0
 
+  /shebang-regex@1.0.0:
+    resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==}
+    engines: {node: '>=0.10.0'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /shebang-regex@3.0.0:
     resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
     engines: {node: '>=8'}
@@ -21934,6 +23261,12 @@ packages:
       is-fullwidth-code-point: 5.0.0
     dev: false
 
+  /slick@1.12.2:
+    resolution: {integrity: sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /smart-buffer@4.2.0:
     resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
     engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
@@ -22308,6 +23641,13 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /strip-eof@1.0.0:
+    resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==}
+    engines: {node: '>=0.10.0'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /strip-final-newline@2.0.0:
     resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
     engines: {node: '>=6'}
@@ -22751,6 +24091,13 @@ packages:
       tslib: 2.6.3
     dev: true
 
+  /tlds@1.255.0:
+    resolution: {integrity: sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==}
+    hasBin: true
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /tmp@0.0.33:
     resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
     engines: {node: '>=0.6.0'}
@@ -22777,6 +24124,12 @@ packages:
     resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
     engines: {node: '>=0.6'}
 
+  /token-stream@1.0.0:
+    resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /token-types@4.2.1:
     resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==}
     engines: {node: '>=10'}
@@ -23328,6 +24681,20 @@ packages:
     hasBin: true
     dev: true
 
+  /uc.micro@2.1.0:
+    resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /uglify-js@3.19.3:
+    resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==}
+    engines: {node: '>=0.8.0'}
+    hasBin: true
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /uid@2.0.2:
     resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==}
     engines: {node: '>=8'}
@@ -23520,6 +24887,12 @@ packages:
       tslib: 2.6.3
     dev: true
 
+  /upper-case@1.1.3:
+    resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /upper-case@2.0.2:
     resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==}
     dependencies:
@@ -23685,6 +25058,13 @@ packages:
       convert-source-map: 2.0.0
     dev: true
 
+  /valid-data-url@3.0.1:
+    resolution: {integrity: sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==}
+    engines: {node: '>=10'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /validate-npm-package-name@6.0.0:
     resolution: {integrity: sha512-d7KLgL1LD3U3fgnvWEY1cQXoO/q6EQ1BSz48Sa149V/5zVTAbgmZIpyI8TRi6U9/JNyeYLlTKsEMPtLC27RFUg==}
     engines: {node: ^18.17.0 || >=20.5.0}
@@ -23725,6 +25105,13 @@ packages:
       '@types/unist': 3.0.3
       vfile-message: 4.0.2
 
+  /void-elements@3.1.0:
+    resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
+    engines: {node: '>=0.10.0'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /w3c-xmlserializer@4.0.0:
     resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==}
     engines: {node: '>=14'}
@@ -23761,6 +25148,22 @@ packages:
     resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
     dev: false
 
+  /web-resource-inliner@6.0.1:
+    resolution: {integrity: sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==}
+    engines: {node: '>=10.0.0'}
+    requiresBuild: true
+    dependencies:
+      ansi-colors: 4.1.3
+      escape-goat: 3.0.0
+      htmlparser2: 5.0.1
+      mime: 2.6.0
+      node-fetch: 2.7.0
+      valid-data-url: 3.0.1
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+    optional: true
+
   /web-streams-polyfill@3.3.3:
     resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
     engines: {node: '>= 8'}
@@ -24155,10 +25558,28 @@ packages:
   /wildcard@2.0.1:
     resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==}
 
+  /with@7.0.2:
+    resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==}
+    engines: {node: '>= 10.0.0'}
+    requiresBuild: true
+    dependencies:
+      '@babel/parser': 7.26.9
+      '@babel/types': 7.26.9
+      assert-never: 1.4.0
+      babel-walk: 3.0.0-canary-5
+    dev: false
+    optional: true
+
   /word-wrap@1.2.5:
     resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
     engines: {node: '>=0.10.0'}
 
+  /wordwrap@1.0.0:
+    resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /wrap-ansi@6.2.0:
     resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
     engines: {node: '>=8'}

From f9cbbc939239ff667f3d97692d5ccfda08a3a0bf Mon Sep 17 00:00:00 2001
From: PengyuChen01 <121841974+PengyuChen01@users.noreply.github.com>
Date: Tue, 4 Mar 2025 21:36:51 -0500
Subject: [PATCH 07/13] feat(frontend): landing page ui optimization (#149)

Co-authored-by: pengyu <pengyuchen01@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
---
 frontend/src/app/(main)/page.tsx              | 26 +++++++++++-----
 frontend/src/components/auth-choice-modal.tsx | 21 +++++++++++--
 frontend/src/components/root/nav.tsx          |  4 +--
 frontend/src/components/root/prompt-form.tsx  |  2 +-
 frontend/src/components/sidebar.tsx           | 30 ++++++++++++++++---
 frontend/src/components/sign-in-modal.tsx     |  4 ++-
 6 files changed, 70 insertions(+), 17 deletions(-)

diff --git a/frontend/src/app/(main)/page.tsx b/frontend/src/app/(main)/page.tsx
index e6842819..1f942a76 100644
--- a/frontend/src/app/(main)/page.tsx
+++ b/frontend/src/app/(main)/page.tsx
@@ -8,9 +8,15 @@ import { useAuthContext } from '@/providers/AuthProvider';
 import { ProjectsSection } from '@/components/root/projects-section';
 import { PromptForm, PromptFormRef } from '@/components/root/prompt-form';
 import { ProjectContext } from '@/components/chat/code-engine/project-context';
-
+import { SignInModal } from '@/components/sign-in-modal';
+import { SignUpModal } from '@/components/sign-up-modal';
+import { useRouter } from 'next/navigation';
 export default function HomePage() {
+  // States for AuthChoiceModal
   const [showAuthChoice, setShowAuthChoice] = useState(false);
+  const router = useRouter();
+  const [showSignIn, setShowSignIn] = useState(false);
+  const [showSignUp, setShowSignUp] = useState(false);
 
   const promptFormRef = useRef<PromptFormRef>(null);
   const { isAuthorized } = useAuthContext();
@@ -19,9 +25,7 @@ export default function HomePage() {
   const handleSubmit = async () => {
     if (!promptFormRef.current) return;
 
-    // Get form data from the prompt form
     const { message, isPublic, model } = promptFormRef.current.getPromptData();
-
     if (!message.trim()) return;
 
     try {
@@ -37,12 +41,11 @@ export default function HomePage() {
       }
     } catch (error) {
       console.error('Error creating project:', error);
-      // Error handling is done via toast in ProjectContext
     }
   };
 
   return (
-    <div className="pt-32 pb-24 px-6 ">
+    <div className="pt-32 pb-24 px-6">
       <motion.div
         className="flex flex-col items-center"
         initial={{ opacity: 0, y: 20 }}
@@ -85,19 +88,28 @@ export default function HomePage() {
         </div>
       </motion.div>
 
-      {/* Modals */}
+      {/* Choice Modal */}
       <AuthChoiceModal
         isOpen={showAuthChoice}
         onClose={() => setShowAuthChoice(false)}
         onSignUpClick={() => {
           setShowAuthChoice(false);
+          setTimeout(() => {
+            setShowSignUp(true);
+          }, 100);
         }}
         onSignInClick={() => {
           setShowAuthChoice(false);
+          setTimeout(() => {
+            setShowSignIn(true);
+          }, 100);
         }}
       />
 
-      {/* Add this to your global CSS for the subtle pulse animation */}
+      {/* SignInModal & SignUpModal */}
+      <SignInModal isOpen={showSignIn} onClose={() => setShowSignIn(false)} />
+      <SignUpModal isOpen={showSignUp} onClose={() => setShowSignUp(false)} />
+
       <style jsx global>{`
         .animate-pulse-subtle {
           animation: pulse-subtle 2s infinite;
diff --git a/frontend/src/components/auth-choice-modal.tsx b/frontend/src/components/auth-choice-modal.tsx
index 02119567..b37de238 100644
--- a/frontend/src/components/auth-choice-modal.tsx
+++ b/frontend/src/components/auth-choice-modal.tsx
@@ -1,4 +1,5 @@
 'use client';
+
 import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
 import { BackgroundGradient } from '@/components/ui/background-gradient';
 import { Button } from '@/components/ui/button';
@@ -24,6 +25,7 @@ export function AuthChoiceModal({
         <VisuallyHidden>
           <DialogTitle>Choose Authentication Method</DialogTitle>
         </VisuallyHidden>
+
         <BackgroundGradient className="rounded-[22px] p-4 bg-white dark:bg-zinc-900">
           <div className="w-full p-6 space-y-6">
             <h2 className="text-2xl font-semibold text-center dark:text-white">
@@ -33,16 +35,31 @@ export function AuthChoiceModal({
               Choose how you want to continue
             </p>
             <div className="space-y-4">
+              {/* Sign In button */}
               <Button
                 className="w-full py-6 text-lg bg-primary hover:bg-primary/90"
-                onClick={onSignInClick}
+                onClick={() => {
+                  // 1) Close current modal
+                  onClose();
+                  // 2) After a brief delay, call onSignInClick
+                  setTimeout(() => {
+                    onSignInClick();
+                  }, 100);
+                }}
               >
                 Sign in
               </Button>
+
+              {/* Sign Up button */}
               <Button
                 variant="outline"
                 className="w-full py-6 text-lg"
-                onClick={onSignUpClick}
+                onClick={() => {
+                  onClose();
+                  setTimeout(() => {
+                    onSignUpClick();
+                  }, 100);
+                }}
               >
                 Create an account
               </Button>
diff --git a/frontend/src/components/root/nav.tsx b/frontend/src/components/root/nav.tsx
index 51a1add3..e24b805a 100644
--- a/frontend/src/components/root/nav.tsx
+++ b/frontend/src/components/root/nav.tsx
@@ -174,7 +174,7 @@ const FloatingNavbar = forwardRef<NavbarRef, FloatingNavbarProps>(
         alert('Coming Soon');
       } else if (label === 'Codefox Journey') {
         e.preventDefault();
-        window.open('https://github.com/Sma1lboy/codefox', '_blank');
+        alert('Coming Soon');
       } else {
         handleTabChange(index);
       }
@@ -182,7 +182,7 @@ const FloatingNavbar = forwardRef<NavbarRef, FloatingNavbarProps>(
 
     return (
       <>
-        <div className={`fixed top-5 left-0 right-0 z-50 ${className}`}>
+        <div className={` top-5 left-0 right-0 z-50 ${className}`}>
           <motion.div
             className={`w-full flex justify-around items-center px-6 py-4 ${containerClassName}`}
             initial="hidden"
diff --git a/frontend/src/components/root/prompt-form.tsx b/frontend/src/components/root/prompt-form.tsx
index 408e489a..3fca2bff 100644
--- a/frontend/src/components/root/prompt-form.tsx
+++ b/frontend/src/components/root/prompt-form.tsx
@@ -211,7 +211,7 @@ export const PromptForm = forwardRef<PromptFormRef, PromptFormProps>(
               >
                 <SelectTrigger
                   className={cn(
-                    'w-[117px] h-6 border-0 focus:ring-0 hover:bg-gray-100 dark:hover:bg-gray-600 pl-1',
+                    'h-6 border-0 focus:ring-0 hover:bg-gray-100 dark:hover:bg-gray-600 pl-1 min-w-max',
                     (isLoading || isRegenerating) &&
                       'opacity-50 cursor-not-allowed'
                   )}
diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx
index 7f5c4311..4e6b7147 100644
--- a/frontend/src/components/sidebar.tsx
+++ b/frontend/src/components/sidebar.tsx
@@ -8,6 +8,7 @@ import UserSettings from './user-settings';
 import { SideBarItem } from './sidebar-item';
 import { Chat } from '@/graphql/type';
 import { EventEnum } from '../const/EventEnum';
+import { useRouter } from 'next/navigation';
 
 import {
   SidebarContent,
@@ -49,6 +50,7 @@ export function ChatSideBar({
   onRefetch,
 }: SidebarProps) {
   // Use a local state only for the currently selected chat.
+  const router = useRouter();
   const [currentChatid, setCurrentChatid] = useState('');
   const { setCurProject, pollChatProject } = useContext(ProjectContext);
   // Handler for starting a new chat.
@@ -84,8 +86,27 @@ export function ChatSideBar({
           className="lg:flex items-center justify-center cursor-pointer p-2 ml-3.5 mt-2"
           onClick={() => setIsCollapsed(!isCollapsed)}
         />
-
-        <div className="flex items-center justify-start w-[85%] h-14 text-sm xl:text-lg font-normal pl-4 gap-2">
+        <Button
+          onClick={() => router.push('/')}
+          variant="ghost"
+          className="
+            w-full
+            h-14
+            flex
+            items-center
+            justify-start
+            px-4
+            gap-2
+            text-sm
+            xl:text-lg
+            font-normal
+            rounded-md
+            hover:bg-yellow-50
+            transition-all
+            duration-200
+            ease-in-out
+          "
+        >
           <Image
             src="/codefox.svg"
             alt="CodeFox Logo"
@@ -98,9 +119,10 @@ export function ChatSideBar({
               CodeFox
             </span>
           )}
-        </div>
+        </Button>
+
         {/* Divider Line */}
-        <div className="border-t border-dotted border-gray-300 my-2 w-[85%] mx-auto"></div>
+        <div className="border-t border-dotted border-gray-300 my-2 w-full mx-auto" />
 
         <Button
           onClick={() => setIsModalOpen(true)}
diff --git a/frontend/src/components/sign-in-modal.tsx b/frontend/src/components/sign-in-modal.tsx
index de69cc63..498a45b2 100644
--- a/frontend/src/components/sign-in-modal.tsx
+++ b/frontend/src/components/sign-in-modal.tsx
@@ -43,7 +43,9 @@ export function SignInModal({ isOpen, onClose }: SignInModalProps) {
       if (data?.login) {
         // Store tokens where desired (session storage for access, local for refresh)
         login(data.login.accessToken, data.login.refreshToken);
-        toast.success('Login successful!');
+        toast.success('Login successful!', {
+          position: 'bottom-right',
+        });
         setErrorMessage(null);
         onClose(); // Close the modal
 

From 20301ed8906bb2d70967ac47939aaacd4547d45f Mon Sep 17 00:00:00 2001
From: NarwhalChen <125920907+NarwhalChen@users.noreply.github.com>
Date: Tue, 4 Mar 2025 20:39:48 -0600
Subject: [PATCH 08/13] fix(frontend): fix cannot redirect into chat/project
 page after create project from home page (#151)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
  - Enhanced file recognition to support .tsx files as TypeScript React.
- Introduced new iframe controls, allowing users to refresh content and
adjust zoom levels.
- Updated the code editor’s default language for a more accurate editing
experience.
- Revised project navigation to use query parameters for improved
clarity.

- **Bug Fixes**
- Improved Docker container management with enhanced logging, error
handling, and retry mechanisms.

- **Refactor**
- Simplified chat and sidebar structures by removing unnecessary context
providers.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---
 frontend/src/components/chat/code-engine/project-context.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/frontend/src/components/chat/code-engine/project-context.tsx b/frontend/src/components/chat/code-engine/project-context.tsx
index 78dbdd16..0aa394e3 100644
--- a/frontend/src/components/chat/code-engine/project-context.tsx
+++ b/frontend/src/components/chat/code-engine/project-context.tsx
@@ -113,7 +113,7 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
       // Navigate to chat page after project creation
       if (data?.createProject?.id) {
         toast.success('Project created successfully!');
-        router.push(`/chat/${data.createProject.id}`);
+        router.push(`/chat?id=${data.createProject.id}`);
       }
     },
     onError: (error) => {

From 34a86c6df9ee0f732db87468c54210ea741975d0 Mon Sep 17 00:00:00 2001
From: NarwhalChen <125920907+NarwhalChen@users.noreply.github.com>
Date: Tue, 4 Mar 2025 20:51:50 -0600
Subject: [PATCH 09/13] =?UTF-8?q?feat(frontend):=20implement=20expandable?=
 =?UTF-8?q?=20project=20cards=20and=20fetch=20public=20p=E2=80=A6=20(#152)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

…rojects query

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced an interactive `ExpandableCard` component for project
previews, featuring a modal overlay with smooth animations.
- Added a custom hook for detecting clicks outside of elements to
enhance user interactions.

- **Refactor**
- Streamlined the public projects display by integrating a new GraphQL
query and simplifying the component structure for improved data
retrieval and presentation.

- **Chores**
- Removed unused props from the `SidebarProps` interface and the
`ChatSideBar` component for a cleaner codebase.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
---
 frontend/src/components/root/expand-card.tsx  | 173 ++++++++++++++++++
 .../src/components/root/projects-section.tsx  |  81 +-------
 frontend/src/components/sidebar.tsx           |   6 -
 frontend/src/graphql/request.ts               |  16 ++
 frontend/src/graphql/schema.gql               |   7 +-
 5 files changed, 199 insertions(+), 84 deletions(-)
 create mode 100644 frontend/src/components/root/expand-card.tsx

diff --git a/frontend/src/components/root/expand-card.tsx b/frontend/src/components/root/expand-card.tsx
new file mode 100644
index 00000000..1095aacc
--- /dev/null
+++ b/frontend/src/components/root/expand-card.tsx
@@ -0,0 +1,173 @@
+'use client';
+import Image from 'next/image';
+import React, { useEffect, useRef, useState } from 'react';
+import { AnimatePresence, motion } from 'framer-motion';
+import { X } from 'lucide-react';
+
+export function ExpandableCard({ projects }) {
+  const [active, setActive] = useState(null);
+  const [iframeUrl, setIframeUrl] = useState('');
+  const ref = useRef<HTMLDivElement>(null);
+
+  useEffect(() => {
+    function onKeyDown(event: KeyboardEvent) {
+      if (event.key === 'Escape') {
+        setActive(null);
+      }
+    }
+
+    if (active && typeof active === 'object') {
+      document.body.style.overflow = 'hidden';
+    } else {
+      document.body.style.overflow = 'auto';
+    }
+
+    window.addEventListener('keydown', onKeyDown);
+    return () => window.removeEventListener('keydown', onKeyDown);
+  }, [active]);
+
+  const getWebUrl = async (project) => {
+    if (!project) return;
+    console.log('project:', project);
+    const projectPath = project.path;
+
+    try {
+      const response = await fetch(
+        `/api/runProject?projectPath=${encodeURIComponent(projectPath)}`,
+        {
+          method: 'GET',
+          headers: {
+            'Content-Type': 'application/json',
+          },
+        }
+      );
+      const json = await response.json();
+      const baseUrl = `http://${json.domain}`;
+      setIframeUrl(baseUrl);
+      setActive(project);
+    } catch (error) {
+      console.error('fetching url error:', error);
+    }
+  };
+
+  return (
+    <>
+      <AnimatePresence mode="wait">
+        {active && (
+          <motion.div
+            onClick={() => setActive(null)}
+            initial={{ opacity: 0 }}
+            animate={{ opacity: 1 }}
+            exit={{ opacity: 0 }}
+            transition={{
+              duration: 0.3,
+              ease: [0.4, 0, 0.2, 1],
+            }}
+            className="fixed inset-0 backdrop-blur-[2px] bg-black/20 h-full w-full z-50"
+            style={{ willChange: 'opacity' }}
+          />
+        )}
+      </AnimatePresence>
+
+      <AnimatePresence mode="wait">
+        {active ? (
+          <div className="fixed inset-0 grid place-items-center z-[80] m-4">
+            <motion.button
+              initial={{ opacity: 0, scale: 0.9 }}
+              animate={{ opacity: 1, scale: 1 }}
+              exit={{ opacity: 0, scale: 0.9 }}
+              transition={{ duration: 0.2 }}
+              className="flex absolute top-4 right-4 items-center justify-center bg-white/90 hover:bg-white rounded-full h-8 w-8"
+              onClick={() => setActive(null)}
+            >
+              <X className="h-4 w-4 z-50" />
+            </motion.button>
+
+            <motion.div
+              layoutId={`card-${active.id}`}
+              ref={ref}
+              className="w-full h-full flex flex-col bg-white dark:bg-neutral-900 rounded-2xl overflow-hidden"
+              style={{ willChange: 'transform, opacity' }}
+            >
+              <motion.div className="flex-1 p-6 h-[50%]">
+                <motion.div
+                  layoutId={`content-${active.id}`}
+                  className="h-full"
+                >
+                  <motion.h3
+                    layoutId={`title-${active.id}`}
+                    className="text-xl font-semibold text-gray-900 dark:text-gray-100"
+                  >
+                    {active.name}
+                  </motion.h3>
+                  <motion.div
+                    layoutId={`meta-${active.id}`}
+                    className="mt-2 w-full h-full"
+                  >
+                    <iframe
+                      src={iframeUrl}
+                      className="w-full h-[100%]"
+                      title="Project Preview"
+                    />
+                  </motion.div>
+                </motion.div>
+              </motion.div>
+            </motion.div>
+          </div>
+        ) : null}
+      </AnimatePresence>
+
+      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
+        {projects.map((project) => (
+          <motion.div
+            key={project.id}
+            layoutId={`card-${project.id}`}
+            onClick={() => getWebUrl(project)}
+            className="group cursor-pointer"
+          >
+            <motion.div
+              layoutId={`image-container-${project.id}`}
+              className="relative rounded-xl overflow-hidden"
+            >
+              <motion.div layoutId={`image-${project.id}`}>
+                <Image
+                  src={project.image}
+                  alt={project.name}
+                  width={600}
+                  height={200}
+                  className="w-full h-48 object-cover transition duration-300 group-hover:scale-105"
+                />
+              </motion.div>
+
+              <motion.div
+                initial={{ opacity: 0 }}
+                whileHover={{ opacity: 1 }}
+                transition={{ duration: 0.2 }}
+                className="absolute inset-0 bg-black/40 flex items-center justify-center"
+              >
+                <span className="text-white font-medium px-4 py-2 rounded-lg bg-white/20 backdrop-blur-sm">
+                  View Project
+                </span>
+              </motion.div>
+            </motion.div>
+
+            <motion.div layoutId={`content-${project.id}`} className="mt-3">
+              <motion.h3
+                layoutId={`title-${project.id}`}
+                className="font-medium text-gray-900 dark:text-gray-100"
+              >
+                {project.name}
+              </motion.h3>
+              <motion.div
+                layoutId={`meta-${project.id}`}
+                className="mt-1 text-sm text-gray-500"
+              >
+                {project.author}
+              </motion.div>
+            </motion.div>
+          </motion.div>
+        ))}
+      </div>
+    </>
+  );
+}
diff --git a/frontend/src/components/root/projects-section.tsx b/frontend/src/components/root/projects-section.tsx
index 42b63eea..792d2a42 100644
--- a/frontend/src/components/root/projects-section.tsx
+++ b/frontend/src/components/root/projects-section.tsx
@@ -1,74 +1,6 @@
-import { gql, useQuery } from '@apollo/client';
-import Image from 'next/image';
-
-const FETCH_PUBLIC_PROJECTS = gql`
-  query FetchPublicProjects($input: FetchPublicProjectsInputs!) {
-    fetchPublicProjects(input: $input) {
-      id
-      projectName
-      createdAt
-      user {
-        username
-      }
-      photoUrl
-      subNumber
-    }
-  }
-`;
-
-const ProjectCard = ({ project }) => (
-  <div className="cursor-pointer group space-y-3">
-    {/* Image section with card styling */}
-    <div className="relative rounded-lg overflow-hidden shadow-md transform transition-all duration-300 hover:-translate-y-2 hover:shadow-xl">
-      <Image
-        src={project.image}
-        alt={project.name}
-        width={600}
-        height={200}
-        className="w-full h-36 object-cover transition-all duration-300 group-hover:brightness-75"
-      />
-      <div className="absolute bottom-0 right-0 bg-black bg-opacity-70 text-white text-xs px-2 py-1 rounded-tl-md">
-        {project.forkNum} forks
-      </div>
-
-      {/* "View Detail" hover effect */}
-      <div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
-        <button className="bg-primary-500 hover:bg-primary-600 text-white px-4 py-2 rounded-md font-medium transform transition-transform duration-300 scale-90 group-hover:scale-100">
-          View Detail
-        </button>
-      </div>
-    </div>
-
-    {/* Info section */}
-    <div className="px-1">
-      <div className="flex flex-col space-y-2">
-        <h3 className="text-sm font-semibold text-gray-800 dark:text-gray-100 truncate">
-          {project.name}
-        </h3>
-        <div className="flex items-center justify-between">
-          <div className="flex items-center">
-            <span className="inline-block w-5 h-5 rounded-full bg-gray-300 dark:bg-gray-600 mr-2"></span>
-            <span className="text-sm font-medium text-gray-700 dark:text-gray-300">
-              {project.author}
-            </span>
-          </div>
-          <span className="text-xs text-gray-500 dark:text-gray-400">
-            {formatDate(project.createDate)}
-          </span>
-        </div>
-      </div>
-    </div>
-  </div>
-);
-
-const formatDate = (dateString) => {
-  const date = new Date(dateString);
-  return date.toLocaleDateString('en-US', {
-    year: 'numeric',
-    month: 'short',
-    day: 'numeric',
-  });
-};
+import { useQuery } from '@apollo/client';
+import { FETCH_PUBLIC_PROJECTS } from '@/graphql/request';
+import { ExpandableCard } from './expand-card';
 
 export function ProjectsSection() {
   // Execute the GraphQL query with provided variables
@@ -83,6 +15,7 @@ export function ProjectsSection() {
   const transformedProjects = fetchedProjects.map((project) => ({
     id: project.id,
     name: project.projectName,
+    path: project.projectPath,
     createDate: project.createdAt
       ? new Date(project.createdAt).toISOString().split('T')[0]
       : '2025-01-01',
@@ -112,11 +45,7 @@ export function ProjectsSection() {
         ) : (
           <div>
             {transformedProjects.length > 0 ? (
-              <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
-                {transformedProjects.map((project) => (
-                  <ProjectCard key={project.id} project={project} />
-                ))}
-              </div>
+              <ExpandableCard projects={transformedProjects} />
             ) : (
               // Show message when no projects are available
               <div className="text-center py-10 text-gray-500 dark:text-gray-400">
diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx
index 4e6b7147..d69c0ede 100644
--- a/frontend/src/components/sidebar.tsx
+++ b/frontend/src/components/sidebar.tsx
@@ -19,9 +19,7 @@ import {
   SidebarRail,
   SidebarFooter,
 } from './ui/sidebar';
-import { cn } from '@/lib/utils';
 import { ProjectContext } from './chat/code-engine/project-context';
-import { useRouter } from 'next/navigation';
 
 interface SidebarProps {
   setIsModalOpen: (value: boolean) => void; // Parent setter to update collapse state
@@ -41,9 +39,6 @@ export function ChatSideBar({
   setIsModalOpen,
   isCollapsed,
   setIsCollapsed,
-  isMobile,
-  chatListUpdated,
-  setChatListUpdated,
   chats,
   loading,
   error,
@@ -60,7 +55,6 @@ export function ChatSideBar({
     const event = new Event(EventEnum.NEW_CHAT);
     window.dispatchEvent(event);
   }, []);
-  const router = useRouter();
 
   if (loading) return <SidebarSkeleton />;
   if (error) {
diff --git a/frontend/src/graphql/request.ts b/frontend/src/graphql/request.ts
index 3a0d5fef..31f60f55 100644
--- a/frontend/src/graphql/request.ts
+++ b/frontend/src/graphql/request.ts
@@ -17,6 +17,22 @@ export interface ModelTagsData {
   getAvailableModelTags: string[];
 }
 
+export const FETCH_PUBLIC_PROJECTS = gql`
+  query FetchPublicProjects($input: FetchPublicProjectsInputs!) {
+    fetchPublicProjects(input: $input) {
+      id
+      projectName
+      projectPath
+      createdAt
+      user {
+        username
+      }
+      photoUrl
+      subNumber
+    }
+  }
+`;
+
 export const CREATE_CHAT = gql`
   mutation CreateChat($input: NewChatInput!) {
     createChat(newChatInput: $input) {
diff --git a/frontend/src/graphql/schema.gql b/frontend/src/graphql/schema.gql
index c637f81d..b14c81ba 100644
--- a/frontend/src/graphql/schema.gql
+++ b/frontend/src/graphql/schema.gql
@@ -137,7 +137,9 @@ type Project {
   projectPath: String!
   subNumber: Float!
 
-  """Projects that are copies of this project"""
+  """
+  Projects that are copies of this project
+  """
   subscribers: [Project!]
   uniqueProjectId: String!
   updatedAt: Date!
@@ -224,7 +226,8 @@ type User {
   isActive: Boolean!
   isDeleted: Boolean!
   projects: [Project!]!
-  subscribedProjects: [Project!] @deprecated(reason: "Use projects with forkedFromId instead")
+  subscribedProjects: [Project!]
+    @deprecated(reason: "Use projects with forkedFromId instead")
   updatedAt: Date!
   username: String!
 }
\ No newline at end of file

From a6cf08512b8793c449a6404aa69b6c9b9a83b9c0 Mon Sep 17 00:00:00 2001
From: Jackson Chen <90215880+Sma1lboy@users.noreply.github.com>
Date: Tue, 4 Mar 2025 20:58:23 -0600
Subject: [PATCH 10/13] chore: remove unused model configuration from
 environment file (#145)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Documentation**
- Revamped the project overview with a clear title, reorganized sections
highlighting key features, architecture, system requirements,
development tools, and additional resources.
- Enhanced installation instructions, troubleshooting guidance, and URLs
for accessing different components.

- **Chores**
- Streamlined configuration by removing redundant AI model integration
settings.



<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---
 README.md | 434 +++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 314 insertions(+), 120 deletions(-)

diff --git a/README.md b/README.md
index 57ce37b8..a1013b3c 100644
--- a/README.md
+++ b/README.md
@@ -1,187 +1,381 @@
-> [!CAUTION]  
-> Still working on it, it will release soon
+# CodeFox
 
 ![LOGO](./assets/badge.svg)
 
-Welcome to CODEFOX! A next generation AI sequence full stack project generator with interactive chatbot
+Welcome to CODEFOX! A next generation AI sequence full stack project generator with interactive chatbot.
 
-# News
+## Key Features
 
-🌟 Oct. 18, 2024: First line of Codefox code committed.
-
-# Exciting features
-
-💻 **Transforming Ideas into Projects**  
-🚀 **Extraordinary Modeling System**: Integrates an AI model to seamlessly connect every aspect of your project.  
+💻 **Transforming Ideas into Projects**
+🚀 **Extraordinary Modeling System**: Integrates an AI model to seamlessly connect every aspect of your project.
 🤖 **Multi-Agent Generator**: Create and manage multiple intelligent agents to enhance project functionality.
-⚡ **One-Click Deployment**: Deploy your project effortlessly to cloud services or clone it locally with ease.  
-✨ **Live Preview**: Interact with your project while engaging in AI-powered conversations to make real-time modifications.  
+⚡ **One-Click Deployment**: Deploy your project effortlessly to cloud services or clone it locally with ease.
+✨ **Live Preview**: Interact with your project while engaging in AI-powered conversations to make real-time modifications.
 🔧 **Precise Code Customization**: Leverage targeted and efficient visual tools for precise module adjustments.
 
-## Support
-
-> [!WARNING]  
-> adding later
-
-**Revolutionize development with this disruptive platform. Join now and set the new standard!**
-
-```mermaid
-graph TD
-    subgraph Project_Generate_Layer[Project Generate Layer]
-        UP[User Project Info] --> PRD[Product Requirements Document]
-        PRD --> FRD[Feature Requirements Document]
-        PRD --> UXSD[UX Sitemap Document]
-        UXSD --> UXDD[UX Datamap Document]
-        UXDD --> DRD[Database Requirements Document]
-        DRD --> DBS[DB/schemas]
-        DRD --> DBP[DB/postgres]
-        DRD --> BRD[Backend Requirements Document]
-
-        %% Frontend related generations
-        UXSD --> USS[ux/sitemap-structure]
-        USS --> ROUTE[frontend/routing]
-        UXDD --> UDS[ux/datamap-structure]
-        UXDD --> UDV[ux/datamap-views]
-
-        %% Webview generations
-        USS --> WV1[webview/page1]
-        USS --> WV2[webview/page2]
-        USS --> WV3[webview/page3]
-        USS --> ROOT[webview/root]
-        UDV --> ROOT
-
-        %% Optional: Show multiple pages with a note
-        note[...more webviews...]
-        USS --> note
-    end
-
-    %% Styling
-    classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px
-    classDef boxStyle fill:#fff,stroke:#666,stroke-width:1px
-    classDef noteStyle fill:#fff4e6,stroke:#d9480f,stroke-width:1px
-    class UP,PRD,FRD,UXSD,UXDD,DRD,DBS,DBP,BRD,USS,UDS,UDV,ROUTE,WV1,WV2,WV3,ROOT boxStyle
-    class note noteStyle
-    classDef layerStyle fill:#f4f4f4,stroke:#666,stroke-width:1px,stroke-dasharray: 5 5
-    class Project_Generate_Layer layerStyle
-```
-
-# CodeFox Development Guide
-
 ## Prerequisites
 
-Before you begin, ensure you have the following installed:
+### System Requirements
 
 - Node.js >= 18.0.0
+- PostgreSQL >= 14.0
+- GPU (Optional, for local LLM model running)
+- Memory: Minimum 16GB RAM recommended
+- Storage: At least 10GB free space
+
+### Development Tools
+
 - PNPM 9.1.2 (`npm install -g pnpm@9.1.2`)
 - Tmux >= 3.2
 - Tmuxinator >= 3.0.0 (`gem install tmuxinator`)
 
-## Project Structure
-
-The project consists of three main components:
+### Optional Requirements
 
-```
-codefox/
-├── backend/         # NestJS backend server
-├── frontend/        # Next.js frontend application
-└── llm-server/     # LLM service
-```
+- NVIDIA CUDA Toolkit (for GPU acceleration)
+- Docker & Docker Compose (for containerized development)
 
 ## Installation
 
-1. Clone the repository:
+1. **Clone the repository**
 
 ```bash
 git clone <repository-url>
 cd codefox
 ```
 
-2. Install dependencies:
+2. **Install dependencies**
 
 ```bash
 pnpm install
 ```
 
-3. Set up environment variables:
+3. **Environment Configuration**
 
-```bash
-# Copy and configure environment files for each service
-cp backend/.env.template backend/.env
-cp frontend/.env.template frontend/.env
-cp llm-server/.env.template llm-server/.env
+Create and configure environment files for each service:
+
+**Backend (.env)**
+
+```env
+PORT=8080
+JWT_SECRET=<your-jwt-secret>
+JWT_REFRESH=<your-refresh-token-secret>
+SALT_ROUNDS=10
+OPENAI_BASE_URI=http://localhost:3001
 ```
 
+**Frontend (.env)**
+
+```env
+NEXT_PUBLIC_GRAPHQL_URL=http://localhost:8080/graphql
+```
+
+**LLM Server (.env)**
+
+```env
+PORT=3001
+MODEL_PATH=/path/to/model
+MODEL_TYPE=llama
+NODE_ENV=development
+```
+
+**Model Configuration (.codefox/config.json)**
+
+Configure the AI models for chat and embedding. For cloud-based models, provide endpoint and token. For local models, omit the endpoint.
+
+```json
+{
+  "$schema": "../config.schema.json",
+  "chat": [
+    {
+      "model": "openai/gpt-4o-mini", // Required: Model identifier
+      "alias": "gpt-4o-mini", // Required: Model alias for reference
+      "endpoint": "https://openrouter.ai/api/v1", // Optional: API endpoint (omit for local models)
+      "token": "<your-openrouter-token>", // Optional: API token (required if endpoint is specified)
+      "default": true, // Optional: Set as default model
+      "rps": 30 // Optional: Requests per second limit
+    }
+  ],
+  "embedding": [
+    {
+      "model": "openai/text-embedding-ada-002", // Required: Model identifier
+      "endpoint": "https://api.openai.com", // Optional: API endpoint (omit for local models)
+      "token": "<your-openai-token>" // Optional: API token (required if endpoint is specified)
+    }
+  ]
+}
+```
+
+Model Configuration Fields:
+
+- **Chat Models**:
+
+  - `model`: (Required) Model identifier
+  - `alias`: (Required) Model reference name in the system
+  - `endpoint`: (Optional) API endpoint URL. Omit for local models
+  - `token`: (Optional) API access token. Required if endpoint is specified
+  - `default`: (Optional) Set as the default model for chat
+  - `rps`: (Optional) Rate limit for API requests per second
+
+- **Embedding Models**:
+  - `model`: (Required) Model identifier
+  - `endpoint`: (Optional) API endpoint URL. Omit for local models
+  - `token`: (Optional) API access token. Required if endpoint is specified
+
 ## Development
 
 ### Using Tmuxinator (Recommended)
 
-The project includes a Tmuxinator configuration for easy development. This will start all services in separate windows with proper layouts:
+Start all services with the pre-configured Tmuxinator setup:
 
 ```bash
-pnpm dev:tmux
+pnpm dev
 ```
 
-This command will create:
-
-- Window 1: Backend server
-- Window 2: Frontend development server (left) and GraphQL codegen watcher (right)
-- Window 3: LLM server
-
-Tmux Navigation:
+This creates a development environment with:
 
-- `Ctrl+a 1/2/3` - Switch between windows
-- `Ctrl+a r` - Restart current pane's service
-- `Ctrl+a d` - Detach from session
+- Backend server (http://localhost:8080)
+- Frontend development server (http://localhost:3000)
+- LLM server (http://localhost:3001)
+- GraphQL codegen watcher
 
 ### Manual Development
 
-If you prefer to run services individually:
+Start services individually:
 
 ```bash
-# Start all services
+# Start backend
+cd backend
 pnpm dev
 
-# Or start services individually
-pnpm dev:backend    # Start backend only
-cd frontend && pnpm dev  # Start frontend only
-cd llm-server && pnpm dev  # Start LLM server only
+# Start frontend
+cd frontend
+pnpm dev
+
+# Start LLM server
+cd llm-server
+pnpm dev
 ```
 
-## Additional Commands
+### Development URLs
+
+- Frontend: http://localhost:3000
+- Backend GraphQL Playground: http://localhost:8080/graphql
+- LLM Server: http://localhost:3001
+
+## Architecture Overview
+
+CodeFox consists of three main components that work together:
 
-```bash
-pnpm build          # Build all packages
-pnpm lint          # Run linting
-pnpm format        # Format code
-pnpm test          # Run tests
+```
+        +-------------+
+        |  Frontend   |
+        | (Next.js)   |
+        +------+------+
+               |
+               | GraphQL
+               |
+        +------v------+
+        |  Backend    |
+        | (NestJS)    |
+        +------+------+
+               |
+               | HTTP/WebSocket
+               |
+        +------v------+
+        | LLM Server  |
+        +-------------+
 ```
 
-## GraphQL Code Generation
+- **Frontend (Next.js)**: Provides the user interface and handles client-side logic
+- **Backend (NestJS)**: Manages business logic, authentication, and project generation
+- **LLM Server**: Handles AI model interactions and code generation
 
-The frontend uses GraphQL with automatic type generation. The codegen watcher is automatically started in the Tmuxinator setup, but you can also run it manually:
+### Build System Architecture
 
-```bash
-cd frontend
-pnpm generate:watch
+The backend includes a sophisticated build system that manages project generation through a sequence of dependent tasks. Here's how it works:
+
+```mermaid
+sequenceDiagram
+    participant User
+    participant Context as BuilderContext
+    participant Manager as HandlerManager
+    participant Handler as BuildHandler
+    participant Monitor as BuildMonitor
+    participant VDir as VirtualDirectory
+
+    User->>Context: Create new build sequence
+    Context->>Manager: Initialize handlers
+    Context->>Monitor: Start sequence monitoring
+
+    loop For each node in sequence
+        Context->>Context: Check dependencies
+        alt Dependencies met
+            Context->>Manager: Get handler instance
+            Manager-->>Context: Return handler
+            Context->>Monitor: Start node execution
+            Context->>Handler: Execute run()
+
+            Handler->>VDir: Update virtual files
+            VDir-->>Handler: Files updated
+
+            Handler-->>Context: Return result
+            Context->>Monitor: End node execution
+        else Dependencies not met
+            Context->>Context: Wait and retry
+        end
+    end
+
+    Context->>Monitor: Generate build report
+    Context-->>User: Return project UUID
 ```
 
-## Troubleshooting
+Key components:
 
-If you encounter any issues:
+1. **BuilderContext**
 
-1. Ensure all environment variables are properly set
-2. Check if all required services are running
-3. Clear node_modules and reinstall dependencies:
+   - Manages the execution state of build nodes
+   - Handles dependency resolution
+   - Coordinates between handlers and virtual filesystem
 
-```bash
-pnpm clean
-pnpm install
+2. **BuildHandlerManager**
+
+   - Singleton managing handler instances
+   - Provides handler registration and retrieval
+   - Manages handler dependencies
+
+3. **BuildHandler**
+
+   - Implements specific build tasks
+   - Can declare dependencies on other handlers
+   - Has access to virtual filesystem and model
+
+4. **BuildMonitor**
+
+   - Tracks execution progress
+   - Records timing and success/failure
+   - Generates build reports
+
+5. **VirtualDirectory**
+   - Manages in-memory file structure
+   - Provides file operations during build
+   - Ensures atomic file updates
+
+### Full-Stack Project Generation Workflow
+
+The build system follows a structured workflow to generate a complete full-stack project:
+
+```mermaid
+graph TD
+    %% Project Initialization
+    Init[Project Initialization] --> Product[Product Requirements]
+    Product --> UX[UX Design]
+
+    %% UX Design Flow
+    UX --> Sitemap[Sitemap Structure]
+    UX --> Datamap[Data Structure]
+
+    %% Backend Development
+   Datamap --> DB[Database Schema]
+   DB --> BE[Backend Structure]
+   BE --> API[API Design]
+   BE --> BackendCode[Backend Code]
+   API --> BackendCode
+
+   %% Frontend Development
+   Sitemap --> Routes[Route Structure]
+   Datamap --> Components[Component Design]
+   Components --> Views[View Implementation]
+
+   %% File Management and Generation
+   Views --> FE[Frontend Code]
+   API --> FE
+
+   %% Subgraphs for different roles
+   subgraph "Product Manager"
+       Init
+       Product
+   end
+
+   subgraph "UX Designer"
+       UX
+       Sitemap
+       Datamap
+   end
+
+   subgraph "Backend Engineer"
+       DB
+       BE
+       API
+       BackendCode
+   end
+
+   subgraph "Frontend Engineer"
+       Routes
+       Components
+       Views
+       FE
+   end
+
+   %% Styling
+   classDef product fill:#e1f5fe,stroke:#01579b
+   classDef ux fill:#f3e5f5,stroke:#4a148c
+   classDef backend fill:#e8f5e9,stroke:#1b5e20
+   classDef frontend fill:#fff3e0,stroke:#e65100
+
+   class Init,Product product
+   class UX,Sitemap,Datamap ux
+   class DB,BE,API,BackendCode backend
+   class Routes,Components,Views,FE frontend
 ```
 
-4. For Tmuxinator issues:
-   - Ensure Tmux is running version 3.2 or higher
-   - Check if the session is already running: `tmux ls`
-   - Kill existing session if needed: `tmux kill-session -t codefox`
+## Troubleshooting
+
+### Common Issues
+
+1. **Port Conflicts**
+
+   - Ensure ports 3000, 8080, and 3001 are available
+   - Check for any running processes: `lsof -i :<port>`
+
+2. **Environment Issues**
+
+   - Verify all environment variables are properly set
+   - Ensure model path is correct in LLM server configuration
+   - Verify model configurations in .codefox/config.json:
+     - Check model identifiers are correct
+     - Validate endpoint URLs for cloud-based models
+     - Ensure API tokens are valid
+     - Verify local model paths for non-cloud models
+
+3. **Build Issues**
+
+   ```bash
+   # Clean installation
+   pnpm clean
+   rm -rf node_modules
+   pnpm install
+
+   # Rebuild all packages
+   pnpm build
+   ```
+
+4. **Tmuxinator Issues**
+   - Ensure Tmux version is >= 3.2: `tmux -V`
+   - Kill existing session: `tmux kill-session -t codefox`
+   - Check session status: `tmux ls`
+
+## Additional Resources
+
+- [API Documentation](./docs/api.md)
+- [Contributing Guidelines](./CONTRIBUTING.md)
+- [Change Log](./CHANGELOG.md)
+
+## Support
+
+For support and questions:
+
+- GitHub Issues: [Create an issue](https://github.com/your-repo/issues)
+- Documentation: [CodeFox Docs](./codefox-docs)
 
 ## License
 

From 4ca3a2c9daefa9798e575e1bf37b8d48cfb21661 Mon Sep 17 00:00:00 2001
From: PengyuChen01 <121841974+PengyuChen01@users.noreply.github.com>
Date: Tue, 4 Mar 2025 21:59:00 -0500
Subject: [PATCH 11/13] Feat fix sidebar need reload when first time load
 (#150)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Improved authentication experience with dedicated sign-in and sign-up
modals featuring smoother transitions.
- Conditional data fetching based on user authorization enhances
security and performance.
- Navigation updates now provide clear alerts for upcoming features
(e.g., a “Coming Soon” message when selecting specific tabs).

- **Style**
- Enhanced UI responsiveness with adjustments to layouts, flexible
component widths, and repositioned notifications for a better visual
experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: pengyu <pengyuchen01@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Sma1lboy <541898146chen@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
 frontend/src/components/chat/chat-layout.tsx    |  4 +++-
 .../chat/code-engine/project-context.tsx        |  4 +++-
 frontend/src/components/chat/index.tsx          |  3 +++
 frontend/src/components/sidebar.tsx             |  8 --------
 frontend/src/hooks/useChatList.ts               |  4 +++-
 frontend/src/hooks/useModels.ts                 |  4 ++--
 frontend/src/providers/AuthProvider.tsx         | 17 ++++++++++++-----
 7 files changed, 26 insertions(+), 18 deletions(-)

diff --git a/frontend/src/components/chat/chat-layout.tsx b/frontend/src/components/chat/chat-layout.tsx
index 0f13d951..70a00070 100644
--- a/frontend/src/components/chat/chat-layout.tsx
+++ b/frontend/src/components/chat/chat-layout.tsx
@@ -14,7 +14,9 @@ export default function ChatLayout({
   const { isAuthorized } = useAuthContext();
   const [isModalOpen, setIsModalOpen] = useState(false);
 
-  const { refetch } = useQuery(GET_USER_PROJECTS);
+  const { refetch } = useQuery(GET_USER_PROJECTS, {
+    skip: !isAuthorized,
+  });
   const router = useRouter();
 
   useEffect(() => {
diff --git a/frontend/src/components/chat/code-engine/project-context.tsx b/frontend/src/components/chat/code-engine/project-context.tsx
index 0aa394e3..df3578c3 100644
--- a/frontend/src/components/chat/code-engine/project-context.tsx
+++ b/frontend/src/components/chat/code-engine/project-context.tsx
@@ -18,6 +18,7 @@ import {
 import { Project } from '../project-modal';
 import { useRouter } from 'next/navigation';
 import { toast } from 'sonner'; // Assuming you use Sonner for toasts
+import { useAuthContext } from '@/providers/AuthProvider';
 
 export interface ProjectContextType {
   projects: Project[];
@@ -48,7 +49,7 @@ export const ProjectContext = createContext<ProjectContextType | undefined>(
 
 export function ProjectProvider({ children }: { children: ReactNode }) {
   const router = useRouter();
-
+  const { isAuthorized } = useAuthContext();
   const [projects, setProjects] = useState<Project[]>([]);
   const [curProject, setCurProject] = useState<Project | undefined>(undefined);
   const [filePath, setFilePath] = useState<string | null>(null);
@@ -85,6 +86,7 @@ export function ProjectProvider({ children }: { children: ReactNode }) {
 
   const { loading, error, refetch } = useQuery(GET_USER_PROJECTS, {
     fetchPolicy: 'network-only',
+    skip: !isAuthorized,
     onCompleted: (data) => {
       setProjects(data.getUserProjects);
       // If we have a current project in the list, update it
diff --git a/frontend/src/components/chat/index.tsx b/frontend/src/components/chat/index.tsx
index e373739c..4ed551c8 100644
--- a/frontend/src/components/chat/index.tsx
+++ b/frontend/src/components/chat/index.tsx
@@ -17,9 +17,11 @@ import { useChatStream } from '@/hooks/useChatStream';
 import { CodeEngine } from './code-engine/code-engine';
 import { useProjectStatusMonitor } from '@/hooks/useProjectStatusMonitor';
 import { Loader2 } from 'lucide-react';
+import { useAuthContext } from '@/providers/AuthProvider';
 
 export default function Chat() {
   // Initialize state, refs, and custom hooks
+  const { isAuthorized } = useAuthContext();
   const urlParams = new URLSearchParams(window.location.search);
   const [chatId, setChatId] = useState('');
   const [messages, setMessages] = useState<any[]>([]);
@@ -36,6 +38,7 @@ export default function Chat() {
   // Apollo query to fetch chat history
   useQuery(GET_CHAT_HISTORY, {
     variables: { chatId },
+    skip: !isAuthorized || !chatId,
     onCompleted: (data) => {
       if (data?.getChatHistory) {
         setMessages(data.getChatHistory);
diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx
index d69c0ede..3dc97c92 100644
--- a/frontend/src/components/sidebar.tsx
+++ b/frontend/src/components/sidebar.tsx
@@ -55,20 +55,12 @@ export function ChatSideBar({
     const event = new Event(EventEnum.NEW_CHAT);
     window.dispatchEvent(event);
   }, []);
-
   if (loading) return <SidebarSkeleton />;
   if (error) {
     console.error('Error loading chats:', error);
     return null;
   }
 
-  console.log(
-    'ChatSideBar state: isCollapsed:',
-    isCollapsed,
-    'currentChatid:',
-    currentChatid
-  );
-
   return (
     <div
       data-collapsed={isCollapsed}
diff --git a/frontend/src/hooks/useChatList.ts b/frontend/src/hooks/useChatList.ts
index 9b3b8e7d..31245a60 100644
--- a/frontend/src/hooks/useChatList.ts
+++ b/frontend/src/hooks/useChatList.ts
@@ -2,10 +2,11 @@ import { useQuery } from '@apollo/client';
 import { GET_USER_CHATS } from '@/graphql/request';
 import { Chat } from '@/graphql/type';
 import { useState, useCallback, useMemo } from 'react';
+import { useAuthContext } from '@/providers/AuthProvider';
 
 export function useChatList() {
   const [chatListUpdated, setChatListUpdated] = useState(false);
-
+  const { isAuthorized } = useAuthContext();
   const {
     data: chatData,
     loading,
@@ -13,6 +14,7 @@ export function useChatList() {
     refetch,
   } = useQuery<{ getUserChats: Chat[] }>(GET_USER_CHATS, {
     fetchPolicy: chatListUpdated ? 'network-only' : 'cache-first',
+    skip: !isAuthorized,
   });
 
   const handleRefetch = useCallback(() => {
diff --git a/frontend/src/hooks/useModels.ts b/frontend/src/hooks/useModels.ts
index 3ccc3260..1a23270d 100644
--- a/frontend/src/hooks/useModels.ts
+++ b/frontend/src/hooks/useModels.ts
@@ -9,9 +9,9 @@ interface ModelsCache {
   models: string[];
   lastUpdate: number;
 }
-
 const CACHE_DURATION = 30 * 60 * 1000;
 export const useModels = () => {
+  const { isAuthorized } = useAuthContext();
   const [selectedModel, setSelectedModel] = useState<string | undefined>(
     undefined
   );
@@ -50,7 +50,7 @@ export const useModels = () => {
   const { data, loading, error } = useQuery<{
     getAvailableModelTags: string[];
   }>(GET_MODEL_TAGS, {
-    skip: !shouldUpdateCache(),
+    skip: !isAuthorized || !shouldUpdateCache(),
     onCompleted: (data) => {
       console.log(data);
       if (data?.getAvailableModelTags) {
diff --git a/frontend/src/providers/AuthProvider.tsx b/frontend/src/providers/AuthProvider.tsx
index 968ca16a..20b99d25 100644
--- a/frontend/src/providers/AuthProvider.tsx
+++ b/frontend/src/providers/AuthProvider.tsx
@@ -53,12 +53,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
       setUser(null);
       return false;
     }
-
     try {
       const { data } = await checkToken({
         variables: { input: { token: storedToken } },
       });
-
       if (data?.checkToken) {
         setToken(storedToken);
         return true;
@@ -91,11 +89,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
         logout();
         return false;
       }
-
       const { data } = await refreshTokenMutation({
         variables: { refreshToken },
       });
-
       if (data?.refreshToken) {
         const newAccess = data.refreshToken.accessToken;
         const newRefresh = data.refreshToken.refreshToken;
@@ -104,7 +100,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
         if (newRefresh) {
           localStorage.setItem(LocalStore.refreshToken, newRefresh);
         }
-
         setToken(newAccess);
         setIsAuthorized(true);
         return newAccess;
@@ -125,6 +120,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
       localStorage.setItem(LocalStore.refreshToken, refreshToken);
 
       setToken(accessToken);
+      if (process.env.NODE_ENV !== 'production') {
+        console.log('Token saved successfully');
+      }
       setIsAuthorized(true);
       fetchUserInfo();
     },
@@ -143,6 +141,15 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
     async function initAuth() {
       setIsLoading(true);
 
+      const storedToken = localStorage.getItem(LocalStore.accessToken);
+      if (!storedToken) {
+        console.log('No stored token found, skip checkToken');
+        setIsAuthorized(false);
+        setUser(null);
+        setIsLoading(false);
+        return;
+      }
+
       let isValid = await validateToken();
 
       if (!isValid) {

From a4f1b2c9f66f1586261abc7be19dba0a4052ab6e Mon Sep 17 00:00:00 2001
From: PengyuChen01 <121841974+PengyuChen01@users.noreply.github.com>
Date: Tue, 4 Mar 2025 22:49:28 -0500
Subject: [PATCH 12/13] feat(frontend): css optimization in frontend (#156)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Refactor**
  - Streamlined the project creation process for a smoother workflow.
- **Style**
- Redesigned sidebar elements with refined spacing and improved
responsiveness.
- **New Features**
- Introduced new account management capabilities, including email
confirmation and project limit querying.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: pengyu <pengyuchen01@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: NarwhalChen <125920907+NarwhalChen@users.noreply.github.com>
---
 frontend/src/app/(main)/page.tsx         |  12 +-
 frontend/src/components/sidebar-item.tsx |   6 +-
 frontend/src/components/sidebar.tsx      | 148 ++++++++++++-----------
 frontend/src/graphql/schema.gql          |  21 +++-
 frontend/src/providers/AuthProvider.tsx  |   2 +
 5 files changed, 103 insertions(+), 86 deletions(-)

diff --git a/frontend/src/app/(main)/page.tsx b/frontend/src/app/(main)/page.tsx
index 1f942a76..accf5439 100644
--- a/frontend/src/app/(main)/page.tsx
+++ b/frontend/src/app/(main)/page.tsx
@@ -29,16 +29,8 @@ export default function HomePage() {
     if (!message.trim()) return;
 
     try {
-      // Create the project
-      const result = await createProjectFromPrompt(message, isPublic, model);
-
-      // If successful, clear the input
-      if (result) {
-        promptFormRef.current.clearMessage();
-
-        // Note: No need to navigate here as the ProjectContext's onCompleted handler
-        // in the createProject mutation will handle navigation to the chat page
-      }
+      await createProjectFromPrompt(message, isPublic, model);
+      promptFormRef.current.clearMessage();
     } catch (error) {
       console.error('Error creating project:', error);
     }
diff --git a/frontend/src/components/sidebar-item.tsx b/frontend/src/components/sidebar-item.tsx
index 91b1897a..d405dca3 100644
--- a/frontend/src/components/sidebar-item.tsx
+++ b/frontend/src/components/sidebar-item.tsx
@@ -104,11 +104,11 @@ export function SideBarItem({
         buttonVariants({
           variant,
         }),
-        'flex justify-between w-full h-14 text-base font-normal items-center group'
+        'relative flex w-full h-14 text-base font-normal items-center group px-2'
       )}
       onClick={handleChatClick}
     >
-      <div className="flex-1 flex gap-3 items-center truncate ml-2">
+      <div className="flex-1 flex items-center truncate ml-2 mr-12 min-w-0">
         <div className="flex flex-col">
           <span className="text-xs font-normal">{title || 'New Chat'}</span>
         </div>
@@ -119,7 +119,7 @@ export function SideBarItem({
           <DropdownMenuTrigger asChild>
             <Button
               variant="ghost"
-              className="flex justify-end items-center dropdown-trigger mr-2"
+              className="absolute right-2 top-1/2 transform -translate-y-1/2 w-10 h-10 rounded-md hover:bg-gray-200"
               onClick={(e) => {
                 e.preventDefault();
                 e.stopPropagation();
diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx
index 3dc97c92..8d22d661 100644
--- a/frontend/src/components/sidebar.tsx
+++ b/frontend/src/components/sidebar.tsx
@@ -1,8 +1,8 @@
 'use client';
+
 import { Button } from '@/components/ui/button';
 import Image from 'next/image';
 import { memo, useCallback, useContext, useState } from 'react';
-import { SquarePen } from 'lucide-react';
 import SidebarSkeleton from './sidebar-skeleton';
 import UserSettings from './user-settings';
 import { SideBarItem } from './sidebar-item';
@@ -22,9 +22,9 @@ import {
 import { ProjectContext } from './chat/code-engine/project-context';
 
 interface SidebarProps {
-  setIsModalOpen: (value: boolean) => void; // Parent setter to update collapse state
+  setIsModalOpen: (value: boolean) => void;
   isCollapsed: boolean;
-  setIsCollapsed: (value: boolean) => void; // Parent setter to update collapse state
+  setIsCollapsed: (value: boolean) => void;
   isMobile: boolean;
   currentChatId?: string;
   chatListUpdated: boolean;
@@ -44,11 +44,10 @@ export function ChatSideBar({
   error,
   onRefetch,
 }: SidebarProps) {
-  // Use a local state only for the currently selected chat.
   const router = useRouter();
   const [currentChatid, setCurrentChatid] = useState('');
   const { setCurProject, pollChatProject } = useContext(ProjectContext);
-  // Handler for starting a new chat.
+
   const handleNewChat = useCallback(() => {
     window.history.replaceState({}, '', '/');
     setCurrentChatid('');
@@ -64,86 +63,97 @@ export function ChatSideBar({
   return (
     <div
       data-collapsed={isCollapsed}
-      className="relative justify-between group lg:bg-accent/0 lg:dark:bg-card/0 flex flex-col h-full"
+      className="relative flex flex-col h-full justify-between group lg:bg-accent/0 lg:dark:bg-card/0"
     >
       <Sidebar collapsible="icon" side="left">
-        {/* Toggle button: Clicking this will toggle the collapse state */}
-        <SidebarTrigger
-          className="lg:flex items-center justify-center cursor-pointer p-2 ml-3.5 mt-2"
-          onClick={() => setIsCollapsed(!isCollapsed)}
-        />
-        <Button
-          onClick={() => router.push('/')}
-          variant="ghost"
-          className="
-            w-full
-            h-14
-            flex
-            items-center
-            justify-start
-            px-4
-            gap-2
-            text-sm
-            xl:text-lg
-            font-normal
-            rounded-md
-            hover:bg-yellow-50
-            transition-all
-            duration-200
-            ease-in-out
-          "
+        {/* Header Row: Fox Logo (clickable) on the left, SidebarTrigger on the right */}
+        <div
+          className={`flex items-center ${isCollapsed ? 'justify-center w-full px-0' : 'justify-between px-3'} pt-3`}
         >
-          <Image
-            src="/codefox.svg"
-            alt="CodeFox Logo"
-            width={32}
-            height={32}
-            className="flex-shrink-0 dark:invert"
-          />
           {!isCollapsed && (
-            <span className="text-primary-500 font-semibold text-lg">
-              CodeFox
-            </span>
+            <div className="flex flex-1 items-center justify-between">
+              <Button
+                onClick={() => router.push('/')}
+                variant="ghost"
+                className="inline-flex items-center gap-2 pl-0 
+          rounded-md  ease-in-out"
+              >
+                <Image
+                  src="/codefox.svg"
+                  alt="CodeFox Logo"
+                  width={40}
+                  height={40}
+                  className="dark:invert"
+                />
+                <span className="text-primary-500 font-semibold text-base">
+                  CodeFox
+                </span>
+              </Button>
+
+              {/* SidebarTrigger 保证在 CodeFox 按钮的中间 */}
+              <SidebarTrigger
+                className="flex items-center justify-center w-12 h-12 "
+                onClick={() => setIsCollapsed(!isCollapsed)}
+              />
+            </div>
+          )}
+
+          {isCollapsed && (
+            <SidebarTrigger
+              className="flex items-center justify-center w-full p-2 mt"
+              onClick={() => setIsCollapsed(!isCollapsed)}
+            />
           )}
-        </Button>
+        </div>
 
         {/* Divider Line */}
         <div className="border-t border-dotted border-gray-300 my-2 w-full mx-auto" />
 
-        <Button
-          onClick={() => setIsModalOpen(true)}
-          size="setting"
-          variant="ghost"
-          className="flex items-center justify-start w-[85%] h-14 text-xs xl:text-sm font-normal gap-2 pl-4 hover:bg-yellow-50 rounded-md transition-all duration-200 ease-in-out"
+        {/* New Project 按钮 - 依然占据整行 */}
+        <div
+          className={`flex ${isCollapsed ? 'justify-center items-center w-full px-0' : ''} w-full mt-4`}
         >
-          <div className="flex items-center gap-2">
+          <Button
+            onClick={() => {
+              if (isCollapsed) {
+                router.push('/');
+              } else {
+                setIsModalOpen(true);
+              }
+            }}
+            variant="ghost"
+            className={`h-7 w-7 flex items-center justify-center rounded-md ease-in-out ${
+              !isCollapsed && 'w-full gap-2 pl-4 justify-start'
+            }`}
+          >
             <svg
+              data-name="Layer 1"
+              viewBox="0 0 32 32"
+              preserveAspectRatio="xMidYMid meet"
               xmlns="http://www.w3.org/2000/svg"
-              fill="none"
-              viewBox="0 0 24 24"
-              strokeWidth="1.5"
-              stroke="currentColor"
-              className="w-5 h-5 text-yellow-500"
+              className={
+                isCollapsed
+                  ? 'w-8 h-8 min-w-[32px] min-h-[32px] ml-[-5px] mt-[-10px]'
+                  : 'w-10 h-10 min-w-[32px] min-h-[32px] mr-1'
+              }
+              strokeWidth="0.1"
             >
-              <path
-                strokeLinecap="round"
-                strokeLinejoin="round"
-                d="M12 18v-5.25m0 0a6.01 6.01 0 0 0 1.5-.189m-1.5.189a6.01 6.01 0 0 1-1.5-.189m3.75 7.478a12.06 12.06 0 0 1-4.5 0m3.75 2.383a14.406 14.406 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"
-              />
+              <g transform="scale(-1,1) translate(-32,0)">
+                <path
+                  d="M5,8A1,1,0,0,0,7,8V7H8A1,1,0,0,0,8,5H7V4A1,1,0,0,0,5,4V5H4A1,1,0,0,0,4,7H5ZM18,5H12a1,1,0,0,0,0,2h6a1,1,0,0,1,1,1v9.72l-1.57-1.45a1,1,0,0,0-.68-.27H8a1,1,0,0,1-1-1V12a1,1,0,0,0-2,0v3a3,3,0,0,0,3,3h8.36l3,2.73A1,1,0,0,0,20,21a1.1,1.1,0,0,0,.4-.08A1,1,0,0,0,21,20V8A3,3,0,0,0,18,5Z"
+                  fill="#808080"
+                />
+              </g>
             </svg>
-
             {!isCollapsed && (
-              <span className="text-primary-600 hover:text-primary-800 transition-colors text-sm">
+              <span className="text-gray-600 hover:text-gray-800 font-semibold text-sm relative -top-0.5">
                 New Project
               </span>
             )}
+          </Button>
+        </div>
 
-            {!isCollapsed && (
-              <SquarePen className="text-primary-400 hover:text-primary-600 transition-colors w-4 h-4" />
-            )}
-          </div>
-        </Button>
-
+        {/* 聊天列表 */}
         <SidebarContent>
           <SidebarGroup>
             <SidebarGroupContent>
@@ -171,12 +181,14 @@ export function ChatSideBar({
           </SidebarGroup>
         </SidebarContent>
 
-        <SidebarFooter>
+        {/* 底部设置 */}
+        <SidebarFooter
+          className={`mt-auto ${isCollapsed ? 'flex justify-center px-0' : ''}`}
+        >
           <UserSettings isSimple={false} />
         </SidebarFooter>
 
         <SidebarRail
-          // Optional: Provide a secondary trigger if needed.
           setIsSimple={() => setIsCollapsed(!isCollapsed)}
           isSimple={false}
         />
diff --git a/frontend/src/graphql/schema.gql b/frontend/src/graphql/schema.gql
index b14c81ba..55d76b75 100644
--- a/frontend/src/graphql/schema.gql
+++ b/frontend/src/graphql/schema.gql
@@ -57,6 +57,11 @@ input CreateProjectInput {
 """Date custom scalar type"""
 scalar Date
 
+type EmailConfirmationResponse {
+  message: String!
+  success: Boolean
+}
+
 input FetchPublicProjectsInputs {
   size: Float!
   strategy: String!
@@ -101,6 +106,7 @@ type Message {
 
 type Mutation {
   clearChatHistory(chatId: String!): Boolean!
+  confirmEmail(token: String!): EmailConfirmationResponse!
   createChat(newChatInput: NewChatInput!): Chat!
   createProject(createProjectInput: CreateProjectInput!): Chat!
   deleteChat(chatId: String!): Boolean!
@@ -110,6 +116,7 @@ type Mutation {
   refreshToken(refreshToken: String!): RefreshTokenResponse!
   regenerateDescription(input: String!): String!
   registerUser(input: RegisterUserInput!): User!
+  resendConfirmationEmail(input: ResendEmailInput!): EmailConfirmationResponse!
   subscribeToProject(projectId: ID!): Project!
   triggerChatStream(input: ChatInputType!): Boolean!
   updateChatTitle(updateChatTitleInput: UpdateChatTitleInput!): Chat
@@ -137,9 +144,7 @@ type Project {
   projectPath: String!
   subNumber: Float!
 
-  """
-  Projects that are copies of this project
-  """
+  """Projects that are copies of this project"""
   subscribers: [Project!]
   uniqueProjectId: String!
   updatedAt: Date!
@@ -171,6 +176,7 @@ type Query {
   getChatHistory(chatId: String!): [Message!]!
   getHello: String!
   getProject(projectId: String!): Project!
+  getRemainingProjectLimit: Int!
   getSubscribedProjects: [Project!]!
   getUserChats: [Chat!]
   getUserProjects: [Project!]!
@@ -190,6 +196,10 @@ input RegisterUserInput {
   username: String!
 }
 
+input ResendEmailInput {
+  email: String!
+}
+
 enum Role {
   Assistant
   System
@@ -225,9 +235,10 @@ type User {
   id: ID!
   isActive: Boolean!
   isDeleted: Boolean!
+  isEmailConfirmed: Boolean!
+  lastEmailSendTime: Date!
   projects: [Project!]!
-  subscribedProjects: [Project!]
-    @deprecated(reason: "Use projects with forkedFromId instead")
+  subscribedProjects: [Project!] @deprecated(reason: "Use projects with forkedFromId instead")
   updatedAt: Date!
   username: String!
 }
\ No newline at end of file
diff --git a/frontend/src/providers/AuthProvider.tsx b/frontend/src/providers/AuthProvider.tsx
index 20b99d25..f4239347 100644
--- a/frontend/src/providers/AuthProvider.tsx
+++ b/frontend/src/providers/AuthProvider.tsx
@@ -152,10 +152,12 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
 
       let isValid = await validateToken();
 
+      // 如果验证失败,再试图刷新
       if (!isValid) {
         isValid = (await refreshAccessToken()) ? true : false;
       }
 
+      // 最终判断
       if (isValid) {
         setIsAuthorized(true);
         await fetchUserInfo();

From 6be83682e7af317c46bc0aebb3eefd3a38a79900 Mon Sep 17 00:00:00 2001
From: Jackson Chen <90215880+Sma1lboy@users.noreply.github.com>
Date: Tue, 4 Mar 2025 21:49:41 -0600
Subject: [PATCH 13/13] feat(frontend): add email confirmation mutations and
 response types and adding control section bg (#157)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Style**
- Refined the prompt form's control section for improved spacing and
consistent background display across light and dark themes.

- **New Features**
- Enhanced the email confirmation process with detailed status responses
for confirmation and resend requests.
- Introduced the ability to query remaining project limits and updated
user account details with email confirmation status and timing
information.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
---
 frontend/src/components/root/prompt-form.tsx |  2 +-
 frontend/src/graphql/type.tsx                | 63 ++++++++++++++++++++
 2 files changed, 64 insertions(+), 1 deletion(-)

diff --git a/frontend/src/components/root/prompt-form.tsx b/frontend/src/components/root/prompt-form.tsx
index 3fca2bff..2d469e64 100644
--- a/frontend/src/components/root/prompt-form.tsx
+++ b/frontend/src/components/root/prompt-form.tsx
@@ -164,7 +164,7 @@ export const PromptForm = forwardRef<PromptFormRef, PromptFormProps>(
           </div>
 
           {/* Controls section - now separated with a background */}
-          <div className="absolute bottom-0 left-0 right-0 py-3 px-3 flex justify-between items-center  rounded-b-lg  border-gray-100 dark:border-gray-600">
+          <div className="absolute bottom-0 left-0 right-0 pb-3 px-3 flex pt-1 justify-between items-center bg-white dark:bg-gray-600 rounded-b-lg dark:border-gray-600">
             <div className="flex items-center gap-2">
               <Select
                 value={visibility}
diff --git a/frontend/src/graphql/type.tsx b/frontend/src/graphql/type.tsx
index 9f8b8b8f..eaa9c3b3 100644
--- a/frontend/src/graphql/type.tsx
+++ b/frontend/src/graphql/type.tsx
@@ -96,6 +96,12 @@ export type CreateProjectInput = {
   public?: InputMaybe<Scalars['Boolean']['input']>;
 };
 
+export type EmailConfirmationResponse = {
+  __typename: 'EmailConfirmationResponse';
+  message: Scalars['String']['output'];
+  success?: Maybe<Scalars['Boolean']['output']>;
+};
+
 export type FetchPublicProjectsInputs = {
   size: Scalars['Float']['input'];
   strategy: Scalars['String']['input'];
@@ -144,6 +150,7 @@ export type Message = {
 export type Mutation = {
   __typename: 'Mutation';
   clearChatHistory: Scalars['Boolean']['output'];
+  confirmEmail: EmailConfirmationResponse;
   createChat: Chat;
   createProject: Chat;
   deleteChat: Scalars['Boolean']['output'];
@@ -153,6 +160,7 @@ export type Mutation = {
   refreshToken: RefreshTokenResponse;
   regenerateDescription: Scalars['String']['output'];
   registerUser: User;
+  resendConfirmationEmail: EmailConfirmationResponse;
   subscribeToProject: Project;
   triggerChatStream: Scalars['Boolean']['output'];
   updateChatTitle?: Maybe<Chat>;
@@ -164,6 +172,10 @@ export type MutationClearChatHistoryArgs = {
   chatId: Scalars['String']['input'];
 };
 
+export type MutationConfirmEmailArgs = {
+  token: Scalars['String']['input'];
+};
+
 export type MutationCreateChatArgs = {
   newChatInput: NewChatInput;
 };
@@ -200,6 +212,10 @@ export type MutationRegisterUserArgs = {
   input: RegisterUserInput;
 };
 
+export type MutationResendConfirmationEmailArgs = {
+  input: ResendEmailInput;
+};
+
 export type MutationSubscribeToProjectArgs = {
   projectId: Scalars['ID']['input'];
 };
@@ -275,6 +291,7 @@ export type Query = {
   getChatHistory: Array<Message>;
   getHello: Scalars['String']['output'];
   getProject: Project;
+  getRemainingProjectLimit: Scalars['Int']['output'];
   getSubscribedProjects: Array<Project>;
   getUserChats?: Maybe<Array<Chat>>;
   getUserProjects: Array<Project>;
@@ -319,6 +336,10 @@ export type RegisterUserInput = {
   username: Scalars['String']['input'];
 };
 
+export type ResendEmailInput = {
+  email: Scalars['String']['input'];
+};
+
 export type Role = 'Assistant' | 'System' | 'User';
 
 export type StreamStatus = 'DONE' | 'STREAMING';
@@ -350,6 +371,8 @@ export type User = {
   id: Scalars['ID']['output'];
   isActive: Scalars['Boolean']['output'];
   isDeleted: Scalars['Boolean']['output'];
+  isEmailConfirmed: Scalars['Boolean']['output'];
+  lastEmailSendTime: Scalars['Date']['output'];
   projects: Array<Project>;
   /** @deprecated Use projects with forkedFromId instead */
   subscribedProjects?: Maybe<Array<Project>>;
@@ -476,9 +499,11 @@ export type ResolversTypes = ResolversObject<{
   CheckTokenInput: CheckTokenInput;
   CreateProjectInput: CreateProjectInput;
   Date: ResolverTypeWrapper<Scalars['Date']['output']>;
+  EmailConfirmationResponse: ResolverTypeWrapper<EmailConfirmationResponse>;
   FetchPublicProjectsInputs: FetchPublicProjectsInputs;
   Float: ResolverTypeWrapper<Scalars['Float']['output']>;
   ID: ResolverTypeWrapper<Scalars['ID']['output']>;
+  Int: ResolverTypeWrapper<Scalars['Int']['output']>;
   IsValidProjectInput: IsValidProjectInput;
   LoginResponse: ResolverTypeWrapper<LoginResponse>;
   LoginUserInput: LoginUserInput;
@@ -492,6 +517,7 @@ export type ResolversTypes = ResolversObject<{
   Query: ResolverTypeWrapper<{}>;
   RefreshTokenResponse: ResolverTypeWrapper<RefreshTokenResponse>;
   RegisterUserInput: RegisterUserInput;
+  ResendEmailInput: ResendEmailInput;
   Role: Role;
   StreamStatus: StreamStatus;
   String: ResolverTypeWrapper<Scalars['String']['output']>;
@@ -513,9 +539,11 @@ export type ResolversParentTypes = ResolversObject<{
   CheckTokenInput: CheckTokenInput;
   CreateProjectInput: CreateProjectInput;
   Date: Scalars['Date']['output'];
+  EmailConfirmationResponse: EmailConfirmationResponse;
   FetchPublicProjectsInputs: FetchPublicProjectsInputs;
   Float: Scalars['Float']['output'];
   ID: Scalars['ID']['output'];
+  Int: Scalars['Int']['output'];
   IsValidProjectInput: IsValidProjectInput;
   LoginResponse: LoginResponse;
   LoginUserInput: LoginUserInput;
@@ -529,6 +557,7 @@ export type ResolversParentTypes = ResolversObject<{
   Query: {};
   RefreshTokenResponse: RefreshTokenResponse;
   RegisterUserInput: RegisterUserInput;
+  ResendEmailInput: ResendEmailInput;
   String: Scalars['String']['output'];
   Subscription: {};
   UpdateChatTitleInput: UpdateChatTitleInput;
@@ -615,6 +644,16 @@ export interface DateScalarConfig
   name: 'Date';
 }
 
+export type EmailConfirmationResponseResolvers<
+  ContextType = any,
+  ParentType extends
+    ResolversParentTypes['EmailConfirmationResponse'] = ResolversParentTypes['EmailConfirmationResponse'],
+> = ResolversObject<{
+  message?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
+  success?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
+  __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
+}>;
+
 export type LoginResponseResolvers<
   ContextType = any,
   ParentType extends
@@ -668,6 +707,12 @@ export type MutationResolvers<
     ContextType,
     RequireFields<MutationClearChatHistoryArgs, 'chatId'>
   >;
+  confirmEmail?: Resolver<
+    ResolversTypes['EmailConfirmationResponse'],
+    ParentType,
+    ContextType,
+    RequireFields<MutationConfirmEmailArgs, 'token'>
+  >;
   createChat?: Resolver<
     ResolversTypes['Chat'],
     ParentType,
@@ -722,6 +767,12 @@ export type MutationResolvers<
     ContextType,
     RequireFields<MutationRegisterUserArgs, 'input'>
   >;
+  resendConfirmationEmail?: Resolver<
+    ResolversTypes['EmailConfirmationResponse'],
+    ParentType,
+    ContextType,
+    RequireFields<MutationResendConfirmationEmailArgs, 'input'>
+  >;
   subscribeToProject?: Resolver<
     ResolversTypes['Project'],
     ParentType,
@@ -861,6 +912,11 @@ export type QueryResolvers<
     ContextType,
     RequireFields<QueryGetProjectArgs, 'projectId'>
   >;
+  getRemainingProjectLimit?: Resolver<
+    ResolversTypes['Int'],
+    ParentType,
+    ContextType
+  >;
   getSubscribedProjects?: Resolver<
     Array<ResolversTypes['Project']>,
     ParentType,
@@ -926,6 +982,12 @@ export type UserResolvers<
   id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
   isActive?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
   isDeleted?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
+  isEmailConfirmed?: Resolver<
+    ResolversTypes['Boolean'],
+    ParentType,
+    ContextType
+  >;
+  lastEmailSendTime?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
   projects?: Resolver<
     Array<ResolversTypes['Project']>,
     ParentType,
@@ -947,6 +1009,7 @@ export type Resolvers<ContextType = any> = ResolversObject<{
   ChatCompletionChunkType?: ChatCompletionChunkTypeResolvers<ContextType>;
   ChatCompletionDeltaType?: ChatCompletionDeltaTypeResolvers<ContextType>;
   Date?: GraphQLScalarType;
+  EmailConfirmationResponse?: EmailConfirmationResponseResolvers<ContextType>;
   LoginResponse?: LoginResponseResolvers<ContextType>;
   Menu?: MenuResolvers<ContextType>;
   Message?: MessageResolvers<ContextType>;