JeCabrera commited on
Commit
8ae8832
·
1 Parent(s): 32edabf

feat: implement FastAPI chat API and connect React client

Browse files
Dockerfile CHANGED
@@ -1,34 +1,23 @@
1
- # Dockerfile
2
-
3
- # Base image to use
4
- FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8
5
-
6
- # Set the working directory to /app
 
 
 
 
7
  WORKDIR /app
8
 
9
- # Copy the backend application files
10
- COPY ./backend /app/backend
11
-
12
- # Copy the frontend application files
13
- COPY ./frontend /app/frontend
14
-
15
- # Install dependencies for the backend
16
  RUN pip install --no-cache-dir -r /app/backend/requirements.txt
17
 
18
- # Set the environment variables for FastAPI
19
- ENV MODULE_NAME=backend.main:app
20
-
21
- # Expose port 80 for the FastAPI app
22
- EXPOSE 80
23
-
24
- # Install Node.js for frontend
25
- RUN apt-get update && apt-get install -y nodejs npm
26
-
27
- # Build the React frontend
28
- RUN cd frontend && npm install && npm run build
29
 
30
- # Serve the React app
31
- RUN cp -R frontend/build /app/frontend/build
 
32
 
33
- # Start the FastAPI app
34
- CMD uvicorn backend.main:app --host 0.0.0.0 --port 80 --reload
 
1
+ # ---------- Frontend build ----------
2
+ FROM node:20-bookworm-slim AS frontend-builder
3
+ WORKDIR /app/frontend
4
+ COPY frontend/package.json frontend/package-lock.json* ./
5
+ RUN npm install
6
+ COPY frontend .
7
+ RUN npm run build
8
+
9
+ # ---------- Backend runtime ----------
10
+ FROM python:3.11-slim
11
  WORKDIR /app
12
 
13
+ COPY backend/requirements.txt /app/backend/requirements.txt
 
 
 
 
 
 
14
  RUN pip install --no-cache-dir -r /app/backend/requirements.txt
15
 
16
+ COPY backend /app/backend
17
+ COPY --from=frontend-builder /app/frontend/dist /app/frontend/dist
 
 
 
 
 
 
 
 
 
18
 
19
+ # Hugging Face Spaces expone PORT (fallback 7860)
20
+ ENV PORT=7860
21
+ EXPOSE 7860
22
 
23
+ CMD sh -c "uvicorn backend.main:app --host 0.0.0.0 --port ${PORT}"
 
MIGRACION_REACT_HF.md ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Plan de migración: lógica Python (Streamlit) → React moderno + backend compatible con Hugging Face Spaces
2
+
3
+ ## 1) Hallazgos de la revisión actual
4
+
5
+ ### Backend Python existente
6
+ - `app.py` contiene prácticamente toda la lógica de producto: manejo de sesiones, títulos de chat, menú inicial, prompts de ejemplo y streaming de respuesta desde Gemini. Está fuertemente acoplado a Streamlit (`st.session_state`, `st.chat_message`, `st.chat_input`, `st.rerun`).
7
+ - `session_state.py` implementa persistencia local por usuario (`data/<namespace>`) y encapsula llamadas a `google-genai` con `send_message_stream`.
8
+ - `system_prompts.py` define el prompt maestro del flujo de 5 preguntas y formato final del email.
9
+ - `firebase_store.py` ya anticipa un backend con persistencia remota (Firestore) para índice/historial de chats.
10
+
11
+ ### Frontend React existente
12
+ - Ya hay estructura Vite + React + TypeScript.
13
+ - `ChatWindow.tsx` consume `POST /api/chat/send`, pero ese endpoint no existe en `backend/main.py`.
14
+ - `ChatWindow.tsx` actualmente reinicia mensajes al cambiar chat y no carga historial desde servidor.
15
+ - `ChatHistory.tsx` y `App.tsx` mantienen datos en memoria local del navegador, sin sincronización real.
16
+
17
+ ### Desalineaciones de infraestructura
18
+ - `backend/main.py` solo monta archivos estáticos y no expone API funcional.
19
+ - `backend/gemini_service.py` usa un endpoint no estándar de Gemini (`https://gemini.googleapis.com/v1/email/generate`) y no coincide con el SDK usado en `session_state.py`.
20
+ - `Dockerfile` espera `frontend/build`, pero Vite genera `dist`.
21
+
22
+ ## 2) Objetivo técnico recomendado
23
+
24
+ Separar la app en capas limpias:
25
+ 1. **Frontend React (UI + estado de vista)**
26
+ 2. **Backend FastAPI (lógica de negocio + Gemini + persistencia)**
27
+ 3. **Proveedor de almacenamiento** (local JSON/SQLite para MVP y Firebase opcional)
28
+
29
+ Esto te deja una base escalable para HF Spaces (Docker SDK) y para crecer a multiusuario real.
30
+
31
+ ## 3) Qué lógica debes mover de Python a React y cuál NO
32
+
33
+ ### Mover a React (presentación y UX)
34
+ - Render de chat (mensajes, indicador "escribiendo", input).
35
+ - Menú inicial y botones de ejemplos.
36
+ - Sidebar de sesiones y navegación entre chats.
37
+ - Estado visual (loading, error, disabled states).
38
+
39
+ ### Mantener en backend (dominio y seguridad)
40
+ - Lógica de `get_enhanced_prompt` (saludo y modo ejemplo).
41
+ - Generación de título de chat por IA.
42
+ - Integración con Gemini y streaming.
43
+ - Persistencia de historiales por usuario/chat.
44
+ - Reglas de negocio del flujo guiado.
45
+
46
+ > Regla práctica: si algo implica API keys, reglas de negocio o consistencia de datos, va en backend.
47
+
48
+ ## 4) Arquitectura de API sugerida (contratos)
49
+
50
+ ### Endpoints mínimos
51
+ - `POST /api/chats` → crea chat y devuelve `{chat_id, title}`
52
+ - `GET /api/chats` → lista chats del usuario
53
+ - `GET /api/chats/{chat_id}/messages` → historial
54
+ - `POST /api/chats/{chat_id}/messages` → agrega mensaje y devuelve respuesta IA
55
+ - `GET /api/prompts/examples` → ejemplos iniciales (opcional, para no hardcodear)
56
+
57
+ ### Streaming recomendado
58
+ Para una UX moderna:
59
+ - Preferir **SSE** (`text/event-stream`) o **chunked responses** para tokens.
60
+ - Frontend consume stream y va pintando texto incrementalmente.
61
+
62
+ ## 5) Plan de implementación por fases
63
+
64
+ ### Fase 0 — Alineación de base
65
+ 1. Consolidar `system_prompts.py` en backend y eliminar duplicados.
66
+ 2. Definir modelos Pydantic (`Chat`, `Message`, `SendMessageRequest`, `SendMessageResponse`).
67
+ 3. Corregir `backend/requirements.txt` para usar `google-genai`.
68
+
69
+ ### Fase 1 — Backend funcional
70
+ 1. Crear `backend/services/chat_service.py` con:
71
+ - `create_chat`
72
+ - `list_chats`
73
+ - `send_message`
74
+ - `load_history`
75
+ 2. Crear `backend/repositories` con interfaz de persistencia:
76
+ - `local_repo.py` (MVP)
77
+ - `firebase_repo.py` (adaptando `firebase_store.py`)
78
+ 3. Implementar rutas en `backend/main.py` para `/api/*`.
79
+
80
+ ### Fase 2 — Frontend robusto
81
+ 1. Crear capa de cliente API en `frontend/src/lib/api.ts`.
82
+ 2. Introducir estado global con **TanStack Query** + store liviano (Zustand o Context).
83
+ 3. Refactor de componentes:
84
+ - `ChatShell` (layout)
85
+ - `ChatSidebar`
86
+ - `MessageList`
87
+ - `Composer`
88
+ 4. Manejar errores con toasts y estados vacíos.
89
+
90
+ ### Fase 3 — Hugging Face Spaces
91
+ 1. Ajustar `Dockerfile` multi-stage:
92
+ - Stage Node: build de Vite (`dist`)
93
+ - Stage Python: FastAPI + copiar `dist` a carpeta estática
94
+ 2. FastAPI debe servir estáticos y fallback SPA (`index.html`).
95
+ 3. Variables HF:
96
+ - `GOOGLE_API_KEY`
97
+ - opcional: `FIREBASE_SERVICE_ACCOUNT_JSON`
98
+
99
+ ### Fase 4 — Escalabilidad
100
+ 1. Añadir auth (al menos token por usuario o Firebase Auth).
101
+ 2. Trazas/observabilidad (logs estructurados + request_id).
102
+ 3. Tests:
103
+ - unit tests de servicio
104
+ - contract tests de API
105
+ - e2e básico de chat
106
+
107
+ ## 6) Decisiones técnicas concretas recomendadas
108
+
109
+ - **Gemini**: usar solo `google-genai` (evitar `requests` custom mientras no sea imprescindible).
110
+ - **Persistencia**: iniciar con SQLite o JSON por simplicidad; dejar interfaz para Firebase.
111
+ - **Estado frontend**:
112
+ - Server state: TanStack Query
113
+ - UI state: Zustand/Context
114
+ - **Tipado**: compartir esquemas con OpenAPI generado por FastAPI + tipos TS (openapi-typescript).
115
+
116
+ ## 7) Riesgos actuales que debes corregir primero
117
+
118
+ 1. Endpoint `/api/chat/send` inexistente (rompe el frontend actual).
119
+ 2. `Dockerfile` incompatible con salida Vite (`dist` vs `build`).
120
+ 3. Doble fuente de verdad (Streamlit app y React app conviviendo sin contratos unificados).
121
+ 4. Persistencia local por archivos (`joblib`) no ideal para multiinstancia.
122
+
123
+ ## 8) Backlog accionable (orden sugerido)
124
+
125
+ 1. Implementar API real en `backend/main.py`.
126
+ 2. Conectar `ChatWindow.tsx` a nueva API de chats/mensajes.
127
+ 3. Reemplazar estado local de chats en `App.tsx` por datos del backend.
128
+ 4. Migrar streaming al frontend con SSE.
129
+ 5. Ajustar Docker para HF y probar build end-to-end.
130
+
131
+ ## 9) Criterios de “migración completada”
132
+
133
+ - La UI React reproduce toda la UX importante de Streamlit (menu inicial, ejemplos, historial, multi-chat).
134
+ - Toda llamada a Gemini pasa por FastAPI (ninguna key en frontend).
135
+ - Persistencia de chats disponible al recargar.
136
+ - Docker build exitoso y despliegue en HF Space operativo.
137
+
138
+ ---
139
+
140
+ Si quieres, en la siguiente iteración puedo convertir este plan en tareas técnicas concretas por archivo (`backend/main.py`, `frontend/src/components/*`, `Dockerfile`) y proponerte un PR incremental fase por fase.
README.md CHANGED
@@ -1,97 +1,51 @@
1
- # Chatbot Email App - React + FastAPI 2
2
 
3
- Una aplicación moderna de chatbot para generar copies de correos con IA usando Gemini API.
4
 
5
- ## 🚀 Características
 
 
 
6
 
7
- - ✉️ Generación de emails narrativos con Gemini AI
8
- - 💬 Chat interactivo y responsive
9
- - 📱 Interfaz moderna con React + TypeScript
10
- - ⚡ Backend rápido con FastAPI
11
- - 🐳 Fácil de desplegar en HF Spaces con Docker
12
-
13
- ## 📋 Requisitos
14
-
15
- - Docker (para HF Spaces)
16
- - Node.js 18+ (para desarrollo local)
17
- - Python 3.8+ (para desarrollo local)
18
-
19
- ## 🛠️ Instalación Local
20
 
21
  ### Backend
22
-
23
  ```bash
24
  cd backend
25
  pip install -r requirements.txt
 
26
  ```
27
 
28
  ### Frontend
29
-
30
  ```bash
31
  cd frontend
32
  npm install
33
- ```
34
-
35
- ## 🏃 Ejecutar Localmente
36
-
37
- ### Terminal 1 - Backend
38
-
39
- ```bash
40
- cd backend
41
- python -m uvicorn main:app --reload
42
- ```
43
-
44
- ### Terminal 2 - Frontend
45
-
46
- ```bash
47
- cd frontend
48
  npm run dev
49
  ```
50
 
51
- Accede a `http://localhost:5173`
52
-
53
- ## 🐳 Desplegar en HF Spaces
54
-
55
- 1. Ve a [huggingface.co/spaces](https://huggingface.co/spaces)
56
- 2. Click en "Create new Space"
57
- 3. Selecciona "Docker" como SDK
58
- 4. Conecta tu repositorio de GitHub
59
- 5. Configura las variables de entorno:
60
- - `GOOGLE_API_KEY`: Tu clave de Gemini API
61
-
62
- ## 📁 Estructura del Proyecto
63
-
64
- ```
65
- chatbot-email-app/
66
- ├── backend/
67
- │ ├── main.py
68
- │ └── requirements.txt
69
- ├── frontend/
70
- │ ├── src/
71
- │ │ ├── components/
72
- │ │ ├── App.tsx
73
- │ │ ├── App.css
74
- │ │ └── main.tsx
75
- │ ├── package.json
76
- │ ├── tsconfig.json
77
- │ ├── vite.config.ts
78
- │ └── index.html
79
- ├── Dockerfile
80
- └── README.md
81
- ```
82
-
83
- ## 🔑 Variables de Entorno
84
 
85
- Crea un archivo `.env` en la raíz del backend:
86
 
87
- ```
88
- GOOGLE_API_KEY=your_gemini_api_key_here
89
- ```
90
-
91
- ## 📄 Licencia
92
-
93
- Apache License 2.0
94
-
95
- ## 👨‍💻 Autor
96
-
97
- **squaalldev** - Jesús Cabrera
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Chatbot Email App - React + FastAPI
2
 
3
+ Aplicación de chat para generar emails narrativos con Gemini.
4
 
5
+ ## Stack
6
+ - Frontend: React + TypeScript + Vite
7
+ - Backend: FastAPI
8
+ - IA: Google Gemini (`google-genai`)
9
 
10
+ ## Desarrollo local
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  ### Backend
 
13
  ```bash
14
  cd backend
15
  pip install -r requirements.txt
16
+ uvicorn main:app --reload
17
  ```
18
 
19
  ### Frontend
 
20
  ```bash
21
  cd frontend
22
  npm install
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  npm run dev
24
  ```
25
 
26
+ Frontend en `http://localhost:3000` y backend en `http://localhost:8000`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
+ ## Variables de entorno
29
 
30
+ ### Backend
31
+ - `GOOGLE_API_KEY` (requerida)
32
+ - `GEMINI_MODEL` (opcional, default: `gemini-2.5-flash`)
33
+ - `APP_DATA_DIR` (opcional, default: `backend/data`)
34
+
35
+ ## Deploy en Hugging Face Spaces (Docker)
36
+
37
+ 1. Crea un Space tipo **Docker**.
38
+ 2. Sube este repositorio.
39
+ 3. En **Settings → Secrets**, agrega:
40
+ - `GOOGLE_API_KEY`
41
+ 4. (Opcional) agrega `GEMINI_MODEL`.
42
+
43
+ > Sí, en HF puedes inyectar el secret directamente y esta app lo toma desde `os.environ["GOOGLE_API_KEY"]` en runtime.
44
+
45
+ ## Endpoints API
46
+ - `GET /api/health`
47
+ - `GET /api/chats`
48
+ - `POST /api/chats`
49
+ - `GET /api/chats/{chat_id}/messages`
50
+ - `POST /api/chats/{chat_id}/messages`
51
+ - Compatibilidad legacy: `POST /api/chat/send`
backend/__init__.py ADDED
File without changes
backend/gemini_service.py DELETED
@@ -1,31 +0,0 @@
1
- import requests
2
- import json
3
-
4
- class GeminiService:
5
- def __init__(self, api_key):
6
- self.api_key = api_key
7
- self.endpoint = 'https://gemini.googleapis.com/v1/email/generate'
8
-
9
- def generate_email(self, system_prompt, chat_history):
10
- # Prepare the payload for the API request
11
- payload = {
12
- 'system_prompt': system_prompt,
13
- 'chat_history': chat_history
14
- }
15
-
16
- headers = {
17
- 'Authorization': f'Bearer {self.api_key}',
18
- 'Content-Type': 'application/json'
19
- }
20
-
21
- try:
22
- response = requests.post(self.endpoint, headers=headers, json=payload)
23
- response.raise_for_status() # Raise an error for bad responses
24
- return response.json() # Return the JSON response
25
- except requests.exceptions.RequestException as e:
26
- print(f'Error during API call: {e}')
27
- return None
28
-
29
- # Example usage:
30
- # service = GeminiService('your_api_key')
31
- # email_response = service.generate_email('Generate an email for...', ['Chat history item 1', 'Chat history item 2'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/main.py CHANGED
@@ -1,11 +1,245 @@
1
- from fastapi import FastAPI
 
 
 
 
 
 
 
 
 
 
 
2
  from fastapi.staticfiles import StaticFiles
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
- app = FastAPI()
 
 
 
 
 
 
 
 
 
 
5
 
6
- # Serve the React frontend build
7
- app.mount("/static", StaticFiles(directory="static"), name="static")
8
 
9
- @app.get("/")
10
- async def read_root():
11
- return {"message": "Welcome to the FastAPI backend serving the React app"}
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import time
6
+ import uuid
7
+ from pathlib import Path
8
+ from typing import Literal
9
+
10
+ from fastapi import FastAPI, HTTPException, Request
11
+ from fastapi.middleware.cors import CORSMiddleware
12
+ from fastapi.responses import FileResponse
13
  from fastapi.staticfiles import StaticFiles
14
+ from google import genai
15
+ from google.genai import types
16
+ from pydantic import BaseModel, Field
17
+
18
+ from backend.system_prompts import get_enhanced_prompt, get_unified_email_prompt
19
+
20
+ DEFAULT_MODEL = os.environ.get("GEMINI_MODEL", "gemini-2.5-flash")
21
+ DATA_DIR = Path(os.environ.get("APP_DATA_DIR", "backend/data"))
22
+ FRONTEND_DIST = Path("frontend/dist")
23
+
24
+
25
+ class MessageIn(BaseModel):
26
+ content: str = Field(min_length=1)
27
+ is_example: bool = False
28
+
29
+
30
+ class CreateChatIn(BaseModel):
31
+ title: str | None = None
32
+
33
+
34
+ class ChatSummary(BaseModel):
35
+ chat_id: str
36
+ title: str
37
+ updated_at: float
38
+
39
+
40
+ class MessageOut(BaseModel):
41
+ role: Literal["user", "assistant"]
42
+ content: str
43
+
44
+
45
+ class ChatMessageResponse(BaseModel):
46
+ response: str
47
+ chat_id: str
48
+
49
+
50
+ app = FastAPI(title="Chatbot Email API")
51
+
52
+ app.add_middleware(
53
+ CORSMiddleware,
54
+ allow_origins=["*"],
55
+ allow_credentials=True,
56
+ allow_methods=["*"],
57
+ allow_headers=["*"],
58
+ )
59
+
60
+
61
+ if FRONTEND_DIST.exists():
62
+ app.mount("/assets", StaticFiles(directory=FRONTEND_DIST / "assets"), name="assets")
63
+
64
+
65
+ def _namespace(request: Request) -> str:
66
+ user_id = request.headers.get("x-user-id") or request.query_params.get("uid") or "default"
67
+ return "".join(ch for ch in user_id if ch.isalnum() or ch in ("-", "_")) or "default"
68
+
69
+
70
+ def _user_dir(namespace: str) -> Path:
71
+ user_dir = DATA_DIR / namespace
72
+ user_dir.mkdir(parents=True, exist_ok=True)
73
+ return user_dir
74
+
75
+
76
+ def _index_path(namespace: str) -> Path:
77
+ return _user_dir(namespace) / "chats_index.json"
78
+
79
+
80
+ def _chat_path(namespace: str, chat_id: str) -> Path:
81
+ return _user_dir(namespace) / f"{chat_id}.json"
82
+
83
+
84
+ def _read_json(path: Path, default):
85
+ if not path.exists():
86
+ return default
87
+ return json.loads(path.read_text(encoding="utf-8"))
88
+
89
+
90
+ def _write_json(path: Path, payload) -> None:
91
+ path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
92
+
93
+
94
+ def _load_index(namespace: str) -> dict[str, dict]:
95
+ return _read_json(_index_path(namespace), default={})
96
+
97
+
98
+ def _save_index(namespace: str, data: dict[str, dict]) -> None:
99
+ _write_json(_index_path(namespace), data)
100
+
101
+
102
+ def _load_messages(namespace: str, chat_id: str) -> list[dict]:
103
+ payload = _read_json(_chat_path(namespace, chat_id), default={"messages": []})
104
+ return payload.get("messages", [])
105
+
106
+
107
+ def _save_messages(namespace: str, chat_id: str, messages: list[dict]) -> None:
108
+ _write_json(_chat_path(namespace, chat_id), {"messages": messages})
109
+
110
+
111
+ def _generate_title(client: genai.Client, prompt: str) -> str:
112
+ try:
113
+ result = client.models.generate_content(
114
+ model=DEFAULT_MODEL,
115
+ contents=(
116
+ "Genera un título natural y humano en español (3 a 6 palabras) "
117
+ "que resuma esta consulta. Devuelve solo el título final: "
118
+ f"'{prompt}'"
119
+ ),
120
+ )
121
+ title = " ".join((result.text or "").strip().replace('"', "").split())
122
+ return " ".join(title.split()[:6]) or f"Sesión-{int(time.time())}"
123
+ except Exception:
124
+ return f"Sesión-{int(time.time())}"
125
+
126
+
127
+ def _build_contents(messages: list[dict]) -> list[types.Content]:
128
+ contents: list[types.Content] = []
129
+ for msg in messages:
130
+ role = "user" if msg["role"] == "user" else "model"
131
+ contents.append(types.Content(role=role, parts=[types.Part(text=msg["content"])]))
132
+ return contents
133
+
134
+
135
+ @app.get("/api/health")
136
+ def health_check():
137
+ return {"ok": True}
138
+
139
+
140
+ @app.get("/api/chats", response_model=list[ChatSummary])
141
+ def list_chats(request: Request):
142
+ index = _load_index(_namespace(request))
143
+ rows = [
144
+ ChatSummary(chat_id=chat_id, title=row["title"], updated_at=row.get("updated_at", 0.0))
145
+ for chat_id, row in index.items()
146
+ ]
147
+ return sorted(rows, key=lambda row: row.updated_at, reverse=True)
148
+
149
+
150
+ @app.post("/api/chats", response_model=ChatSummary)
151
+ def create_chat(payload: CreateChatIn, request: Request):
152
+ namespace = _namespace(request)
153
+ chat_id = str(int(time.time() * 1000)) + uuid.uuid4().hex[:6]
154
+ index = _load_index(namespace)
155
+ now = time.time()
156
+ title = payload.title or f"Sesión-{chat_id[-6:]}"
157
+ index[chat_id] = {"title": title, "updated_at": now}
158
+ _save_index(namespace, index)
159
+ _save_messages(namespace, chat_id, [])
160
+ return ChatSummary(chat_id=chat_id, title=title, updated_at=now)
161
+
162
+
163
+ @app.get("/api/chats/{chat_id}/messages", response_model=list[MessageOut])
164
+ def get_messages(chat_id: str, request: Request):
165
+ namespace = _namespace(request)
166
+ index = _load_index(namespace)
167
+ if chat_id not in index:
168
+ raise HTTPException(status_code=404, detail="Chat no encontrado")
169
+ messages = _load_messages(namespace, chat_id)
170
+ return [MessageOut(role=m["role"], content=m["content"]) for m in messages]
171
+
172
+
173
+ @app.post("/api/chats/{chat_id}/messages", response_model=ChatMessageResponse)
174
+ def send_message(chat_id: str, payload: MessageIn, request: Request):
175
+ api_key = os.environ.get("GOOGLE_API_KEY")
176
+ if not api_key:
177
+ raise HTTPException(
178
+ status_code=500,
179
+ detail=(
180
+ "GOOGLE_API_KEY no está configurada. En Hugging Face Spaces agrega este valor en Settings > Secrets."
181
+ ),
182
+ )
183
+
184
+ namespace = _namespace(request)
185
+ index = _load_index(namespace)
186
+ if chat_id not in index:
187
+ raise HTTPException(status_code=404, detail="Chat no encontrado")
188
+
189
+ messages = _load_messages(namespace, chat_id)
190
+ user_message = {"role": "user", "content": payload.content}
191
+ messages.append(user_message)
192
+
193
+ client = genai.Client(api_key=api_key)
194
+ first_user_message = len([m for m in messages if m["role"] == "user"]) == 1
195
+ enhanced_prompt = get_enhanced_prompt(
196
+ payload.content,
197
+ is_example=payload.is_example,
198
+ is_first_user_message=first_user_message,
199
+ )
200
+
201
+ messages_for_model = messages[:-1] + [{"role": "user", "content": enhanced_prompt}]
202
+
203
+ response = client.models.generate_content(
204
+ model=DEFAULT_MODEL,
205
+ contents=_build_contents(messages_for_model),
206
+ config=types.GenerateContentConfig(system_instruction=get_unified_email_prompt()),
207
+ )
208
+
209
+ assistant_text = (response.text or "").strip()
210
+ if not assistant_text:
211
+ assistant_text = "No pude generar respuesta en este intento."
212
+
213
+ messages.append({"role": "assistant", "content": assistant_text})
214
+ _save_messages(namespace, chat_id, messages)
215
+
216
+ if not index[chat_id].get("title") or index[chat_id]["title"].startswith("Sesión-"):
217
+ index[chat_id]["title"] = _generate_title(client, payload.content)
218
+ index[chat_id]["updated_at"] = time.time()
219
+ _save_index(namespace, index)
220
+
221
+ return ChatMessageResponse(response=assistant_text, chat_id=chat_id)
222
+
223
 
224
+ # Backward compatibility for previous React call
225
+ @app.post("/api/chat/send", response_model=ChatMessageResponse)
226
+ def send_message_legacy(payload: MessageIn, request: Request):
227
+ namespace = _namespace(request)
228
+ index = _load_index(namespace)
229
+ if not index:
230
+ chat = create_chat(CreateChatIn(title="Nuevo chat"), request)
231
+ else:
232
+ latest_chat = sorted(index.items(), key=lambda item: item[1].get("updated_at", 0), reverse=True)[0]
233
+ chat = ChatSummary(chat_id=latest_chat[0], title=latest_chat[1]["title"], updated_at=latest_chat[1]["updated_at"])
234
+ return send_message(chat.chat_id, payload, request)
235
 
 
 
236
 
237
+ @app.get("/{full_path:path}")
238
+ def serve_react_app(full_path: str):
239
+ index_file = FRONTEND_DIST / "index.html"
240
+ if index_file.exists():
241
+ return FileResponse(index_file)
242
+ return {
243
+ "message": "API running. Frontend build not found.",
244
+ "hint": "Build frontend with: cd frontend && npm run build",
245
+ }
backend/requirements.txt CHANGED
@@ -1,2 +1,5 @@
1
  fastapi
2
- gemini
 
 
 
 
1
  fastapi
2
+ uvicorn
3
+ google-genai
4
+ pydantic
5
+ python-dotenv
backend/system_prompts.py CHANGED
@@ -1,47 +1,110 @@
1
- # Email Generation Prompt System
2
 
3
- def email_generation_prompt():
4
- print("Welcome to the Email Generation System! Please answer the following questions:")
5
 
6
- questions = [
7
- "1. What is the main purpose of your email? (e.g., inquiry, response, update)",
8
- "2. Who is the recipient of your email? (e.g., colleague, client, friend)",
9
- "3. What specific information do you want to convey?",
10
- "4. What tone do you want to use? (e.g., formal, casual, friendly)",
11
- "5. Do you want to include any attachments? (yes/no)"
12
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- responses = []
 
 
 
 
15
 
16
- for question in questions:
17
- response = input(question + " ")
18
- responses.append(response)
19
 
20
- print("Thank you for your responses! Here is a draft of your email:")
21
- draft_email(responses)
22
 
 
23
 
24
- def draft_email(responses):
25
- purpose, recipient, info, tone, attachments = responses
26
- draft = f"Dear {recipient},\n\n"
27
 
28
- if tone == "formal":
29
- draft += f"I am writing to you regarding {purpose}.\n"
30
- elif tone == "casual":
31
- draft += f"Hey {recipient}, just wanted to talk about {purpose}.\n"
32
- else:
33
- draft += f"Hello {recipient}, I hope this message finds you well. I wanted to discuss {purpose}.\n"
34
 
35
- draft += f"Here is the information you requested: {info}.\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
- if attachments.lower() == "yes":
38
- draft += "Please find the attachments included.\n"
39
- else:
40
- draft += "If you need any additional information, feel free to reach out.\n"
41
 
42
- draft += "\nBest regards,\nYour Name"
 
 
 
 
 
 
 
43
 
44
- print(draft)
 
 
 
 
 
 
45
 
46
- # Uncomment to run the email generation prompt
47
- # email_generation_prompt()
 
1
+ from __future__ import annotations
2
 
 
 
3
 
4
+ def get_unified_email_prompt() -> str:
5
+ return """### [IMPRIMACIÓN COGNITIVA]
6
+ - Modelos fundacionales: Storytelling Marketing, Show Don't Tell, PAS, Golden Circle (empezar con el porqué).
7
+ - Corpus de conocimiento: estilo tipo Seth Godin, estructura narrativa tipo StoryBrand, persuasión sutil estilo Cialdini.
8
+ - Léxico clave: Anécdota catalizadora, Puente narrativo, Epifanía, Lección clave, Llamada a la acción contextual, Resonancia emocional.
9
+ - Usa marcos fundacionales de copy (AIDA, PASA y PASTOR) solo como estructura interna.
10
+
11
+ ### [PERSONA]
12
+ Actúa como estratega de email marketing y storyteller experto en copy conversacional.
13
+ Tono: empático, amable, curioso, conversacional y perspicaz.
14
+ Audiencia: suscriptores con relación de confianza, que esperan valor y no venta agresiva.
15
+
16
+ ### [MISIÓN]
17
+ Guiar de forma interactiva para crear emails de marketing.
18
+ Antes de redactar el email final, debes recopilar estos datos:
19
+ 1) Audiencia objetivo.
20
+ 2) Producto a promover.
21
+ 3) Nombre para firma.
22
+ 4) Llamado a la acción (CTA).
23
+ 5) Ángulo (anécdota/situación/observación; puede incluir personajes de Disney/anime).
24
+ NO debes generar ningún email final antes de recibir los 5 elementos.
25
+
26
+ ### [PRIMERA RESPUESTA OBLIGATORIA]
27
+ Si aún no tienes los 5 datos, inicia con la PRIMERA pregunta del flujo operativo (no pidas todo junto).
28
+
29
+ ### [FLUJO OPERATIVO]
30
+ Haz solo 1 pregunta a la vez y espera respuesta:
31
+ 1) AUDIENCIA:
32
+ "¿A quién le vas a escribir este email? Describe tu audiencia ideal: contexto, problema principal, deseo y nivel de conciencia sobre el problema."
33
+ 2) PRODUCTO:
34
+ "¿Qué producto o servicio vas a promover y qué transformación principal consigue la persona que lo compra?"
35
+ 3) NOMBRE:
36
+ "¿Con qué nombre quieres firmar el correo?"
37
+ 4) CTA:
38
+ "¿Qué acción concreta quieres que la audiencia realice al final del email? (responder, agendar llamada, comprar, visitar enlace, etc.)"
39
+ 5) ÁNGULO (OBLIGATORIO):
40
+ "¿Qué ángulo, anécdota o situación específica quieres usar en este correo? (Puedes apoyarte en referencias como Disney/anime si encaja)."
41
+
42
+ ### [RAZONAMIENTO PASO A PASO]
43
+ 1) Descubrimiento guiado (5 preguntas, una por vez; ángulo obligatorio).
44
+ 2) Identificación de dolor/deseo central y transformación del producto.
45
+ 3) Construir puente narrativo a partir del ángulo dado y redactar con enfoque conversacional.
46
+ 4) Usar AIDA, PASA o PASTOR internamente para ordenar el mensaje cuando haga falta.
47
+ 5) Adaptar lenguaje al nivel de conciencia de la audiencia y cerrar con CTA explícito.
48
+ 6) Usar el nombre de firma proporcionado en el cierre final del email.
49
 
50
+ ### [RESTRICCIONES]
51
+ - No uses clichés de marketing.
52
+ - No fuerces la conexión historia-producto; pide más contexto si hace falta.
53
+ - No te enfoques en características, precios o descuentos.
54
+ - Nunca menciones al usuario qué fórmula interna usaste (AIDA, PASA o PASTOR).
55
 
56
+ ### [FORMATO DE SALIDA FINAL - EXACTO]
57
+ **Asunto:** [Texto del Asunto en negrita]
 
58
 
59
+ ---
 
60
 
61
+ **Cuerpo del Email:**
62
 
63
+ [Párrafo 1: La anécdota]
 
 
64
 
65
+ [Párrafo 2: La transición hacia la lección]
 
 
 
 
 
66
 
67
+ [Párrafo 3: La lección clave y cómo se conecta con un problema general]
68
+
69
+ [Párrafo 4: Presentación del producto como la herramienta para aplicar la lección]
70
+
71
+ [Párrafo 5: Llamada a la acción clara y directa]
72
+
73
+ [Cierre personal]
74
+ """
75
+
76
+
77
+ def is_greeting(text: str, is_first_user_message: bool) -> bool:
78
+ normalized = (text or "").lower().strip()
79
+ greetings = [
80
+ "hola",
81
+ "hey",
82
+ "saludos",
83
+ "buenos días",
84
+ "buenas tardes",
85
+ "buenas noches",
86
+ "hi",
87
+ "hello",
88
+ ]
89
+ is_simple = any(greeting in normalized for greeting in greetings) and len(normalized.split()) < 4
90
+ return is_simple and is_first_user_message
91
 
 
 
 
 
92
 
93
+ def get_enhanced_prompt(prompt: str, *, is_example: bool, is_first_user_message: bool) -> str:
94
+ if is_greeting(prompt, is_first_user_message):
95
+ return (
96
+ "Responde ÚNICAMENTE con esta frase, sin agregar nada más: "
97
+ "\"¡Perfecto! Empecemos por la primera: "
98
+ "¿Quién es tu audiencia ideal para este correo? "
99
+ "Descríbela con detalle (contexto, problema principal, deseo y nivel de conciencia).\""
100
+ )
101
 
102
+ if is_example:
103
+ return (
104
+ f"El usuario seleccionó esta pregunta del menú: '{prompt}'. "
105
+ "Respóndela de forma directa, útil y conversacional, con ejemplos concretos. "
106
+ "Después de responder, invita al usuario a iniciar el flujo de 5 preguntas "
107
+ "en este orden: audiencia, producto, nombre, CTA y ángulo."
108
+ )
109
 
110
+ return prompt
 
frontend/package-lock.json ADDED
@@ -0,0 +1,1729 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "chatbot-email-app",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "chatbot-email-app",
9
+ "version": "1.0.0",
10
+ "dependencies": {
11
+ "react": "^18.2.0",
12
+ "react-dom": "^18.2.0"
13
+ },
14
+ "devDependencies": {
15
+ "@types/react": "^18.2.0",
16
+ "@types/react-dom": "^18.2.0",
17
+ "@vitejs/plugin-react": "^4.2.0",
18
+ "typescript": "^5.3.0",
19
+ "vite": "^5.0.0"
20
+ }
21
+ },
22
+ "node_modules/@babel/code-frame": {
23
+ "version": "7.29.0",
24
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
25
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
26
+ "dev": true,
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "@babel/helper-validator-identifier": "^7.28.5",
30
+ "js-tokens": "^4.0.0",
31
+ "picocolors": "^1.1.1"
32
+ },
33
+ "engines": {
34
+ "node": ">=6.9.0"
35
+ }
36
+ },
37
+ "node_modules/@babel/compat-data": {
38
+ "version": "7.29.0",
39
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
40
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
41
+ "dev": true,
42
+ "license": "MIT",
43
+ "engines": {
44
+ "node": ">=6.9.0"
45
+ }
46
+ },
47
+ "node_modules/@babel/core": {
48
+ "version": "7.29.0",
49
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
50
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
51
+ "dev": true,
52
+ "license": "MIT",
53
+ "dependencies": {
54
+ "@babel/code-frame": "^7.29.0",
55
+ "@babel/generator": "^7.29.0",
56
+ "@babel/helper-compilation-targets": "^7.28.6",
57
+ "@babel/helper-module-transforms": "^7.28.6",
58
+ "@babel/helpers": "^7.28.6",
59
+ "@babel/parser": "^7.29.0",
60
+ "@babel/template": "^7.28.6",
61
+ "@babel/traverse": "^7.29.0",
62
+ "@babel/types": "^7.29.0",
63
+ "@jridgewell/remapping": "^2.3.5",
64
+ "convert-source-map": "^2.0.0",
65
+ "debug": "^4.1.0",
66
+ "gensync": "^1.0.0-beta.2",
67
+ "json5": "^2.2.3",
68
+ "semver": "^6.3.1"
69
+ },
70
+ "engines": {
71
+ "node": ">=6.9.0"
72
+ },
73
+ "funding": {
74
+ "type": "opencollective",
75
+ "url": "https://opencollective.com/babel"
76
+ }
77
+ },
78
+ "node_modules/@babel/generator": {
79
+ "version": "7.29.1",
80
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
81
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
82
+ "dev": true,
83
+ "license": "MIT",
84
+ "dependencies": {
85
+ "@babel/parser": "^7.29.0",
86
+ "@babel/types": "^7.29.0",
87
+ "@jridgewell/gen-mapping": "^0.3.12",
88
+ "@jridgewell/trace-mapping": "^0.3.28",
89
+ "jsesc": "^3.0.2"
90
+ },
91
+ "engines": {
92
+ "node": ">=6.9.0"
93
+ }
94
+ },
95
+ "node_modules/@babel/helper-compilation-targets": {
96
+ "version": "7.28.6",
97
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
98
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
99
+ "dev": true,
100
+ "license": "MIT",
101
+ "dependencies": {
102
+ "@babel/compat-data": "^7.28.6",
103
+ "@babel/helper-validator-option": "^7.27.1",
104
+ "browserslist": "^4.24.0",
105
+ "lru-cache": "^5.1.1",
106
+ "semver": "^6.3.1"
107
+ },
108
+ "engines": {
109
+ "node": ">=6.9.0"
110
+ }
111
+ },
112
+ "node_modules/@babel/helper-globals": {
113
+ "version": "7.28.0",
114
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
115
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
116
+ "dev": true,
117
+ "license": "MIT",
118
+ "engines": {
119
+ "node": ">=6.9.0"
120
+ }
121
+ },
122
+ "node_modules/@babel/helper-module-imports": {
123
+ "version": "7.28.6",
124
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
125
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
126
+ "dev": true,
127
+ "license": "MIT",
128
+ "dependencies": {
129
+ "@babel/traverse": "^7.28.6",
130
+ "@babel/types": "^7.28.6"
131
+ },
132
+ "engines": {
133
+ "node": ">=6.9.0"
134
+ }
135
+ },
136
+ "node_modules/@babel/helper-module-transforms": {
137
+ "version": "7.28.6",
138
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
139
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
140
+ "dev": true,
141
+ "license": "MIT",
142
+ "dependencies": {
143
+ "@babel/helper-module-imports": "^7.28.6",
144
+ "@babel/helper-validator-identifier": "^7.28.5",
145
+ "@babel/traverse": "^7.28.6"
146
+ },
147
+ "engines": {
148
+ "node": ">=6.9.0"
149
+ },
150
+ "peerDependencies": {
151
+ "@babel/core": "^7.0.0"
152
+ }
153
+ },
154
+ "node_modules/@babel/helper-plugin-utils": {
155
+ "version": "7.28.6",
156
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
157
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
158
+ "dev": true,
159
+ "license": "MIT",
160
+ "engines": {
161
+ "node": ">=6.9.0"
162
+ }
163
+ },
164
+ "node_modules/@babel/helper-string-parser": {
165
+ "version": "7.27.1",
166
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
167
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
168
+ "dev": true,
169
+ "license": "MIT",
170
+ "engines": {
171
+ "node": ">=6.9.0"
172
+ }
173
+ },
174
+ "node_modules/@babel/helper-validator-identifier": {
175
+ "version": "7.28.5",
176
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
177
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
178
+ "dev": true,
179
+ "license": "MIT",
180
+ "engines": {
181
+ "node": ">=6.9.0"
182
+ }
183
+ },
184
+ "node_modules/@babel/helper-validator-option": {
185
+ "version": "7.27.1",
186
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
187
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
188
+ "dev": true,
189
+ "license": "MIT",
190
+ "engines": {
191
+ "node": ">=6.9.0"
192
+ }
193
+ },
194
+ "node_modules/@babel/helpers": {
195
+ "version": "7.29.2",
196
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
197
+ "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
198
+ "dev": true,
199
+ "license": "MIT",
200
+ "dependencies": {
201
+ "@babel/template": "^7.28.6",
202
+ "@babel/types": "^7.29.0"
203
+ },
204
+ "engines": {
205
+ "node": ">=6.9.0"
206
+ }
207
+ },
208
+ "node_modules/@babel/parser": {
209
+ "version": "7.29.2",
210
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
211
+ "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
212
+ "dev": true,
213
+ "license": "MIT",
214
+ "dependencies": {
215
+ "@babel/types": "^7.29.0"
216
+ },
217
+ "bin": {
218
+ "parser": "bin/babel-parser.js"
219
+ },
220
+ "engines": {
221
+ "node": ">=6.0.0"
222
+ }
223
+ },
224
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
225
+ "version": "7.27.1",
226
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
227
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
228
+ "dev": true,
229
+ "license": "MIT",
230
+ "dependencies": {
231
+ "@babel/helper-plugin-utils": "^7.27.1"
232
+ },
233
+ "engines": {
234
+ "node": ">=6.9.0"
235
+ },
236
+ "peerDependencies": {
237
+ "@babel/core": "^7.0.0-0"
238
+ }
239
+ },
240
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
241
+ "version": "7.27.1",
242
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
243
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
244
+ "dev": true,
245
+ "license": "MIT",
246
+ "dependencies": {
247
+ "@babel/helper-plugin-utils": "^7.27.1"
248
+ },
249
+ "engines": {
250
+ "node": ">=6.9.0"
251
+ },
252
+ "peerDependencies": {
253
+ "@babel/core": "^7.0.0-0"
254
+ }
255
+ },
256
+ "node_modules/@babel/template": {
257
+ "version": "7.28.6",
258
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
259
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
260
+ "dev": true,
261
+ "license": "MIT",
262
+ "dependencies": {
263
+ "@babel/code-frame": "^7.28.6",
264
+ "@babel/parser": "^7.28.6",
265
+ "@babel/types": "^7.28.6"
266
+ },
267
+ "engines": {
268
+ "node": ">=6.9.0"
269
+ }
270
+ },
271
+ "node_modules/@babel/traverse": {
272
+ "version": "7.29.0",
273
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
274
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
275
+ "dev": true,
276
+ "license": "MIT",
277
+ "dependencies": {
278
+ "@babel/code-frame": "^7.29.0",
279
+ "@babel/generator": "^7.29.0",
280
+ "@babel/helper-globals": "^7.28.0",
281
+ "@babel/parser": "^7.29.0",
282
+ "@babel/template": "^7.28.6",
283
+ "@babel/types": "^7.29.0",
284
+ "debug": "^4.3.1"
285
+ },
286
+ "engines": {
287
+ "node": ">=6.9.0"
288
+ }
289
+ },
290
+ "node_modules/@babel/types": {
291
+ "version": "7.29.0",
292
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
293
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
294
+ "dev": true,
295
+ "license": "MIT",
296
+ "dependencies": {
297
+ "@babel/helper-string-parser": "^7.27.1",
298
+ "@babel/helper-validator-identifier": "^7.28.5"
299
+ },
300
+ "engines": {
301
+ "node": ">=6.9.0"
302
+ }
303
+ },
304
+ "node_modules/@esbuild/aix-ppc64": {
305
+ "version": "0.21.5",
306
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
307
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
308
+ "cpu": [
309
+ "ppc64"
310
+ ],
311
+ "dev": true,
312
+ "license": "MIT",
313
+ "optional": true,
314
+ "os": [
315
+ "aix"
316
+ ],
317
+ "engines": {
318
+ "node": ">=12"
319
+ }
320
+ },
321
+ "node_modules/@esbuild/android-arm": {
322
+ "version": "0.21.5",
323
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
324
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
325
+ "cpu": [
326
+ "arm"
327
+ ],
328
+ "dev": true,
329
+ "license": "MIT",
330
+ "optional": true,
331
+ "os": [
332
+ "android"
333
+ ],
334
+ "engines": {
335
+ "node": ">=12"
336
+ }
337
+ },
338
+ "node_modules/@esbuild/android-arm64": {
339
+ "version": "0.21.5",
340
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
341
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
342
+ "cpu": [
343
+ "arm64"
344
+ ],
345
+ "dev": true,
346
+ "license": "MIT",
347
+ "optional": true,
348
+ "os": [
349
+ "android"
350
+ ],
351
+ "engines": {
352
+ "node": ">=12"
353
+ }
354
+ },
355
+ "node_modules/@esbuild/android-x64": {
356
+ "version": "0.21.5",
357
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
358
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
359
+ "cpu": [
360
+ "x64"
361
+ ],
362
+ "dev": true,
363
+ "license": "MIT",
364
+ "optional": true,
365
+ "os": [
366
+ "android"
367
+ ],
368
+ "engines": {
369
+ "node": ">=12"
370
+ }
371
+ },
372
+ "node_modules/@esbuild/darwin-arm64": {
373
+ "version": "0.21.5",
374
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
375
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
376
+ "cpu": [
377
+ "arm64"
378
+ ],
379
+ "dev": true,
380
+ "license": "MIT",
381
+ "optional": true,
382
+ "os": [
383
+ "darwin"
384
+ ],
385
+ "engines": {
386
+ "node": ">=12"
387
+ }
388
+ },
389
+ "node_modules/@esbuild/darwin-x64": {
390
+ "version": "0.21.5",
391
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
392
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
393
+ "cpu": [
394
+ "x64"
395
+ ],
396
+ "dev": true,
397
+ "license": "MIT",
398
+ "optional": true,
399
+ "os": [
400
+ "darwin"
401
+ ],
402
+ "engines": {
403
+ "node": ">=12"
404
+ }
405
+ },
406
+ "node_modules/@esbuild/freebsd-arm64": {
407
+ "version": "0.21.5",
408
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
409
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
410
+ "cpu": [
411
+ "arm64"
412
+ ],
413
+ "dev": true,
414
+ "license": "MIT",
415
+ "optional": true,
416
+ "os": [
417
+ "freebsd"
418
+ ],
419
+ "engines": {
420
+ "node": ">=12"
421
+ }
422
+ },
423
+ "node_modules/@esbuild/freebsd-x64": {
424
+ "version": "0.21.5",
425
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
426
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
427
+ "cpu": [
428
+ "x64"
429
+ ],
430
+ "dev": true,
431
+ "license": "MIT",
432
+ "optional": true,
433
+ "os": [
434
+ "freebsd"
435
+ ],
436
+ "engines": {
437
+ "node": ">=12"
438
+ }
439
+ },
440
+ "node_modules/@esbuild/linux-arm": {
441
+ "version": "0.21.5",
442
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
443
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
444
+ "cpu": [
445
+ "arm"
446
+ ],
447
+ "dev": true,
448
+ "license": "MIT",
449
+ "optional": true,
450
+ "os": [
451
+ "linux"
452
+ ],
453
+ "engines": {
454
+ "node": ">=12"
455
+ }
456
+ },
457
+ "node_modules/@esbuild/linux-arm64": {
458
+ "version": "0.21.5",
459
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
460
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
461
+ "cpu": [
462
+ "arm64"
463
+ ],
464
+ "dev": true,
465
+ "license": "MIT",
466
+ "optional": true,
467
+ "os": [
468
+ "linux"
469
+ ],
470
+ "engines": {
471
+ "node": ">=12"
472
+ }
473
+ },
474
+ "node_modules/@esbuild/linux-ia32": {
475
+ "version": "0.21.5",
476
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
477
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
478
+ "cpu": [
479
+ "ia32"
480
+ ],
481
+ "dev": true,
482
+ "license": "MIT",
483
+ "optional": true,
484
+ "os": [
485
+ "linux"
486
+ ],
487
+ "engines": {
488
+ "node": ">=12"
489
+ }
490
+ },
491
+ "node_modules/@esbuild/linux-loong64": {
492
+ "version": "0.21.5",
493
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
494
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
495
+ "cpu": [
496
+ "loong64"
497
+ ],
498
+ "dev": true,
499
+ "license": "MIT",
500
+ "optional": true,
501
+ "os": [
502
+ "linux"
503
+ ],
504
+ "engines": {
505
+ "node": ">=12"
506
+ }
507
+ },
508
+ "node_modules/@esbuild/linux-mips64el": {
509
+ "version": "0.21.5",
510
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
511
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
512
+ "cpu": [
513
+ "mips64el"
514
+ ],
515
+ "dev": true,
516
+ "license": "MIT",
517
+ "optional": true,
518
+ "os": [
519
+ "linux"
520
+ ],
521
+ "engines": {
522
+ "node": ">=12"
523
+ }
524
+ },
525
+ "node_modules/@esbuild/linux-ppc64": {
526
+ "version": "0.21.5",
527
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
528
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
529
+ "cpu": [
530
+ "ppc64"
531
+ ],
532
+ "dev": true,
533
+ "license": "MIT",
534
+ "optional": true,
535
+ "os": [
536
+ "linux"
537
+ ],
538
+ "engines": {
539
+ "node": ">=12"
540
+ }
541
+ },
542
+ "node_modules/@esbuild/linux-riscv64": {
543
+ "version": "0.21.5",
544
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
545
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
546
+ "cpu": [
547
+ "riscv64"
548
+ ],
549
+ "dev": true,
550
+ "license": "MIT",
551
+ "optional": true,
552
+ "os": [
553
+ "linux"
554
+ ],
555
+ "engines": {
556
+ "node": ">=12"
557
+ }
558
+ },
559
+ "node_modules/@esbuild/linux-s390x": {
560
+ "version": "0.21.5",
561
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
562
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
563
+ "cpu": [
564
+ "s390x"
565
+ ],
566
+ "dev": true,
567
+ "license": "MIT",
568
+ "optional": true,
569
+ "os": [
570
+ "linux"
571
+ ],
572
+ "engines": {
573
+ "node": ">=12"
574
+ }
575
+ },
576
+ "node_modules/@esbuild/linux-x64": {
577
+ "version": "0.21.5",
578
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
579
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
580
+ "cpu": [
581
+ "x64"
582
+ ],
583
+ "dev": true,
584
+ "license": "MIT",
585
+ "optional": true,
586
+ "os": [
587
+ "linux"
588
+ ],
589
+ "engines": {
590
+ "node": ">=12"
591
+ }
592
+ },
593
+ "node_modules/@esbuild/netbsd-x64": {
594
+ "version": "0.21.5",
595
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
596
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
597
+ "cpu": [
598
+ "x64"
599
+ ],
600
+ "dev": true,
601
+ "license": "MIT",
602
+ "optional": true,
603
+ "os": [
604
+ "netbsd"
605
+ ],
606
+ "engines": {
607
+ "node": ">=12"
608
+ }
609
+ },
610
+ "node_modules/@esbuild/openbsd-x64": {
611
+ "version": "0.21.5",
612
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
613
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
614
+ "cpu": [
615
+ "x64"
616
+ ],
617
+ "dev": true,
618
+ "license": "MIT",
619
+ "optional": true,
620
+ "os": [
621
+ "openbsd"
622
+ ],
623
+ "engines": {
624
+ "node": ">=12"
625
+ }
626
+ },
627
+ "node_modules/@esbuild/sunos-x64": {
628
+ "version": "0.21.5",
629
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
630
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
631
+ "cpu": [
632
+ "x64"
633
+ ],
634
+ "dev": true,
635
+ "license": "MIT",
636
+ "optional": true,
637
+ "os": [
638
+ "sunos"
639
+ ],
640
+ "engines": {
641
+ "node": ">=12"
642
+ }
643
+ },
644
+ "node_modules/@esbuild/win32-arm64": {
645
+ "version": "0.21.5",
646
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
647
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
648
+ "cpu": [
649
+ "arm64"
650
+ ],
651
+ "dev": true,
652
+ "license": "MIT",
653
+ "optional": true,
654
+ "os": [
655
+ "win32"
656
+ ],
657
+ "engines": {
658
+ "node": ">=12"
659
+ }
660
+ },
661
+ "node_modules/@esbuild/win32-ia32": {
662
+ "version": "0.21.5",
663
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
664
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
665
+ "cpu": [
666
+ "ia32"
667
+ ],
668
+ "dev": true,
669
+ "license": "MIT",
670
+ "optional": true,
671
+ "os": [
672
+ "win32"
673
+ ],
674
+ "engines": {
675
+ "node": ">=12"
676
+ }
677
+ },
678
+ "node_modules/@esbuild/win32-x64": {
679
+ "version": "0.21.5",
680
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
681
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
682
+ "cpu": [
683
+ "x64"
684
+ ],
685
+ "dev": true,
686
+ "license": "MIT",
687
+ "optional": true,
688
+ "os": [
689
+ "win32"
690
+ ],
691
+ "engines": {
692
+ "node": ">=12"
693
+ }
694
+ },
695
+ "node_modules/@jridgewell/gen-mapping": {
696
+ "version": "0.3.13",
697
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
698
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
699
+ "dev": true,
700
+ "license": "MIT",
701
+ "dependencies": {
702
+ "@jridgewell/sourcemap-codec": "^1.5.0",
703
+ "@jridgewell/trace-mapping": "^0.3.24"
704
+ }
705
+ },
706
+ "node_modules/@jridgewell/remapping": {
707
+ "version": "2.3.5",
708
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
709
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
710
+ "dev": true,
711
+ "license": "MIT",
712
+ "dependencies": {
713
+ "@jridgewell/gen-mapping": "^0.3.5",
714
+ "@jridgewell/trace-mapping": "^0.3.24"
715
+ }
716
+ },
717
+ "node_modules/@jridgewell/resolve-uri": {
718
+ "version": "3.1.2",
719
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
720
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
721
+ "dev": true,
722
+ "license": "MIT",
723
+ "engines": {
724
+ "node": ">=6.0.0"
725
+ }
726
+ },
727
+ "node_modules/@jridgewell/sourcemap-codec": {
728
+ "version": "1.5.5",
729
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
730
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
731
+ "dev": true,
732
+ "license": "MIT"
733
+ },
734
+ "node_modules/@jridgewell/trace-mapping": {
735
+ "version": "0.3.31",
736
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
737
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
738
+ "dev": true,
739
+ "license": "MIT",
740
+ "dependencies": {
741
+ "@jridgewell/resolve-uri": "^3.1.0",
742
+ "@jridgewell/sourcemap-codec": "^1.4.14"
743
+ }
744
+ },
745
+ "node_modules/@rolldown/pluginutils": {
746
+ "version": "1.0.0-beta.27",
747
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
748
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
749
+ "dev": true,
750
+ "license": "MIT"
751
+ },
752
+ "node_modules/@rollup/rollup-android-arm-eabi": {
753
+ "version": "4.60.1",
754
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
755
+ "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==",
756
+ "cpu": [
757
+ "arm"
758
+ ],
759
+ "dev": true,
760
+ "license": "MIT",
761
+ "optional": true,
762
+ "os": [
763
+ "android"
764
+ ]
765
+ },
766
+ "node_modules/@rollup/rollup-android-arm64": {
767
+ "version": "4.60.1",
768
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz",
769
+ "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==",
770
+ "cpu": [
771
+ "arm64"
772
+ ],
773
+ "dev": true,
774
+ "license": "MIT",
775
+ "optional": true,
776
+ "os": [
777
+ "android"
778
+ ]
779
+ },
780
+ "node_modules/@rollup/rollup-darwin-arm64": {
781
+ "version": "4.60.1",
782
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz",
783
+ "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==",
784
+ "cpu": [
785
+ "arm64"
786
+ ],
787
+ "dev": true,
788
+ "license": "MIT",
789
+ "optional": true,
790
+ "os": [
791
+ "darwin"
792
+ ]
793
+ },
794
+ "node_modules/@rollup/rollup-darwin-x64": {
795
+ "version": "4.60.1",
796
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz",
797
+ "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==",
798
+ "cpu": [
799
+ "x64"
800
+ ],
801
+ "dev": true,
802
+ "license": "MIT",
803
+ "optional": true,
804
+ "os": [
805
+ "darwin"
806
+ ]
807
+ },
808
+ "node_modules/@rollup/rollup-freebsd-arm64": {
809
+ "version": "4.60.1",
810
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz",
811
+ "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==",
812
+ "cpu": [
813
+ "arm64"
814
+ ],
815
+ "dev": true,
816
+ "license": "MIT",
817
+ "optional": true,
818
+ "os": [
819
+ "freebsd"
820
+ ]
821
+ },
822
+ "node_modules/@rollup/rollup-freebsd-x64": {
823
+ "version": "4.60.1",
824
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz",
825
+ "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==",
826
+ "cpu": [
827
+ "x64"
828
+ ],
829
+ "dev": true,
830
+ "license": "MIT",
831
+ "optional": true,
832
+ "os": [
833
+ "freebsd"
834
+ ]
835
+ },
836
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
837
+ "version": "4.60.1",
838
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz",
839
+ "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==",
840
+ "cpu": [
841
+ "arm"
842
+ ],
843
+ "dev": true,
844
+ "license": "MIT",
845
+ "optional": true,
846
+ "os": [
847
+ "linux"
848
+ ]
849
+ },
850
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
851
+ "version": "4.60.1",
852
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz",
853
+ "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==",
854
+ "cpu": [
855
+ "arm"
856
+ ],
857
+ "dev": true,
858
+ "license": "MIT",
859
+ "optional": true,
860
+ "os": [
861
+ "linux"
862
+ ]
863
+ },
864
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
865
+ "version": "4.60.1",
866
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz",
867
+ "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==",
868
+ "cpu": [
869
+ "arm64"
870
+ ],
871
+ "dev": true,
872
+ "license": "MIT",
873
+ "optional": true,
874
+ "os": [
875
+ "linux"
876
+ ]
877
+ },
878
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
879
+ "version": "4.60.1",
880
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz",
881
+ "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==",
882
+ "cpu": [
883
+ "arm64"
884
+ ],
885
+ "dev": true,
886
+ "license": "MIT",
887
+ "optional": true,
888
+ "os": [
889
+ "linux"
890
+ ]
891
+ },
892
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
893
+ "version": "4.60.1",
894
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz",
895
+ "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==",
896
+ "cpu": [
897
+ "loong64"
898
+ ],
899
+ "dev": true,
900
+ "license": "MIT",
901
+ "optional": true,
902
+ "os": [
903
+ "linux"
904
+ ]
905
+ },
906
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
907
+ "version": "4.60.1",
908
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz",
909
+ "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==",
910
+ "cpu": [
911
+ "loong64"
912
+ ],
913
+ "dev": true,
914
+ "license": "MIT",
915
+ "optional": true,
916
+ "os": [
917
+ "linux"
918
+ ]
919
+ },
920
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
921
+ "version": "4.60.1",
922
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz",
923
+ "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==",
924
+ "cpu": [
925
+ "ppc64"
926
+ ],
927
+ "dev": true,
928
+ "license": "MIT",
929
+ "optional": true,
930
+ "os": [
931
+ "linux"
932
+ ]
933
+ },
934
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
935
+ "version": "4.60.1",
936
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz",
937
+ "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==",
938
+ "cpu": [
939
+ "ppc64"
940
+ ],
941
+ "dev": true,
942
+ "license": "MIT",
943
+ "optional": true,
944
+ "os": [
945
+ "linux"
946
+ ]
947
+ },
948
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
949
+ "version": "4.60.1",
950
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz",
951
+ "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==",
952
+ "cpu": [
953
+ "riscv64"
954
+ ],
955
+ "dev": true,
956
+ "license": "MIT",
957
+ "optional": true,
958
+ "os": [
959
+ "linux"
960
+ ]
961
+ },
962
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
963
+ "version": "4.60.1",
964
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz",
965
+ "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==",
966
+ "cpu": [
967
+ "riscv64"
968
+ ],
969
+ "dev": true,
970
+ "license": "MIT",
971
+ "optional": true,
972
+ "os": [
973
+ "linux"
974
+ ]
975
+ },
976
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
977
+ "version": "4.60.1",
978
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz",
979
+ "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==",
980
+ "cpu": [
981
+ "s390x"
982
+ ],
983
+ "dev": true,
984
+ "license": "MIT",
985
+ "optional": true,
986
+ "os": [
987
+ "linux"
988
+ ]
989
+ },
990
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
991
+ "version": "4.60.1",
992
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz",
993
+ "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==",
994
+ "cpu": [
995
+ "x64"
996
+ ],
997
+ "dev": true,
998
+ "license": "MIT",
999
+ "optional": true,
1000
+ "os": [
1001
+ "linux"
1002
+ ]
1003
+ },
1004
+ "node_modules/@rollup/rollup-linux-x64-musl": {
1005
+ "version": "4.60.1",
1006
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz",
1007
+ "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==",
1008
+ "cpu": [
1009
+ "x64"
1010
+ ],
1011
+ "dev": true,
1012
+ "license": "MIT",
1013
+ "optional": true,
1014
+ "os": [
1015
+ "linux"
1016
+ ]
1017
+ },
1018
+ "node_modules/@rollup/rollup-openbsd-x64": {
1019
+ "version": "4.60.1",
1020
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz",
1021
+ "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==",
1022
+ "cpu": [
1023
+ "x64"
1024
+ ],
1025
+ "dev": true,
1026
+ "license": "MIT",
1027
+ "optional": true,
1028
+ "os": [
1029
+ "openbsd"
1030
+ ]
1031
+ },
1032
+ "node_modules/@rollup/rollup-openharmony-arm64": {
1033
+ "version": "4.60.1",
1034
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz",
1035
+ "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==",
1036
+ "cpu": [
1037
+ "arm64"
1038
+ ],
1039
+ "dev": true,
1040
+ "license": "MIT",
1041
+ "optional": true,
1042
+ "os": [
1043
+ "openharmony"
1044
+ ]
1045
+ },
1046
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
1047
+ "version": "4.60.1",
1048
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz",
1049
+ "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==",
1050
+ "cpu": [
1051
+ "arm64"
1052
+ ],
1053
+ "dev": true,
1054
+ "license": "MIT",
1055
+ "optional": true,
1056
+ "os": [
1057
+ "win32"
1058
+ ]
1059
+ },
1060
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
1061
+ "version": "4.60.1",
1062
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz",
1063
+ "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==",
1064
+ "cpu": [
1065
+ "ia32"
1066
+ ],
1067
+ "dev": true,
1068
+ "license": "MIT",
1069
+ "optional": true,
1070
+ "os": [
1071
+ "win32"
1072
+ ]
1073
+ },
1074
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
1075
+ "version": "4.60.1",
1076
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz",
1077
+ "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==",
1078
+ "cpu": [
1079
+ "x64"
1080
+ ],
1081
+ "dev": true,
1082
+ "license": "MIT",
1083
+ "optional": true,
1084
+ "os": [
1085
+ "win32"
1086
+ ]
1087
+ },
1088
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
1089
+ "version": "4.60.1",
1090
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz",
1091
+ "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==",
1092
+ "cpu": [
1093
+ "x64"
1094
+ ],
1095
+ "dev": true,
1096
+ "license": "MIT",
1097
+ "optional": true,
1098
+ "os": [
1099
+ "win32"
1100
+ ]
1101
+ },
1102
+ "node_modules/@types/babel__core": {
1103
+ "version": "7.20.5",
1104
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
1105
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
1106
+ "dev": true,
1107
+ "license": "MIT",
1108
+ "dependencies": {
1109
+ "@babel/parser": "^7.20.7",
1110
+ "@babel/types": "^7.20.7",
1111
+ "@types/babel__generator": "*",
1112
+ "@types/babel__template": "*",
1113
+ "@types/babel__traverse": "*"
1114
+ }
1115
+ },
1116
+ "node_modules/@types/babel__generator": {
1117
+ "version": "7.27.0",
1118
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
1119
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
1120
+ "dev": true,
1121
+ "license": "MIT",
1122
+ "dependencies": {
1123
+ "@babel/types": "^7.0.0"
1124
+ }
1125
+ },
1126
+ "node_modules/@types/babel__template": {
1127
+ "version": "7.4.4",
1128
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
1129
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
1130
+ "dev": true,
1131
+ "license": "MIT",
1132
+ "dependencies": {
1133
+ "@babel/parser": "^7.1.0",
1134
+ "@babel/types": "^7.0.0"
1135
+ }
1136
+ },
1137
+ "node_modules/@types/babel__traverse": {
1138
+ "version": "7.28.0",
1139
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
1140
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
1141
+ "dev": true,
1142
+ "license": "MIT",
1143
+ "dependencies": {
1144
+ "@babel/types": "^7.28.2"
1145
+ }
1146
+ },
1147
+ "node_modules/@types/estree": {
1148
+ "version": "1.0.8",
1149
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
1150
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
1151
+ "dev": true,
1152
+ "license": "MIT"
1153
+ },
1154
+ "node_modules/@types/prop-types": {
1155
+ "version": "15.7.15",
1156
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
1157
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
1158
+ "dev": true,
1159
+ "license": "MIT"
1160
+ },
1161
+ "node_modules/@types/react": {
1162
+ "version": "18.3.28",
1163
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
1164
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
1165
+ "dev": true,
1166
+ "license": "MIT",
1167
+ "dependencies": {
1168
+ "@types/prop-types": "*",
1169
+ "csstype": "^3.2.2"
1170
+ }
1171
+ },
1172
+ "node_modules/@types/react-dom": {
1173
+ "version": "18.3.7",
1174
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
1175
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
1176
+ "dev": true,
1177
+ "license": "MIT",
1178
+ "peerDependencies": {
1179
+ "@types/react": "^18.0.0"
1180
+ }
1181
+ },
1182
+ "node_modules/@vitejs/plugin-react": {
1183
+ "version": "4.7.0",
1184
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
1185
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
1186
+ "dev": true,
1187
+ "license": "MIT",
1188
+ "dependencies": {
1189
+ "@babel/core": "^7.28.0",
1190
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
1191
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
1192
+ "@rolldown/pluginutils": "1.0.0-beta.27",
1193
+ "@types/babel__core": "^7.20.5",
1194
+ "react-refresh": "^0.17.0"
1195
+ },
1196
+ "engines": {
1197
+ "node": "^14.18.0 || >=16.0.0"
1198
+ },
1199
+ "peerDependencies": {
1200
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
1201
+ }
1202
+ },
1203
+ "node_modules/baseline-browser-mapping": {
1204
+ "version": "2.10.19",
1205
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.19.tgz",
1206
+ "integrity": "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==",
1207
+ "dev": true,
1208
+ "license": "Apache-2.0",
1209
+ "bin": {
1210
+ "baseline-browser-mapping": "dist/cli.cjs"
1211
+ },
1212
+ "engines": {
1213
+ "node": ">=6.0.0"
1214
+ }
1215
+ },
1216
+ "node_modules/browserslist": {
1217
+ "version": "4.28.2",
1218
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
1219
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
1220
+ "dev": true,
1221
+ "funding": [
1222
+ {
1223
+ "type": "opencollective",
1224
+ "url": "https://opencollective.com/browserslist"
1225
+ },
1226
+ {
1227
+ "type": "tidelift",
1228
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1229
+ },
1230
+ {
1231
+ "type": "github",
1232
+ "url": "https://github.com/sponsors/ai"
1233
+ }
1234
+ ],
1235
+ "license": "MIT",
1236
+ "dependencies": {
1237
+ "baseline-browser-mapping": "^2.10.12",
1238
+ "caniuse-lite": "^1.0.30001782",
1239
+ "electron-to-chromium": "^1.5.328",
1240
+ "node-releases": "^2.0.36",
1241
+ "update-browserslist-db": "^1.2.3"
1242
+ },
1243
+ "bin": {
1244
+ "browserslist": "cli.js"
1245
+ },
1246
+ "engines": {
1247
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
1248
+ }
1249
+ },
1250
+ "node_modules/caniuse-lite": {
1251
+ "version": "1.0.30001788",
1252
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz",
1253
+ "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==",
1254
+ "dev": true,
1255
+ "funding": [
1256
+ {
1257
+ "type": "opencollective",
1258
+ "url": "https://opencollective.com/browserslist"
1259
+ },
1260
+ {
1261
+ "type": "tidelift",
1262
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
1263
+ },
1264
+ {
1265
+ "type": "github",
1266
+ "url": "https://github.com/sponsors/ai"
1267
+ }
1268
+ ],
1269
+ "license": "CC-BY-4.0"
1270
+ },
1271
+ "node_modules/convert-source-map": {
1272
+ "version": "2.0.0",
1273
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
1274
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
1275
+ "dev": true,
1276
+ "license": "MIT"
1277
+ },
1278
+ "node_modules/csstype": {
1279
+ "version": "3.2.3",
1280
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
1281
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
1282
+ "dev": true,
1283
+ "license": "MIT"
1284
+ },
1285
+ "node_modules/debug": {
1286
+ "version": "4.4.3",
1287
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
1288
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
1289
+ "dev": true,
1290
+ "license": "MIT",
1291
+ "dependencies": {
1292
+ "ms": "^2.1.3"
1293
+ },
1294
+ "engines": {
1295
+ "node": ">=6.0"
1296
+ },
1297
+ "peerDependenciesMeta": {
1298
+ "supports-color": {
1299
+ "optional": true
1300
+ }
1301
+ }
1302
+ },
1303
+ "node_modules/electron-to-chromium": {
1304
+ "version": "1.5.340",
1305
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz",
1306
+ "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==",
1307
+ "dev": true,
1308
+ "license": "ISC"
1309
+ },
1310
+ "node_modules/esbuild": {
1311
+ "version": "0.21.5",
1312
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
1313
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
1314
+ "dev": true,
1315
+ "hasInstallScript": true,
1316
+ "license": "MIT",
1317
+ "bin": {
1318
+ "esbuild": "bin/esbuild"
1319
+ },
1320
+ "engines": {
1321
+ "node": ">=12"
1322
+ },
1323
+ "optionalDependencies": {
1324
+ "@esbuild/aix-ppc64": "0.21.5",
1325
+ "@esbuild/android-arm": "0.21.5",
1326
+ "@esbuild/android-arm64": "0.21.5",
1327
+ "@esbuild/android-x64": "0.21.5",
1328
+ "@esbuild/darwin-arm64": "0.21.5",
1329
+ "@esbuild/darwin-x64": "0.21.5",
1330
+ "@esbuild/freebsd-arm64": "0.21.5",
1331
+ "@esbuild/freebsd-x64": "0.21.5",
1332
+ "@esbuild/linux-arm": "0.21.5",
1333
+ "@esbuild/linux-arm64": "0.21.5",
1334
+ "@esbuild/linux-ia32": "0.21.5",
1335
+ "@esbuild/linux-loong64": "0.21.5",
1336
+ "@esbuild/linux-mips64el": "0.21.5",
1337
+ "@esbuild/linux-ppc64": "0.21.5",
1338
+ "@esbuild/linux-riscv64": "0.21.5",
1339
+ "@esbuild/linux-s390x": "0.21.5",
1340
+ "@esbuild/linux-x64": "0.21.5",
1341
+ "@esbuild/netbsd-x64": "0.21.5",
1342
+ "@esbuild/openbsd-x64": "0.21.5",
1343
+ "@esbuild/sunos-x64": "0.21.5",
1344
+ "@esbuild/win32-arm64": "0.21.5",
1345
+ "@esbuild/win32-ia32": "0.21.5",
1346
+ "@esbuild/win32-x64": "0.21.5"
1347
+ }
1348
+ },
1349
+ "node_modules/escalade": {
1350
+ "version": "3.2.0",
1351
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1352
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1353
+ "dev": true,
1354
+ "license": "MIT",
1355
+ "engines": {
1356
+ "node": ">=6"
1357
+ }
1358
+ },
1359
+ "node_modules/fsevents": {
1360
+ "version": "2.3.3",
1361
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1362
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1363
+ "dev": true,
1364
+ "hasInstallScript": true,
1365
+ "license": "MIT",
1366
+ "optional": true,
1367
+ "os": [
1368
+ "darwin"
1369
+ ],
1370
+ "engines": {
1371
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1372
+ }
1373
+ },
1374
+ "node_modules/gensync": {
1375
+ "version": "1.0.0-beta.2",
1376
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
1377
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
1378
+ "dev": true,
1379
+ "license": "MIT",
1380
+ "engines": {
1381
+ "node": ">=6.9.0"
1382
+ }
1383
+ },
1384
+ "node_modules/js-tokens": {
1385
+ "version": "4.0.0",
1386
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
1387
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
1388
+ "license": "MIT"
1389
+ },
1390
+ "node_modules/jsesc": {
1391
+ "version": "3.1.0",
1392
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
1393
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
1394
+ "dev": true,
1395
+ "license": "MIT",
1396
+ "bin": {
1397
+ "jsesc": "bin/jsesc"
1398
+ },
1399
+ "engines": {
1400
+ "node": ">=6"
1401
+ }
1402
+ },
1403
+ "node_modules/json5": {
1404
+ "version": "2.2.3",
1405
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
1406
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
1407
+ "dev": true,
1408
+ "license": "MIT",
1409
+ "bin": {
1410
+ "json5": "lib/cli.js"
1411
+ },
1412
+ "engines": {
1413
+ "node": ">=6"
1414
+ }
1415
+ },
1416
+ "node_modules/loose-envify": {
1417
+ "version": "1.4.0",
1418
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
1419
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
1420
+ "license": "MIT",
1421
+ "dependencies": {
1422
+ "js-tokens": "^3.0.0 || ^4.0.0"
1423
+ },
1424
+ "bin": {
1425
+ "loose-envify": "cli.js"
1426
+ }
1427
+ },
1428
+ "node_modules/lru-cache": {
1429
+ "version": "5.1.1",
1430
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
1431
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
1432
+ "dev": true,
1433
+ "license": "ISC",
1434
+ "dependencies": {
1435
+ "yallist": "^3.0.2"
1436
+ }
1437
+ },
1438
+ "node_modules/ms": {
1439
+ "version": "2.1.3",
1440
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1441
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1442
+ "dev": true,
1443
+ "license": "MIT"
1444
+ },
1445
+ "node_modules/nanoid": {
1446
+ "version": "3.3.11",
1447
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
1448
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
1449
+ "dev": true,
1450
+ "funding": [
1451
+ {
1452
+ "type": "github",
1453
+ "url": "https://github.com/sponsors/ai"
1454
+ }
1455
+ ],
1456
+ "license": "MIT",
1457
+ "bin": {
1458
+ "nanoid": "bin/nanoid.cjs"
1459
+ },
1460
+ "engines": {
1461
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1462
+ }
1463
+ },
1464
+ "node_modules/node-releases": {
1465
+ "version": "2.0.37",
1466
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz",
1467
+ "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==",
1468
+ "dev": true,
1469
+ "license": "MIT"
1470
+ },
1471
+ "node_modules/picocolors": {
1472
+ "version": "1.1.1",
1473
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1474
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1475
+ "dev": true,
1476
+ "license": "ISC"
1477
+ },
1478
+ "node_modules/postcss": {
1479
+ "version": "8.5.10",
1480
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
1481
+ "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
1482
+ "dev": true,
1483
+ "funding": [
1484
+ {
1485
+ "type": "opencollective",
1486
+ "url": "https://opencollective.com/postcss/"
1487
+ },
1488
+ {
1489
+ "type": "tidelift",
1490
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1491
+ },
1492
+ {
1493
+ "type": "github",
1494
+ "url": "https://github.com/sponsors/ai"
1495
+ }
1496
+ ],
1497
+ "license": "MIT",
1498
+ "dependencies": {
1499
+ "nanoid": "^3.3.11",
1500
+ "picocolors": "^1.1.1",
1501
+ "source-map-js": "^1.2.1"
1502
+ },
1503
+ "engines": {
1504
+ "node": "^10 || ^12 || >=14"
1505
+ }
1506
+ },
1507
+ "node_modules/react": {
1508
+ "version": "18.3.1",
1509
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
1510
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
1511
+ "license": "MIT",
1512
+ "dependencies": {
1513
+ "loose-envify": "^1.1.0"
1514
+ },
1515
+ "engines": {
1516
+ "node": ">=0.10.0"
1517
+ }
1518
+ },
1519
+ "node_modules/react-dom": {
1520
+ "version": "18.3.1",
1521
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
1522
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
1523
+ "license": "MIT",
1524
+ "dependencies": {
1525
+ "loose-envify": "^1.1.0",
1526
+ "scheduler": "^0.23.2"
1527
+ },
1528
+ "peerDependencies": {
1529
+ "react": "^18.3.1"
1530
+ }
1531
+ },
1532
+ "node_modules/react-refresh": {
1533
+ "version": "0.17.0",
1534
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
1535
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
1536
+ "dev": true,
1537
+ "license": "MIT",
1538
+ "engines": {
1539
+ "node": ">=0.10.0"
1540
+ }
1541
+ },
1542
+ "node_modules/rollup": {
1543
+ "version": "4.60.1",
1544
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
1545
+ "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
1546
+ "dev": true,
1547
+ "license": "MIT",
1548
+ "dependencies": {
1549
+ "@types/estree": "1.0.8"
1550
+ },
1551
+ "bin": {
1552
+ "rollup": "dist/bin/rollup"
1553
+ },
1554
+ "engines": {
1555
+ "node": ">=18.0.0",
1556
+ "npm": ">=8.0.0"
1557
+ },
1558
+ "optionalDependencies": {
1559
+ "@rollup/rollup-android-arm-eabi": "4.60.1",
1560
+ "@rollup/rollup-android-arm64": "4.60.1",
1561
+ "@rollup/rollup-darwin-arm64": "4.60.1",
1562
+ "@rollup/rollup-darwin-x64": "4.60.1",
1563
+ "@rollup/rollup-freebsd-arm64": "4.60.1",
1564
+ "@rollup/rollup-freebsd-x64": "4.60.1",
1565
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.1",
1566
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.1",
1567
+ "@rollup/rollup-linux-arm64-gnu": "4.60.1",
1568
+ "@rollup/rollup-linux-arm64-musl": "4.60.1",
1569
+ "@rollup/rollup-linux-loong64-gnu": "4.60.1",
1570
+ "@rollup/rollup-linux-loong64-musl": "4.60.1",
1571
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.1",
1572
+ "@rollup/rollup-linux-ppc64-musl": "4.60.1",
1573
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.1",
1574
+ "@rollup/rollup-linux-riscv64-musl": "4.60.1",
1575
+ "@rollup/rollup-linux-s390x-gnu": "4.60.1",
1576
+ "@rollup/rollup-linux-x64-gnu": "4.60.1",
1577
+ "@rollup/rollup-linux-x64-musl": "4.60.1",
1578
+ "@rollup/rollup-openbsd-x64": "4.60.1",
1579
+ "@rollup/rollup-openharmony-arm64": "4.60.1",
1580
+ "@rollup/rollup-win32-arm64-msvc": "4.60.1",
1581
+ "@rollup/rollup-win32-ia32-msvc": "4.60.1",
1582
+ "@rollup/rollup-win32-x64-gnu": "4.60.1",
1583
+ "@rollup/rollup-win32-x64-msvc": "4.60.1",
1584
+ "fsevents": "~2.3.2"
1585
+ }
1586
+ },
1587
+ "node_modules/scheduler": {
1588
+ "version": "0.23.2",
1589
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
1590
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
1591
+ "license": "MIT",
1592
+ "dependencies": {
1593
+ "loose-envify": "^1.1.0"
1594
+ }
1595
+ },
1596
+ "node_modules/semver": {
1597
+ "version": "6.3.1",
1598
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
1599
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
1600
+ "dev": true,
1601
+ "license": "ISC",
1602
+ "bin": {
1603
+ "semver": "bin/semver.js"
1604
+ }
1605
+ },
1606
+ "node_modules/source-map-js": {
1607
+ "version": "1.2.1",
1608
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1609
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1610
+ "dev": true,
1611
+ "license": "BSD-3-Clause",
1612
+ "engines": {
1613
+ "node": ">=0.10.0"
1614
+ }
1615
+ },
1616
+ "node_modules/typescript": {
1617
+ "version": "5.9.3",
1618
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
1619
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
1620
+ "dev": true,
1621
+ "license": "Apache-2.0",
1622
+ "bin": {
1623
+ "tsc": "bin/tsc",
1624
+ "tsserver": "bin/tsserver"
1625
+ },
1626
+ "engines": {
1627
+ "node": ">=14.17"
1628
+ }
1629
+ },
1630
+ "node_modules/update-browserslist-db": {
1631
+ "version": "1.2.3",
1632
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
1633
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
1634
+ "dev": true,
1635
+ "funding": [
1636
+ {
1637
+ "type": "opencollective",
1638
+ "url": "https://opencollective.com/browserslist"
1639
+ },
1640
+ {
1641
+ "type": "tidelift",
1642
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1643
+ },
1644
+ {
1645
+ "type": "github",
1646
+ "url": "https://github.com/sponsors/ai"
1647
+ }
1648
+ ],
1649
+ "license": "MIT",
1650
+ "dependencies": {
1651
+ "escalade": "^3.2.0",
1652
+ "picocolors": "^1.1.1"
1653
+ },
1654
+ "bin": {
1655
+ "update-browserslist-db": "cli.js"
1656
+ },
1657
+ "peerDependencies": {
1658
+ "browserslist": ">= 4.21.0"
1659
+ }
1660
+ },
1661
+ "node_modules/vite": {
1662
+ "version": "5.4.21",
1663
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
1664
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
1665
+ "dev": true,
1666
+ "license": "MIT",
1667
+ "dependencies": {
1668
+ "esbuild": "^0.21.3",
1669
+ "postcss": "^8.4.43",
1670
+ "rollup": "^4.20.0"
1671
+ },
1672
+ "bin": {
1673
+ "vite": "bin/vite.js"
1674
+ },
1675
+ "engines": {
1676
+ "node": "^18.0.0 || >=20.0.0"
1677
+ },
1678
+ "funding": {
1679
+ "url": "https://github.com/vitejs/vite?sponsor=1"
1680
+ },
1681
+ "optionalDependencies": {
1682
+ "fsevents": "~2.3.3"
1683
+ },
1684
+ "peerDependencies": {
1685
+ "@types/node": "^18.0.0 || >=20.0.0",
1686
+ "less": "*",
1687
+ "lightningcss": "^1.21.0",
1688
+ "sass": "*",
1689
+ "sass-embedded": "*",
1690
+ "stylus": "*",
1691
+ "sugarss": "*",
1692
+ "terser": "^5.4.0"
1693
+ },
1694
+ "peerDependenciesMeta": {
1695
+ "@types/node": {
1696
+ "optional": true
1697
+ },
1698
+ "less": {
1699
+ "optional": true
1700
+ },
1701
+ "lightningcss": {
1702
+ "optional": true
1703
+ },
1704
+ "sass": {
1705
+ "optional": true
1706
+ },
1707
+ "sass-embedded": {
1708
+ "optional": true
1709
+ },
1710
+ "stylus": {
1711
+ "optional": true
1712
+ },
1713
+ "sugarss": {
1714
+ "optional": true
1715
+ },
1716
+ "terser": {
1717
+ "optional": true
1718
+ }
1719
+ }
1720
+ },
1721
+ "node_modules/yallist": {
1722
+ "version": "3.1.1",
1723
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
1724
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
1725
+ "dev": true,
1726
+ "license": "ISC"
1727
+ }
1728
+ }
1729
+ }
frontend/src/App.tsx CHANGED
@@ -1,32 +1,61 @@
1
- import React, { useState } from 'react';
2
  import './App.css';
3
  import ChatWindow from './components/ChatWindow';
4
  import ChatHistory from './components/ChatHistory';
5
 
 
 
 
 
 
 
6
  function App() {
7
- const [currentChatId, setCurrentChatId] = useState(Date.now().toString());
8
- const [chats, setChats] = useState<Record<string, any[]>>({});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  return (
11
  <div className="app-container">
12
  <div className="sidebar">
13
- <ChatHistory
14
  chats={chats}
15
  currentChatId={currentChatId}
16
  onSelectChat={setCurrentChatId}
17
- onNewChat={() => setCurrentChatId(Date.now().toString())}
18
  />
19
  </div>
20
  <div className="main-content">
21
- <ChatWindow
22
- chatId={currentChatId}
23
- onUpdateChat={(messages: any[]) => {
24
- setChats(prev => ({
25
- ...prev,
26
- [currentChatId]: messages
27
- }));
28
- }}
29
- />
30
  </div>
31
  </div>
32
  );
 
1
+ import React, { useEffect, useState } from 'react';
2
  import './App.css';
3
  import ChatWindow from './components/ChatWindow';
4
  import ChatHistory from './components/ChatHistory';
5
 
6
+ export interface ChatSummary {
7
+ chat_id: string;
8
+ title: string;
9
+ updated_at: number;
10
+ }
11
+
12
  function App() {
13
+ const [currentChatId, setCurrentChatId] = useState<string | null>(null);
14
+ const [chats, setChats] = useState<ChatSummary[]>([]);
15
+
16
+ const fetchChats = async () => {
17
+ const response = await fetch('/api/chats');
18
+ if (!response.ok) {
19
+ throw new Error('No se pudieron cargar los chats');
20
+ }
21
+ const data: ChatSummary[] = await response.json();
22
+ setChats(data);
23
+ if (!currentChatId && data.length > 0) {
24
+ setCurrentChatId(data[0].chat_id);
25
+ }
26
+ };
27
+
28
+ useEffect(() => {
29
+ fetchChats().catch(console.error);
30
+ }, []);
31
+
32
+ const handleNewChat = async () => {
33
+ const response = await fetch('/api/chats', {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/json' },
36
+ body: JSON.stringify({}),
37
+ });
38
+ if (!response.ok) {
39
+ throw new Error('No se pudo crear el chat');
40
+ }
41
+
42
+ const created: ChatSummary = await response.json();
43
+ await fetchChats();
44
+ setCurrentChatId(created.chat_id);
45
+ };
46
 
47
  return (
48
  <div className="app-container">
49
  <div className="sidebar">
50
+ <ChatHistory
51
  chats={chats}
52
  currentChatId={currentChatId}
53
  onSelectChat={setCurrentChatId}
54
+ onNewChat={handleNewChat}
55
  />
56
  </div>
57
  <div className="main-content">
58
+ <ChatWindow chatId={currentChatId} onRefreshChats={fetchChats} />
 
 
 
 
 
 
 
 
59
  </div>
60
  </div>
61
  );
frontend/src/components/ChatHistory.tsx CHANGED
@@ -1,23 +1,28 @@
1
  import React from 'react';
 
2
 
3
- function ChatHistory({ chats, currentChatId, onSelectChat, onNewChat }: any) {
 
 
 
 
 
 
 
4
  return (
5
  <div className="chat-history">
6
  <button className="new-chat-btn" onClick={onNewChat}>
7
  + Nuevo chat
8
  </button>
9
  <div className="chat-list">
10
- {Object.entries(chats).map(([chatId, messages]: any) => (
11
  <div
12
- key={chatId}
13
- className={`chat-item ${chatId === currentChatId ? 'active' : ''}`}
14
- onClick={() => onSelectChat(chatId)}
15
  >
16
- {chatId === currentChatId ? '● ' : ''}
17
- {messages && messages.length > 0
18
- ? messages[0].content.substring(0, 30) + '...'
19
- : `Chat ${chatId}`
20
- }
21
  </div>
22
  ))}
23
  </div>
 
1
  import React from 'react';
2
+ import type { ChatSummary } from '../App';
3
 
4
+ interface Props {
5
+ chats: ChatSummary[];
6
+ currentChatId: string | null;
7
+ onSelectChat: (chatId: string) => void;
8
+ onNewChat: () => void;
9
+ }
10
+
11
+ function ChatHistory({ chats, currentChatId, onSelectChat, onNewChat }: Props) {
12
  return (
13
  <div className="chat-history">
14
  <button className="new-chat-btn" onClick={onNewChat}>
15
  + Nuevo chat
16
  </button>
17
  <div className="chat-list">
18
+ {chats.map((chat) => (
19
  <div
20
+ key={chat.chat_id}
21
+ className={`chat-item ${chat.chat_id === currentChatId ? 'active' : ''}`}
22
+ onClick={() => onSelectChat(chat.chat_id)}
23
  >
24
+ {chat.chat_id === currentChatId ? '● ' : ''}
25
+ {chat.title}
 
 
 
26
  </div>
27
  ))}
28
  </div>
frontend/src/components/ChatWindow.tsx CHANGED
@@ -1,70 +1,118 @@
1
- import React, { useState, useEffect } from 'react';
2
 
3
  interface Message {
4
- id: string;
5
  role: 'user' | 'assistant' | 'error';
6
  content: string;
7
  avatar: string;
8
  }
9
 
10
- function ChatWindow({ chatId, onUpdateChat }: any) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  const [messages, setMessages] = useState<Message[]>([]);
12
  const [input, setInput] = useState('');
13
  const [loading, setLoading] = useState(false);
14
 
 
 
 
 
 
 
 
 
 
 
15
  useEffect(() => {
16
- setMessages([]);
 
 
 
 
 
 
 
 
17
  }, [chatId]);
18
 
19
- const handleSendMessage = async () => {
20
- if (!input.trim()) return;
21
 
22
  const userMessage: Message = {
23
- id: Date.now().toString(),
24
  role: 'user',
25
- content: input,
26
  avatar: '👤',
27
  };
28
 
29
- setMessages(prev => [...prev, userMessage]);
30
- setInput('');
31
  setLoading(true);
32
 
33
  try {
34
- const response = await fetch('/api/chat/send', {
35
  method: 'POST',
36
  headers: { 'Content-Type': 'application/json' },
37
- body: JSON.stringify({
38
- chat_id: chatId,
39
- content: input,
40
- }),
41
  });
42
 
43
- if (!response.ok) throw new Error('Error sending message');
 
 
 
44
 
45
  const data = await response.json();
46
  const assistantMessage: Message = {
47
- id: (Date.now() + 1).toString(),
48
  role: 'assistant',
49
  content: data.response,
50
  avatar: '🤖',
51
  };
52
 
53
- setMessages(prev => [...prev, assistantMessage]);
54
- onUpdateChat([...messages, userMessage, assistantMessage]);
55
  } catch (error) {
56
- console.error('Error:', error);
57
- setMessages(prev => [...prev, {
58
- id: Date.now().toString(),
59
- role: 'error',
60
- content: 'Error sending message',
61
- avatar: '⚠️',
62
- }]);
 
 
63
  } finally {
64
  setLoading(false);
65
  }
66
  };
67
 
 
 
 
 
 
 
 
68
  return (
69
  <div className="chat-window">
70
  <div className="messages">
@@ -73,15 +121,23 @@ function ChatWindow({ chatId, onUpdateChat }: any) {
73
  <h1>Email Story Creator</h1>
74
  <p>✉️ Experto en emails narrativos que conectan historias con ventas</p>
75
  <div className="example-buttons">
76
- <button className="example-btn">Definir audiencia 🎯</button>
77
- <button className="example-btn">Propuesta de valor 💎</button>
78
- <button className="example-btn">CTA que convierte 🚀</button>
79
- <button className="example-btn">Asunto + gancho ✉️</button>
 
 
 
 
 
 
 
 
80
  </div>
81
  </div>
82
  )}
83
- {messages.map(msg => (
84
- <div key={msg.id} className={`message ${msg.role}`}>
85
  <span className="avatar">{msg.avatar}</span>
86
  <div className="content">{msg.content}</div>
87
  </div>
@@ -94,11 +150,11 @@ function ChatWindow({ chatId, onUpdateChat }: any) {
94
  type="text"
95
  value={input}
96
  onChange={(e) => setInput(e.target.value)}
97
- onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
98
- placeholder="Escribe aquí tus instrucciones"
99
- disabled={loading}
100
  />
101
- <button onClick={handleSendMessage} disabled={loading}>
102
  Enviar
103
  </button>
104
  </div>
 
1
+ import React, { useEffect, useState } from 'react';
2
 
3
  interface Message {
 
4
  role: 'user' | 'assistant' | 'error';
5
  content: string;
6
  avatar: string;
7
  }
8
 
9
+ interface ApiMessage {
10
+ role: 'user' | 'assistant';
11
+ content: string;
12
+ }
13
+
14
+ interface Props {
15
+ chatId: string | null;
16
+ onRefreshChats: () => Promise<void>;
17
+ }
18
+
19
+ const EXAMPLE_PROMPTS = [
20
+ 'Ayúdame a definir una audiencia concreta para este correo: dolor principal, deseo y nivel de conciencia.',
21
+ 'Convierte mi producto en una promesa clara de transformación sin listar características aburridas.',
22
+ 'Dame 3 opciones de CTA claras para este email, con baja fricción y orientadas a una sola acción.',
23
+ 'Propón 5 asuntos y 3 ganchos de apertura para aumentar aperturas y clics de este correo.',
24
+ ];
25
+
26
+ function toUiMessage(msg: ApiMessage): Message {
27
+ return {
28
+ role: msg.role,
29
+ content: msg.content,
30
+ avatar: msg.role === 'user' ? '👤' : '🤖',
31
+ };
32
+ }
33
+
34
+ function ChatWindow({ chatId, onRefreshChats }: Props) {
35
  const [messages, setMessages] = useState<Message[]>([]);
36
  const [input, setInput] = useState('');
37
  const [loading, setLoading] = useState(false);
38
 
39
+ const loadMessages = async (targetChatId: string) => {
40
+ const response = await fetch(`/api/chats/${targetChatId}/messages`);
41
+ if (!response.ok) {
42
+ throw new Error('No se pudo cargar el historial');
43
+ }
44
+
45
+ const data: ApiMessage[] = await response.json();
46
+ setMessages(data.map(toUiMessage));
47
+ };
48
+
49
  useEffect(() => {
50
+ if (!chatId) {
51
+ setMessages([]);
52
+ return;
53
+ }
54
+
55
+ loadMessages(chatId).catch((error) => {
56
+ console.error(error);
57
+ setMessages([]);
58
+ });
59
  }, [chatId]);
60
 
61
+ const sendToCurrentChat = async (content: string, isExample = false) => {
62
+ if (!chatId) return;
63
 
64
  const userMessage: Message = {
 
65
  role: 'user',
66
+ content,
67
  avatar: '👤',
68
  };
69
 
70
+ setMessages((prev) => [...prev, userMessage]);
 
71
  setLoading(true);
72
 
73
  try {
74
+ const response = await fetch(`/api/chats/${chatId}/messages`, {
75
  method: 'POST',
76
  headers: { 'Content-Type': 'application/json' },
77
+ body: JSON.stringify({ content, is_example: isExample }),
 
 
 
78
  });
79
 
80
+ if (!response.ok) {
81
+ const payload = await response.json().catch(() => ({}));
82
+ throw new Error(payload.detail || 'Error enviando mensaje');
83
+ }
84
 
85
  const data = await response.json();
86
  const assistantMessage: Message = {
 
87
  role: 'assistant',
88
  content: data.response,
89
  avatar: '🤖',
90
  };
91
 
92
+ setMessages((prev) => [...prev, assistantMessage]);
93
+ await onRefreshChats();
94
  } catch (error) {
95
+ const msg = error instanceof Error ? error.message : 'Error enviando mensaje';
96
+ setMessages((prev) => [
97
+ ...prev,
98
+ {
99
+ role: 'error',
100
+ content: msg,
101
+ avatar: '⚠️',
102
+ },
103
+ ]);
104
  } finally {
105
  setLoading(false);
106
  }
107
  };
108
 
109
+ const handleSendMessage = async () => {
110
+ if (!input.trim()) return;
111
+ const content = input;
112
+ setInput('');
113
+ await sendToCurrentChat(content, false);
114
+ };
115
+
116
  return (
117
  <div className="chat-window">
118
  <div className="messages">
 
121
  <h1>Email Story Creator</h1>
122
  <p>✉️ Experto en emails narrativos que conectan historias con ventas</p>
123
  <div className="example-buttons">
124
+ <button className="example-btn" onClick={() => sendToCurrentChat(EXAMPLE_PROMPTS[0], true)}>
125
+ Definir audiencia 🎯
126
+ </button>
127
+ <button className="example-btn" onClick={() => sendToCurrentChat(EXAMPLE_PROMPTS[1], true)}>
128
+ Propuesta de valor 💎
129
+ </button>
130
+ <button className="example-btn" onClick={() => sendToCurrentChat(EXAMPLE_PROMPTS[2], true)}>
131
+ CTA que convierte 🚀
132
+ </button>
133
+ <button className="example-btn" onClick={() => sendToCurrentChat(EXAMPLE_PROMPTS[3], true)}>
134
+ Asunto + gancho ✉️
135
+ </button>
136
  </div>
137
  </div>
138
  )}
139
+ {messages.map((msg, idx) => (
140
+ <div key={`${msg.role}-${idx}-${msg.content.slice(0, 20)}`} className={`message ${msg.role}`}>
141
  <span className="avatar">{msg.avatar}</span>
142
  <div className="content">{msg.content}</div>
143
  </div>
 
150
  type="text"
151
  value={input}
152
  onChange={(e) => setInput(e.target.value)}
153
+ onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()}
154
+ placeholder={chatId ? 'Escribe aquí tus instrucciones' : 'Crea un chat con “+ Nuevo chat”'}
155
+ disabled={loading || !chatId}
156
  />
157
+ <button onClick={handleSendMessage} disabled={loading || !chatId}>
158
  Enviar
159
  </button>
160
  </div>
frontend/tsconfig.json CHANGED
@@ -1 +1,18 @@
1
- {\n "compilerOptions": {\n "target": "es5",\n "module": "commonjs",\n "strict": true,\n "jsx": "react",\n "moduleResolution": "node",\n "esModuleInterop": true,\n "skipLibCheck": true,\n "forceConsistentCasingInFileNames": true\n },\n "include": [\n "src/**/*"\n ]\n}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "Bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true
15
+ },
16
+ "include": ["src"],
17
+ "references": [{ "path": "./tsconfig.node.json" }]
18
+ }
frontend/tsconfig.node.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": ["vite.config.ts"]
10
+ }