diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ui/tts-client-app/package-lock.json b/ui/tts-client-app/package-lock.json index 03a0868..6f05481 100644 --- a/ui/tts-client-app/package-lock.json +++ b/ui/tts-client-app/package-lock.json @@ -15,13 +15,15 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } }, "node_modules/@adobe/css-tools": { @@ -3754,6 +3756,11 @@ "tailwindcss": "dist/index.mjs" } }, + "node_modules/@tailwindcss/cli/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -3787,6 +3794,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", @@ -4029,6 +4041,25 @@ "node": ">=8" } }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5458,7 +5489,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -13097,7 +13127,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14788,6 +14817,14 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16791,10 +16828,41 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ui/tts-client-app/package-lock.json b/ui/tts-client-app/package-lock.json index 03a0868..6f05481 100644 --- a/ui/tts-client-app/package-lock.json +++ b/ui/tts-client-app/package-lock.json @@ -15,13 +15,15 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } }, "node_modules/@adobe/css-tools": { @@ -3754,6 +3756,11 @@ "tailwindcss": "dist/index.mjs" } }, + "node_modules/@tailwindcss/cli/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -3787,6 +3794,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", @@ -4029,6 +4041,25 @@ "node": ">=8" } }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5458,7 +5489,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -13097,7 +13127,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14788,6 +14817,14 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16791,10 +16828,41 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/ui/tts-client-app/package.json b/ui/tts-client-app/package.json index f07a347..4d75c14 100644 --- a/ui/tts-client-app/package.json +++ b/ui/tts-client-app/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -38,8 +39,9 @@ ] }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } } diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ui/tts-client-app/package-lock.json b/ui/tts-client-app/package-lock.json index 03a0868..6f05481 100644 --- a/ui/tts-client-app/package-lock.json +++ b/ui/tts-client-app/package-lock.json @@ -15,13 +15,15 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } }, "node_modules/@adobe/css-tools": { @@ -3754,6 +3756,11 @@ "tailwindcss": "dist/index.mjs" } }, + "node_modules/@tailwindcss/cli/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -3787,6 +3794,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", @@ -4029,6 +4041,25 @@ "node": ">=8" } }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5458,7 +5489,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -13097,7 +13127,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14788,6 +14817,14 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16791,10 +16828,41 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/ui/tts-client-app/package.json b/ui/tts-client-app/package.json index f07a347..4d75c14 100644 --- a/ui/tts-client-app/package.json +++ b/ui/tts-client-app/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -38,8 +39,9 @@ ] }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } } diff --git a/ui/tts-client-app/public/index.html b/ui/tts-client-app/public/index.html index 8e0f148..3893a7f 100644 --- a/ui/tts-client-app/public/index.html +++ b/ui/tts-client-app/public/index.html @@ -25,7 +25,6 @@ Learn how to configure a non-root public URL by running `npm run build`. --> React App - diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ui/tts-client-app/package-lock.json b/ui/tts-client-app/package-lock.json index 03a0868..6f05481 100644 --- a/ui/tts-client-app/package-lock.json +++ b/ui/tts-client-app/package-lock.json @@ -15,13 +15,15 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } }, "node_modules/@adobe/css-tools": { @@ -3754,6 +3756,11 @@ "tailwindcss": "dist/index.mjs" } }, + "node_modules/@tailwindcss/cli/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -3787,6 +3794,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", @@ -4029,6 +4041,25 @@ "node": ">=8" } }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5458,7 +5489,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -13097,7 +13127,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14788,6 +14817,14 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16791,10 +16828,41 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/ui/tts-client-app/package.json b/ui/tts-client-app/package.json index f07a347..4d75c14 100644 --- a/ui/tts-client-app/package.json +++ b/ui/tts-client-app/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -38,8 +39,9 @@ ] }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } } diff --git a/ui/tts-client-app/public/index.html b/ui/tts-client-app/public/index.html index 8e0f148..3893a7f 100644 --- a/ui/tts-client-app/public/index.html +++ b/ui/tts-client-app/public/index.html @@ -25,7 +25,6 @@ Learn how to configure a non-root public URL by running `npm run build`. --> React App - diff --git a/ui/tts-client-app/src/App.js b/ui/tts-client-app/src/App.js index 6cfd356..74ebba9 100644 --- a/ui/tts-client-app/src/App.js +++ b/ui/tts-client-app/src/App.js @@ -1,781 +1,10 @@ -import React, { useState, useRef, useEffect } from "react"; +// src/App.js +import React from "react"; +import HomePage from "./pages/HomePage"; +import "./styles/main.css"; -// The main application component for the voice chat -const App = () => { - // State for managing the chat history, storing both user and AI messages - const [chatHistory, setChatHistory] = useState([ - { - text: "Hello! I'm an AI assistant. How can I help you today?", - isUser: false, - }, - ]); - // State to manage the UI status, like "Recording...", "Transcribing...", etc. - const [status, setStatus] = useState("Click the microphone to start recording."); - // State to track if the application is currently busy with a request - const [isBusy, setIsBusy] = useState(false); - // State to track the recording status for UI feedback - const [isRecording, setIsRecording] = useState(false); - // State to manage the visibility of the error modal - const [showErrorModal, setShowErrorModal] = useState(false); - // State to hold the error message to be displayed in the modal - const [errorMessage, setErrorMessage] = useState(""); - - // State for managing the chat session - const [sessionId, setSessionId] = useState(null); - // FIX: The userId was being assigned but not used in the rest of the component. - // It's already handled inside the useEffect, so we don't need a state variable for it. - // The previous state variable has been removed. - - // State to toggle between manual and automatic recording modes - const [isAutoMode, setIsAutoMode] = useState(false); - // State to indicate if the app is actively listening for voice in auto mode - const [isAutoListening, setIsAutoListening] = useState(false); - - // Refs for managing Web Audio API and MediaRecorder instances - const mediaRecorderRef = useRef(null); - const audioChunksRef = useRef([]); - const audioContextRef = useRef(null); - const playbackTimeRef = useRef(0); - // Ref to hold the current recording state reliably, to avoid stale closures in event handlers - const isRecordingRef = useRef(false); - - // Keep track of currently playing audio sources so we can stop them on demand - const playingSourcesRef = useRef([]); - - // Refs for managing the VAD (Voice Activity Detection) process - const vadStreamRef = useRef(null); - const scriptProcessorRef = useRef(null); - const silenceTimeoutRef = useRef(null); - - // --- New Refs for Debouncing in Automatic Mode --- - // Ref to track the last time a request was completed. This is used to implement a cooldown. - const lastRequestTimeRef = useRef(0); - // Ref to hold the stream from getUserMedia to be able to stop it later for manual mode - const streamRef = useRef(null); - - // --- New Ref for scrolling to bottom of chat history --- - const chatContainerRef = useRef(null); - - // --- Configuration --- - // Please replace with your actual endpoints - const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; - const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; - const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; - const TTS_ENDPOINT = "http://localhost:8001/speech"; - - // Configuration for Voice Activity Detection - const VAD_THRESHOLD = 0.01; // Adjust this value to control sensitivity (0 to 1) - const VAD_SILENCE_DURATION = 2500; // Duration of silence in ms before stopping recording - - // --- New Configuration for Audio Filtering and Debouncing --- - const MINIMUM_AUDIO_DURATION_MS = 500; // Minimum duration for an audio recording to be processed - const AUTO_MODE_COOLDOWN_MS = 3000; // Cooldown period in milliseconds to prevent rapid re-recordings in automatic mode - - // useEffect hook to create a new session when the component mounts - useEffect(() => { - const createSession = async () => { - setIsBusy(true); - setStatus("Starting new chat session..."); - console.log("Attempting to create a new session."); - try { - const generatedUserId = crypto.randomUUID(); - // FIX: The `setUserId(generatedUserId)` call has been removed - // because the state variable `userId` is no longer needed. - // The `generatedUserId` is still used in the API call. - - const response = await fetch(SESSIONS_CREATE_ENDPOINT, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ user_id: generatedUserId }), - }); - - if (!response.ok) { - throw new Error(`Failed to create session. Status: ${response.status}`); - } - - const session = await response.json(); - setSessionId(session.id); - console.log(`Session created with ID: ${session.id}`); - setStatus("Click the microphone to start recording."); - } catch (err) { - console.error("Error creating session:", err); - setStatus(`Error: Could not start session. ${err.message}`); - setErrorMessage(`Failed to create a chat session: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - } - }; - - createSession(); - - // Cleanup function to stop all streams when the component unmounts - return () => { - stopAllStreams(); - }; - }, []); // Empty dependency array ensures this runs only once on mount - - // New useEffect hook to automatically scroll to the bottom of the chat history - useEffect(() => { - if (chatContainerRef.current) { - chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; - } - }, [chatHistory]); - - /** - * Cleans up all active audio streams and timers. - */ - const stopAllStreams = () => { - if (vadStreamRef.current) { - vadStreamRef.current.getTracks().forEach((track) => track.stop()); - vadStreamRef.current = null; - } - if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { - mediaRecorderRef.current.stop(); - } - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - if (scriptProcessorRef.current) { - scriptProcessorRef.current.disconnect(); - scriptProcessorRef.current = null; - } - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - isRecordingRef.current = false; - setIsRecording(false); - console.log("All audio streams and timers have been stopped."); - }; - - /** - * Adds a new message to the chat history state. The useEffect hook will handle the scrolling. - * @param {string} text - The text content of the message. - * @param {boolean} isUser - True for user messages, false for AI. - */ - const addMessage = (text, isUser) => { - setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); - }; - - /** - * Resamples a Float32Array buffer from a source rate to a destination rate. - * @param {Float32Array} buffer - The input audio buffer. - * @param {number} srcRate - The source sample rate (e.g., 24000). - * @param {number} dstRate - The destination sample rate (AudioContext's). - * @returns {Float32Array} The resampled audio buffer. - */ - const resampleBuffer = (buffer, srcRate, dstRate) => { - if (srcRate === dstRate) return buffer; - const ratio = srcRate / dstRate; - const newLength = Math.round(buffer.length / ratio); - const resampled = new Float32Array(newLength); - for (let i = 0; i < newLength; i++) { - const srcIndex = i * ratio; - const srcIndexFloor = Math.floor(srcIndex); - const srcIndexCeil = Math.min(srcIndexFloor + 1, buffer.length - 1); - const weight = srcIndex - srcIndexFloor; - resampled[i] = - buffer[srcIndexFloor] * (1 - weight) + buffer[srcIndexCeil] * weight; - } - return resampled; - }; - - /** - * Converts raw 16-bit PCM byte data to a normalized Float32Array. - * @param {Uint8Array} pcmBytes - The raw PCM audio data. - * @returns {Float32Array} The converted and normalized audio data. - */ - const convertPcmToFloat32 = (pcmBytes) => { - const int16Array = new Int16Array( - pcmBytes.buffer, - pcmBytes.byteOffset, - pcmBytes.byteLength / 2 - ); - const float32Array = new Float32Array(int16Array.length); - for (let i = 0; i < int16Array.length; i++) { - float32Array[i] = int16Array[i] / 32768; - } - return float32Array; - }; - - /** - * Plays a stream of audio chunks using the Web Audio API. - * @param {string} text - The text to be synthesized by the TTS service. - */ - const playStreamingAudio = async (text) => { - setIsBusy(true); - setStatus("Streaming audio..."); - stopAllPlayingAudio(); // Stop any previous playback - - try { - if (!audioContextRef.current) { - audioContextRef.current = - new (window.AudioContext || window.webkitAudioContext)(); - playbackTimeRef.current = audioContextRef.current.currentTime; - } - - const audioContext = audioContextRef.current; - const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; - - const response = await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ text }), - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const reader = response.body.getReader(); - let leftover = new Uint8Array(0); - - while (true) { - const { done, value: chunk } = await reader.read(); - if (done) { - if (leftover.length > 0) { - console.warn("Leftover bytes discarded:", leftover.length); - } - console.log("TTS Stream complete."); - break; - } - - let combined = new Uint8Array(leftover.length + chunk.length); - combined.set(leftover); - combined.set(chunk, leftover.length); - - let length = combined.length; - if (length % 2 !== 0) { - length -= 1; - } - - const toConvert = combined.slice(0, length); - leftover = combined.slice(length); - const float32Raw = convertPcmToFloat32(toConvert); - const float32Resampled = resampleBuffer( - float32Raw, - 24000, - audioContext.sampleRate - ); - const audioBuffer = audioContext.createBuffer( - 1, - float32Resampled.length, - audioContext.sampleRate - ); - - audioBuffer.copyToChannel(float32Resampled, 0); - - const source = audioContext.createBufferSource(); - source.buffer = audioBuffer; - source.connect(audioContext.destination); - - const currentTime = audioContext.currentTime; - const startTime = Math.max(playbackTimeRef.current, currentTime); - - source.start(startTime); - playbackTimeRef.current = startTime + audioBuffer.duration; - - playingSourcesRef.current.push(source); - source.onended = () => { - playingSourcesRef.current = playingSourcesRef.current.filter( - (s) => s !== source - ); - }; - } - } catch (err) { - console.error("Failed to stream speech:", err); - setStatus(`Error: Failed to stream speech. ${err.message}`); - setErrorMessage(`Failed to stream speech: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Stops all currently playing TTS audio sources immediately. - */ - const stopAllPlayingAudio = () => { - if (audioContextRef.current) { - playingSourcesRef.current.forEach((source) => { - try { - source.stop(); - } catch (e) { - // Ignore errors from stopping already stopped sources - } - }); - playingSourcesRef.current = []; - playbackTimeRef.current = audioContextRef.current.currentTime; - console.log("All playing audio has been stopped."); - } - }; - - /** - * Starts the audio recording process for manual mode. - */ - const startManualRecording = async () => { - if (isRecording) { - console.log("Already recording, ignoring start request."); - return; - } - - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting manual recording..."); - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - streamRef.current = stream; // Store the stream to stop it later - mediaRecorderRef.current = new MediaRecorder(stream); - mediaRecorderRef.current.start(); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // Ensure the stream is stopped after recording has finished. - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - - // Clear the media recorder ref to allow a new recording to be created. - mediaRecorderRef.current = null; - - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - }; - - setIsRecording(true); - isRecordingRef.current = true; - setStatus("Recording... Click to stop."); - } catch (err) { - console.error("Error accessing microphone:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops manual recording and finalizes the audio blob. - */ - const stopManualRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("Stopping manual recording."); - // Set isBusy immediately to prevent multiple stop requests. - setIsBusy(true); - setIsRecording(false); // Update UI immediately - mediaRecorderRef.current.stop(); - // The isRecording state will be set to false by the mediaRecorder.onstop handler after processing. - } - }; - - /** - * Starts the voice activity detection process. - */ - const startAutoListening = async () => { - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting automatic listening (VAD)."); - // Request microphone access for VAD. - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - vadStreamRef.current = stream; - - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const source = audioContext.createMediaStreamSource(stream); - - // Use the maximum buffer size for less frequent onaudioprocess calls - const bufferSize = 4096; - const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); - scriptProcessorRef.current = scriptProcessor; - - source.connect(scriptProcessor); - scriptProcessor.connect(audioContext.destination); - - scriptProcessor.onaudioprocess = (event) => { - const inputBuffer = event.inputBuffer.getChannelData(0); - let sum = 0.0; - for (let i = 0; i < inputBuffer.length; i++) { - sum += inputBuffer[i] * inputBuffer[i]; - } - const volume = Math.sqrt(sum / inputBuffer.length); - - console.log(`Current audio volume: ${volume.toFixed(2)}`); - - const isVoiceDetected = (volume > VAD_THRESHOLD); - - if (isVoiceDetected) { - // Voice is detected, so clear the silence timeout - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - - // --- NEW LOGIC: Check for cooldown before starting recording --- - const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; - const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; - - // Start recording only if it's not already running AND the cooldown has passed - if (!isRecordingRef.current && isCooldownPassed) { - startAutoRecording(stream); - } - } else if (isRecordingRef.current) { - // We are currently recording, but silence is detected - // If a silence timeout is not already running, start one - if (!silenceTimeoutRef.current) { - console.log("Silence detected. Starting timeout."); - silenceTimeoutRef.current = setTimeout(() => { - // If the timeout expires, stop the recording and process the audio - stopAutoRecording(); - }, VAD_SILENCE_DURATION); - } - } - }; - - setIsAutoListening(true); - setStatus("Listening for voice..."); - } catch (err) { - console.error("Error accessing microphone for VAD:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops the VAD process and cleans up resources. - */ - const stopAutoListening = () => { - setIsAutoListening(false); - // We only stop all streams when the user explicitly stops auto mode. - stopAllStreams(); - setStatus("Click to start listening."); - console.log("Stopping automatic listening."); - }; - - /** - * Starts the MediaRecorder for automatic mode. - */ - const startAutoRecording = (stream) => { - // Prevent starting a new recorder if one is already running - if (mediaRecorderRef.current?.state === "recording") { - console.log("Auto recording is already running, ignoring start request."); - return; - } - - console.log("Starting a new MediaRecorder for automatic mode."); - mediaRecorderRef.current = new MediaRecorder(stream); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // This flag is essential to prevent the VAD loop from re-triggering a recording - // before the current one is fully processed. - isRecordingRef.current = false; - setIsRecording(false); - if (audioChunksRef.current.length > 0) { - // Set isBusy to prevent the user from starting a new request while processing - setIsBusy(true); - setStatus("Transcribing audio..."); - console.log("Auto recording stopped. Processing conversation..."); - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - } else { - console.warn("Auto recording stopped but no audio chunks were collected."); - setIsBusy(false); - setStatus("Listening for voice..."); - } - }; - - mediaRecorderRef.current.start(); - isRecordingRef.current = true; - setIsRecording(true); - setStatus("Recording..."); - console.log("MediaRecorder started for automatic mode."); - }; - - /** - * Stops the MediaRecorder for automatic mode. - */ - const stopAutoRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("MediaRecorder stopped for automatic mode."); - mediaRecorderRef.current.stop(); - } - }; - - - /** - * Handles the complete conversation flow: STT -> LLM -> TTS. - * @param {Blob} audioBlob - The recorded audio from the user. - */ - const processConversation = async (audioBlob) => { - console.log("Processing conversation..."); - try { - // Step 1: Filter out audio that is too short to be meaningful - // Get the duration of the audio blob. Assuming a 48kHz sample rate, each byte is ~1/48000th of a second - // For a 'audio/wav' blob, this can be complex. A simple check on blob size is a good heuristic. - // Let's assume an average bitrate and calculate duration. Or even simpler, check the raw size. - // A more robust method would be to analyze the audio header, but for this context, a simple size check is fine. - const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; // Assuming 48kHz, 16-bit mono - if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { - console.log(`Audio is too short (${audioDuration.toFixed(2)}ms), skipping.`); - setStatus("Audio was too short. Please speak a little longer."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; // Exit the function early - } - - setStatus("Transcribing audio..."); - if (audioBlob.size === 0) { - console.warn("Audio blob is empty, skipping STT API call."); - setStatus("Recording stopped, but no audio was captured. Please try again."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; - } - - const formData = new FormData(); - formData.append("audio_file", audioBlob, "audio.wav"); - - const sttResponse = await fetch(STT_ENDPOINT, { - method: "POST", - body: formData, - }); - if (!sttResponse.ok) { - console.error(`STT API failed with status: ${sttResponse.status}`); - throw new Error("STT API failed"); - } - const sttResult = await sttResponse.json(); - const userText = sttResult.transcript; - console.log(`STT transcript received: "${userText}"`); - addMessage(userText, true); - - // Step 2: Text-to-Text (LLM) - setStatus("AI is thinking..."); - const llmResponse = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ prompt: userText, model: "gemini" }), - }); - if (!llmResponse.ok) { - console.error(`LLM API failed with status: ${llmResponse.status}`); - throw new Error("LLM API failed"); - } - const llmResult = await llmResponse.json(); - const aiText = llmResult.answer; - console.log(`LLM response received: "${aiText}"`); - addMessage(aiText, false); - - // Step 3: Text-to-Speech (TTS) - await playStreamingAudio(aiText); - } catch (error) { - console.error("Conversation processing failed:", error); - setStatus(`Error: ${error.message}`); - setErrorMessage(`An error occurred: ${error.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - // Ensure refs are nullified after processing for a clean state - mediaRecorderRef.current = null; - streamRef.current = null; - } else if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Toggles the recording state based on the current mode. - */ - const handleMicClick = () => { - stopAllPlayingAudio(); // Interrupt any ongoing TTS playback immediately - - // Prevent new actions if the app is busy processing a request - if (isBusy) { - console.log("App is busy, ignoring mic click."); - return; - } - - if (isAutoMode) { - if (isAutoListening) { - stopAutoListening(); - } else { - startAutoListening(); - } - } else { // Manual Mode - if (isRecording) { - stopManualRecording(); - } else { - startManualRecording(); - } - } - }; - - // FIX: The microphoneButtonState variable was assigned but never used. - // The logic has been moved directly into the JSX below. - // The previous variable declaration has been removed. - - const micButtonColorClass = isRecording - ? "bg-red-600 hover:bg-red-700 active:bg-red-800" - : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; - - return ( -
-
- {/* Header */} -
-

Voice Chat Assistant

-

- Ask me anything and I'll respond! -

-
- - {/* Chat History */} -
- {chatHistory.map((message, index) => ( -
-
- {message.text} -
-
- ))} -
- - {/* Status and Controls */} -
-
- {status} -
- -
- {/* Microphone Button */} - -
- - {/* Mode Toggle */} -
- -
- -
-
- - {/* Error Modal */} - {showErrorModal && ( -
-
-

Error

-

{errorMessage}

- -
-
- )} -
- ); -}; +function App() { + return ; +} export default App; \ No newline at end of file diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ui/tts-client-app/package-lock.json b/ui/tts-client-app/package-lock.json index 03a0868..6f05481 100644 --- a/ui/tts-client-app/package-lock.json +++ b/ui/tts-client-app/package-lock.json @@ -15,13 +15,15 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } }, "node_modules/@adobe/css-tools": { @@ -3754,6 +3756,11 @@ "tailwindcss": "dist/index.mjs" } }, + "node_modules/@tailwindcss/cli/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -3787,6 +3794,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", @@ -4029,6 +4041,25 @@ "node": ">=8" } }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5458,7 +5489,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -13097,7 +13127,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14788,6 +14817,14 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16791,10 +16828,41 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/ui/tts-client-app/package.json b/ui/tts-client-app/package.json index f07a347..4d75c14 100644 --- a/ui/tts-client-app/package.json +++ b/ui/tts-client-app/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -38,8 +39,9 @@ ] }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } } diff --git a/ui/tts-client-app/public/index.html b/ui/tts-client-app/public/index.html index 8e0f148..3893a7f 100644 --- a/ui/tts-client-app/public/index.html +++ b/ui/tts-client-app/public/index.html @@ -25,7 +25,6 @@ Learn how to configure a non-root public URL by running `npm run build`. --> React App - diff --git a/ui/tts-client-app/src/App.js b/ui/tts-client-app/src/App.js index 6cfd356..74ebba9 100644 --- a/ui/tts-client-app/src/App.js +++ b/ui/tts-client-app/src/App.js @@ -1,781 +1,10 @@ -import React, { useState, useRef, useEffect } from "react"; +// src/App.js +import React from "react"; +import HomePage from "./pages/HomePage"; +import "./styles/main.css"; -// The main application component for the voice chat -const App = () => { - // State for managing the chat history, storing both user and AI messages - const [chatHistory, setChatHistory] = useState([ - { - text: "Hello! I'm an AI assistant. How can I help you today?", - isUser: false, - }, - ]); - // State to manage the UI status, like "Recording...", "Transcribing...", etc. - const [status, setStatus] = useState("Click the microphone to start recording."); - // State to track if the application is currently busy with a request - const [isBusy, setIsBusy] = useState(false); - // State to track the recording status for UI feedback - const [isRecording, setIsRecording] = useState(false); - // State to manage the visibility of the error modal - const [showErrorModal, setShowErrorModal] = useState(false); - // State to hold the error message to be displayed in the modal - const [errorMessage, setErrorMessage] = useState(""); - - // State for managing the chat session - const [sessionId, setSessionId] = useState(null); - // FIX: The userId was being assigned but not used in the rest of the component. - // It's already handled inside the useEffect, so we don't need a state variable for it. - // The previous state variable has been removed. - - // State to toggle between manual and automatic recording modes - const [isAutoMode, setIsAutoMode] = useState(false); - // State to indicate if the app is actively listening for voice in auto mode - const [isAutoListening, setIsAutoListening] = useState(false); - - // Refs for managing Web Audio API and MediaRecorder instances - const mediaRecorderRef = useRef(null); - const audioChunksRef = useRef([]); - const audioContextRef = useRef(null); - const playbackTimeRef = useRef(0); - // Ref to hold the current recording state reliably, to avoid stale closures in event handlers - const isRecordingRef = useRef(false); - - // Keep track of currently playing audio sources so we can stop them on demand - const playingSourcesRef = useRef([]); - - // Refs for managing the VAD (Voice Activity Detection) process - const vadStreamRef = useRef(null); - const scriptProcessorRef = useRef(null); - const silenceTimeoutRef = useRef(null); - - // --- New Refs for Debouncing in Automatic Mode --- - // Ref to track the last time a request was completed. This is used to implement a cooldown. - const lastRequestTimeRef = useRef(0); - // Ref to hold the stream from getUserMedia to be able to stop it later for manual mode - const streamRef = useRef(null); - - // --- New Ref for scrolling to bottom of chat history --- - const chatContainerRef = useRef(null); - - // --- Configuration --- - // Please replace with your actual endpoints - const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; - const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; - const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; - const TTS_ENDPOINT = "http://localhost:8001/speech"; - - // Configuration for Voice Activity Detection - const VAD_THRESHOLD = 0.01; // Adjust this value to control sensitivity (0 to 1) - const VAD_SILENCE_DURATION = 2500; // Duration of silence in ms before stopping recording - - // --- New Configuration for Audio Filtering and Debouncing --- - const MINIMUM_AUDIO_DURATION_MS = 500; // Minimum duration for an audio recording to be processed - const AUTO_MODE_COOLDOWN_MS = 3000; // Cooldown period in milliseconds to prevent rapid re-recordings in automatic mode - - // useEffect hook to create a new session when the component mounts - useEffect(() => { - const createSession = async () => { - setIsBusy(true); - setStatus("Starting new chat session..."); - console.log("Attempting to create a new session."); - try { - const generatedUserId = crypto.randomUUID(); - // FIX: The `setUserId(generatedUserId)` call has been removed - // because the state variable `userId` is no longer needed. - // The `generatedUserId` is still used in the API call. - - const response = await fetch(SESSIONS_CREATE_ENDPOINT, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ user_id: generatedUserId }), - }); - - if (!response.ok) { - throw new Error(`Failed to create session. Status: ${response.status}`); - } - - const session = await response.json(); - setSessionId(session.id); - console.log(`Session created with ID: ${session.id}`); - setStatus("Click the microphone to start recording."); - } catch (err) { - console.error("Error creating session:", err); - setStatus(`Error: Could not start session. ${err.message}`); - setErrorMessage(`Failed to create a chat session: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - } - }; - - createSession(); - - // Cleanup function to stop all streams when the component unmounts - return () => { - stopAllStreams(); - }; - }, []); // Empty dependency array ensures this runs only once on mount - - // New useEffect hook to automatically scroll to the bottom of the chat history - useEffect(() => { - if (chatContainerRef.current) { - chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; - } - }, [chatHistory]); - - /** - * Cleans up all active audio streams and timers. - */ - const stopAllStreams = () => { - if (vadStreamRef.current) { - vadStreamRef.current.getTracks().forEach((track) => track.stop()); - vadStreamRef.current = null; - } - if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { - mediaRecorderRef.current.stop(); - } - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - if (scriptProcessorRef.current) { - scriptProcessorRef.current.disconnect(); - scriptProcessorRef.current = null; - } - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - isRecordingRef.current = false; - setIsRecording(false); - console.log("All audio streams and timers have been stopped."); - }; - - /** - * Adds a new message to the chat history state. The useEffect hook will handle the scrolling. - * @param {string} text - The text content of the message. - * @param {boolean} isUser - True for user messages, false for AI. - */ - const addMessage = (text, isUser) => { - setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); - }; - - /** - * Resamples a Float32Array buffer from a source rate to a destination rate. - * @param {Float32Array} buffer - The input audio buffer. - * @param {number} srcRate - The source sample rate (e.g., 24000). - * @param {number} dstRate - The destination sample rate (AudioContext's). - * @returns {Float32Array} The resampled audio buffer. - */ - const resampleBuffer = (buffer, srcRate, dstRate) => { - if (srcRate === dstRate) return buffer; - const ratio = srcRate / dstRate; - const newLength = Math.round(buffer.length / ratio); - const resampled = new Float32Array(newLength); - for (let i = 0; i < newLength; i++) { - const srcIndex = i * ratio; - const srcIndexFloor = Math.floor(srcIndex); - const srcIndexCeil = Math.min(srcIndexFloor + 1, buffer.length - 1); - const weight = srcIndex - srcIndexFloor; - resampled[i] = - buffer[srcIndexFloor] * (1 - weight) + buffer[srcIndexCeil] * weight; - } - return resampled; - }; - - /** - * Converts raw 16-bit PCM byte data to a normalized Float32Array. - * @param {Uint8Array} pcmBytes - The raw PCM audio data. - * @returns {Float32Array} The converted and normalized audio data. - */ - const convertPcmToFloat32 = (pcmBytes) => { - const int16Array = new Int16Array( - pcmBytes.buffer, - pcmBytes.byteOffset, - pcmBytes.byteLength / 2 - ); - const float32Array = new Float32Array(int16Array.length); - for (let i = 0; i < int16Array.length; i++) { - float32Array[i] = int16Array[i] / 32768; - } - return float32Array; - }; - - /** - * Plays a stream of audio chunks using the Web Audio API. - * @param {string} text - The text to be synthesized by the TTS service. - */ - const playStreamingAudio = async (text) => { - setIsBusy(true); - setStatus("Streaming audio..."); - stopAllPlayingAudio(); // Stop any previous playback - - try { - if (!audioContextRef.current) { - audioContextRef.current = - new (window.AudioContext || window.webkitAudioContext)(); - playbackTimeRef.current = audioContextRef.current.currentTime; - } - - const audioContext = audioContextRef.current; - const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; - - const response = await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ text }), - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const reader = response.body.getReader(); - let leftover = new Uint8Array(0); - - while (true) { - const { done, value: chunk } = await reader.read(); - if (done) { - if (leftover.length > 0) { - console.warn("Leftover bytes discarded:", leftover.length); - } - console.log("TTS Stream complete."); - break; - } - - let combined = new Uint8Array(leftover.length + chunk.length); - combined.set(leftover); - combined.set(chunk, leftover.length); - - let length = combined.length; - if (length % 2 !== 0) { - length -= 1; - } - - const toConvert = combined.slice(0, length); - leftover = combined.slice(length); - const float32Raw = convertPcmToFloat32(toConvert); - const float32Resampled = resampleBuffer( - float32Raw, - 24000, - audioContext.sampleRate - ); - const audioBuffer = audioContext.createBuffer( - 1, - float32Resampled.length, - audioContext.sampleRate - ); - - audioBuffer.copyToChannel(float32Resampled, 0); - - const source = audioContext.createBufferSource(); - source.buffer = audioBuffer; - source.connect(audioContext.destination); - - const currentTime = audioContext.currentTime; - const startTime = Math.max(playbackTimeRef.current, currentTime); - - source.start(startTime); - playbackTimeRef.current = startTime + audioBuffer.duration; - - playingSourcesRef.current.push(source); - source.onended = () => { - playingSourcesRef.current = playingSourcesRef.current.filter( - (s) => s !== source - ); - }; - } - } catch (err) { - console.error("Failed to stream speech:", err); - setStatus(`Error: Failed to stream speech. ${err.message}`); - setErrorMessage(`Failed to stream speech: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Stops all currently playing TTS audio sources immediately. - */ - const stopAllPlayingAudio = () => { - if (audioContextRef.current) { - playingSourcesRef.current.forEach((source) => { - try { - source.stop(); - } catch (e) { - // Ignore errors from stopping already stopped sources - } - }); - playingSourcesRef.current = []; - playbackTimeRef.current = audioContextRef.current.currentTime; - console.log("All playing audio has been stopped."); - } - }; - - /** - * Starts the audio recording process for manual mode. - */ - const startManualRecording = async () => { - if (isRecording) { - console.log("Already recording, ignoring start request."); - return; - } - - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting manual recording..."); - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - streamRef.current = stream; // Store the stream to stop it later - mediaRecorderRef.current = new MediaRecorder(stream); - mediaRecorderRef.current.start(); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // Ensure the stream is stopped after recording has finished. - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - - // Clear the media recorder ref to allow a new recording to be created. - mediaRecorderRef.current = null; - - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - }; - - setIsRecording(true); - isRecordingRef.current = true; - setStatus("Recording... Click to stop."); - } catch (err) { - console.error("Error accessing microphone:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops manual recording and finalizes the audio blob. - */ - const stopManualRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("Stopping manual recording."); - // Set isBusy immediately to prevent multiple stop requests. - setIsBusy(true); - setIsRecording(false); // Update UI immediately - mediaRecorderRef.current.stop(); - // The isRecording state will be set to false by the mediaRecorder.onstop handler after processing. - } - }; - - /** - * Starts the voice activity detection process. - */ - const startAutoListening = async () => { - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting automatic listening (VAD)."); - // Request microphone access for VAD. - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - vadStreamRef.current = stream; - - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const source = audioContext.createMediaStreamSource(stream); - - // Use the maximum buffer size for less frequent onaudioprocess calls - const bufferSize = 4096; - const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); - scriptProcessorRef.current = scriptProcessor; - - source.connect(scriptProcessor); - scriptProcessor.connect(audioContext.destination); - - scriptProcessor.onaudioprocess = (event) => { - const inputBuffer = event.inputBuffer.getChannelData(0); - let sum = 0.0; - for (let i = 0; i < inputBuffer.length; i++) { - sum += inputBuffer[i] * inputBuffer[i]; - } - const volume = Math.sqrt(sum / inputBuffer.length); - - console.log(`Current audio volume: ${volume.toFixed(2)}`); - - const isVoiceDetected = (volume > VAD_THRESHOLD); - - if (isVoiceDetected) { - // Voice is detected, so clear the silence timeout - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - - // --- NEW LOGIC: Check for cooldown before starting recording --- - const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; - const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; - - // Start recording only if it's not already running AND the cooldown has passed - if (!isRecordingRef.current && isCooldownPassed) { - startAutoRecording(stream); - } - } else if (isRecordingRef.current) { - // We are currently recording, but silence is detected - // If a silence timeout is not already running, start one - if (!silenceTimeoutRef.current) { - console.log("Silence detected. Starting timeout."); - silenceTimeoutRef.current = setTimeout(() => { - // If the timeout expires, stop the recording and process the audio - stopAutoRecording(); - }, VAD_SILENCE_DURATION); - } - } - }; - - setIsAutoListening(true); - setStatus("Listening for voice..."); - } catch (err) { - console.error("Error accessing microphone for VAD:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops the VAD process and cleans up resources. - */ - const stopAutoListening = () => { - setIsAutoListening(false); - // We only stop all streams when the user explicitly stops auto mode. - stopAllStreams(); - setStatus("Click to start listening."); - console.log("Stopping automatic listening."); - }; - - /** - * Starts the MediaRecorder for automatic mode. - */ - const startAutoRecording = (stream) => { - // Prevent starting a new recorder if one is already running - if (mediaRecorderRef.current?.state === "recording") { - console.log("Auto recording is already running, ignoring start request."); - return; - } - - console.log("Starting a new MediaRecorder for automatic mode."); - mediaRecorderRef.current = new MediaRecorder(stream); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // This flag is essential to prevent the VAD loop from re-triggering a recording - // before the current one is fully processed. - isRecordingRef.current = false; - setIsRecording(false); - if (audioChunksRef.current.length > 0) { - // Set isBusy to prevent the user from starting a new request while processing - setIsBusy(true); - setStatus("Transcribing audio..."); - console.log("Auto recording stopped. Processing conversation..."); - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - } else { - console.warn("Auto recording stopped but no audio chunks were collected."); - setIsBusy(false); - setStatus("Listening for voice..."); - } - }; - - mediaRecorderRef.current.start(); - isRecordingRef.current = true; - setIsRecording(true); - setStatus("Recording..."); - console.log("MediaRecorder started for automatic mode."); - }; - - /** - * Stops the MediaRecorder for automatic mode. - */ - const stopAutoRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("MediaRecorder stopped for automatic mode."); - mediaRecorderRef.current.stop(); - } - }; - - - /** - * Handles the complete conversation flow: STT -> LLM -> TTS. - * @param {Blob} audioBlob - The recorded audio from the user. - */ - const processConversation = async (audioBlob) => { - console.log("Processing conversation..."); - try { - // Step 1: Filter out audio that is too short to be meaningful - // Get the duration of the audio blob. Assuming a 48kHz sample rate, each byte is ~1/48000th of a second - // For a 'audio/wav' blob, this can be complex. A simple check on blob size is a good heuristic. - // Let's assume an average bitrate and calculate duration. Or even simpler, check the raw size. - // A more robust method would be to analyze the audio header, but for this context, a simple size check is fine. - const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; // Assuming 48kHz, 16-bit mono - if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { - console.log(`Audio is too short (${audioDuration.toFixed(2)}ms), skipping.`); - setStatus("Audio was too short. Please speak a little longer."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; // Exit the function early - } - - setStatus("Transcribing audio..."); - if (audioBlob.size === 0) { - console.warn("Audio blob is empty, skipping STT API call."); - setStatus("Recording stopped, but no audio was captured. Please try again."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; - } - - const formData = new FormData(); - formData.append("audio_file", audioBlob, "audio.wav"); - - const sttResponse = await fetch(STT_ENDPOINT, { - method: "POST", - body: formData, - }); - if (!sttResponse.ok) { - console.error(`STT API failed with status: ${sttResponse.status}`); - throw new Error("STT API failed"); - } - const sttResult = await sttResponse.json(); - const userText = sttResult.transcript; - console.log(`STT transcript received: "${userText}"`); - addMessage(userText, true); - - // Step 2: Text-to-Text (LLM) - setStatus("AI is thinking..."); - const llmResponse = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ prompt: userText, model: "gemini" }), - }); - if (!llmResponse.ok) { - console.error(`LLM API failed with status: ${llmResponse.status}`); - throw new Error("LLM API failed"); - } - const llmResult = await llmResponse.json(); - const aiText = llmResult.answer; - console.log(`LLM response received: "${aiText}"`); - addMessage(aiText, false); - - // Step 3: Text-to-Speech (TTS) - await playStreamingAudio(aiText); - } catch (error) { - console.error("Conversation processing failed:", error); - setStatus(`Error: ${error.message}`); - setErrorMessage(`An error occurred: ${error.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - // Ensure refs are nullified after processing for a clean state - mediaRecorderRef.current = null; - streamRef.current = null; - } else if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Toggles the recording state based on the current mode. - */ - const handleMicClick = () => { - stopAllPlayingAudio(); // Interrupt any ongoing TTS playback immediately - - // Prevent new actions if the app is busy processing a request - if (isBusy) { - console.log("App is busy, ignoring mic click."); - return; - } - - if (isAutoMode) { - if (isAutoListening) { - stopAutoListening(); - } else { - startAutoListening(); - } - } else { // Manual Mode - if (isRecording) { - stopManualRecording(); - } else { - startManualRecording(); - } - } - }; - - // FIX: The microphoneButtonState variable was assigned but never used. - // The logic has been moved directly into the JSX below. - // The previous variable declaration has been removed. - - const micButtonColorClass = isRecording - ? "bg-red-600 hover:bg-red-700 active:bg-red-800" - : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; - - return ( -
-
- {/* Header */} -
-

Voice Chat Assistant

-

- Ask me anything and I'll respond! -

-
- - {/* Chat History */} -
- {chatHistory.map((message, index) => ( -
-
- {message.text} -
-
- ))} -
- - {/* Status and Controls */} -
-
- {status} -
- -
- {/* Microphone Button */} - -
- - {/* Mode Toggle */} -
- -
- -
-
- - {/* Error Modal */} - {showErrorModal && ( -
-
-

Error

-

{errorMessage}

- -
-
- )} -
- ); -}; +function App() { + return ; +} export default App; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/ChatWindow.js b/ui/tts-client-app/src/components/ChatWindow.js new file mode 100644 index 0000000..28397a4 --- /dev/null +++ b/ui/tts-client-app/src/components/ChatWindow.js @@ -0,0 +1,29 @@ +// src/components/ChatWindow.js +import React from "react"; + +const ChatWindow = ({ chatHistory }) => { + return ( +
+ {chatHistory.map((message, index) => ( +
+
+

{message.text}

+
+
+ ))} +
+ ); +}; + +export default ChatWindow; \ No newline at end of file diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ui/tts-client-app/package-lock.json b/ui/tts-client-app/package-lock.json index 03a0868..6f05481 100644 --- a/ui/tts-client-app/package-lock.json +++ b/ui/tts-client-app/package-lock.json @@ -15,13 +15,15 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } }, "node_modules/@adobe/css-tools": { @@ -3754,6 +3756,11 @@ "tailwindcss": "dist/index.mjs" } }, + "node_modules/@tailwindcss/cli/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -3787,6 +3794,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", @@ -4029,6 +4041,25 @@ "node": ">=8" } }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5458,7 +5489,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -13097,7 +13127,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14788,6 +14817,14 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16791,10 +16828,41 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/ui/tts-client-app/package.json b/ui/tts-client-app/package.json index f07a347..4d75c14 100644 --- a/ui/tts-client-app/package.json +++ b/ui/tts-client-app/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -38,8 +39,9 @@ ] }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } } diff --git a/ui/tts-client-app/public/index.html b/ui/tts-client-app/public/index.html index 8e0f148..3893a7f 100644 --- a/ui/tts-client-app/public/index.html +++ b/ui/tts-client-app/public/index.html @@ -25,7 +25,6 @@ Learn how to configure a non-root public URL by running `npm run build`. --> React App - diff --git a/ui/tts-client-app/src/App.js b/ui/tts-client-app/src/App.js index 6cfd356..74ebba9 100644 --- a/ui/tts-client-app/src/App.js +++ b/ui/tts-client-app/src/App.js @@ -1,781 +1,10 @@ -import React, { useState, useRef, useEffect } from "react"; +// src/App.js +import React from "react"; +import HomePage from "./pages/HomePage"; +import "./styles/main.css"; -// The main application component for the voice chat -const App = () => { - // State for managing the chat history, storing both user and AI messages - const [chatHistory, setChatHistory] = useState([ - { - text: "Hello! I'm an AI assistant. How can I help you today?", - isUser: false, - }, - ]); - // State to manage the UI status, like "Recording...", "Transcribing...", etc. - const [status, setStatus] = useState("Click the microphone to start recording."); - // State to track if the application is currently busy with a request - const [isBusy, setIsBusy] = useState(false); - // State to track the recording status for UI feedback - const [isRecording, setIsRecording] = useState(false); - // State to manage the visibility of the error modal - const [showErrorModal, setShowErrorModal] = useState(false); - // State to hold the error message to be displayed in the modal - const [errorMessage, setErrorMessage] = useState(""); - - // State for managing the chat session - const [sessionId, setSessionId] = useState(null); - // FIX: The userId was being assigned but not used in the rest of the component. - // It's already handled inside the useEffect, so we don't need a state variable for it. - // The previous state variable has been removed. - - // State to toggle between manual and automatic recording modes - const [isAutoMode, setIsAutoMode] = useState(false); - // State to indicate if the app is actively listening for voice in auto mode - const [isAutoListening, setIsAutoListening] = useState(false); - - // Refs for managing Web Audio API and MediaRecorder instances - const mediaRecorderRef = useRef(null); - const audioChunksRef = useRef([]); - const audioContextRef = useRef(null); - const playbackTimeRef = useRef(0); - // Ref to hold the current recording state reliably, to avoid stale closures in event handlers - const isRecordingRef = useRef(false); - - // Keep track of currently playing audio sources so we can stop them on demand - const playingSourcesRef = useRef([]); - - // Refs for managing the VAD (Voice Activity Detection) process - const vadStreamRef = useRef(null); - const scriptProcessorRef = useRef(null); - const silenceTimeoutRef = useRef(null); - - // --- New Refs for Debouncing in Automatic Mode --- - // Ref to track the last time a request was completed. This is used to implement a cooldown. - const lastRequestTimeRef = useRef(0); - // Ref to hold the stream from getUserMedia to be able to stop it later for manual mode - const streamRef = useRef(null); - - // --- New Ref for scrolling to bottom of chat history --- - const chatContainerRef = useRef(null); - - // --- Configuration --- - // Please replace with your actual endpoints - const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; - const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; - const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; - const TTS_ENDPOINT = "http://localhost:8001/speech"; - - // Configuration for Voice Activity Detection - const VAD_THRESHOLD = 0.01; // Adjust this value to control sensitivity (0 to 1) - const VAD_SILENCE_DURATION = 2500; // Duration of silence in ms before stopping recording - - // --- New Configuration for Audio Filtering and Debouncing --- - const MINIMUM_AUDIO_DURATION_MS = 500; // Minimum duration for an audio recording to be processed - const AUTO_MODE_COOLDOWN_MS = 3000; // Cooldown period in milliseconds to prevent rapid re-recordings in automatic mode - - // useEffect hook to create a new session when the component mounts - useEffect(() => { - const createSession = async () => { - setIsBusy(true); - setStatus("Starting new chat session..."); - console.log("Attempting to create a new session."); - try { - const generatedUserId = crypto.randomUUID(); - // FIX: The `setUserId(generatedUserId)` call has been removed - // because the state variable `userId` is no longer needed. - // The `generatedUserId` is still used in the API call. - - const response = await fetch(SESSIONS_CREATE_ENDPOINT, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ user_id: generatedUserId }), - }); - - if (!response.ok) { - throw new Error(`Failed to create session. Status: ${response.status}`); - } - - const session = await response.json(); - setSessionId(session.id); - console.log(`Session created with ID: ${session.id}`); - setStatus("Click the microphone to start recording."); - } catch (err) { - console.error("Error creating session:", err); - setStatus(`Error: Could not start session. ${err.message}`); - setErrorMessage(`Failed to create a chat session: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - } - }; - - createSession(); - - // Cleanup function to stop all streams when the component unmounts - return () => { - stopAllStreams(); - }; - }, []); // Empty dependency array ensures this runs only once on mount - - // New useEffect hook to automatically scroll to the bottom of the chat history - useEffect(() => { - if (chatContainerRef.current) { - chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; - } - }, [chatHistory]); - - /** - * Cleans up all active audio streams and timers. - */ - const stopAllStreams = () => { - if (vadStreamRef.current) { - vadStreamRef.current.getTracks().forEach((track) => track.stop()); - vadStreamRef.current = null; - } - if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { - mediaRecorderRef.current.stop(); - } - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - if (scriptProcessorRef.current) { - scriptProcessorRef.current.disconnect(); - scriptProcessorRef.current = null; - } - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - isRecordingRef.current = false; - setIsRecording(false); - console.log("All audio streams and timers have been stopped."); - }; - - /** - * Adds a new message to the chat history state. The useEffect hook will handle the scrolling. - * @param {string} text - The text content of the message. - * @param {boolean} isUser - True for user messages, false for AI. - */ - const addMessage = (text, isUser) => { - setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); - }; - - /** - * Resamples a Float32Array buffer from a source rate to a destination rate. - * @param {Float32Array} buffer - The input audio buffer. - * @param {number} srcRate - The source sample rate (e.g., 24000). - * @param {number} dstRate - The destination sample rate (AudioContext's). - * @returns {Float32Array} The resampled audio buffer. - */ - const resampleBuffer = (buffer, srcRate, dstRate) => { - if (srcRate === dstRate) return buffer; - const ratio = srcRate / dstRate; - const newLength = Math.round(buffer.length / ratio); - const resampled = new Float32Array(newLength); - for (let i = 0; i < newLength; i++) { - const srcIndex = i * ratio; - const srcIndexFloor = Math.floor(srcIndex); - const srcIndexCeil = Math.min(srcIndexFloor + 1, buffer.length - 1); - const weight = srcIndex - srcIndexFloor; - resampled[i] = - buffer[srcIndexFloor] * (1 - weight) + buffer[srcIndexCeil] * weight; - } - return resampled; - }; - - /** - * Converts raw 16-bit PCM byte data to a normalized Float32Array. - * @param {Uint8Array} pcmBytes - The raw PCM audio data. - * @returns {Float32Array} The converted and normalized audio data. - */ - const convertPcmToFloat32 = (pcmBytes) => { - const int16Array = new Int16Array( - pcmBytes.buffer, - pcmBytes.byteOffset, - pcmBytes.byteLength / 2 - ); - const float32Array = new Float32Array(int16Array.length); - for (let i = 0; i < int16Array.length; i++) { - float32Array[i] = int16Array[i] / 32768; - } - return float32Array; - }; - - /** - * Plays a stream of audio chunks using the Web Audio API. - * @param {string} text - The text to be synthesized by the TTS service. - */ - const playStreamingAudio = async (text) => { - setIsBusy(true); - setStatus("Streaming audio..."); - stopAllPlayingAudio(); // Stop any previous playback - - try { - if (!audioContextRef.current) { - audioContextRef.current = - new (window.AudioContext || window.webkitAudioContext)(); - playbackTimeRef.current = audioContextRef.current.currentTime; - } - - const audioContext = audioContextRef.current; - const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; - - const response = await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ text }), - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const reader = response.body.getReader(); - let leftover = new Uint8Array(0); - - while (true) { - const { done, value: chunk } = await reader.read(); - if (done) { - if (leftover.length > 0) { - console.warn("Leftover bytes discarded:", leftover.length); - } - console.log("TTS Stream complete."); - break; - } - - let combined = new Uint8Array(leftover.length + chunk.length); - combined.set(leftover); - combined.set(chunk, leftover.length); - - let length = combined.length; - if (length % 2 !== 0) { - length -= 1; - } - - const toConvert = combined.slice(0, length); - leftover = combined.slice(length); - const float32Raw = convertPcmToFloat32(toConvert); - const float32Resampled = resampleBuffer( - float32Raw, - 24000, - audioContext.sampleRate - ); - const audioBuffer = audioContext.createBuffer( - 1, - float32Resampled.length, - audioContext.sampleRate - ); - - audioBuffer.copyToChannel(float32Resampled, 0); - - const source = audioContext.createBufferSource(); - source.buffer = audioBuffer; - source.connect(audioContext.destination); - - const currentTime = audioContext.currentTime; - const startTime = Math.max(playbackTimeRef.current, currentTime); - - source.start(startTime); - playbackTimeRef.current = startTime + audioBuffer.duration; - - playingSourcesRef.current.push(source); - source.onended = () => { - playingSourcesRef.current = playingSourcesRef.current.filter( - (s) => s !== source - ); - }; - } - } catch (err) { - console.error("Failed to stream speech:", err); - setStatus(`Error: Failed to stream speech. ${err.message}`); - setErrorMessage(`Failed to stream speech: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Stops all currently playing TTS audio sources immediately. - */ - const stopAllPlayingAudio = () => { - if (audioContextRef.current) { - playingSourcesRef.current.forEach((source) => { - try { - source.stop(); - } catch (e) { - // Ignore errors from stopping already stopped sources - } - }); - playingSourcesRef.current = []; - playbackTimeRef.current = audioContextRef.current.currentTime; - console.log("All playing audio has been stopped."); - } - }; - - /** - * Starts the audio recording process for manual mode. - */ - const startManualRecording = async () => { - if (isRecording) { - console.log("Already recording, ignoring start request."); - return; - } - - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting manual recording..."); - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - streamRef.current = stream; // Store the stream to stop it later - mediaRecorderRef.current = new MediaRecorder(stream); - mediaRecorderRef.current.start(); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // Ensure the stream is stopped after recording has finished. - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - - // Clear the media recorder ref to allow a new recording to be created. - mediaRecorderRef.current = null; - - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - }; - - setIsRecording(true); - isRecordingRef.current = true; - setStatus("Recording... Click to stop."); - } catch (err) { - console.error("Error accessing microphone:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops manual recording and finalizes the audio blob. - */ - const stopManualRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("Stopping manual recording."); - // Set isBusy immediately to prevent multiple stop requests. - setIsBusy(true); - setIsRecording(false); // Update UI immediately - mediaRecorderRef.current.stop(); - // The isRecording state will be set to false by the mediaRecorder.onstop handler after processing. - } - }; - - /** - * Starts the voice activity detection process. - */ - const startAutoListening = async () => { - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting automatic listening (VAD)."); - // Request microphone access for VAD. - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - vadStreamRef.current = stream; - - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const source = audioContext.createMediaStreamSource(stream); - - // Use the maximum buffer size for less frequent onaudioprocess calls - const bufferSize = 4096; - const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); - scriptProcessorRef.current = scriptProcessor; - - source.connect(scriptProcessor); - scriptProcessor.connect(audioContext.destination); - - scriptProcessor.onaudioprocess = (event) => { - const inputBuffer = event.inputBuffer.getChannelData(0); - let sum = 0.0; - for (let i = 0; i < inputBuffer.length; i++) { - sum += inputBuffer[i] * inputBuffer[i]; - } - const volume = Math.sqrt(sum / inputBuffer.length); - - console.log(`Current audio volume: ${volume.toFixed(2)}`); - - const isVoiceDetected = (volume > VAD_THRESHOLD); - - if (isVoiceDetected) { - // Voice is detected, so clear the silence timeout - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - - // --- NEW LOGIC: Check for cooldown before starting recording --- - const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; - const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; - - // Start recording only if it's not already running AND the cooldown has passed - if (!isRecordingRef.current && isCooldownPassed) { - startAutoRecording(stream); - } - } else if (isRecordingRef.current) { - // We are currently recording, but silence is detected - // If a silence timeout is not already running, start one - if (!silenceTimeoutRef.current) { - console.log("Silence detected. Starting timeout."); - silenceTimeoutRef.current = setTimeout(() => { - // If the timeout expires, stop the recording and process the audio - stopAutoRecording(); - }, VAD_SILENCE_DURATION); - } - } - }; - - setIsAutoListening(true); - setStatus("Listening for voice..."); - } catch (err) { - console.error("Error accessing microphone for VAD:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops the VAD process and cleans up resources. - */ - const stopAutoListening = () => { - setIsAutoListening(false); - // We only stop all streams when the user explicitly stops auto mode. - stopAllStreams(); - setStatus("Click to start listening."); - console.log("Stopping automatic listening."); - }; - - /** - * Starts the MediaRecorder for automatic mode. - */ - const startAutoRecording = (stream) => { - // Prevent starting a new recorder if one is already running - if (mediaRecorderRef.current?.state === "recording") { - console.log("Auto recording is already running, ignoring start request."); - return; - } - - console.log("Starting a new MediaRecorder for automatic mode."); - mediaRecorderRef.current = new MediaRecorder(stream); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // This flag is essential to prevent the VAD loop from re-triggering a recording - // before the current one is fully processed. - isRecordingRef.current = false; - setIsRecording(false); - if (audioChunksRef.current.length > 0) { - // Set isBusy to prevent the user from starting a new request while processing - setIsBusy(true); - setStatus("Transcribing audio..."); - console.log("Auto recording stopped. Processing conversation..."); - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - } else { - console.warn("Auto recording stopped but no audio chunks were collected."); - setIsBusy(false); - setStatus("Listening for voice..."); - } - }; - - mediaRecorderRef.current.start(); - isRecordingRef.current = true; - setIsRecording(true); - setStatus("Recording..."); - console.log("MediaRecorder started for automatic mode."); - }; - - /** - * Stops the MediaRecorder for automatic mode. - */ - const stopAutoRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("MediaRecorder stopped for automatic mode."); - mediaRecorderRef.current.stop(); - } - }; - - - /** - * Handles the complete conversation flow: STT -> LLM -> TTS. - * @param {Blob} audioBlob - The recorded audio from the user. - */ - const processConversation = async (audioBlob) => { - console.log("Processing conversation..."); - try { - // Step 1: Filter out audio that is too short to be meaningful - // Get the duration of the audio blob. Assuming a 48kHz sample rate, each byte is ~1/48000th of a second - // For a 'audio/wav' blob, this can be complex. A simple check on blob size is a good heuristic. - // Let's assume an average bitrate and calculate duration. Or even simpler, check the raw size. - // A more robust method would be to analyze the audio header, but for this context, a simple size check is fine. - const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; // Assuming 48kHz, 16-bit mono - if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { - console.log(`Audio is too short (${audioDuration.toFixed(2)}ms), skipping.`); - setStatus("Audio was too short. Please speak a little longer."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; // Exit the function early - } - - setStatus("Transcribing audio..."); - if (audioBlob.size === 0) { - console.warn("Audio blob is empty, skipping STT API call."); - setStatus("Recording stopped, but no audio was captured. Please try again."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; - } - - const formData = new FormData(); - formData.append("audio_file", audioBlob, "audio.wav"); - - const sttResponse = await fetch(STT_ENDPOINT, { - method: "POST", - body: formData, - }); - if (!sttResponse.ok) { - console.error(`STT API failed with status: ${sttResponse.status}`); - throw new Error("STT API failed"); - } - const sttResult = await sttResponse.json(); - const userText = sttResult.transcript; - console.log(`STT transcript received: "${userText}"`); - addMessage(userText, true); - - // Step 2: Text-to-Text (LLM) - setStatus("AI is thinking..."); - const llmResponse = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ prompt: userText, model: "gemini" }), - }); - if (!llmResponse.ok) { - console.error(`LLM API failed with status: ${llmResponse.status}`); - throw new Error("LLM API failed"); - } - const llmResult = await llmResponse.json(); - const aiText = llmResult.answer; - console.log(`LLM response received: "${aiText}"`); - addMessage(aiText, false); - - // Step 3: Text-to-Speech (TTS) - await playStreamingAudio(aiText); - } catch (error) { - console.error("Conversation processing failed:", error); - setStatus(`Error: ${error.message}`); - setErrorMessage(`An error occurred: ${error.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - // Ensure refs are nullified after processing for a clean state - mediaRecorderRef.current = null; - streamRef.current = null; - } else if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Toggles the recording state based on the current mode. - */ - const handleMicClick = () => { - stopAllPlayingAudio(); // Interrupt any ongoing TTS playback immediately - - // Prevent new actions if the app is busy processing a request - if (isBusy) { - console.log("App is busy, ignoring mic click."); - return; - } - - if (isAutoMode) { - if (isAutoListening) { - stopAutoListening(); - } else { - startAutoListening(); - } - } else { // Manual Mode - if (isRecording) { - stopManualRecording(); - } else { - startManualRecording(); - } - } - }; - - // FIX: The microphoneButtonState variable was assigned but never used. - // The logic has been moved directly into the JSX below. - // The previous variable declaration has been removed. - - const micButtonColorClass = isRecording - ? "bg-red-600 hover:bg-red-700 active:bg-red-800" - : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; - - return ( -
-
- {/* Header */} -
-

Voice Chat Assistant

-

- Ask me anything and I'll respond! -

-
- - {/* Chat History */} -
- {chatHistory.map((message, index) => ( -
-
- {message.text} -
-
- ))} -
- - {/* Status and Controls */} -
-
- {status} -
- -
- {/* Microphone Button */} - -
- - {/* Mode Toggle */} -
- -
- -
-
- - {/* Error Modal */} - {showErrorModal && ( -
-
-

Error

-

{errorMessage}

- -
-
- )} -
- ); -}; +function App() { + return ; +} export default App; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/ChatWindow.js b/ui/tts-client-app/src/components/ChatWindow.js new file mode 100644 index 0000000..28397a4 --- /dev/null +++ b/ui/tts-client-app/src/components/ChatWindow.js @@ -0,0 +1,29 @@ +// src/components/ChatWindow.js +import React from "react"; + +const ChatWindow = ({ chatHistory }) => { + return ( +
+ {chatHistory.map((message, index) => ( +
+
+

{message.text}

+
+
+ ))} +
+ ); +}; + +export default ChatWindow; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/Controls.js b/ui/tts-client-app/src/components/Controls.js new file mode 100644 index 0000000..f78e2e1 --- /dev/null +++ b/ui/tts-client-app/src/components/Controls.js @@ -0,0 +1,57 @@ +// src/components/Controls.js +import React from "react"; +import { FaMicrophone, FaRegStopCircle, FaCog } from "react-icons/fa"; + +const Controls = ({ + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + onMicClick, + onToggleAutoMode, +}) => { + const micButtonColorClass = isRecording + ? "bg-red-600 hover:bg-red-700 active:bg-red-800" + : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; + + const micButtonState = + isAutoMode && isAutoListening ? isAutoListening : isRecording; + + return ( +
+
+ {status} +
+
+ + +
+ + +
+
+
+ ); +}; + +export default Controls; \ No newline at end of file diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ui/tts-client-app/package-lock.json b/ui/tts-client-app/package-lock.json index 03a0868..6f05481 100644 --- a/ui/tts-client-app/package-lock.json +++ b/ui/tts-client-app/package-lock.json @@ -15,13 +15,15 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } }, "node_modules/@adobe/css-tools": { @@ -3754,6 +3756,11 @@ "tailwindcss": "dist/index.mjs" } }, + "node_modules/@tailwindcss/cli/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -3787,6 +3794,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", @@ -4029,6 +4041,25 @@ "node": ">=8" } }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5458,7 +5489,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -13097,7 +13127,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14788,6 +14817,14 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16791,10 +16828,41 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/ui/tts-client-app/package.json b/ui/tts-client-app/package.json index f07a347..4d75c14 100644 --- a/ui/tts-client-app/package.json +++ b/ui/tts-client-app/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -38,8 +39,9 @@ ] }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } } diff --git a/ui/tts-client-app/public/index.html b/ui/tts-client-app/public/index.html index 8e0f148..3893a7f 100644 --- a/ui/tts-client-app/public/index.html +++ b/ui/tts-client-app/public/index.html @@ -25,7 +25,6 @@ Learn how to configure a non-root public URL by running `npm run build`. --> React App - diff --git a/ui/tts-client-app/src/App.js b/ui/tts-client-app/src/App.js index 6cfd356..74ebba9 100644 --- a/ui/tts-client-app/src/App.js +++ b/ui/tts-client-app/src/App.js @@ -1,781 +1,10 @@ -import React, { useState, useRef, useEffect } from "react"; +// src/App.js +import React from "react"; +import HomePage from "./pages/HomePage"; +import "./styles/main.css"; -// The main application component for the voice chat -const App = () => { - // State for managing the chat history, storing both user and AI messages - const [chatHistory, setChatHistory] = useState([ - { - text: "Hello! I'm an AI assistant. How can I help you today?", - isUser: false, - }, - ]); - // State to manage the UI status, like "Recording...", "Transcribing...", etc. - const [status, setStatus] = useState("Click the microphone to start recording."); - // State to track if the application is currently busy with a request - const [isBusy, setIsBusy] = useState(false); - // State to track the recording status for UI feedback - const [isRecording, setIsRecording] = useState(false); - // State to manage the visibility of the error modal - const [showErrorModal, setShowErrorModal] = useState(false); - // State to hold the error message to be displayed in the modal - const [errorMessage, setErrorMessage] = useState(""); - - // State for managing the chat session - const [sessionId, setSessionId] = useState(null); - // FIX: The userId was being assigned but not used in the rest of the component. - // It's already handled inside the useEffect, so we don't need a state variable for it. - // The previous state variable has been removed. - - // State to toggle between manual and automatic recording modes - const [isAutoMode, setIsAutoMode] = useState(false); - // State to indicate if the app is actively listening for voice in auto mode - const [isAutoListening, setIsAutoListening] = useState(false); - - // Refs for managing Web Audio API and MediaRecorder instances - const mediaRecorderRef = useRef(null); - const audioChunksRef = useRef([]); - const audioContextRef = useRef(null); - const playbackTimeRef = useRef(0); - // Ref to hold the current recording state reliably, to avoid stale closures in event handlers - const isRecordingRef = useRef(false); - - // Keep track of currently playing audio sources so we can stop them on demand - const playingSourcesRef = useRef([]); - - // Refs for managing the VAD (Voice Activity Detection) process - const vadStreamRef = useRef(null); - const scriptProcessorRef = useRef(null); - const silenceTimeoutRef = useRef(null); - - // --- New Refs for Debouncing in Automatic Mode --- - // Ref to track the last time a request was completed. This is used to implement a cooldown. - const lastRequestTimeRef = useRef(0); - // Ref to hold the stream from getUserMedia to be able to stop it later for manual mode - const streamRef = useRef(null); - - // --- New Ref for scrolling to bottom of chat history --- - const chatContainerRef = useRef(null); - - // --- Configuration --- - // Please replace with your actual endpoints - const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; - const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; - const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; - const TTS_ENDPOINT = "http://localhost:8001/speech"; - - // Configuration for Voice Activity Detection - const VAD_THRESHOLD = 0.01; // Adjust this value to control sensitivity (0 to 1) - const VAD_SILENCE_DURATION = 2500; // Duration of silence in ms before stopping recording - - // --- New Configuration for Audio Filtering and Debouncing --- - const MINIMUM_AUDIO_DURATION_MS = 500; // Minimum duration for an audio recording to be processed - const AUTO_MODE_COOLDOWN_MS = 3000; // Cooldown period in milliseconds to prevent rapid re-recordings in automatic mode - - // useEffect hook to create a new session when the component mounts - useEffect(() => { - const createSession = async () => { - setIsBusy(true); - setStatus("Starting new chat session..."); - console.log("Attempting to create a new session."); - try { - const generatedUserId = crypto.randomUUID(); - // FIX: The `setUserId(generatedUserId)` call has been removed - // because the state variable `userId` is no longer needed. - // The `generatedUserId` is still used in the API call. - - const response = await fetch(SESSIONS_CREATE_ENDPOINT, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ user_id: generatedUserId }), - }); - - if (!response.ok) { - throw new Error(`Failed to create session. Status: ${response.status}`); - } - - const session = await response.json(); - setSessionId(session.id); - console.log(`Session created with ID: ${session.id}`); - setStatus("Click the microphone to start recording."); - } catch (err) { - console.error("Error creating session:", err); - setStatus(`Error: Could not start session. ${err.message}`); - setErrorMessage(`Failed to create a chat session: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - } - }; - - createSession(); - - // Cleanup function to stop all streams when the component unmounts - return () => { - stopAllStreams(); - }; - }, []); // Empty dependency array ensures this runs only once on mount - - // New useEffect hook to automatically scroll to the bottom of the chat history - useEffect(() => { - if (chatContainerRef.current) { - chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; - } - }, [chatHistory]); - - /** - * Cleans up all active audio streams and timers. - */ - const stopAllStreams = () => { - if (vadStreamRef.current) { - vadStreamRef.current.getTracks().forEach((track) => track.stop()); - vadStreamRef.current = null; - } - if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { - mediaRecorderRef.current.stop(); - } - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - if (scriptProcessorRef.current) { - scriptProcessorRef.current.disconnect(); - scriptProcessorRef.current = null; - } - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - isRecordingRef.current = false; - setIsRecording(false); - console.log("All audio streams and timers have been stopped."); - }; - - /** - * Adds a new message to the chat history state. The useEffect hook will handle the scrolling. - * @param {string} text - The text content of the message. - * @param {boolean} isUser - True for user messages, false for AI. - */ - const addMessage = (text, isUser) => { - setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); - }; - - /** - * Resamples a Float32Array buffer from a source rate to a destination rate. - * @param {Float32Array} buffer - The input audio buffer. - * @param {number} srcRate - The source sample rate (e.g., 24000). - * @param {number} dstRate - The destination sample rate (AudioContext's). - * @returns {Float32Array} The resampled audio buffer. - */ - const resampleBuffer = (buffer, srcRate, dstRate) => { - if (srcRate === dstRate) return buffer; - const ratio = srcRate / dstRate; - const newLength = Math.round(buffer.length / ratio); - const resampled = new Float32Array(newLength); - for (let i = 0; i < newLength; i++) { - const srcIndex = i * ratio; - const srcIndexFloor = Math.floor(srcIndex); - const srcIndexCeil = Math.min(srcIndexFloor + 1, buffer.length - 1); - const weight = srcIndex - srcIndexFloor; - resampled[i] = - buffer[srcIndexFloor] * (1 - weight) + buffer[srcIndexCeil] * weight; - } - return resampled; - }; - - /** - * Converts raw 16-bit PCM byte data to a normalized Float32Array. - * @param {Uint8Array} pcmBytes - The raw PCM audio data. - * @returns {Float32Array} The converted and normalized audio data. - */ - const convertPcmToFloat32 = (pcmBytes) => { - const int16Array = new Int16Array( - pcmBytes.buffer, - pcmBytes.byteOffset, - pcmBytes.byteLength / 2 - ); - const float32Array = new Float32Array(int16Array.length); - for (let i = 0; i < int16Array.length; i++) { - float32Array[i] = int16Array[i] / 32768; - } - return float32Array; - }; - - /** - * Plays a stream of audio chunks using the Web Audio API. - * @param {string} text - The text to be synthesized by the TTS service. - */ - const playStreamingAudio = async (text) => { - setIsBusy(true); - setStatus("Streaming audio..."); - stopAllPlayingAudio(); // Stop any previous playback - - try { - if (!audioContextRef.current) { - audioContextRef.current = - new (window.AudioContext || window.webkitAudioContext)(); - playbackTimeRef.current = audioContextRef.current.currentTime; - } - - const audioContext = audioContextRef.current; - const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; - - const response = await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ text }), - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const reader = response.body.getReader(); - let leftover = new Uint8Array(0); - - while (true) { - const { done, value: chunk } = await reader.read(); - if (done) { - if (leftover.length > 0) { - console.warn("Leftover bytes discarded:", leftover.length); - } - console.log("TTS Stream complete."); - break; - } - - let combined = new Uint8Array(leftover.length + chunk.length); - combined.set(leftover); - combined.set(chunk, leftover.length); - - let length = combined.length; - if (length % 2 !== 0) { - length -= 1; - } - - const toConvert = combined.slice(0, length); - leftover = combined.slice(length); - const float32Raw = convertPcmToFloat32(toConvert); - const float32Resampled = resampleBuffer( - float32Raw, - 24000, - audioContext.sampleRate - ); - const audioBuffer = audioContext.createBuffer( - 1, - float32Resampled.length, - audioContext.sampleRate - ); - - audioBuffer.copyToChannel(float32Resampled, 0); - - const source = audioContext.createBufferSource(); - source.buffer = audioBuffer; - source.connect(audioContext.destination); - - const currentTime = audioContext.currentTime; - const startTime = Math.max(playbackTimeRef.current, currentTime); - - source.start(startTime); - playbackTimeRef.current = startTime + audioBuffer.duration; - - playingSourcesRef.current.push(source); - source.onended = () => { - playingSourcesRef.current = playingSourcesRef.current.filter( - (s) => s !== source - ); - }; - } - } catch (err) { - console.error("Failed to stream speech:", err); - setStatus(`Error: Failed to stream speech. ${err.message}`); - setErrorMessage(`Failed to stream speech: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Stops all currently playing TTS audio sources immediately. - */ - const stopAllPlayingAudio = () => { - if (audioContextRef.current) { - playingSourcesRef.current.forEach((source) => { - try { - source.stop(); - } catch (e) { - // Ignore errors from stopping already stopped sources - } - }); - playingSourcesRef.current = []; - playbackTimeRef.current = audioContextRef.current.currentTime; - console.log("All playing audio has been stopped."); - } - }; - - /** - * Starts the audio recording process for manual mode. - */ - const startManualRecording = async () => { - if (isRecording) { - console.log("Already recording, ignoring start request."); - return; - } - - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting manual recording..."); - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - streamRef.current = stream; // Store the stream to stop it later - mediaRecorderRef.current = new MediaRecorder(stream); - mediaRecorderRef.current.start(); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // Ensure the stream is stopped after recording has finished. - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - - // Clear the media recorder ref to allow a new recording to be created. - mediaRecorderRef.current = null; - - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - }; - - setIsRecording(true); - isRecordingRef.current = true; - setStatus("Recording... Click to stop."); - } catch (err) { - console.error("Error accessing microphone:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops manual recording and finalizes the audio blob. - */ - const stopManualRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("Stopping manual recording."); - // Set isBusy immediately to prevent multiple stop requests. - setIsBusy(true); - setIsRecording(false); // Update UI immediately - mediaRecorderRef.current.stop(); - // The isRecording state will be set to false by the mediaRecorder.onstop handler after processing. - } - }; - - /** - * Starts the voice activity detection process. - */ - const startAutoListening = async () => { - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting automatic listening (VAD)."); - // Request microphone access for VAD. - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - vadStreamRef.current = stream; - - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const source = audioContext.createMediaStreamSource(stream); - - // Use the maximum buffer size for less frequent onaudioprocess calls - const bufferSize = 4096; - const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); - scriptProcessorRef.current = scriptProcessor; - - source.connect(scriptProcessor); - scriptProcessor.connect(audioContext.destination); - - scriptProcessor.onaudioprocess = (event) => { - const inputBuffer = event.inputBuffer.getChannelData(0); - let sum = 0.0; - for (let i = 0; i < inputBuffer.length; i++) { - sum += inputBuffer[i] * inputBuffer[i]; - } - const volume = Math.sqrt(sum / inputBuffer.length); - - console.log(`Current audio volume: ${volume.toFixed(2)}`); - - const isVoiceDetected = (volume > VAD_THRESHOLD); - - if (isVoiceDetected) { - // Voice is detected, so clear the silence timeout - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - - // --- NEW LOGIC: Check for cooldown before starting recording --- - const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; - const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; - - // Start recording only if it's not already running AND the cooldown has passed - if (!isRecordingRef.current && isCooldownPassed) { - startAutoRecording(stream); - } - } else if (isRecordingRef.current) { - // We are currently recording, but silence is detected - // If a silence timeout is not already running, start one - if (!silenceTimeoutRef.current) { - console.log("Silence detected. Starting timeout."); - silenceTimeoutRef.current = setTimeout(() => { - // If the timeout expires, stop the recording and process the audio - stopAutoRecording(); - }, VAD_SILENCE_DURATION); - } - } - }; - - setIsAutoListening(true); - setStatus("Listening for voice..."); - } catch (err) { - console.error("Error accessing microphone for VAD:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops the VAD process and cleans up resources. - */ - const stopAutoListening = () => { - setIsAutoListening(false); - // We only stop all streams when the user explicitly stops auto mode. - stopAllStreams(); - setStatus("Click to start listening."); - console.log("Stopping automatic listening."); - }; - - /** - * Starts the MediaRecorder for automatic mode. - */ - const startAutoRecording = (stream) => { - // Prevent starting a new recorder if one is already running - if (mediaRecorderRef.current?.state === "recording") { - console.log("Auto recording is already running, ignoring start request."); - return; - } - - console.log("Starting a new MediaRecorder for automatic mode."); - mediaRecorderRef.current = new MediaRecorder(stream); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // This flag is essential to prevent the VAD loop from re-triggering a recording - // before the current one is fully processed. - isRecordingRef.current = false; - setIsRecording(false); - if (audioChunksRef.current.length > 0) { - // Set isBusy to prevent the user from starting a new request while processing - setIsBusy(true); - setStatus("Transcribing audio..."); - console.log("Auto recording stopped. Processing conversation..."); - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - } else { - console.warn("Auto recording stopped but no audio chunks were collected."); - setIsBusy(false); - setStatus("Listening for voice..."); - } - }; - - mediaRecorderRef.current.start(); - isRecordingRef.current = true; - setIsRecording(true); - setStatus("Recording..."); - console.log("MediaRecorder started for automatic mode."); - }; - - /** - * Stops the MediaRecorder for automatic mode. - */ - const stopAutoRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("MediaRecorder stopped for automatic mode."); - mediaRecorderRef.current.stop(); - } - }; - - - /** - * Handles the complete conversation flow: STT -> LLM -> TTS. - * @param {Blob} audioBlob - The recorded audio from the user. - */ - const processConversation = async (audioBlob) => { - console.log("Processing conversation..."); - try { - // Step 1: Filter out audio that is too short to be meaningful - // Get the duration of the audio blob. Assuming a 48kHz sample rate, each byte is ~1/48000th of a second - // For a 'audio/wav' blob, this can be complex. A simple check on blob size is a good heuristic. - // Let's assume an average bitrate and calculate duration. Or even simpler, check the raw size. - // A more robust method would be to analyze the audio header, but for this context, a simple size check is fine. - const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; // Assuming 48kHz, 16-bit mono - if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { - console.log(`Audio is too short (${audioDuration.toFixed(2)}ms), skipping.`); - setStatus("Audio was too short. Please speak a little longer."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; // Exit the function early - } - - setStatus("Transcribing audio..."); - if (audioBlob.size === 0) { - console.warn("Audio blob is empty, skipping STT API call."); - setStatus("Recording stopped, but no audio was captured. Please try again."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; - } - - const formData = new FormData(); - formData.append("audio_file", audioBlob, "audio.wav"); - - const sttResponse = await fetch(STT_ENDPOINT, { - method: "POST", - body: formData, - }); - if (!sttResponse.ok) { - console.error(`STT API failed with status: ${sttResponse.status}`); - throw new Error("STT API failed"); - } - const sttResult = await sttResponse.json(); - const userText = sttResult.transcript; - console.log(`STT transcript received: "${userText}"`); - addMessage(userText, true); - - // Step 2: Text-to-Text (LLM) - setStatus("AI is thinking..."); - const llmResponse = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ prompt: userText, model: "gemini" }), - }); - if (!llmResponse.ok) { - console.error(`LLM API failed with status: ${llmResponse.status}`); - throw new Error("LLM API failed"); - } - const llmResult = await llmResponse.json(); - const aiText = llmResult.answer; - console.log(`LLM response received: "${aiText}"`); - addMessage(aiText, false); - - // Step 3: Text-to-Speech (TTS) - await playStreamingAudio(aiText); - } catch (error) { - console.error("Conversation processing failed:", error); - setStatus(`Error: ${error.message}`); - setErrorMessage(`An error occurred: ${error.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - // Ensure refs are nullified after processing for a clean state - mediaRecorderRef.current = null; - streamRef.current = null; - } else if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Toggles the recording state based on the current mode. - */ - const handleMicClick = () => { - stopAllPlayingAudio(); // Interrupt any ongoing TTS playback immediately - - // Prevent new actions if the app is busy processing a request - if (isBusy) { - console.log("App is busy, ignoring mic click."); - return; - } - - if (isAutoMode) { - if (isAutoListening) { - stopAutoListening(); - } else { - startAutoListening(); - } - } else { // Manual Mode - if (isRecording) { - stopManualRecording(); - } else { - startManualRecording(); - } - } - }; - - // FIX: The microphoneButtonState variable was assigned but never used. - // The logic has been moved directly into the JSX below. - // The previous variable declaration has been removed. - - const micButtonColorClass = isRecording - ? "bg-red-600 hover:bg-red-700 active:bg-red-800" - : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; - - return ( -
-
- {/* Header */} -
-

Voice Chat Assistant

-

- Ask me anything and I'll respond! -

-
- - {/* Chat History */} -
- {chatHistory.map((message, index) => ( -
-
- {message.text} -
-
- ))} -
- - {/* Status and Controls */} -
-
- {status} -
- -
- {/* Microphone Button */} - -
- - {/* Mode Toggle */} -
- -
- -
-
- - {/* Error Modal */} - {showErrorModal && ( -
-
-

Error

-

{errorMessage}

- -
-
- )} -
- ); -}; +function App() { + return ; +} export default App; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/ChatWindow.js b/ui/tts-client-app/src/components/ChatWindow.js new file mode 100644 index 0000000..28397a4 --- /dev/null +++ b/ui/tts-client-app/src/components/ChatWindow.js @@ -0,0 +1,29 @@ +// src/components/ChatWindow.js +import React from "react"; + +const ChatWindow = ({ chatHistory }) => { + return ( +
+ {chatHistory.map((message, index) => ( +
+
+

{message.text}

+
+
+ ))} +
+ ); +}; + +export default ChatWindow; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/Controls.js b/ui/tts-client-app/src/components/Controls.js new file mode 100644 index 0000000..f78e2e1 --- /dev/null +++ b/ui/tts-client-app/src/components/Controls.js @@ -0,0 +1,57 @@ +// src/components/Controls.js +import React from "react"; +import { FaMicrophone, FaRegStopCircle, FaCog } from "react-icons/fa"; + +const Controls = ({ + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + onMicClick, + onToggleAutoMode, +}) => { + const micButtonColorClass = isRecording + ? "bg-red-600 hover:bg-red-700 active:bg-red-800" + : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; + + const micButtonState = + isAutoMode && isAutoListening ? isAutoListening : isRecording; + + return ( +
+
+ {status} +
+
+ + +
+ + +
+
+
+ ); +}; + +export default Controls; \ No newline at end of file diff --git a/ui/tts-client-app/src/hooks/useVoiceChat.js b/ui/tts-client-app/src/hooks/useVoiceChat.js new file mode 100644 index 0000000..4c74745 --- /dev/null +++ b/ui/tts-client-app/src/hooks/useVoiceChat.js @@ -0,0 +1,394 @@ +// src/hooks/useVoiceChat.js + +// This file is a custom React hook that contains all the stateful logic +// and side effects for the voice chat application. + +import { useState, useRef, useEffect } from "react"; +import { + createSession, + transcribeAudio, + chatWithAI, + streamSpeech, +} from "../services/apiService"; +import { + stopAllPlayingAudio, + stopAllMediaStreams, + resampleBuffer, +} from "../services/audioUtils"; + +// Constants for Voice Activity Detection and timing +const VAD_THRESHOLD = 0.01; +const VAD_SILENCE_DURATION = 2500; +const MINIMUM_AUDIO_DURATION_MS = 500; +const AUTO_MODE_COOLDOWN_MS = 3000; + +const useVoiceChat = ({ chatContainerRef }) => { + const [chatHistory, setChatHistory] = useState([ + { + text: "Hello! I'm an AI assistant. How can I help you today?", + isUser: false, + }, + ]); + const [status, setStatus] = useState("Click the microphone to start recording."); + const [isBusy, setIsBusy] = useState(false); + const [isRecording, setIsRecording] = useState(false); + const [showErrorModal, setShowErrorModal] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const [sessionId, setSessionId] = useState(null); + const [isAutoMode, setIsAutoMode] = useState(false); + const [isAutoListening, setIsAutoListening] = useState(false); + + // All refs must be declared here, inside the custom hook. + const mediaRecorderRef = useRef(null); + const audioChunksRef = useRef([]); + const audioContextRef = useRef(null); + const playbackTimeRef = useRef(0); + const isRecordingRef = useRef(false); + const playingSourcesRef = useRef([]); + const vadStreamRef = useRef(null); + const scriptProcessorRef = useRef(null); + const silenceTimeoutRef = useRef(null); + const lastRequestTimeRef = useRef(0); + const streamRef = useRef(null); + + // --- Initial Session Creation Effect --- + useEffect(() => { + const startSession = async () => { + setIsBusy(true); + setStatus("Starting new chat session..."); + try { + const session = await createSession(); + setSessionId(session.id); + console.log(`Session created with ID: ${session.id}`); + setStatus("Click the microphone to start recording."); + } catch (err) { + console.error("Error creating session:", err); + setStatus(`Error: Could not start session. ${err.message}`); + setErrorMessage(`Failed to create a chat session: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + } + }; + startSession(); + + return () => { + // Pass the refs to the utility function here + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + }; + }, []); + + // New useEffect hook to automatically scroll to the bottom of the chat history + // The fix: `chatContainerRef` is now included in the dependency array. + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [chatHistory, chatContainerRef]); + + const addMessage = (text, isUser) => { + setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); + }; + + /** + * Plays a stream of audio chunks using the Web Audio API by fetching them from the API. + * This is the orchestrator that uses the stateless streamSpeech API function. + * @param {string} text - The text to be synthesized by the TTS service. + */ + const playStreamingAudio = async (text) => { + setIsBusy(true); + setStatus("Streaming audio..."); + // Pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + + try { + if (!audioContextRef.current) { + audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); + playbackTimeRef.current = audioContextRef.current.currentTime; + } + + const audioContext = audioContextRef.current; + + const onChunkReceived = (rawFloat32Data) => { + // This is the callback that receives processed audio data from apiService. + // It's responsible for using the Web Audio API to play the sound. + const float32Resampled = resampleBuffer( + rawFloat32Data, + 24000, // The model's sample rate is hardcoded to 24000 + audioContext.sampleRate + ); + const audioBuffer = audioContext.createBuffer( + 1, + float32Resampled.length, + audioContext.sampleRate + ); + audioBuffer.copyToChannel(float32Resampled, 0); + + const source = audioContext.createBufferSource(); + source.buffer = audioBuffer; + source.connect(audioContext.destination); + + const currentTime = audioContext.currentTime; + const startTime = Math.max(playbackTimeRef.current, currentTime); + + source.start(startTime); + playbackTimeRef.current = startTime + audioBuffer.duration; + + playingSourcesRef.current.push(source); + source.onended = () => { + playingSourcesRef.current = playingSourcesRef.current.filter( + (s) => s !== source + ); + }; + }; + + const onStreamDone = () => { + // This callback is triggered when the stream finishes. + console.log("TTS Stream complete."); + }; + + // Call the stateless API function, passing the UI-related callbacks + await streamSpeech(text, onChunkReceived, onStreamDone); + + } catch (err) { + console.error("Failed to stream speech:", err); + setStatus(`Error: Failed to stream speech. ${err.message}`); + setErrorMessage(`Failed to stream speech: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + } else { + setStatus("Click to start listening."); + } + } + }; + + + const processConversation = async (audioBlob) => { + console.log("Processing conversation..."); + try { + const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; + if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { + console.log(`Audio too short (${audioDuration.toFixed(2)}ms), skipping.`); + setStatus("Audio was too short. Please speak a little longer."); + lastRequestTimeRef.current = Date.now(); + return; + } + if (audioBlob.size === 0) { + console.warn("Audio blob is empty, skipping STT API call."); + setStatus("Recording stopped, but no audio was captured. Please try again."); + lastRequestTimeRef.current = Date.now(); + return; + } + + setStatus("Transcribing audio..."); + const userText = await transcribeAudio(audioBlob); + addMessage(userText, true); + + setStatus("AI is thinking..."); + const aiText = await chatWithAI(sessionId, userText); + addMessage(aiText, false); + + await playStreamingAudio(aiText); + } catch (error) { + console.error("Conversation processing failed:", error); + setStatus(`Error: ${error.message}`); + setErrorMessage(`An error occurred: ${error.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + // This is the main correction: only stop streams if not in auto-listening mode + if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + } else if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else { + setStatus("Click to start listening."); + } + } + }; + + const startManualRecording = async () => { + if (isRecording) return; + + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + streamRef.current = stream; + mediaRecorderRef.current = new MediaRecorder(stream); + mediaRecorderRef.current.start(); + audioChunksRef.current = []; + + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + + mediaRecorderRef.current.onstop = async () => { + if (streamRef.current) { + streamRef.current.getTracks().forEach(track => track.stop()); + streamRef.current = null; + } + mediaRecorderRef.current = null; + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + }; + setIsRecording(true); + isRecordingRef.current = true; + setStatus("Recording... Click to stop."); + } catch (err) { + console.error("Error accessing microphone:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopManualRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + setIsBusy(true); + setIsRecording(false); + mediaRecorderRef.current.stop(); + } + }; + + const startAutoListening = async () => { + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + vadStreamRef.current = stream; + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const source = audioContext.createMediaStreamSource(stream); + const bufferSize = 4096; + const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); + scriptProcessorRef.current = scriptProcessor; + source.connect(scriptProcessor); + scriptProcessor.connect(audioContext.destination); + + scriptProcessor.onaudioprocess = (event) => { + const inputBuffer = event.inputBuffer.getChannelData(0); + let sum = 0.0; + for (let i = 0; i < inputBuffer.length; i++) { + sum += inputBuffer[i] * inputBuffer[i]; + } + const volume = Math.sqrt(sum / inputBuffer.length); + const isVoiceDetected = volume > VAD_THRESHOLD; + const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; + const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; + + if (isVoiceDetected) { + if (silenceTimeoutRef.current) { + clearTimeout(silenceTimeoutRef.current); + silenceTimeoutRef.current = null; + } + if (!isRecordingRef.current && isCooldownPassed) { + startAutoRecording(stream); + } + } else if (isRecordingRef.current) { + if (!silenceTimeoutRef.current) { + silenceTimeoutRef.current = setTimeout(() => { + stopAutoRecording(); + }, VAD_SILENCE_DURATION); + } + } + }; + setIsAutoListening(true); + setStatus("Listening for voice..."); + } catch (err) { + console.error("Error accessing microphone for VAD:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopAutoListening = () => { + setIsAutoListening(false); + // Pass the refs here to the utility function + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + setStatus("Click to start listening."); + }; + + const startAutoRecording = (stream) => { + if (mediaRecorderRef.current?.state === "recording") return; + mediaRecorderRef.current = new MediaRecorder(stream); + audioChunksRef.current = []; + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + mediaRecorderRef.current.onstop = async () => { + isRecordingRef.current = false; + setIsRecording(false); + if (audioChunksRef.current.length > 0) { + setIsBusy(true); + setStatus("Transcribing audio..."); + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + } else { + setIsBusy(false); + setStatus("Listening for voice..."); + } + }; + mediaRecorderRef.current.start(); + isRecordingRef.current = true; + setIsRecording(true); + setStatus("Recording..."); + }; + + const stopAutoRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + mediaRecorderRef.current.stop(); + } + }; + + const handleMicClick = () => { + // Correctly pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + if (isBusy) return; + + if (isAutoMode) { + if (isAutoListening) { + stopAutoListening(); + } else { + startAutoListening(); + } + } else { + if (isRecording) { + stopManualRecording(); + } else { + startManualRecording(); + } + } + }; + + return { + chatHistory, + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + sessionId, + showErrorModal, + errorMessage, + setIsAutoMode, + handleMicClick, + setShowErrorModal, + }; +}; + +export default useVoiceChat; diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ui/tts-client-app/package-lock.json b/ui/tts-client-app/package-lock.json index 03a0868..6f05481 100644 --- a/ui/tts-client-app/package-lock.json +++ b/ui/tts-client-app/package-lock.json @@ -15,13 +15,15 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } }, "node_modules/@adobe/css-tools": { @@ -3754,6 +3756,11 @@ "tailwindcss": "dist/index.mjs" } }, + "node_modules/@tailwindcss/cli/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -3787,6 +3794,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", @@ -4029,6 +4041,25 @@ "node": ">=8" } }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5458,7 +5489,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -13097,7 +13127,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14788,6 +14817,14 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16791,10 +16828,41 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/ui/tts-client-app/package.json b/ui/tts-client-app/package.json index f07a347..4d75c14 100644 --- a/ui/tts-client-app/package.json +++ b/ui/tts-client-app/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -38,8 +39,9 @@ ] }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } } diff --git a/ui/tts-client-app/public/index.html b/ui/tts-client-app/public/index.html index 8e0f148..3893a7f 100644 --- a/ui/tts-client-app/public/index.html +++ b/ui/tts-client-app/public/index.html @@ -25,7 +25,6 @@ Learn how to configure a non-root public URL by running `npm run build`. --> React App - diff --git a/ui/tts-client-app/src/App.js b/ui/tts-client-app/src/App.js index 6cfd356..74ebba9 100644 --- a/ui/tts-client-app/src/App.js +++ b/ui/tts-client-app/src/App.js @@ -1,781 +1,10 @@ -import React, { useState, useRef, useEffect } from "react"; +// src/App.js +import React from "react"; +import HomePage from "./pages/HomePage"; +import "./styles/main.css"; -// The main application component for the voice chat -const App = () => { - // State for managing the chat history, storing both user and AI messages - const [chatHistory, setChatHistory] = useState([ - { - text: "Hello! I'm an AI assistant. How can I help you today?", - isUser: false, - }, - ]); - // State to manage the UI status, like "Recording...", "Transcribing...", etc. - const [status, setStatus] = useState("Click the microphone to start recording."); - // State to track if the application is currently busy with a request - const [isBusy, setIsBusy] = useState(false); - // State to track the recording status for UI feedback - const [isRecording, setIsRecording] = useState(false); - // State to manage the visibility of the error modal - const [showErrorModal, setShowErrorModal] = useState(false); - // State to hold the error message to be displayed in the modal - const [errorMessage, setErrorMessage] = useState(""); - - // State for managing the chat session - const [sessionId, setSessionId] = useState(null); - // FIX: The userId was being assigned but not used in the rest of the component. - // It's already handled inside the useEffect, so we don't need a state variable for it. - // The previous state variable has been removed. - - // State to toggle between manual and automatic recording modes - const [isAutoMode, setIsAutoMode] = useState(false); - // State to indicate if the app is actively listening for voice in auto mode - const [isAutoListening, setIsAutoListening] = useState(false); - - // Refs for managing Web Audio API and MediaRecorder instances - const mediaRecorderRef = useRef(null); - const audioChunksRef = useRef([]); - const audioContextRef = useRef(null); - const playbackTimeRef = useRef(0); - // Ref to hold the current recording state reliably, to avoid stale closures in event handlers - const isRecordingRef = useRef(false); - - // Keep track of currently playing audio sources so we can stop them on demand - const playingSourcesRef = useRef([]); - - // Refs for managing the VAD (Voice Activity Detection) process - const vadStreamRef = useRef(null); - const scriptProcessorRef = useRef(null); - const silenceTimeoutRef = useRef(null); - - // --- New Refs for Debouncing in Automatic Mode --- - // Ref to track the last time a request was completed. This is used to implement a cooldown. - const lastRequestTimeRef = useRef(0); - // Ref to hold the stream from getUserMedia to be able to stop it later for manual mode - const streamRef = useRef(null); - - // --- New Ref for scrolling to bottom of chat history --- - const chatContainerRef = useRef(null); - - // --- Configuration --- - // Please replace with your actual endpoints - const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; - const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; - const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; - const TTS_ENDPOINT = "http://localhost:8001/speech"; - - // Configuration for Voice Activity Detection - const VAD_THRESHOLD = 0.01; // Adjust this value to control sensitivity (0 to 1) - const VAD_SILENCE_DURATION = 2500; // Duration of silence in ms before stopping recording - - // --- New Configuration for Audio Filtering and Debouncing --- - const MINIMUM_AUDIO_DURATION_MS = 500; // Minimum duration for an audio recording to be processed - const AUTO_MODE_COOLDOWN_MS = 3000; // Cooldown period in milliseconds to prevent rapid re-recordings in automatic mode - - // useEffect hook to create a new session when the component mounts - useEffect(() => { - const createSession = async () => { - setIsBusy(true); - setStatus("Starting new chat session..."); - console.log("Attempting to create a new session."); - try { - const generatedUserId = crypto.randomUUID(); - // FIX: The `setUserId(generatedUserId)` call has been removed - // because the state variable `userId` is no longer needed. - // The `generatedUserId` is still used in the API call. - - const response = await fetch(SESSIONS_CREATE_ENDPOINT, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ user_id: generatedUserId }), - }); - - if (!response.ok) { - throw new Error(`Failed to create session. Status: ${response.status}`); - } - - const session = await response.json(); - setSessionId(session.id); - console.log(`Session created with ID: ${session.id}`); - setStatus("Click the microphone to start recording."); - } catch (err) { - console.error("Error creating session:", err); - setStatus(`Error: Could not start session. ${err.message}`); - setErrorMessage(`Failed to create a chat session: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - } - }; - - createSession(); - - // Cleanup function to stop all streams when the component unmounts - return () => { - stopAllStreams(); - }; - }, []); // Empty dependency array ensures this runs only once on mount - - // New useEffect hook to automatically scroll to the bottom of the chat history - useEffect(() => { - if (chatContainerRef.current) { - chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; - } - }, [chatHistory]); - - /** - * Cleans up all active audio streams and timers. - */ - const stopAllStreams = () => { - if (vadStreamRef.current) { - vadStreamRef.current.getTracks().forEach((track) => track.stop()); - vadStreamRef.current = null; - } - if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { - mediaRecorderRef.current.stop(); - } - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - if (scriptProcessorRef.current) { - scriptProcessorRef.current.disconnect(); - scriptProcessorRef.current = null; - } - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - isRecordingRef.current = false; - setIsRecording(false); - console.log("All audio streams and timers have been stopped."); - }; - - /** - * Adds a new message to the chat history state. The useEffect hook will handle the scrolling. - * @param {string} text - The text content of the message. - * @param {boolean} isUser - True for user messages, false for AI. - */ - const addMessage = (text, isUser) => { - setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); - }; - - /** - * Resamples a Float32Array buffer from a source rate to a destination rate. - * @param {Float32Array} buffer - The input audio buffer. - * @param {number} srcRate - The source sample rate (e.g., 24000). - * @param {number} dstRate - The destination sample rate (AudioContext's). - * @returns {Float32Array} The resampled audio buffer. - */ - const resampleBuffer = (buffer, srcRate, dstRate) => { - if (srcRate === dstRate) return buffer; - const ratio = srcRate / dstRate; - const newLength = Math.round(buffer.length / ratio); - const resampled = new Float32Array(newLength); - for (let i = 0; i < newLength; i++) { - const srcIndex = i * ratio; - const srcIndexFloor = Math.floor(srcIndex); - const srcIndexCeil = Math.min(srcIndexFloor + 1, buffer.length - 1); - const weight = srcIndex - srcIndexFloor; - resampled[i] = - buffer[srcIndexFloor] * (1 - weight) + buffer[srcIndexCeil] * weight; - } - return resampled; - }; - - /** - * Converts raw 16-bit PCM byte data to a normalized Float32Array. - * @param {Uint8Array} pcmBytes - The raw PCM audio data. - * @returns {Float32Array} The converted and normalized audio data. - */ - const convertPcmToFloat32 = (pcmBytes) => { - const int16Array = new Int16Array( - pcmBytes.buffer, - pcmBytes.byteOffset, - pcmBytes.byteLength / 2 - ); - const float32Array = new Float32Array(int16Array.length); - for (let i = 0; i < int16Array.length; i++) { - float32Array[i] = int16Array[i] / 32768; - } - return float32Array; - }; - - /** - * Plays a stream of audio chunks using the Web Audio API. - * @param {string} text - The text to be synthesized by the TTS service. - */ - const playStreamingAudio = async (text) => { - setIsBusy(true); - setStatus("Streaming audio..."); - stopAllPlayingAudio(); // Stop any previous playback - - try { - if (!audioContextRef.current) { - audioContextRef.current = - new (window.AudioContext || window.webkitAudioContext)(); - playbackTimeRef.current = audioContextRef.current.currentTime; - } - - const audioContext = audioContextRef.current; - const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; - - const response = await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ text }), - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const reader = response.body.getReader(); - let leftover = new Uint8Array(0); - - while (true) { - const { done, value: chunk } = await reader.read(); - if (done) { - if (leftover.length > 0) { - console.warn("Leftover bytes discarded:", leftover.length); - } - console.log("TTS Stream complete."); - break; - } - - let combined = new Uint8Array(leftover.length + chunk.length); - combined.set(leftover); - combined.set(chunk, leftover.length); - - let length = combined.length; - if (length % 2 !== 0) { - length -= 1; - } - - const toConvert = combined.slice(0, length); - leftover = combined.slice(length); - const float32Raw = convertPcmToFloat32(toConvert); - const float32Resampled = resampleBuffer( - float32Raw, - 24000, - audioContext.sampleRate - ); - const audioBuffer = audioContext.createBuffer( - 1, - float32Resampled.length, - audioContext.sampleRate - ); - - audioBuffer.copyToChannel(float32Resampled, 0); - - const source = audioContext.createBufferSource(); - source.buffer = audioBuffer; - source.connect(audioContext.destination); - - const currentTime = audioContext.currentTime; - const startTime = Math.max(playbackTimeRef.current, currentTime); - - source.start(startTime); - playbackTimeRef.current = startTime + audioBuffer.duration; - - playingSourcesRef.current.push(source); - source.onended = () => { - playingSourcesRef.current = playingSourcesRef.current.filter( - (s) => s !== source - ); - }; - } - } catch (err) { - console.error("Failed to stream speech:", err); - setStatus(`Error: Failed to stream speech. ${err.message}`); - setErrorMessage(`Failed to stream speech: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Stops all currently playing TTS audio sources immediately. - */ - const stopAllPlayingAudio = () => { - if (audioContextRef.current) { - playingSourcesRef.current.forEach((source) => { - try { - source.stop(); - } catch (e) { - // Ignore errors from stopping already stopped sources - } - }); - playingSourcesRef.current = []; - playbackTimeRef.current = audioContextRef.current.currentTime; - console.log("All playing audio has been stopped."); - } - }; - - /** - * Starts the audio recording process for manual mode. - */ - const startManualRecording = async () => { - if (isRecording) { - console.log("Already recording, ignoring start request."); - return; - } - - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting manual recording..."); - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - streamRef.current = stream; // Store the stream to stop it later - mediaRecorderRef.current = new MediaRecorder(stream); - mediaRecorderRef.current.start(); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // Ensure the stream is stopped after recording has finished. - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - - // Clear the media recorder ref to allow a new recording to be created. - mediaRecorderRef.current = null; - - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - }; - - setIsRecording(true); - isRecordingRef.current = true; - setStatus("Recording... Click to stop."); - } catch (err) { - console.error("Error accessing microphone:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops manual recording and finalizes the audio blob. - */ - const stopManualRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("Stopping manual recording."); - // Set isBusy immediately to prevent multiple stop requests. - setIsBusy(true); - setIsRecording(false); // Update UI immediately - mediaRecorderRef.current.stop(); - // The isRecording state will be set to false by the mediaRecorder.onstop handler after processing. - } - }; - - /** - * Starts the voice activity detection process. - */ - const startAutoListening = async () => { - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting automatic listening (VAD)."); - // Request microphone access for VAD. - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - vadStreamRef.current = stream; - - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const source = audioContext.createMediaStreamSource(stream); - - // Use the maximum buffer size for less frequent onaudioprocess calls - const bufferSize = 4096; - const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); - scriptProcessorRef.current = scriptProcessor; - - source.connect(scriptProcessor); - scriptProcessor.connect(audioContext.destination); - - scriptProcessor.onaudioprocess = (event) => { - const inputBuffer = event.inputBuffer.getChannelData(0); - let sum = 0.0; - for (let i = 0; i < inputBuffer.length; i++) { - sum += inputBuffer[i] * inputBuffer[i]; - } - const volume = Math.sqrt(sum / inputBuffer.length); - - console.log(`Current audio volume: ${volume.toFixed(2)}`); - - const isVoiceDetected = (volume > VAD_THRESHOLD); - - if (isVoiceDetected) { - // Voice is detected, so clear the silence timeout - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - - // --- NEW LOGIC: Check for cooldown before starting recording --- - const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; - const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; - - // Start recording only if it's not already running AND the cooldown has passed - if (!isRecordingRef.current && isCooldownPassed) { - startAutoRecording(stream); - } - } else if (isRecordingRef.current) { - // We are currently recording, but silence is detected - // If a silence timeout is not already running, start one - if (!silenceTimeoutRef.current) { - console.log("Silence detected. Starting timeout."); - silenceTimeoutRef.current = setTimeout(() => { - // If the timeout expires, stop the recording and process the audio - stopAutoRecording(); - }, VAD_SILENCE_DURATION); - } - } - }; - - setIsAutoListening(true); - setStatus("Listening for voice..."); - } catch (err) { - console.error("Error accessing microphone for VAD:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops the VAD process and cleans up resources. - */ - const stopAutoListening = () => { - setIsAutoListening(false); - // We only stop all streams when the user explicitly stops auto mode. - stopAllStreams(); - setStatus("Click to start listening."); - console.log("Stopping automatic listening."); - }; - - /** - * Starts the MediaRecorder for automatic mode. - */ - const startAutoRecording = (stream) => { - // Prevent starting a new recorder if one is already running - if (mediaRecorderRef.current?.state === "recording") { - console.log("Auto recording is already running, ignoring start request."); - return; - } - - console.log("Starting a new MediaRecorder for automatic mode."); - mediaRecorderRef.current = new MediaRecorder(stream); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // This flag is essential to prevent the VAD loop from re-triggering a recording - // before the current one is fully processed. - isRecordingRef.current = false; - setIsRecording(false); - if (audioChunksRef.current.length > 0) { - // Set isBusy to prevent the user from starting a new request while processing - setIsBusy(true); - setStatus("Transcribing audio..."); - console.log("Auto recording stopped. Processing conversation..."); - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - } else { - console.warn("Auto recording stopped but no audio chunks were collected."); - setIsBusy(false); - setStatus("Listening for voice..."); - } - }; - - mediaRecorderRef.current.start(); - isRecordingRef.current = true; - setIsRecording(true); - setStatus("Recording..."); - console.log("MediaRecorder started for automatic mode."); - }; - - /** - * Stops the MediaRecorder for automatic mode. - */ - const stopAutoRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("MediaRecorder stopped for automatic mode."); - mediaRecorderRef.current.stop(); - } - }; - - - /** - * Handles the complete conversation flow: STT -> LLM -> TTS. - * @param {Blob} audioBlob - The recorded audio from the user. - */ - const processConversation = async (audioBlob) => { - console.log("Processing conversation..."); - try { - // Step 1: Filter out audio that is too short to be meaningful - // Get the duration of the audio blob. Assuming a 48kHz sample rate, each byte is ~1/48000th of a second - // For a 'audio/wav' blob, this can be complex. A simple check on blob size is a good heuristic. - // Let's assume an average bitrate and calculate duration. Or even simpler, check the raw size. - // A more robust method would be to analyze the audio header, but for this context, a simple size check is fine. - const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; // Assuming 48kHz, 16-bit mono - if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { - console.log(`Audio is too short (${audioDuration.toFixed(2)}ms), skipping.`); - setStatus("Audio was too short. Please speak a little longer."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; // Exit the function early - } - - setStatus("Transcribing audio..."); - if (audioBlob.size === 0) { - console.warn("Audio blob is empty, skipping STT API call."); - setStatus("Recording stopped, but no audio was captured. Please try again."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; - } - - const formData = new FormData(); - formData.append("audio_file", audioBlob, "audio.wav"); - - const sttResponse = await fetch(STT_ENDPOINT, { - method: "POST", - body: formData, - }); - if (!sttResponse.ok) { - console.error(`STT API failed with status: ${sttResponse.status}`); - throw new Error("STT API failed"); - } - const sttResult = await sttResponse.json(); - const userText = sttResult.transcript; - console.log(`STT transcript received: "${userText}"`); - addMessage(userText, true); - - // Step 2: Text-to-Text (LLM) - setStatus("AI is thinking..."); - const llmResponse = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ prompt: userText, model: "gemini" }), - }); - if (!llmResponse.ok) { - console.error(`LLM API failed with status: ${llmResponse.status}`); - throw new Error("LLM API failed"); - } - const llmResult = await llmResponse.json(); - const aiText = llmResult.answer; - console.log(`LLM response received: "${aiText}"`); - addMessage(aiText, false); - - // Step 3: Text-to-Speech (TTS) - await playStreamingAudio(aiText); - } catch (error) { - console.error("Conversation processing failed:", error); - setStatus(`Error: ${error.message}`); - setErrorMessage(`An error occurred: ${error.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - // Ensure refs are nullified after processing for a clean state - mediaRecorderRef.current = null; - streamRef.current = null; - } else if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Toggles the recording state based on the current mode. - */ - const handleMicClick = () => { - stopAllPlayingAudio(); // Interrupt any ongoing TTS playback immediately - - // Prevent new actions if the app is busy processing a request - if (isBusy) { - console.log("App is busy, ignoring mic click."); - return; - } - - if (isAutoMode) { - if (isAutoListening) { - stopAutoListening(); - } else { - startAutoListening(); - } - } else { // Manual Mode - if (isRecording) { - stopManualRecording(); - } else { - startManualRecording(); - } - } - }; - - // FIX: The microphoneButtonState variable was assigned but never used. - // The logic has been moved directly into the JSX below. - // The previous variable declaration has been removed. - - const micButtonColorClass = isRecording - ? "bg-red-600 hover:bg-red-700 active:bg-red-800" - : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; - - return ( -
-
- {/* Header */} -
-

Voice Chat Assistant

-

- Ask me anything and I'll respond! -

-
- - {/* Chat History */} -
- {chatHistory.map((message, index) => ( -
-
- {message.text} -
-
- ))} -
- - {/* Status and Controls */} -
-
- {status} -
- -
- {/* Microphone Button */} - -
- - {/* Mode Toggle */} -
- -
- -
-
- - {/* Error Modal */} - {showErrorModal && ( -
-
-

Error

-

{errorMessage}

- -
-
- )} -
- ); -}; +function App() { + return ; +} export default App; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/ChatWindow.js b/ui/tts-client-app/src/components/ChatWindow.js new file mode 100644 index 0000000..28397a4 --- /dev/null +++ b/ui/tts-client-app/src/components/ChatWindow.js @@ -0,0 +1,29 @@ +// src/components/ChatWindow.js +import React from "react"; + +const ChatWindow = ({ chatHistory }) => { + return ( +
+ {chatHistory.map((message, index) => ( +
+
+

{message.text}

+
+
+ ))} +
+ ); +}; + +export default ChatWindow; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/Controls.js b/ui/tts-client-app/src/components/Controls.js new file mode 100644 index 0000000..f78e2e1 --- /dev/null +++ b/ui/tts-client-app/src/components/Controls.js @@ -0,0 +1,57 @@ +// src/components/Controls.js +import React from "react"; +import { FaMicrophone, FaRegStopCircle, FaCog } from "react-icons/fa"; + +const Controls = ({ + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + onMicClick, + onToggleAutoMode, +}) => { + const micButtonColorClass = isRecording + ? "bg-red-600 hover:bg-red-700 active:bg-red-800" + : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; + + const micButtonState = + isAutoMode && isAutoListening ? isAutoListening : isRecording; + + return ( +
+
+ {status} +
+
+ + +
+ + +
+
+
+ ); +}; + +export default Controls; \ No newline at end of file diff --git a/ui/tts-client-app/src/hooks/useVoiceChat.js b/ui/tts-client-app/src/hooks/useVoiceChat.js new file mode 100644 index 0000000..4c74745 --- /dev/null +++ b/ui/tts-client-app/src/hooks/useVoiceChat.js @@ -0,0 +1,394 @@ +// src/hooks/useVoiceChat.js + +// This file is a custom React hook that contains all the stateful logic +// and side effects for the voice chat application. + +import { useState, useRef, useEffect } from "react"; +import { + createSession, + transcribeAudio, + chatWithAI, + streamSpeech, +} from "../services/apiService"; +import { + stopAllPlayingAudio, + stopAllMediaStreams, + resampleBuffer, +} from "../services/audioUtils"; + +// Constants for Voice Activity Detection and timing +const VAD_THRESHOLD = 0.01; +const VAD_SILENCE_DURATION = 2500; +const MINIMUM_AUDIO_DURATION_MS = 500; +const AUTO_MODE_COOLDOWN_MS = 3000; + +const useVoiceChat = ({ chatContainerRef }) => { + const [chatHistory, setChatHistory] = useState([ + { + text: "Hello! I'm an AI assistant. How can I help you today?", + isUser: false, + }, + ]); + const [status, setStatus] = useState("Click the microphone to start recording."); + const [isBusy, setIsBusy] = useState(false); + const [isRecording, setIsRecording] = useState(false); + const [showErrorModal, setShowErrorModal] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const [sessionId, setSessionId] = useState(null); + const [isAutoMode, setIsAutoMode] = useState(false); + const [isAutoListening, setIsAutoListening] = useState(false); + + // All refs must be declared here, inside the custom hook. + const mediaRecorderRef = useRef(null); + const audioChunksRef = useRef([]); + const audioContextRef = useRef(null); + const playbackTimeRef = useRef(0); + const isRecordingRef = useRef(false); + const playingSourcesRef = useRef([]); + const vadStreamRef = useRef(null); + const scriptProcessorRef = useRef(null); + const silenceTimeoutRef = useRef(null); + const lastRequestTimeRef = useRef(0); + const streamRef = useRef(null); + + // --- Initial Session Creation Effect --- + useEffect(() => { + const startSession = async () => { + setIsBusy(true); + setStatus("Starting new chat session..."); + try { + const session = await createSession(); + setSessionId(session.id); + console.log(`Session created with ID: ${session.id}`); + setStatus("Click the microphone to start recording."); + } catch (err) { + console.error("Error creating session:", err); + setStatus(`Error: Could not start session. ${err.message}`); + setErrorMessage(`Failed to create a chat session: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + } + }; + startSession(); + + return () => { + // Pass the refs to the utility function here + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + }; + }, []); + + // New useEffect hook to automatically scroll to the bottom of the chat history + // The fix: `chatContainerRef` is now included in the dependency array. + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [chatHistory, chatContainerRef]); + + const addMessage = (text, isUser) => { + setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); + }; + + /** + * Plays a stream of audio chunks using the Web Audio API by fetching them from the API. + * This is the orchestrator that uses the stateless streamSpeech API function. + * @param {string} text - The text to be synthesized by the TTS service. + */ + const playStreamingAudio = async (text) => { + setIsBusy(true); + setStatus("Streaming audio..."); + // Pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + + try { + if (!audioContextRef.current) { + audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); + playbackTimeRef.current = audioContextRef.current.currentTime; + } + + const audioContext = audioContextRef.current; + + const onChunkReceived = (rawFloat32Data) => { + // This is the callback that receives processed audio data from apiService. + // It's responsible for using the Web Audio API to play the sound. + const float32Resampled = resampleBuffer( + rawFloat32Data, + 24000, // The model's sample rate is hardcoded to 24000 + audioContext.sampleRate + ); + const audioBuffer = audioContext.createBuffer( + 1, + float32Resampled.length, + audioContext.sampleRate + ); + audioBuffer.copyToChannel(float32Resampled, 0); + + const source = audioContext.createBufferSource(); + source.buffer = audioBuffer; + source.connect(audioContext.destination); + + const currentTime = audioContext.currentTime; + const startTime = Math.max(playbackTimeRef.current, currentTime); + + source.start(startTime); + playbackTimeRef.current = startTime + audioBuffer.duration; + + playingSourcesRef.current.push(source); + source.onended = () => { + playingSourcesRef.current = playingSourcesRef.current.filter( + (s) => s !== source + ); + }; + }; + + const onStreamDone = () => { + // This callback is triggered when the stream finishes. + console.log("TTS Stream complete."); + }; + + // Call the stateless API function, passing the UI-related callbacks + await streamSpeech(text, onChunkReceived, onStreamDone); + + } catch (err) { + console.error("Failed to stream speech:", err); + setStatus(`Error: Failed to stream speech. ${err.message}`); + setErrorMessage(`Failed to stream speech: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + } else { + setStatus("Click to start listening."); + } + } + }; + + + const processConversation = async (audioBlob) => { + console.log("Processing conversation..."); + try { + const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; + if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { + console.log(`Audio too short (${audioDuration.toFixed(2)}ms), skipping.`); + setStatus("Audio was too short. Please speak a little longer."); + lastRequestTimeRef.current = Date.now(); + return; + } + if (audioBlob.size === 0) { + console.warn("Audio blob is empty, skipping STT API call."); + setStatus("Recording stopped, but no audio was captured. Please try again."); + lastRequestTimeRef.current = Date.now(); + return; + } + + setStatus("Transcribing audio..."); + const userText = await transcribeAudio(audioBlob); + addMessage(userText, true); + + setStatus("AI is thinking..."); + const aiText = await chatWithAI(sessionId, userText); + addMessage(aiText, false); + + await playStreamingAudio(aiText); + } catch (error) { + console.error("Conversation processing failed:", error); + setStatus(`Error: ${error.message}`); + setErrorMessage(`An error occurred: ${error.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + // This is the main correction: only stop streams if not in auto-listening mode + if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + } else if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else { + setStatus("Click to start listening."); + } + } + }; + + const startManualRecording = async () => { + if (isRecording) return; + + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + streamRef.current = stream; + mediaRecorderRef.current = new MediaRecorder(stream); + mediaRecorderRef.current.start(); + audioChunksRef.current = []; + + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + + mediaRecorderRef.current.onstop = async () => { + if (streamRef.current) { + streamRef.current.getTracks().forEach(track => track.stop()); + streamRef.current = null; + } + mediaRecorderRef.current = null; + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + }; + setIsRecording(true); + isRecordingRef.current = true; + setStatus("Recording... Click to stop."); + } catch (err) { + console.error("Error accessing microphone:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopManualRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + setIsBusy(true); + setIsRecording(false); + mediaRecorderRef.current.stop(); + } + }; + + const startAutoListening = async () => { + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + vadStreamRef.current = stream; + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const source = audioContext.createMediaStreamSource(stream); + const bufferSize = 4096; + const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); + scriptProcessorRef.current = scriptProcessor; + source.connect(scriptProcessor); + scriptProcessor.connect(audioContext.destination); + + scriptProcessor.onaudioprocess = (event) => { + const inputBuffer = event.inputBuffer.getChannelData(0); + let sum = 0.0; + for (let i = 0; i < inputBuffer.length; i++) { + sum += inputBuffer[i] * inputBuffer[i]; + } + const volume = Math.sqrt(sum / inputBuffer.length); + const isVoiceDetected = volume > VAD_THRESHOLD; + const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; + const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; + + if (isVoiceDetected) { + if (silenceTimeoutRef.current) { + clearTimeout(silenceTimeoutRef.current); + silenceTimeoutRef.current = null; + } + if (!isRecordingRef.current && isCooldownPassed) { + startAutoRecording(stream); + } + } else if (isRecordingRef.current) { + if (!silenceTimeoutRef.current) { + silenceTimeoutRef.current = setTimeout(() => { + stopAutoRecording(); + }, VAD_SILENCE_DURATION); + } + } + }; + setIsAutoListening(true); + setStatus("Listening for voice..."); + } catch (err) { + console.error("Error accessing microphone for VAD:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopAutoListening = () => { + setIsAutoListening(false); + // Pass the refs here to the utility function + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + setStatus("Click to start listening."); + }; + + const startAutoRecording = (stream) => { + if (mediaRecorderRef.current?.state === "recording") return; + mediaRecorderRef.current = new MediaRecorder(stream); + audioChunksRef.current = []; + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + mediaRecorderRef.current.onstop = async () => { + isRecordingRef.current = false; + setIsRecording(false); + if (audioChunksRef.current.length > 0) { + setIsBusy(true); + setStatus("Transcribing audio..."); + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + } else { + setIsBusy(false); + setStatus("Listening for voice..."); + } + }; + mediaRecorderRef.current.start(); + isRecordingRef.current = true; + setIsRecording(true); + setStatus("Recording..."); + }; + + const stopAutoRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + mediaRecorderRef.current.stop(); + } + }; + + const handleMicClick = () => { + // Correctly pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + if (isBusy) return; + + if (isAutoMode) { + if (isAutoListening) { + stopAutoListening(); + } else { + startAutoListening(); + } + } else { + if (isRecording) { + stopManualRecording(); + } else { + startManualRecording(); + } + } + }; + + return { + chatHistory, + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + sessionId, + showErrorModal, + errorMessage, + setIsAutoMode, + handleMicClick, + setShowErrorModal, + }; +}; + +export default useVoiceChat; diff --git a/ui/tts-client-app/src/index.css b/ui/tts-client-app/src/index.css index ec2585e..bd6213e 100644 --- a/ui/tts-client-app/src/index.css +++ b/ui/tts-client-app/src/index.css @@ -1,13 +1,3 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ui/tts-client-app/package-lock.json b/ui/tts-client-app/package-lock.json index 03a0868..6f05481 100644 --- a/ui/tts-client-app/package-lock.json +++ b/ui/tts-client-app/package-lock.json @@ -15,13 +15,15 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } }, "node_modules/@adobe/css-tools": { @@ -3754,6 +3756,11 @@ "tailwindcss": "dist/index.mjs" } }, + "node_modules/@tailwindcss/cli/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -3787,6 +3794,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", @@ -4029,6 +4041,25 @@ "node": ">=8" } }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5458,7 +5489,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -13097,7 +13127,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14788,6 +14817,14 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16791,10 +16828,41 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/ui/tts-client-app/package.json b/ui/tts-client-app/package.json index f07a347..4d75c14 100644 --- a/ui/tts-client-app/package.json +++ b/ui/tts-client-app/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -38,8 +39,9 @@ ] }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } } diff --git a/ui/tts-client-app/public/index.html b/ui/tts-client-app/public/index.html index 8e0f148..3893a7f 100644 --- a/ui/tts-client-app/public/index.html +++ b/ui/tts-client-app/public/index.html @@ -25,7 +25,6 @@ Learn how to configure a non-root public URL by running `npm run build`. --> React App - diff --git a/ui/tts-client-app/src/App.js b/ui/tts-client-app/src/App.js index 6cfd356..74ebba9 100644 --- a/ui/tts-client-app/src/App.js +++ b/ui/tts-client-app/src/App.js @@ -1,781 +1,10 @@ -import React, { useState, useRef, useEffect } from "react"; +// src/App.js +import React from "react"; +import HomePage from "./pages/HomePage"; +import "./styles/main.css"; -// The main application component for the voice chat -const App = () => { - // State for managing the chat history, storing both user and AI messages - const [chatHistory, setChatHistory] = useState([ - { - text: "Hello! I'm an AI assistant. How can I help you today?", - isUser: false, - }, - ]); - // State to manage the UI status, like "Recording...", "Transcribing...", etc. - const [status, setStatus] = useState("Click the microphone to start recording."); - // State to track if the application is currently busy with a request - const [isBusy, setIsBusy] = useState(false); - // State to track the recording status for UI feedback - const [isRecording, setIsRecording] = useState(false); - // State to manage the visibility of the error modal - const [showErrorModal, setShowErrorModal] = useState(false); - // State to hold the error message to be displayed in the modal - const [errorMessage, setErrorMessage] = useState(""); - - // State for managing the chat session - const [sessionId, setSessionId] = useState(null); - // FIX: The userId was being assigned but not used in the rest of the component. - // It's already handled inside the useEffect, so we don't need a state variable for it. - // The previous state variable has been removed. - - // State to toggle between manual and automatic recording modes - const [isAutoMode, setIsAutoMode] = useState(false); - // State to indicate if the app is actively listening for voice in auto mode - const [isAutoListening, setIsAutoListening] = useState(false); - - // Refs for managing Web Audio API and MediaRecorder instances - const mediaRecorderRef = useRef(null); - const audioChunksRef = useRef([]); - const audioContextRef = useRef(null); - const playbackTimeRef = useRef(0); - // Ref to hold the current recording state reliably, to avoid stale closures in event handlers - const isRecordingRef = useRef(false); - - // Keep track of currently playing audio sources so we can stop them on demand - const playingSourcesRef = useRef([]); - - // Refs for managing the VAD (Voice Activity Detection) process - const vadStreamRef = useRef(null); - const scriptProcessorRef = useRef(null); - const silenceTimeoutRef = useRef(null); - - // --- New Refs for Debouncing in Automatic Mode --- - // Ref to track the last time a request was completed. This is used to implement a cooldown. - const lastRequestTimeRef = useRef(0); - // Ref to hold the stream from getUserMedia to be able to stop it later for manual mode - const streamRef = useRef(null); - - // --- New Ref for scrolling to bottom of chat history --- - const chatContainerRef = useRef(null); - - // --- Configuration --- - // Please replace with your actual endpoints - const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; - const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; - const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; - const TTS_ENDPOINT = "http://localhost:8001/speech"; - - // Configuration for Voice Activity Detection - const VAD_THRESHOLD = 0.01; // Adjust this value to control sensitivity (0 to 1) - const VAD_SILENCE_DURATION = 2500; // Duration of silence in ms before stopping recording - - // --- New Configuration for Audio Filtering and Debouncing --- - const MINIMUM_AUDIO_DURATION_MS = 500; // Minimum duration for an audio recording to be processed - const AUTO_MODE_COOLDOWN_MS = 3000; // Cooldown period in milliseconds to prevent rapid re-recordings in automatic mode - - // useEffect hook to create a new session when the component mounts - useEffect(() => { - const createSession = async () => { - setIsBusy(true); - setStatus("Starting new chat session..."); - console.log("Attempting to create a new session."); - try { - const generatedUserId = crypto.randomUUID(); - // FIX: The `setUserId(generatedUserId)` call has been removed - // because the state variable `userId` is no longer needed. - // The `generatedUserId` is still used in the API call. - - const response = await fetch(SESSIONS_CREATE_ENDPOINT, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ user_id: generatedUserId }), - }); - - if (!response.ok) { - throw new Error(`Failed to create session. Status: ${response.status}`); - } - - const session = await response.json(); - setSessionId(session.id); - console.log(`Session created with ID: ${session.id}`); - setStatus("Click the microphone to start recording."); - } catch (err) { - console.error("Error creating session:", err); - setStatus(`Error: Could not start session. ${err.message}`); - setErrorMessage(`Failed to create a chat session: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - } - }; - - createSession(); - - // Cleanup function to stop all streams when the component unmounts - return () => { - stopAllStreams(); - }; - }, []); // Empty dependency array ensures this runs only once on mount - - // New useEffect hook to automatically scroll to the bottom of the chat history - useEffect(() => { - if (chatContainerRef.current) { - chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; - } - }, [chatHistory]); - - /** - * Cleans up all active audio streams and timers. - */ - const stopAllStreams = () => { - if (vadStreamRef.current) { - vadStreamRef.current.getTracks().forEach((track) => track.stop()); - vadStreamRef.current = null; - } - if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { - mediaRecorderRef.current.stop(); - } - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - if (scriptProcessorRef.current) { - scriptProcessorRef.current.disconnect(); - scriptProcessorRef.current = null; - } - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - isRecordingRef.current = false; - setIsRecording(false); - console.log("All audio streams and timers have been stopped."); - }; - - /** - * Adds a new message to the chat history state. The useEffect hook will handle the scrolling. - * @param {string} text - The text content of the message. - * @param {boolean} isUser - True for user messages, false for AI. - */ - const addMessage = (text, isUser) => { - setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); - }; - - /** - * Resamples a Float32Array buffer from a source rate to a destination rate. - * @param {Float32Array} buffer - The input audio buffer. - * @param {number} srcRate - The source sample rate (e.g., 24000). - * @param {number} dstRate - The destination sample rate (AudioContext's). - * @returns {Float32Array} The resampled audio buffer. - */ - const resampleBuffer = (buffer, srcRate, dstRate) => { - if (srcRate === dstRate) return buffer; - const ratio = srcRate / dstRate; - const newLength = Math.round(buffer.length / ratio); - const resampled = new Float32Array(newLength); - for (let i = 0; i < newLength; i++) { - const srcIndex = i * ratio; - const srcIndexFloor = Math.floor(srcIndex); - const srcIndexCeil = Math.min(srcIndexFloor + 1, buffer.length - 1); - const weight = srcIndex - srcIndexFloor; - resampled[i] = - buffer[srcIndexFloor] * (1 - weight) + buffer[srcIndexCeil] * weight; - } - return resampled; - }; - - /** - * Converts raw 16-bit PCM byte data to a normalized Float32Array. - * @param {Uint8Array} pcmBytes - The raw PCM audio data. - * @returns {Float32Array} The converted and normalized audio data. - */ - const convertPcmToFloat32 = (pcmBytes) => { - const int16Array = new Int16Array( - pcmBytes.buffer, - pcmBytes.byteOffset, - pcmBytes.byteLength / 2 - ); - const float32Array = new Float32Array(int16Array.length); - for (let i = 0; i < int16Array.length; i++) { - float32Array[i] = int16Array[i] / 32768; - } - return float32Array; - }; - - /** - * Plays a stream of audio chunks using the Web Audio API. - * @param {string} text - The text to be synthesized by the TTS service. - */ - const playStreamingAudio = async (text) => { - setIsBusy(true); - setStatus("Streaming audio..."); - stopAllPlayingAudio(); // Stop any previous playback - - try { - if (!audioContextRef.current) { - audioContextRef.current = - new (window.AudioContext || window.webkitAudioContext)(); - playbackTimeRef.current = audioContextRef.current.currentTime; - } - - const audioContext = audioContextRef.current; - const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; - - const response = await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ text }), - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const reader = response.body.getReader(); - let leftover = new Uint8Array(0); - - while (true) { - const { done, value: chunk } = await reader.read(); - if (done) { - if (leftover.length > 0) { - console.warn("Leftover bytes discarded:", leftover.length); - } - console.log("TTS Stream complete."); - break; - } - - let combined = new Uint8Array(leftover.length + chunk.length); - combined.set(leftover); - combined.set(chunk, leftover.length); - - let length = combined.length; - if (length % 2 !== 0) { - length -= 1; - } - - const toConvert = combined.slice(0, length); - leftover = combined.slice(length); - const float32Raw = convertPcmToFloat32(toConvert); - const float32Resampled = resampleBuffer( - float32Raw, - 24000, - audioContext.sampleRate - ); - const audioBuffer = audioContext.createBuffer( - 1, - float32Resampled.length, - audioContext.sampleRate - ); - - audioBuffer.copyToChannel(float32Resampled, 0); - - const source = audioContext.createBufferSource(); - source.buffer = audioBuffer; - source.connect(audioContext.destination); - - const currentTime = audioContext.currentTime; - const startTime = Math.max(playbackTimeRef.current, currentTime); - - source.start(startTime); - playbackTimeRef.current = startTime + audioBuffer.duration; - - playingSourcesRef.current.push(source); - source.onended = () => { - playingSourcesRef.current = playingSourcesRef.current.filter( - (s) => s !== source - ); - }; - } - } catch (err) { - console.error("Failed to stream speech:", err); - setStatus(`Error: Failed to stream speech. ${err.message}`); - setErrorMessage(`Failed to stream speech: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Stops all currently playing TTS audio sources immediately. - */ - const stopAllPlayingAudio = () => { - if (audioContextRef.current) { - playingSourcesRef.current.forEach((source) => { - try { - source.stop(); - } catch (e) { - // Ignore errors from stopping already stopped sources - } - }); - playingSourcesRef.current = []; - playbackTimeRef.current = audioContextRef.current.currentTime; - console.log("All playing audio has been stopped."); - } - }; - - /** - * Starts the audio recording process for manual mode. - */ - const startManualRecording = async () => { - if (isRecording) { - console.log("Already recording, ignoring start request."); - return; - } - - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting manual recording..."); - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - streamRef.current = stream; // Store the stream to stop it later - mediaRecorderRef.current = new MediaRecorder(stream); - mediaRecorderRef.current.start(); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // Ensure the stream is stopped after recording has finished. - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - - // Clear the media recorder ref to allow a new recording to be created. - mediaRecorderRef.current = null; - - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - }; - - setIsRecording(true); - isRecordingRef.current = true; - setStatus("Recording... Click to stop."); - } catch (err) { - console.error("Error accessing microphone:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops manual recording and finalizes the audio blob. - */ - const stopManualRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("Stopping manual recording."); - // Set isBusy immediately to prevent multiple stop requests. - setIsBusy(true); - setIsRecording(false); // Update UI immediately - mediaRecorderRef.current.stop(); - // The isRecording state will be set to false by the mediaRecorder.onstop handler after processing. - } - }; - - /** - * Starts the voice activity detection process. - */ - const startAutoListening = async () => { - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting automatic listening (VAD)."); - // Request microphone access for VAD. - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - vadStreamRef.current = stream; - - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const source = audioContext.createMediaStreamSource(stream); - - // Use the maximum buffer size for less frequent onaudioprocess calls - const bufferSize = 4096; - const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); - scriptProcessorRef.current = scriptProcessor; - - source.connect(scriptProcessor); - scriptProcessor.connect(audioContext.destination); - - scriptProcessor.onaudioprocess = (event) => { - const inputBuffer = event.inputBuffer.getChannelData(0); - let sum = 0.0; - for (let i = 0; i < inputBuffer.length; i++) { - sum += inputBuffer[i] * inputBuffer[i]; - } - const volume = Math.sqrt(sum / inputBuffer.length); - - console.log(`Current audio volume: ${volume.toFixed(2)}`); - - const isVoiceDetected = (volume > VAD_THRESHOLD); - - if (isVoiceDetected) { - // Voice is detected, so clear the silence timeout - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - - // --- NEW LOGIC: Check for cooldown before starting recording --- - const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; - const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; - - // Start recording only if it's not already running AND the cooldown has passed - if (!isRecordingRef.current && isCooldownPassed) { - startAutoRecording(stream); - } - } else if (isRecordingRef.current) { - // We are currently recording, but silence is detected - // If a silence timeout is not already running, start one - if (!silenceTimeoutRef.current) { - console.log("Silence detected. Starting timeout."); - silenceTimeoutRef.current = setTimeout(() => { - // If the timeout expires, stop the recording and process the audio - stopAutoRecording(); - }, VAD_SILENCE_DURATION); - } - } - }; - - setIsAutoListening(true); - setStatus("Listening for voice..."); - } catch (err) { - console.error("Error accessing microphone for VAD:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops the VAD process and cleans up resources. - */ - const stopAutoListening = () => { - setIsAutoListening(false); - // We only stop all streams when the user explicitly stops auto mode. - stopAllStreams(); - setStatus("Click to start listening."); - console.log("Stopping automatic listening."); - }; - - /** - * Starts the MediaRecorder for automatic mode. - */ - const startAutoRecording = (stream) => { - // Prevent starting a new recorder if one is already running - if (mediaRecorderRef.current?.state === "recording") { - console.log("Auto recording is already running, ignoring start request."); - return; - } - - console.log("Starting a new MediaRecorder for automatic mode."); - mediaRecorderRef.current = new MediaRecorder(stream); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // This flag is essential to prevent the VAD loop from re-triggering a recording - // before the current one is fully processed. - isRecordingRef.current = false; - setIsRecording(false); - if (audioChunksRef.current.length > 0) { - // Set isBusy to prevent the user from starting a new request while processing - setIsBusy(true); - setStatus("Transcribing audio..."); - console.log("Auto recording stopped. Processing conversation..."); - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - } else { - console.warn("Auto recording stopped but no audio chunks were collected."); - setIsBusy(false); - setStatus("Listening for voice..."); - } - }; - - mediaRecorderRef.current.start(); - isRecordingRef.current = true; - setIsRecording(true); - setStatus("Recording..."); - console.log("MediaRecorder started for automatic mode."); - }; - - /** - * Stops the MediaRecorder for automatic mode. - */ - const stopAutoRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("MediaRecorder stopped for automatic mode."); - mediaRecorderRef.current.stop(); - } - }; - - - /** - * Handles the complete conversation flow: STT -> LLM -> TTS. - * @param {Blob} audioBlob - The recorded audio from the user. - */ - const processConversation = async (audioBlob) => { - console.log("Processing conversation..."); - try { - // Step 1: Filter out audio that is too short to be meaningful - // Get the duration of the audio blob. Assuming a 48kHz sample rate, each byte is ~1/48000th of a second - // For a 'audio/wav' blob, this can be complex. A simple check on blob size is a good heuristic. - // Let's assume an average bitrate and calculate duration. Or even simpler, check the raw size. - // A more robust method would be to analyze the audio header, but for this context, a simple size check is fine. - const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; // Assuming 48kHz, 16-bit mono - if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { - console.log(`Audio is too short (${audioDuration.toFixed(2)}ms), skipping.`); - setStatus("Audio was too short. Please speak a little longer."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; // Exit the function early - } - - setStatus("Transcribing audio..."); - if (audioBlob.size === 0) { - console.warn("Audio blob is empty, skipping STT API call."); - setStatus("Recording stopped, but no audio was captured. Please try again."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; - } - - const formData = new FormData(); - formData.append("audio_file", audioBlob, "audio.wav"); - - const sttResponse = await fetch(STT_ENDPOINT, { - method: "POST", - body: formData, - }); - if (!sttResponse.ok) { - console.error(`STT API failed with status: ${sttResponse.status}`); - throw new Error("STT API failed"); - } - const sttResult = await sttResponse.json(); - const userText = sttResult.transcript; - console.log(`STT transcript received: "${userText}"`); - addMessage(userText, true); - - // Step 2: Text-to-Text (LLM) - setStatus("AI is thinking..."); - const llmResponse = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ prompt: userText, model: "gemini" }), - }); - if (!llmResponse.ok) { - console.error(`LLM API failed with status: ${llmResponse.status}`); - throw new Error("LLM API failed"); - } - const llmResult = await llmResponse.json(); - const aiText = llmResult.answer; - console.log(`LLM response received: "${aiText}"`); - addMessage(aiText, false); - - // Step 3: Text-to-Speech (TTS) - await playStreamingAudio(aiText); - } catch (error) { - console.error("Conversation processing failed:", error); - setStatus(`Error: ${error.message}`); - setErrorMessage(`An error occurred: ${error.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - // Ensure refs are nullified after processing for a clean state - mediaRecorderRef.current = null; - streamRef.current = null; - } else if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Toggles the recording state based on the current mode. - */ - const handleMicClick = () => { - stopAllPlayingAudio(); // Interrupt any ongoing TTS playback immediately - - // Prevent new actions if the app is busy processing a request - if (isBusy) { - console.log("App is busy, ignoring mic click."); - return; - } - - if (isAutoMode) { - if (isAutoListening) { - stopAutoListening(); - } else { - startAutoListening(); - } - } else { // Manual Mode - if (isRecording) { - stopManualRecording(); - } else { - startManualRecording(); - } - } - }; - - // FIX: The microphoneButtonState variable was assigned but never used. - // The logic has been moved directly into the JSX below. - // The previous variable declaration has been removed. - - const micButtonColorClass = isRecording - ? "bg-red-600 hover:bg-red-700 active:bg-red-800" - : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; - - return ( -
-
- {/* Header */} -
-

Voice Chat Assistant

-

- Ask me anything and I'll respond! -

-
- - {/* Chat History */} -
- {chatHistory.map((message, index) => ( -
-
- {message.text} -
-
- ))} -
- - {/* Status and Controls */} -
-
- {status} -
- -
- {/* Microphone Button */} - -
- - {/* Mode Toggle */} -
- -
- -
-
- - {/* Error Modal */} - {showErrorModal && ( -
-
-

Error

-

{errorMessage}

- -
-
- )} -
- ); -}; +function App() { + return ; +} export default App; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/ChatWindow.js b/ui/tts-client-app/src/components/ChatWindow.js new file mode 100644 index 0000000..28397a4 --- /dev/null +++ b/ui/tts-client-app/src/components/ChatWindow.js @@ -0,0 +1,29 @@ +// src/components/ChatWindow.js +import React from "react"; + +const ChatWindow = ({ chatHistory }) => { + return ( +
+ {chatHistory.map((message, index) => ( +
+
+

{message.text}

+
+
+ ))} +
+ ); +}; + +export default ChatWindow; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/Controls.js b/ui/tts-client-app/src/components/Controls.js new file mode 100644 index 0000000..f78e2e1 --- /dev/null +++ b/ui/tts-client-app/src/components/Controls.js @@ -0,0 +1,57 @@ +// src/components/Controls.js +import React from "react"; +import { FaMicrophone, FaRegStopCircle, FaCog } from "react-icons/fa"; + +const Controls = ({ + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + onMicClick, + onToggleAutoMode, +}) => { + const micButtonColorClass = isRecording + ? "bg-red-600 hover:bg-red-700 active:bg-red-800" + : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; + + const micButtonState = + isAutoMode && isAutoListening ? isAutoListening : isRecording; + + return ( +
+
+ {status} +
+
+ + +
+ + +
+
+
+ ); +}; + +export default Controls; \ No newline at end of file diff --git a/ui/tts-client-app/src/hooks/useVoiceChat.js b/ui/tts-client-app/src/hooks/useVoiceChat.js new file mode 100644 index 0000000..4c74745 --- /dev/null +++ b/ui/tts-client-app/src/hooks/useVoiceChat.js @@ -0,0 +1,394 @@ +// src/hooks/useVoiceChat.js + +// This file is a custom React hook that contains all the stateful logic +// and side effects for the voice chat application. + +import { useState, useRef, useEffect } from "react"; +import { + createSession, + transcribeAudio, + chatWithAI, + streamSpeech, +} from "../services/apiService"; +import { + stopAllPlayingAudio, + stopAllMediaStreams, + resampleBuffer, +} from "../services/audioUtils"; + +// Constants for Voice Activity Detection and timing +const VAD_THRESHOLD = 0.01; +const VAD_SILENCE_DURATION = 2500; +const MINIMUM_AUDIO_DURATION_MS = 500; +const AUTO_MODE_COOLDOWN_MS = 3000; + +const useVoiceChat = ({ chatContainerRef }) => { + const [chatHistory, setChatHistory] = useState([ + { + text: "Hello! I'm an AI assistant. How can I help you today?", + isUser: false, + }, + ]); + const [status, setStatus] = useState("Click the microphone to start recording."); + const [isBusy, setIsBusy] = useState(false); + const [isRecording, setIsRecording] = useState(false); + const [showErrorModal, setShowErrorModal] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const [sessionId, setSessionId] = useState(null); + const [isAutoMode, setIsAutoMode] = useState(false); + const [isAutoListening, setIsAutoListening] = useState(false); + + // All refs must be declared here, inside the custom hook. + const mediaRecorderRef = useRef(null); + const audioChunksRef = useRef([]); + const audioContextRef = useRef(null); + const playbackTimeRef = useRef(0); + const isRecordingRef = useRef(false); + const playingSourcesRef = useRef([]); + const vadStreamRef = useRef(null); + const scriptProcessorRef = useRef(null); + const silenceTimeoutRef = useRef(null); + const lastRequestTimeRef = useRef(0); + const streamRef = useRef(null); + + // --- Initial Session Creation Effect --- + useEffect(() => { + const startSession = async () => { + setIsBusy(true); + setStatus("Starting new chat session..."); + try { + const session = await createSession(); + setSessionId(session.id); + console.log(`Session created with ID: ${session.id}`); + setStatus("Click the microphone to start recording."); + } catch (err) { + console.error("Error creating session:", err); + setStatus(`Error: Could not start session. ${err.message}`); + setErrorMessage(`Failed to create a chat session: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + } + }; + startSession(); + + return () => { + // Pass the refs to the utility function here + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + }; + }, []); + + // New useEffect hook to automatically scroll to the bottom of the chat history + // The fix: `chatContainerRef` is now included in the dependency array. + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [chatHistory, chatContainerRef]); + + const addMessage = (text, isUser) => { + setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); + }; + + /** + * Plays a stream of audio chunks using the Web Audio API by fetching them from the API. + * This is the orchestrator that uses the stateless streamSpeech API function. + * @param {string} text - The text to be synthesized by the TTS service. + */ + const playStreamingAudio = async (text) => { + setIsBusy(true); + setStatus("Streaming audio..."); + // Pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + + try { + if (!audioContextRef.current) { + audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); + playbackTimeRef.current = audioContextRef.current.currentTime; + } + + const audioContext = audioContextRef.current; + + const onChunkReceived = (rawFloat32Data) => { + // This is the callback that receives processed audio data from apiService. + // It's responsible for using the Web Audio API to play the sound. + const float32Resampled = resampleBuffer( + rawFloat32Data, + 24000, // The model's sample rate is hardcoded to 24000 + audioContext.sampleRate + ); + const audioBuffer = audioContext.createBuffer( + 1, + float32Resampled.length, + audioContext.sampleRate + ); + audioBuffer.copyToChannel(float32Resampled, 0); + + const source = audioContext.createBufferSource(); + source.buffer = audioBuffer; + source.connect(audioContext.destination); + + const currentTime = audioContext.currentTime; + const startTime = Math.max(playbackTimeRef.current, currentTime); + + source.start(startTime); + playbackTimeRef.current = startTime + audioBuffer.duration; + + playingSourcesRef.current.push(source); + source.onended = () => { + playingSourcesRef.current = playingSourcesRef.current.filter( + (s) => s !== source + ); + }; + }; + + const onStreamDone = () => { + // This callback is triggered when the stream finishes. + console.log("TTS Stream complete."); + }; + + // Call the stateless API function, passing the UI-related callbacks + await streamSpeech(text, onChunkReceived, onStreamDone); + + } catch (err) { + console.error("Failed to stream speech:", err); + setStatus(`Error: Failed to stream speech. ${err.message}`); + setErrorMessage(`Failed to stream speech: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + } else { + setStatus("Click to start listening."); + } + } + }; + + + const processConversation = async (audioBlob) => { + console.log("Processing conversation..."); + try { + const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; + if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { + console.log(`Audio too short (${audioDuration.toFixed(2)}ms), skipping.`); + setStatus("Audio was too short. Please speak a little longer."); + lastRequestTimeRef.current = Date.now(); + return; + } + if (audioBlob.size === 0) { + console.warn("Audio blob is empty, skipping STT API call."); + setStatus("Recording stopped, but no audio was captured. Please try again."); + lastRequestTimeRef.current = Date.now(); + return; + } + + setStatus("Transcribing audio..."); + const userText = await transcribeAudio(audioBlob); + addMessage(userText, true); + + setStatus("AI is thinking..."); + const aiText = await chatWithAI(sessionId, userText); + addMessage(aiText, false); + + await playStreamingAudio(aiText); + } catch (error) { + console.error("Conversation processing failed:", error); + setStatus(`Error: ${error.message}`); + setErrorMessage(`An error occurred: ${error.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + // This is the main correction: only stop streams if not in auto-listening mode + if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + } else if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else { + setStatus("Click to start listening."); + } + } + }; + + const startManualRecording = async () => { + if (isRecording) return; + + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + streamRef.current = stream; + mediaRecorderRef.current = new MediaRecorder(stream); + mediaRecorderRef.current.start(); + audioChunksRef.current = []; + + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + + mediaRecorderRef.current.onstop = async () => { + if (streamRef.current) { + streamRef.current.getTracks().forEach(track => track.stop()); + streamRef.current = null; + } + mediaRecorderRef.current = null; + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + }; + setIsRecording(true); + isRecordingRef.current = true; + setStatus("Recording... Click to stop."); + } catch (err) { + console.error("Error accessing microphone:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopManualRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + setIsBusy(true); + setIsRecording(false); + mediaRecorderRef.current.stop(); + } + }; + + const startAutoListening = async () => { + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + vadStreamRef.current = stream; + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const source = audioContext.createMediaStreamSource(stream); + const bufferSize = 4096; + const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); + scriptProcessorRef.current = scriptProcessor; + source.connect(scriptProcessor); + scriptProcessor.connect(audioContext.destination); + + scriptProcessor.onaudioprocess = (event) => { + const inputBuffer = event.inputBuffer.getChannelData(0); + let sum = 0.0; + for (let i = 0; i < inputBuffer.length; i++) { + sum += inputBuffer[i] * inputBuffer[i]; + } + const volume = Math.sqrt(sum / inputBuffer.length); + const isVoiceDetected = volume > VAD_THRESHOLD; + const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; + const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; + + if (isVoiceDetected) { + if (silenceTimeoutRef.current) { + clearTimeout(silenceTimeoutRef.current); + silenceTimeoutRef.current = null; + } + if (!isRecordingRef.current && isCooldownPassed) { + startAutoRecording(stream); + } + } else if (isRecordingRef.current) { + if (!silenceTimeoutRef.current) { + silenceTimeoutRef.current = setTimeout(() => { + stopAutoRecording(); + }, VAD_SILENCE_DURATION); + } + } + }; + setIsAutoListening(true); + setStatus("Listening for voice..."); + } catch (err) { + console.error("Error accessing microphone for VAD:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopAutoListening = () => { + setIsAutoListening(false); + // Pass the refs here to the utility function + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + setStatus("Click to start listening."); + }; + + const startAutoRecording = (stream) => { + if (mediaRecorderRef.current?.state === "recording") return; + mediaRecorderRef.current = new MediaRecorder(stream); + audioChunksRef.current = []; + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + mediaRecorderRef.current.onstop = async () => { + isRecordingRef.current = false; + setIsRecording(false); + if (audioChunksRef.current.length > 0) { + setIsBusy(true); + setStatus("Transcribing audio..."); + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + } else { + setIsBusy(false); + setStatus("Listening for voice..."); + } + }; + mediaRecorderRef.current.start(); + isRecordingRef.current = true; + setIsRecording(true); + setStatus("Recording..."); + }; + + const stopAutoRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + mediaRecorderRef.current.stop(); + } + }; + + const handleMicClick = () => { + // Correctly pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + if (isBusy) return; + + if (isAutoMode) { + if (isAutoListening) { + stopAutoListening(); + } else { + startAutoListening(); + } + } else { + if (isRecording) { + stopManualRecording(); + } else { + startManualRecording(); + } + } + }; + + return { + chatHistory, + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + sessionId, + showErrorModal, + errorMessage, + setIsAutoMode, + handleMicClick, + setShowErrorModal, + }; +}; + +export default useVoiceChat; diff --git a/ui/tts-client-app/src/index.css b/ui/tts-client-app/src/index.css index ec2585e..bd6213e 100644 --- a/ui/tts-client-app/src/index.css +++ b/ui/tts-client-app/src/index.css @@ -1,13 +1,3 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/ui/tts-client-app/src/pages/HomePage.js b/ui/tts-client-app/src/pages/HomePage.js new file mode 100644 index 0000000..648a249 --- /dev/null +++ b/ui/tts-client-app/src/pages/HomePage.js @@ -0,0 +1,75 @@ +// src/pages/HomePage.js +import React, { useRef, useEffect } from "react"; +import ChatWindow from "../components/ChatWindow"; +import Controls from "../components/Controls.js"; +import useVoiceChat from "../hooks/useVoiceChat"; + +const HomePage = () => { + // Chat container ref for scrolling + const chatContainerRef = useRef(null); + + // Custom hook for all voice chat logic + // The 'sessionId' variable has been removed from the destructuring + // because it is not used in this component. + const { + chatHistory, + status, + isRecording, + isBusy, + isAutoMode, + isAutoListening, + showErrorModal, + errorMessage, + setIsAutoMode, + handleMicClick, + setShowErrorModal, + } = useVoiceChat({ chatContainerRef }); + + // Effect to automatically scroll to the bottom of the chat history + // This useEffect is duplicated from the custom hook; it might be redundant, + // but it's kept here to match the provided code's logic. + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [chatHistory]); + + const toggleAutoMode = () => { + setIsAutoMode(!isAutoMode); + }; + + return ( +
+
+ +
+ + + + {showErrorModal && ( +
+
+

Error

+

{errorMessage}

+ +
+
+ )} +
+ ); +}; + +export default HomePage; diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ui/tts-client-app/package-lock.json b/ui/tts-client-app/package-lock.json index 03a0868..6f05481 100644 --- a/ui/tts-client-app/package-lock.json +++ b/ui/tts-client-app/package-lock.json @@ -15,13 +15,15 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } }, "node_modules/@adobe/css-tools": { @@ -3754,6 +3756,11 @@ "tailwindcss": "dist/index.mjs" } }, + "node_modules/@tailwindcss/cli/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -3787,6 +3794,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", @@ -4029,6 +4041,25 @@ "node": ">=8" } }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5458,7 +5489,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -13097,7 +13127,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14788,6 +14817,14 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16791,10 +16828,41 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/ui/tts-client-app/package.json b/ui/tts-client-app/package.json index f07a347..4d75c14 100644 --- a/ui/tts-client-app/package.json +++ b/ui/tts-client-app/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -38,8 +39,9 @@ ] }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } } diff --git a/ui/tts-client-app/public/index.html b/ui/tts-client-app/public/index.html index 8e0f148..3893a7f 100644 --- a/ui/tts-client-app/public/index.html +++ b/ui/tts-client-app/public/index.html @@ -25,7 +25,6 @@ Learn how to configure a non-root public URL by running `npm run build`. --> React App - diff --git a/ui/tts-client-app/src/App.js b/ui/tts-client-app/src/App.js index 6cfd356..74ebba9 100644 --- a/ui/tts-client-app/src/App.js +++ b/ui/tts-client-app/src/App.js @@ -1,781 +1,10 @@ -import React, { useState, useRef, useEffect } from "react"; +// src/App.js +import React from "react"; +import HomePage from "./pages/HomePage"; +import "./styles/main.css"; -// The main application component for the voice chat -const App = () => { - // State for managing the chat history, storing both user and AI messages - const [chatHistory, setChatHistory] = useState([ - { - text: "Hello! I'm an AI assistant. How can I help you today?", - isUser: false, - }, - ]); - // State to manage the UI status, like "Recording...", "Transcribing...", etc. - const [status, setStatus] = useState("Click the microphone to start recording."); - // State to track if the application is currently busy with a request - const [isBusy, setIsBusy] = useState(false); - // State to track the recording status for UI feedback - const [isRecording, setIsRecording] = useState(false); - // State to manage the visibility of the error modal - const [showErrorModal, setShowErrorModal] = useState(false); - // State to hold the error message to be displayed in the modal - const [errorMessage, setErrorMessage] = useState(""); - - // State for managing the chat session - const [sessionId, setSessionId] = useState(null); - // FIX: The userId was being assigned but not used in the rest of the component. - // It's already handled inside the useEffect, so we don't need a state variable for it. - // The previous state variable has been removed. - - // State to toggle between manual and automatic recording modes - const [isAutoMode, setIsAutoMode] = useState(false); - // State to indicate if the app is actively listening for voice in auto mode - const [isAutoListening, setIsAutoListening] = useState(false); - - // Refs for managing Web Audio API and MediaRecorder instances - const mediaRecorderRef = useRef(null); - const audioChunksRef = useRef([]); - const audioContextRef = useRef(null); - const playbackTimeRef = useRef(0); - // Ref to hold the current recording state reliably, to avoid stale closures in event handlers - const isRecordingRef = useRef(false); - - // Keep track of currently playing audio sources so we can stop them on demand - const playingSourcesRef = useRef([]); - - // Refs for managing the VAD (Voice Activity Detection) process - const vadStreamRef = useRef(null); - const scriptProcessorRef = useRef(null); - const silenceTimeoutRef = useRef(null); - - // --- New Refs for Debouncing in Automatic Mode --- - // Ref to track the last time a request was completed. This is used to implement a cooldown. - const lastRequestTimeRef = useRef(0); - // Ref to hold the stream from getUserMedia to be able to stop it later for manual mode - const streamRef = useRef(null); - - // --- New Ref for scrolling to bottom of chat history --- - const chatContainerRef = useRef(null); - - // --- Configuration --- - // Please replace with your actual endpoints - const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; - const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; - const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; - const TTS_ENDPOINT = "http://localhost:8001/speech"; - - // Configuration for Voice Activity Detection - const VAD_THRESHOLD = 0.01; // Adjust this value to control sensitivity (0 to 1) - const VAD_SILENCE_DURATION = 2500; // Duration of silence in ms before stopping recording - - // --- New Configuration for Audio Filtering and Debouncing --- - const MINIMUM_AUDIO_DURATION_MS = 500; // Minimum duration for an audio recording to be processed - const AUTO_MODE_COOLDOWN_MS = 3000; // Cooldown period in milliseconds to prevent rapid re-recordings in automatic mode - - // useEffect hook to create a new session when the component mounts - useEffect(() => { - const createSession = async () => { - setIsBusy(true); - setStatus("Starting new chat session..."); - console.log("Attempting to create a new session."); - try { - const generatedUserId = crypto.randomUUID(); - // FIX: The `setUserId(generatedUserId)` call has been removed - // because the state variable `userId` is no longer needed. - // The `generatedUserId` is still used in the API call. - - const response = await fetch(SESSIONS_CREATE_ENDPOINT, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ user_id: generatedUserId }), - }); - - if (!response.ok) { - throw new Error(`Failed to create session. Status: ${response.status}`); - } - - const session = await response.json(); - setSessionId(session.id); - console.log(`Session created with ID: ${session.id}`); - setStatus("Click the microphone to start recording."); - } catch (err) { - console.error("Error creating session:", err); - setStatus(`Error: Could not start session. ${err.message}`); - setErrorMessage(`Failed to create a chat session: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - } - }; - - createSession(); - - // Cleanup function to stop all streams when the component unmounts - return () => { - stopAllStreams(); - }; - }, []); // Empty dependency array ensures this runs only once on mount - - // New useEffect hook to automatically scroll to the bottom of the chat history - useEffect(() => { - if (chatContainerRef.current) { - chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; - } - }, [chatHistory]); - - /** - * Cleans up all active audio streams and timers. - */ - const stopAllStreams = () => { - if (vadStreamRef.current) { - vadStreamRef.current.getTracks().forEach((track) => track.stop()); - vadStreamRef.current = null; - } - if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { - mediaRecorderRef.current.stop(); - } - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - if (scriptProcessorRef.current) { - scriptProcessorRef.current.disconnect(); - scriptProcessorRef.current = null; - } - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - isRecordingRef.current = false; - setIsRecording(false); - console.log("All audio streams and timers have been stopped."); - }; - - /** - * Adds a new message to the chat history state. The useEffect hook will handle the scrolling. - * @param {string} text - The text content of the message. - * @param {boolean} isUser - True for user messages, false for AI. - */ - const addMessage = (text, isUser) => { - setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); - }; - - /** - * Resamples a Float32Array buffer from a source rate to a destination rate. - * @param {Float32Array} buffer - The input audio buffer. - * @param {number} srcRate - The source sample rate (e.g., 24000). - * @param {number} dstRate - The destination sample rate (AudioContext's). - * @returns {Float32Array} The resampled audio buffer. - */ - const resampleBuffer = (buffer, srcRate, dstRate) => { - if (srcRate === dstRate) return buffer; - const ratio = srcRate / dstRate; - const newLength = Math.round(buffer.length / ratio); - const resampled = new Float32Array(newLength); - for (let i = 0; i < newLength; i++) { - const srcIndex = i * ratio; - const srcIndexFloor = Math.floor(srcIndex); - const srcIndexCeil = Math.min(srcIndexFloor + 1, buffer.length - 1); - const weight = srcIndex - srcIndexFloor; - resampled[i] = - buffer[srcIndexFloor] * (1 - weight) + buffer[srcIndexCeil] * weight; - } - return resampled; - }; - - /** - * Converts raw 16-bit PCM byte data to a normalized Float32Array. - * @param {Uint8Array} pcmBytes - The raw PCM audio data. - * @returns {Float32Array} The converted and normalized audio data. - */ - const convertPcmToFloat32 = (pcmBytes) => { - const int16Array = new Int16Array( - pcmBytes.buffer, - pcmBytes.byteOffset, - pcmBytes.byteLength / 2 - ); - const float32Array = new Float32Array(int16Array.length); - for (let i = 0; i < int16Array.length; i++) { - float32Array[i] = int16Array[i] / 32768; - } - return float32Array; - }; - - /** - * Plays a stream of audio chunks using the Web Audio API. - * @param {string} text - The text to be synthesized by the TTS service. - */ - const playStreamingAudio = async (text) => { - setIsBusy(true); - setStatus("Streaming audio..."); - stopAllPlayingAudio(); // Stop any previous playback - - try { - if (!audioContextRef.current) { - audioContextRef.current = - new (window.AudioContext || window.webkitAudioContext)(); - playbackTimeRef.current = audioContextRef.current.currentTime; - } - - const audioContext = audioContextRef.current; - const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; - - const response = await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ text }), - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const reader = response.body.getReader(); - let leftover = new Uint8Array(0); - - while (true) { - const { done, value: chunk } = await reader.read(); - if (done) { - if (leftover.length > 0) { - console.warn("Leftover bytes discarded:", leftover.length); - } - console.log("TTS Stream complete."); - break; - } - - let combined = new Uint8Array(leftover.length + chunk.length); - combined.set(leftover); - combined.set(chunk, leftover.length); - - let length = combined.length; - if (length % 2 !== 0) { - length -= 1; - } - - const toConvert = combined.slice(0, length); - leftover = combined.slice(length); - const float32Raw = convertPcmToFloat32(toConvert); - const float32Resampled = resampleBuffer( - float32Raw, - 24000, - audioContext.sampleRate - ); - const audioBuffer = audioContext.createBuffer( - 1, - float32Resampled.length, - audioContext.sampleRate - ); - - audioBuffer.copyToChannel(float32Resampled, 0); - - const source = audioContext.createBufferSource(); - source.buffer = audioBuffer; - source.connect(audioContext.destination); - - const currentTime = audioContext.currentTime; - const startTime = Math.max(playbackTimeRef.current, currentTime); - - source.start(startTime); - playbackTimeRef.current = startTime + audioBuffer.duration; - - playingSourcesRef.current.push(source); - source.onended = () => { - playingSourcesRef.current = playingSourcesRef.current.filter( - (s) => s !== source - ); - }; - } - } catch (err) { - console.error("Failed to stream speech:", err); - setStatus(`Error: Failed to stream speech. ${err.message}`); - setErrorMessage(`Failed to stream speech: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Stops all currently playing TTS audio sources immediately. - */ - const stopAllPlayingAudio = () => { - if (audioContextRef.current) { - playingSourcesRef.current.forEach((source) => { - try { - source.stop(); - } catch (e) { - // Ignore errors from stopping already stopped sources - } - }); - playingSourcesRef.current = []; - playbackTimeRef.current = audioContextRef.current.currentTime; - console.log("All playing audio has been stopped."); - } - }; - - /** - * Starts the audio recording process for manual mode. - */ - const startManualRecording = async () => { - if (isRecording) { - console.log("Already recording, ignoring start request."); - return; - } - - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting manual recording..."); - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - streamRef.current = stream; // Store the stream to stop it later - mediaRecorderRef.current = new MediaRecorder(stream); - mediaRecorderRef.current.start(); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // Ensure the stream is stopped after recording has finished. - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - - // Clear the media recorder ref to allow a new recording to be created. - mediaRecorderRef.current = null; - - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - }; - - setIsRecording(true); - isRecordingRef.current = true; - setStatus("Recording... Click to stop."); - } catch (err) { - console.error("Error accessing microphone:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops manual recording and finalizes the audio blob. - */ - const stopManualRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("Stopping manual recording."); - // Set isBusy immediately to prevent multiple stop requests. - setIsBusy(true); - setIsRecording(false); // Update UI immediately - mediaRecorderRef.current.stop(); - // The isRecording state will be set to false by the mediaRecorder.onstop handler after processing. - } - }; - - /** - * Starts the voice activity detection process. - */ - const startAutoListening = async () => { - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting automatic listening (VAD)."); - // Request microphone access for VAD. - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - vadStreamRef.current = stream; - - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const source = audioContext.createMediaStreamSource(stream); - - // Use the maximum buffer size for less frequent onaudioprocess calls - const bufferSize = 4096; - const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); - scriptProcessorRef.current = scriptProcessor; - - source.connect(scriptProcessor); - scriptProcessor.connect(audioContext.destination); - - scriptProcessor.onaudioprocess = (event) => { - const inputBuffer = event.inputBuffer.getChannelData(0); - let sum = 0.0; - for (let i = 0; i < inputBuffer.length; i++) { - sum += inputBuffer[i] * inputBuffer[i]; - } - const volume = Math.sqrt(sum / inputBuffer.length); - - console.log(`Current audio volume: ${volume.toFixed(2)}`); - - const isVoiceDetected = (volume > VAD_THRESHOLD); - - if (isVoiceDetected) { - // Voice is detected, so clear the silence timeout - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - - // --- NEW LOGIC: Check for cooldown before starting recording --- - const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; - const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; - - // Start recording only if it's not already running AND the cooldown has passed - if (!isRecordingRef.current && isCooldownPassed) { - startAutoRecording(stream); - } - } else if (isRecordingRef.current) { - // We are currently recording, but silence is detected - // If a silence timeout is not already running, start one - if (!silenceTimeoutRef.current) { - console.log("Silence detected. Starting timeout."); - silenceTimeoutRef.current = setTimeout(() => { - // If the timeout expires, stop the recording and process the audio - stopAutoRecording(); - }, VAD_SILENCE_DURATION); - } - } - }; - - setIsAutoListening(true); - setStatus("Listening for voice..."); - } catch (err) { - console.error("Error accessing microphone for VAD:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops the VAD process and cleans up resources. - */ - const stopAutoListening = () => { - setIsAutoListening(false); - // We only stop all streams when the user explicitly stops auto mode. - stopAllStreams(); - setStatus("Click to start listening."); - console.log("Stopping automatic listening."); - }; - - /** - * Starts the MediaRecorder for automatic mode. - */ - const startAutoRecording = (stream) => { - // Prevent starting a new recorder if one is already running - if (mediaRecorderRef.current?.state === "recording") { - console.log("Auto recording is already running, ignoring start request."); - return; - } - - console.log("Starting a new MediaRecorder for automatic mode."); - mediaRecorderRef.current = new MediaRecorder(stream); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // This flag is essential to prevent the VAD loop from re-triggering a recording - // before the current one is fully processed. - isRecordingRef.current = false; - setIsRecording(false); - if (audioChunksRef.current.length > 0) { - // Set isBusy to prevent the user from starting a new request while processing - setIsBusy(true); - setStatus("Transcribing audio..."); - console.log("Auto recording stopped. Processing conversation..."); - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - } else { - console.warn("Auto recording stopped but no audio chunks were collected."); - setIsBusy(false); - setStatus("Listening for voice..."); - } - }; - - mediaRecorderRef.current.start(); - isRecordingRef.current = true; - setIsRecording(true); - setStatus("Recording..."); - console.log("MediaRecorder started for automatic mode."); - }; - - /** - * Stops the MediaRecorder for automatic mode. - */ - const stopAutoRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("MediaRecorder stopped for automatic mode."); - mediaRecorderRef.current.stop(); - } - }; - - - /** - * Handles the complete conversation flow: STT -> LLM -> TTS. - * @param {Blob} audioBlob - The recorded audio from the user. - */ - const processConversation = async (audioBlob) => { - console.log("Processing conversation..."); - try { - // Step 1: Filter out audio that is too short to be meaningful - // Get the duration of the audio blob. Assuming a 48kHz sample rate, each byte is ~1/48000th of a second - // For a 'audio/wav' blob, this can be complex. A simple check on blob size is a good heuristic. - // Let's assume an average bitrate and calculate duration. Or even simpler, check the raw size. - // A more robust method would be to analyze the audio header, but for this context, a simple size check is fine. - const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; // Assuming 48kHz, 16-bit mono - if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { - console.log(`Audio is too short (${audioDuration.toFixed(2)}ms), skipping.`); - setStatus("Audio was too short. Please speak a little longer."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; // Exit the function early - } - - setStatus("Transcribing audio..."); - if (audioBlob.size === 0) { - console.warn("Audio blob is empty, skipping STT API call."); - setStatus("Recording stopped, but no audio was captured. Please try again."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; - } - - const formData = new FormData(); - formData.append("audio_file", audioBlob, "audio.wav"); - - const sttResponse = await fetch(STT_ENDPOINT, { - method: "POST", - body: formData, - }); - if (!sttResponse.ok) { - console.error(`STT API failed with status: ${sttResponse.status}`); - throw new Error("STT API failed"); - } - const sttResult = await sttResponse.json(); - const userText = sttResult.transcript; - console.log(`STT transcript received: "${userText}"`); - addMessage(userText, true); - - // Step 2: Text-to-Text (LLM) - setStatus("AI is thinking..."); - const llmResponse = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ prompt: userText, model: "gemini" }), - }); - if (!llmResponse.ok) { - console.error(`LLM API failed with status: ${llmResponse.status}`); - throw new Error("LLM API failed"); - } - const llmResult = await llmResponse.json(); - const aiText = llmResult.answer; - console.log(`LLM response received: "${aiText}"`); - addMessage(aiText, false); - - // Step 3: Text-to-Speech (TTS) - await playStreamingAudio(aiText); - } catch (error) { - console.error("Conversation processing failed:", error); - setStatus(`Error: ${error.message}`); - setErrorMessage(`An error occurred: ${error.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - // Ensure refs are nullified after processing for a clean state - mediaRecorderRef.current = null; - streamRef.current = null; - } else if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Toggles the recording state based on the current mode. - */ - const handleMicClick = () => { - stopAllPlayingAudio(); // Interrupt any ongoing TTS playback immediately - - // Prevent new actions if the app is busy processing a request - if (isBusy) { - console.log("App is busy, ignoring mic click."); - return; - } - - if (isAutoMode) { - if (isAutoListening) { - stopAutoListening(); - } else { - startAutoListening(); - } - } else { // Manual Mode - if (isRecording) { - stopManualRecording(); - } else { - startManualRecording(); - } - } - }; - - // FIX: The microphoneButtonState variable was assigned but never used. - // The logic has been moved directly into the JSX below. - // The previous variable declaration has been removed. - - const micButtonColorClass = isRecording - ? "bg-red-600 hover:bg-red-700 active:bg-red-800" - : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; - - return ( -
-
- {/* Header */} -
-

Voice Chat Assistant

-

- Ask me anything and I'll respond! -

-
- - {/* Chat History */} -
- {chatHistory.map((message, index) => ( -
-
- {message.text} -
-
- ))} -
- - {/* Status and Controls */} -
-
- {status} -
- -
- {/* Microphone Button */} - -
- - {/* Mode Toggle */} -
- -
- -
-
- - {/* Error Modal */} - {showErrorModal && ( -
-
-

Error

-

{errorMessage}

- -
-
- )} -
- ); -}; +function App() { + return ; +} export default App; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/ChatWindow.js b/ui/tts-client-app/src/components/ChatWindow.js new file mode 100644 index 0000000..28397a4 --- /dev/null +++ b/ui/tts-client-app/src/components/ChatWindow.js @@ -0,0 +1,29 @@ +// src/components/ChatWindow.js +import React from "react"; + +const ChatWindow = ({ chatHistory }) => { + return ( +
+ {chatHistory.map((message, index) => ( +
+
+

{message.text}

+
+
+ ))} +
+ ); +}; + +export default ChatWindow; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/Controls.js b/ui/tts-client-app/src/components/Controls.js new file mode 100644 index 0000000..f78e2e1 --- /dev/null +++ b/ui/tts-client-app/src/components/Controls.js @@ -0,0 +1,57 @@ +// src/components/Controls.js +import React from "react"; +import { FaMicrophone, FaRegStopCircle, FaCog } from "react-icons/fa"; + +const Controls = ({ + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + onMicClick, + onToggleAutoMode, +}) => { + const micButtonColorClass = isRecording + ? "bg-red-600 hover:bg-red-700 active:bg-red-800" + : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; + + const micButtonState = + isAutoMode && isAutoListening ? isAutoListening : isRecording; + + return ( +
+
+ {status} +
+
+ + +
+ + +
+
+
+ ); +}; + +export default Controls; \ No newline at end of file diff --git a/ui/tts-client-app/src/hooks/useVoiceChat.js b/ui/tts-client-app/src/hooks/useVoiceChat.js new file mode 100644 index 0000000..4c74745 --- /dev/null +++ b/ui/tts-client-app/src/hooks/useVoiceChat.js @@ -0,0 +1,394 @@ +// src/hooks/useVoiceChat.js + +// This file is a custom React hook that contains all the stateful logic +// and side effects for the voice chat application. + +import { useState, useRef, useEffect } from "react"; +import { + createSession, + transcribeAudio, + chatWithAI, + streamSpeech, +} from "../services/apiService"; +import { + stopAllPlayingAudio, + stopAllMediaStreams, + resampleBuffer, +} from "../services/audioUtils"; + +// Constants for Voice Activity Detection and timing +const VAD_THRESHOLD = 0.01; +const VAD_SILENCE_DURATION = 2500; +const MINIMUM_AUDIO_DURATION_MS = 500; +const AUTO_MODE_COOLDOWN_MS = 3000; + +const useVoiceChat = ({ chatContainerRef }) => { + const [chatHistory, setChatHistory] = useState([ + { + text: "Hello! I'm an AI assistant. How can I help you today?", + isUser: false, + }, + ]); + const [status, setStatus] = useState("Click the microphone to start recording."); + const [isBusy, setIsBusy] = useState(false); + const [isRecording, setIsRecording] = useState(false); + const [showErrorModal, setShowErrorModal] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const [sessionId, setSessionId] = useState(null); + const [isAutoMode, setIsAutoMode] = useState(false); + const [isAutoListening, setIsAutoListening] = useState(false); + + // All refs must be declared here, inside the custom hook. + const mediaRecorderRef = useRef(null); + const audioChunksRef = useRef([]); + const audioContextRef = useRef(null); + const playbackTimeRef = useRef(0); + const isRecordingRef = useRef(false); + const playingSourcesRef = useRef([]); + const vadStreamRef = useRef(null); + const scriptProcessorRef = useRef(null); + const silenceTimeoutRef = useRef(null); + const lastRequestTimeRef = useRef(0); + const streamRef = useRef(null); + + // --- Initial Session Creation Effect --- + useEffect(() => { + const startSession = async () => { + setIsBusy(true); + setStatus("Starting new chat session..."); + try { + const session = await createSession(); + setSessionId(session.id); + console.log(`Session created with ID: ${session.id}`); + setStatus("Click the microphone to start recording."); + } catch (err) { + console.error("Error creating session:", err); + setStatus(`Error: Could not start session. ${err.message}`); + setErrorMessage(`Failed to create a chat session: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + } + }; + startSession(); + + return () => { + // Pass the refs to the utility function here + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + }; + }, []); + + // New useEffect hook to automatically scroll to the bottom of the chat history + // The fix: `chatContainerRef` is now included in the dependency array. + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [chatHistory, chatContainerRef]); + + const addMessage = (text, isUser) => { + setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); + }; + + /** + * Plays a stream of audio chunks using the Web Audio API by fetching them from the API. + * This is the orchestrator that uses the stateless streamSpeech API function. + * @param {string} text - The text to be synthesized by the TTS service. + */ + const playStreamingAudio = async (text) => { + setIsBusy(true); + setStatus("Streaming audio..."); + // Pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + + try { + if (!audioContextRef.current) { + audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); + playbackTimeRef.current = audioContextRef.current.currentTime; + } + + const audioContext = audioContextRef.current; + + const onChunkReceived = (rawFloat32Data) => { + // This is the callback that receives processed audio data from apiService. + // It's responsible for using the Web Audio API to play the sound. + const float32Resampled = resampleBuffer( + rawFloat32Data, + 24000, // The model's sample rate is hardcoded to 24000 + audioContext.sampleRate + ); + const audioBuffer = audioContext.createBuffer( + 1, + float32Resampled.length, + audioContext.sampleRate + ); + audioBuffer.copyToChannel(float32Resampled, 0); + + const source = audioContext.createBufferSource(); + source.buffer = audioBuffer; + source.connect(audioContext.destination); + + const currentTime = audioContext.currentTime; + const startTime = Math.max(playbackTimeRef.current, currentTime); + + source.start(startTime); + playbackTimeRef.current = startTime + audioBuffer.duration; + + playingSourcesRef.current.push(source); + source.onended = () => { + playingSourcesRef.current = playingSourcesRef.current.filter( + (s) => s !== source + ); + }; + }; + + const onStreamDone = () => { + // This callback is triggered when the stream finishes. + console.log("TTS Stream complete."); + }; + + // Call the stateless API function, passing the UI-related callbacks + await streamSpeech(text, onChunkReceived, onStreamDone); + + } catch (err) { + console.error("Failed to stream speech:", err); + setStatus(`Error: Failed to stream speech. ${err.message}`); + setErrorMessage(`Failed to stream speech: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + } else { + setStatus("Click to start listening."); + } + } + }; + + + const processConversation = async (audioBlob) => { + console.log("Processing conversation..."); + try { + const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; + if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { + console.log(`Audio too short (${audioDuration.toFixed(2)}ms), skipping.`); + setStatus("Audio was too short. Please speak a little longer."); + lastRequestTimeRef.current = Date.now(); + return; + } + if (audioBlob.size === 0) { + console.warn("Audio blob is empty, skipping STT API call."); + setStatus("Recording stopped, but no audio was captured. Please try again."); + lastRequestTimeRef.current = Date.now(); + return; + } + + setStatus("Transcribing audio..."); + const userText = await transcribeAudio(audioBlob); + addMessage(userText, true); + + setStatus("AI is thinking..."); + const aiText = await chatWithAI(sessionId, userText); + addMessage(aiText, false); + + await playStreamingAudio(aiText); + } catch (error) { + console.error("Conversation processing failed:", error); + setStatus(`Error: ${error.message}`); + setErrorMessage(`An error occurred: ${error.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + // This is the main correction: only stop streams if not in auto-listening mode + if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + } else if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else { + setStatus("Click to start listening."); + } + } + }; + + const startManualRecording = async () => { + if (isRecording) return; + + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + streamRef.current = stream; + mediaRecorderRef.current = new MediaRecorder(stream); + mediaRecorderRef.current.start(); + audioChunksRef.current = []; + + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + + mediaRecorderRef.current.onstop = async () => { + if (streamRef.current) { + streamRef.current.getTracks().forEach(track => track.stop()); + streamRef.current = null; + } + mediaRecorderRef.current = null; + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + }; + setIsRecording(true); + isRecordingRef.current = true; + setStatus("Recording... Click to stop."); + } catch (err) { + console.error("Error accessing microphone:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopManualRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + setIsBusy(true); + setIsRecording(false); + mediaRecorderRef.current.stop(); + } + }; + + const startAutoListening = async () => { + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + vadStreamRef.current = stream; + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const source = audioContext.createMediaStreamSource(stream); + const bufferSize = 4096; + const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); + scriptProcessorRef.current = scriptProcessor; + source.connect(scriptProcessor); + scriptProcessor.connect(audioContext.destination); + + scriptProcessor.onaudioprocess = (event) => { + const inputBuffer = event.inputBuffer.getChannelData(0); + let sum = 0.0; + for (let i = 0; i < inputBuffer.length; i++) { + sum += inputBuffer[i] * inputBuffer[i]; + } + const volume = Math.sqrt(sum / inputBuffer.length); + const isVoiceDetected = volume > VAD_THRESHOLD; + const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; + const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; + + if (isVoiceDetected) { + if (silenceTimeoutRef.current) { + clearTimeout(silenceTimeoutRef.current); + silenceTimeoutRef.current = null; + } + if (!isRecordingRef.current && isCooldownPassed) { + startAutoRecording(stream); + } + } else if (isRecordingRef.current) { + if (!silenceTimeoutRef.current) { + silenceTimeoutRef.current = setTimeout(() => { + stopAutoRecording(); + }, VAD_SILENCE_DURATION); + } + } + }; + setIsAutoListening(true); + setStatus("Listening for voice..."); + } catch (err) { + console.error("Error accessing microphone for VAD:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopAutoListening = () => { + setIsAutoListening(false); + // Pass the refs here to the utility function + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + setStatus("Click to start listening."); + }; + + const startAutoRecording = (stream) => { + if (mediaRecorderRef.current?.state === "recording") return; + mediaRecorderRef.current = new MediaRecorder(stream); + audioChunksRef.current = []; + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + mediaRecorderRef.current.onstop = async () => { + isRecordingRef.current = false; + setIsRecording(false); + if (audioChunksRef.current.length > 0) { + setIsBusy(true); + setStatus("Transcribing audio..."); + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + } else { + setIsBusy(false); + setStatus("Listening for voice..."); + } + }; + mediaRecorderRef.current.start(); + isRecordingRef.current = true; + setIsRecording(true); + setStatus("Recording..."); + }; + + const stopAutoRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + mediaRecorderRef.current.stop(); + } + }; + + const handleMicClick = () => { + // Correctly pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + if (isBusy) return; + + if (isAutoMode) { + if (isAutoListening) { + stopAutoListening(); + } else { + startAutoListening(); + } + } else { + if (isRecording) { + stopManualRecording(); + } else { + startManualRecording(); + } + } + }; + + return { + chatHistory, + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + sessionId, + showErrorModal, + errorMessage, + setIsAutoMode, + handleMicClick, + setShowErrorModal, + }; +}; + +export default useVoiceChat; diff --git a/ui/tts-client-app/src/index.css b/ui/tts-client-app/src/index.css index ec2585e..bd6213e 100644 --- a/ui/tts-client-app/src/index.css +++ b/ui/tts-client-app/src/index.css @@ -1,13 +1,3 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/ui/tts-client-app/src/pages/HomePage.js b/ui/tts-client-app/src/pages/HomePage.js new file mode 100644 index 0000000..648a249 --- /dev/null +++ b/ui/tts-client-app/src/pages/HomePage.js @@ -0,0 +1,75 @@ +// src/pages/HomePage.js +import React, { useRef, useEffect } from "react"; +import ChatWindow from "../components/ChatWindow"; +import Controls from "../components/Controls.js"; +import useVoiceChat from "../hooks/useVoiceChat"; + +const HomePage = () => { + // Chat container ref for scrolling + const chatContainerRef = useRef(null); + + // Custom hook for all voice chat logic + // The 'sessionId' variable has been removed from the destructuring + // because it is not used in this component. + const { + chatHistory, + status, + isRecording, + isBusy, + isAutoMode, + isAutoListening, + showErrorModal, + errorMessage, + setIsAutoMode, + handleMicClick, + setShowErrorModal, + } = useVoiceChat({ chatContainerRef }); + + // Effect to automatically scroll to the bottom of the chat history + // This useEffect is duplicated from the custom hook; it might be redundant, + // but it's kept here to match the provided code's logic. + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [chatHistory]); + + const toggleAutoMode = () => { + setIsAutoMode(!isAutoMode); + }; + + return ( +
+
+ +
+ + + + {showErrorModal && ( +
+
+

Error

+

{errorMessage}

+ +
+
+ )} +
+ ); +}; + +export default HomePage; diff --git a/ui/tts-client-app/src/services/apiService.js b/ui/tts-client-app/src/services/apiService.js new file mode 100644 index 0000000..ed4c4ec --- /dev/null +++ b/ui/tts-client-app/src/services/apiService.js @@ -0,0 +1,126 @@ +// src/services/apiService.js + +// This file handles all communication with your API endpoints. +// It is designed to be stateless and does not use any React hooks. + +import { convertPcmToFloat32 } from "./audioUtils"; + +// Please replace with your actual endpoints +const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; +const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; +const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; +const TTS_ENDPOINT = "http://localhost:8001/speech"; + +/** + * Creates a new chat session with a unique user ID. + * @returns {Promise} The session object from the API response. + */ +export const createSession = async () => { + const generatedUserId = crypto.randomUUID(); + const response = await fetch(SESSIONS_CREATE_ENDPOINT, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ user_id: generatedUserId }), + }); + if (!response.ok) { + throw new Error(`Failed to create session. Status: ${response.status}`); + } + return await response.json(); +}; + +/** + * Sends an audio blob to the STT endpoint for transcription. + * @param {Blob} audioBlob - The recorded audio data. + * @returns {Promise} The transcribed text. + */ +export const transcribeAudio = async (audioBlob) => { + const formData = new FormData(); + formData.append("audio_file", audioBlob, "audio.wav"); + const response = await fetch(STT_ENDPOINT, { + method: "POST", + body: formData, + }); + if (!response.ok) { + throw new Error("STT API failed"); + } + const result = await response.json(); + return result.transcript; +}; + +/** + * Sends a text prompt to the LLM endpoint and gets a text response. + * @param {string} sessionId - The current chat session ID. + * @param {string} prompt - The user's text prompt. + * @returns {Promise} The AI's text response. + */ +export const chatWithAI = async (sessionId, prompt) => { + const response = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ prompt: prompt, model: "gemini" }), + }); + if (!response.ok) { + throw new Error("LLM API failed"); + } + const result = await response.json(); + return result.answer; +}; + +/** + * Streams speech from the TTS endpoint and processes each chunk. + * It uses a callback to pass the processed audio data back to the caller. + * @param {string} text - The text to be synthesized. + * @param {function(Float32Array): void} onData - Callback for each audio chunk. + * @param {function(): void} onDone - Callback to execute when the stream is finished. + * @returns {Promise} + */ +export const streamSpeech = async (text, onData, onDone) => { + try { + const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; + + const response = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ text }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const reader = response.body.getReader(); + let leftover = new Uint8Array(0); + + while (true) { + const { done, value: chunk } = await reader.read(); + if (done) { + if (leftover.length > 0) { + console.warn("Leftover bytes discarded:", leftover.length); + } + break; + } + + let combined = new Uint8Array(leftover.length + chunk.length); + combined.set(leftover); + combined.set(chunk, leftover.length); + + let length = combined.length; + if (length % 2 !== 0) { + length -= 1; + } + + const toConvert = combined.slice(0, length); + leftover = combined.slice(length); + const float32Raw = convertPcmToFloat32(toConvert); + + // Pass the raw float32 data to the caller for resampling + onData(float32Raw); + } + } catch (error) { + console.error("Failed to stream speech:", error); + throw error; + } finally { + // We call the onDone callback to let the hook know the stream has ended. + onDone(); + } +}; diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ui/tts-client-app/package-lock.json b/ui/tts-client-app/package-lock.json index 03a0868..6f05481 100644 --- a/ui/tts-client-app/package-lock.json +++ b/ui/tts-client-app/package-lock.json @@ -15,13 +15,15 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } }, "node_modules/@adobe/css-tools": { @@ -3754,6 +3756,11 @@ "tailwindcss": "dist/index.mjs" } }, + "node_modules/@tailwindcss/cli/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -3787,6 +3794,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", @@ -4029,6 +4041,25 @@ "node": ">=8" } }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5458,7 +5489,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -13097,7 +13127,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14788,6 +14817,14 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16791,10 +16828,41 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/ui/tts-client-app/package.json b/ui/tts-client-app/package.json index f07a347..4d75c14 100644 --- a/ui/tts-client-app/package.json +++ b/ui/tts-client-app/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -38,8 +39,9 @@ ] }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } } diff --git a/ui/tts-client-app/public/index.html b/ui/tts-client-app/public/index.html index 8e0f148..3893a7f 100644 --- a/ui/tts-client-app/public/index.html +++ b/ui/tts-client-app/public/index.html @@ -25,7 +25,6 @@ Learn how to configure a non-root public URL by running `npm run build`. --> React App - diff --git a/ui/tts-client-app/src/App.js b/ui/tts-client-app/src/App.js index 6cfd356..74ebba9 100644 --- a/ui/tts-client-app/src/App.js +++ b/ui/tts-client-app/src/App.js @@ -1,781 +1,10 @@ -import React, { useState, useRef, useEffect } from "react"; +// src/App.js +import React from "react"; +import HomePage from "./pages/HomePage"; +import "./styles/main.css"; -// The main application component for the voice chat -const App = () => { - // State for managing the chat history, storing both user and AI messages - const [chatHistory, setChatHistory] = useState([ - { - text: "Hello! I'm an AI assistant. How can I help you today?", - isUser: false, - }, - ]); - // State to manage the UI status, like "Recording...", "Transcribing...", etc. - const [status, setStatus] = useState("Click the microphone to start recording."); - // State to track if the application is currently busy with a request - const [isBusy, setIsBusy] = useState(false); - // State to track the recording status for UI feedback - const [isRecording, setIsRecording] = useState(false); - // State to manage the visibility of the error modal - const [showErrorModal, setShowErrorModal] = useState(false); - // State to hold the error message to be displayed in the modal - const [errorMessage, setErrorMessage] = useState(""); - - // State for managing the chat session - const [sessionId, setSessionId] = useState(null); - // FIX: The userId was being assigned but not used in the rest of the component. - // It's already handled inside the useEffect, so we don't need a state variable for it. - // The previous state variable has been removed. - - // State to toggle between manual and automatic recording modes - const [isAutoMode, setIsAutoMode] = useState(false); - // State to indicate if the app is actively listening for voice in auto mode - const [isAutoListening, setIsAutoListening] = useState(false); - - // Refs for managing Web Audio API and MediaRecorder instances - const mediaRecorderRef = useRef(null); - const audioChunksRef = useRef([]); - const audioContextRef = useRef(null); - const playbackTimeRef = useRef(0); - // Ref to hold the current recording state reliably, to avoid stale closures in event handlers - const isRecordingRef = useRef(false); - - // Keep track of currently playing audio sources so we can stop them on demand - const playingSourcesRef = useRef([]); - - // Refs for managing the VAD (Voice Activity Detection) process - const vadStreamRef = useRef(null); - const scriptProcessorRef = useRef(null); - const silenceTimeoutRef = useRef(null); - - // --- New Refs for Debouncing in Automatic Mode --- - // Ref to track the last time a request was completed. This is used to implement a cooldown. - const lastRequestTimeRef = useRef(0); - // Ref to hold the stream from getUserMedia to be able to stop it later for manual mode - const streamRef = useRef(null); - - // --- New Ref for scrolling to bottom of chat history --- - const chatContainerRef = useRef(null); - - // --- Configuration --- - // Please replace with your actual endpoints - const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; - const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; - const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; - const TTS_ENDPOINT = "http://localhost:8001/speech"; - - // Configuration for Voice Activity Detection - const VAD_THRESHOLD = 0.01; // Adjust this value to control sensitivity (0 to 1) - const VAD_SILENCE_DURATION = 2500; // Duration of silence in ms before stopping recording - - // --- New Configuration for Audio Filtering and Debouncing --- - const MINIMUM_AUDIO_DURATION_MS = 500; // Minimum duration for an audio recording to be processed - const AUTO_MODE_COOLDOWN_MS = 3000; // Cooldown period in milliseconds to prevent rapid re-recordings in automatic mode - - // useEffect hook to create a new session when the component mounts - useEffect(() => { - const createSession = async () => { - setIsBusy(true); - setStatus("Starting new chat session..."); - console.log("Attempting to create a new session."); - try { - const generatedUserId = crypto.randomUUID(); - // FIX: The `setUserId(generatedUserId)` call has been removed - // because the state variable `userId` is no longer needed. - // The `generatedUserId` is still used in the API call. - - const response = await fetch(SESSIONS_CREATE_ENDPOINT, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ user_id: generatedUserId }), - }); - - if (!response.ok) { - throw new Error(`Failed to create session. Status: ${response.status}`); - } - - const session = await response.json(); - setSessionId(session.id); - console.log(`Session created with ID: ${session.id}`); - setStatus("Click the microphone to start recording."); - } catch (err) { - console.error("Error creating session:", err); - setStatus(`Error: Could not start session. ${err.message}`); - setErrorMessage(`Failed to create a chat session: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - } - }; - - createSession(); - - // Cleanup function to stop all streams when the component unmounts - return () => { - stopAllStreams(); - }; - }, []); // Empty dependency array ensures this runs only once on mount - - // New useEffect hook to automatically scroll to the bottom of the chat history - useEffect(() => { - if (chatContainerRef.current) { - chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; - } - }, [chatHistory]); - - /** - * Cleans up all active audio streams and timers. - */ - const stopAllStreams = () => { - if (vadStreamRef.current) { - vadStreamRef.current.getTracks().forEach((track) => track.stop()); - vadStreamRef.current = null; - } - if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { - mediaRecorderRef.current.stop(); - } - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - if (scriptProcessorRef.current) { - scriptProcessorRef.current.disconnect(); - scriptProcessorRef.current = null; - } - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - isRecordingRef.current = false; - setIsRecording(false); - console.log("All audio streams and timers have been stopped."); - }; - - /** - * Adds a new message to the chat history state. The useEffect hook will handle the scrolling. - * @param {string} text - The text content of the message. - * @param {boolean} isUser - True for user messages, false for AI. - */ - const addMessage = (text, isUser) => { - setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); - }; - - /** - * Resamples a Float32Array buffer from a source rate to a destination rate. - * @param {Float32Array} buffer - The input audio buffer. - * @param {number} srcRate - The source sample rate (e.g., 24000). - * @param {number} dstRate - The destination sample rate (AudioContext's). - * @returns {Float32Array} The resampled audio buffer. - */ - const resampleBuffer = (buffer, srcRate, dstRate) => { - if (srcRate === dstRate) return buffer; - const ratio = srcRate / dstRate; - const newLength = Math.round(buffer.length / ratio); - const resampled = new Float32Array(newLength); - for (let i = 0; i < newLength; i++) { - const srcIndex = i * ratio; - const srcIndexFloor = Math.floor(srcIndex); - const srcIndexCeil = Math.min(srcIndexFloor + 1, buffer.length - 1); - const weight = srcIndex - srcIndexFloor; - resampled[i] = - buffer[srcIndexFloor] * (1 - weight) + buffer[srcIndexCeil] * weight; - } - return resampled; - }; - - /** - * Converts raw 16-bit PCM byte data to a normalized Float32Array. - * @param {Uint8Array} pcmBytes - The raw PCM audio data. - * @returns {Float32Array} The converted and normalized audio data. - */ - const convertPcmToFloat32 = (pcmBytes) => { - const int16Array = new Int16Array( - pcmBytes.buffer, - pcmBytes.byteOffset, - pcmBytes.byteLength / 2 - ); - const float32Array = new Float32Array(int16Array.length); - for (let i = 0; i < int16Array.length; i++) { - float32Array[i] = int16Array[i] / 32768; - } - return float32Array; - }; - - /** - * Plays a stream of audio chunks using the Web Audio API. - * @param {string} text - The text to be synthesized by the TTS service. - */ - const playStreamingAudio = async (text) => { - setIsBusy(true); - setStatus("Streaming audio..."); - stopAllPlayingAudio(); // Stop any previous playback - - try { - if (!audioContextRef.current) { - audioContextRef.current = - new (window.AudioContext || window.webkitAudioContext)(); - playbackTimeRef.current = audioContextRef.current.currentTime; - } - - const audioContext = audioContextRef.current; - const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; - - const response = await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ text }), - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const reader = response.body.getReader(); - let leftover = new Uint8Array(0); - - while (true) { - const { done, value: chunk } = await reader.read(); - if (done) { - if (leftover.length > 0) { - console.warn("Leftover bytes discarded:", leftover.length); - } - console.log("TTS Stream complete."); - break; - } - - let combined = new Uint8Array(leftover.length + chunk.length); - combined.set(leftover); - combined.set(chunk, leftover.length); - - let length = combined.length; - if (length % 2 !== 0) { - length -= 1; - } - - const toConvert = combined.slice(0, length); - leftover = combined.slice(length); - const float32Raw = convertPcmToFloat32(toConvert); - const float32Resampled = resampleBuffer( - float32Raw, - 24000, - audioContext.sampleRate - ); - const audioBuffer = audioContext.createBuffer( - 1, - float32Resampled.length, - audioContext.sampleRate - ); - - audioBuffer.copyToChannel(float32Resampled, 0); - - const source = audioContext.createBufferSource(); - source.buffer = audioBuffer; - source.connect(audioContext.destination); - - const currentTime = audioContext.currentTime; - const startTime = Math.max(playbackTimeRef.current, currentTime); - - source.start(startTime); - playbackTimeRef.current = startTime + audioBuffer.duration; - - playingSourcesRef.current.push(source); - source.onended = () => { - playingSourcesRef.current = playingSourcesRef.current.filter( - (s) => s !== source - ); - }; - } - } catch (err) { - console.error("Failed to stream speech:", err); - setStatus(`Error: Failed to stream speech. ${err.message}`); - setErrorMessage(`Failed to stream speech: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Stops all currently playing TTS audio sources immediately. - */ - const stopAllPlayingAudio = () => { - if (audioContextRef.current) { - playingSourcesRef.current.forEach((source) => { - try { - source.stop(); - } catch (e) { - // Ignore errors from stopping already stopped sources - } - }); - playingSourcesRef.current = []; - playbackTimeRef.current = audioContextRef.current.currentTime; - console.log("All playing audio has been stopped."); - } - }; - - /** - * Starts the audio recording process for manual mode. - */ - const startManualRecording = async () => { - if (isRecording) { - console.log("Already recording, ignoring start request."); - return; - } - - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting manual recording..."); - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - streamRef.current = stream; // Store the stream to stop it later - mediaRecorderRef.current = new MediaRecorder(stream); - mediaRecorderRef.current.start(); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // Ensure the stream is stopped after recording has finished. - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - - // Clear the media recorder ref to allow a new recording to be created. - mediaRecorderRef.current = null; - - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - }; - - setIsRecording(true); - isRecordingRef.current = true; - setStatus("Recording... Click to stop."); - } catch (err) { - console.error("Error accessing microphone:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops manual recording and finalizes the audio blob. - */ - const stopManualRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("Stopping manual recording."); - // Set isBusy immediately to prevent multiple stop requests. - setIsBusy(true); - setIsRecording(false); // Update UI immediately - mediaRecorderRef.current.stop(); - // The isRecording state will be set to false by the mediaRecorder.onstop handler after processing. - } - }; - - /** - * Starts the voice activity detection process. - */ - const startAutoListening = async () => { - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting automatic listening (VAD)."); - // Request microphone access for VAD. - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - vadStreamRef.current = stream; - - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const source = audioContext.createMediaStreamSource(stream); - - // Use the maximum buffer size for less frequent onaudioprocess calls - const bufferSize = 4096; - const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); - scriptProcessorRef.current = scriptProcessor; - - source.connect(scriptProcessor); - scriptProcessor.connect(audioContext.destination); - - scriptProcessor.onaudioprocess = (event) => { - const inputBuffer = event.inputBuffer.getChannelData(0); - let sum = 0.0; - for (let i = 0; i < inputBuffer.length; i++) { - sum += inputBuffer[i] * inputBuffer[i]; - } - const volume = Math.sqrt(sum / inputBuffer.length); - - console.log(`Current audio volume: ${volume.toFixed(2)}`); - - const isVoiceDetected = (volume > VAD_THRESHOLD); - - if (isVoiceDetected) { - // Voice is detected, so clear the silence timeout - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - - // --- NEW LOGIC: Check for cooldown before starting recording --- - const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; - const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; - - // Start recording only if it's not already running AND the cooldown has passed - if (!isRecordingRef.current && isCooldownPassed) { - startAutoRecording(stream); - } - } else if (isRecordingRef.current) { - // We are currently recording, but silence is detected - // If a silence timeout is not already running, start one - if (!silenceTimeoutRef.current) { - console.log("Silence detected. Starting timeout."); - silenceTimeoutRef.current = setTimeout(() => { - // If the timeout expires, stop the recording and process the audio - stopAutoRecording(); - }, VAD_SILENCE_DURATION); - } - } - }; - - setIsAutoListening(true); - setStatus("Listening for voice..."); - } catch (err) { - console.error("Error accessing microphone for VAD:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops the VAD process and cleans up resources. - */ - const stopAutoListening = () => { - setIsAutoListening(false); - // We only stop all streams when the user explicitly stops auto mode. - stopAllStreams(); - setStatus("Click to start listening."); - console.log("Stopping automatic listening."); - }; - - /** - * Starts the MediaRecorder for automatic mode. - */ - const startAutoRecording = (stream) => { - // Prevent starting a new recorder if one is already running - if (mediaRecorderRef.current?.state === "recording") { - console.log("Auto recording is already running, ignoring start request."); - return; - } - - console.log("Starting a new MediaRecorder for automatic mode."); - mediaRecorderRef.current = new MediaRecorder(stream); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // This flag is essential to prevent the VAD loop from re-triggering a recording - // before the current one is fully processed. - isRecordingRef.current = false; - setIsRecording(false); - if (audioChunksRef.current.length > 0) { - // Set isBusy to prevent the user from starting a new request while processing - setIsBusy(true); - setStatus("Transcribing audio..."); - console.log("Auto recording stopped. Processing conversation..."); - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - } else { - console.warn("Auto recording stopped but no audio chunks were collected."); - setIsBusy(false); - setStatus("Listening for voice..."); - } - }; - - mediaRecorderRef.current.start(); - isRecordingRef.current = true; - setIsRecording(true); - setStatus("Recording..."); - console.log("MediaRecorder started for automatic mode."); - }; - - /** - * Stops the MediaRecorder for automatic mode. - */ - const stopAutoRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("MediaRecorder stopped for automatic mode."); - mediaRecorderRef.current.stop(); - } - }; - - - /** - * Handles the complete conversation flow: STT -> LLM -> TTS. - * @param {Blob} audioBlob - The recorded audio from the user. - */ - const processConversation = async (audioBlob) => { - console.log("Processing conversation..."); - try { - // Step 1: Filter out audio that is too short to be meaningful - // Get the duration of the audio blob. Assuming a 48kHz sample rate, each byte is ~1/48000th of a second - // For a 'audio/wav' blob, this can be complex. A simple check on blob size is a good heuristic. - // Let's assume an average bitrate and calculate duration. Or even simpler, check the raw size. - // A more robust method would be to analyze the audio header, but for this context, a simple size check is fine. - const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; // Assuming 48kHz, 16-bit mono - if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { - console.log(`Audio is too short (${audioDuration.toFixed(2)}ms), skipping.`); - setStatus("Audio was too short. Please speak a little longer."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; // Exit the function early - } - - setStatus("Transcribing audio..."); - if (audioBlob.size === 0) { - console.warn("Audio blob is empty, skipping STT API call."); - setStatus("Recording stopped, but no audio was captured. Please try again."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; - } - - const formData = new FormData(); - formData.append("audio_file", audioBlob, "audio.wav"); - - const sttResponse = await fetch(STT_ENDPOINT, { - method: "POST", - body: formData, - }); - if (!sttResponse.ok) { - console.error(`STT API failed with status: ${sttResponse.status}`); - throw new Error("STT API failed"); - } - const sttResult = await sttResponse.json(); - const userText = sttResult.transcript; - console.log(`STT transcript received: "${userText}"`); - addMessage(userText, true); - - // Step 2: Text-to-Text (LLM) - setStatus("AI is thinking..."); - const llmResponse = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ prompt: userText, model: "gemini" }), - }); - if (!llmResponse.ok) { - console.error(`LLM API failed with status: ${llmResponse.status}`); - throw new Error("LLM API failed"); - } - const llmResult = await llmResponse.json(); - const aiText = llmResult.answer; - console.log(`LLM response received: "${aiText}"`); - addMessage(aiText, false); - - // Step 3: Text-to-Speech (TTS) - await playStreamingAudio(aiText); - } catch (error) { - console.error("Conversation processing failed:", error); - setStatus(`Error: ${error.message}`); - setErrorMessage(`An error occurred: ${error.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - // Ensure refs are nullified after processing for a clean state - mediaRecorderRef.current = null; - streamRef.current = null; - } else if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Toggles the recording state based on the current mode. - */ - const handleMicClick = () => { - stopAllPlayingAudio(); // Interrupt any ongoing TTS playback immediately - - // Prevent new actions if the app is busy processing a request - if (isBusy) { - console.log("App is busy, ignoring mic click."); - return; - } - - if (isAutoMode) { - if (isAutoListening) { - stopAutoListening(); - } else { - startAutoListening(); - } - } else { // Manual Mode - if (isRecording) { - stopManualRecording(); - } else { - startManualRecording(); - } - } - }; - - // FIX: The microphoneButtonState variable was assigned but never used. - // The logic has been moved directly into the JSX below. - // The previous variable declaration has been removed. - - const micButtonColorClass = isRecording - ? "bg-red-600 hover:bg-red-700 active:bg-red-800" - : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; - - return ( -
-
- {/* Header */} -
-

Voice Chat Assistant

-

- Ask me anything and I'll respond! -

-
- - {/* Chat History */} -
- {chatHistory.map((message, index) => ( -
-
- {message.text} -
-
- ))} -
- - {/* Status and Controls */} -
-
- {status} -
- -
- {/* Microphone Button */} - -
- - {/* Mode Toggle */} -
- -
- -
-
- - {/* Error Modal */} - {showErrorModal && ( -
-
-

Error

-

{errorMessage}

- -
-
- )} -
- ); -}; +function App() { + return ; +} export default App; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/ChatWindow.js b/ui/tts-client-app/src/components/ChatWindow.js new file mode 100644 index 0000000..28397a4 --- /dev/null +++ b/ui/tts-client-app/src/components/ChatWindow.js @@ -0,0 +1,29 @@ +// src/components/ChatWindow.js +import React from "react"; + +const ChatWindow = ({ chatHistory }) => { + return ( +
+ {chatHistory.map((message, index) => ( +
+
+

{message.text}

+
+
+ ))} +
+ ); +}; + +export default ChatWindow; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/Controls.js b/ui/tts-client-app/src/components/Controls.js new file mode 100644 index 0000000..f78e2e1 --- /dev/null +++ b/ui/tts-client-app/src/components/Controls.js @@ -0,0 +1,57 @@ +// src/components/Controls.js +import React from "react"; +import { FaMicrophone, FaRegStopCircle, FaCog } from "react-icons/fa"; + +const Controls = ({ + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + onMicClick, + onToggleAutoMode, +}) => { + const micButtonColorClass = isRecording + ? "bg-red-600 hover:bg-red-700 active:bg-red-800" + : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; + + const micButtonState = + isAutoMode && isAutoListening ? isAutoListening : isRecording; + + return ( +
+
+ {status} +
+
+ + +
+ + +
+
+
+ ); +}; + +export default Controls; \ No newline at end of file diff --git a/ui/tts-client-app/src/hooks/useVoiceChat.js b/ui/tts-client-app/src/hooks/useVoiceChat.js new file mode 100644 index 0000000..4c74745 --- /dev/null +++ b/ui/tts-client-app/src/hooks/useVoiceChat.js @@ -0,0 +1,394 @@ +// src/hooks/useVoiceChat.js + +// This file is a custom React hook that contains all the stateful logic +// and side effects for the voice chat application. + +import { useState, useRef, useEffect } from "react"; +import { + createSession, + transcribeAudio, + chatWithAI, + streamSpeech, +} from "../services/apiService"; +import { + stopAllPlayingAudio, + stopAllMediaStreams, + resampleBuffer, +} from "../services/audioUtils"; + +// Constants for Voice Activity Detection and timing +const VAD_THRESHOLD = 0.01; +const VAD_SILENCE_DURATION = 2500; +const MINIMUM_AUDIO_DURATION_MS = 500; +const AUTO_MODE_COOLDOWN_MS = 3000; + +const useVoiceChat = ({ chatContainerRef }) => { + const [chatHistory, setChatHistory] = useState([ + { + text: "Hello! I'm an AI assistant. How can I help you today?", + isUser: false, + }, + ]); + const [status, setStatus] = useState("Click the microphone to start recording."); + const [isBusy, setIsBusy] = useState(false); + const [isRecording, setIsRecording] = useState(false); + const [showErrorModal, setShowErrorModal] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const [sessionId, setSessionId] = useState(null); + const [isAutoMode, setIsAutoMode] = useState(false); + const [isAutoListening, setIsAutoListening] = useState(false); + + // All refs must be declared here, inside the custom hook. + const mediaRecorderRef = useRef(null); + const audioChunksRef = useRef([]); + const audioContextRef = useRef(null); + const playbackTimeRef = useRef(0); + const isRecordingRef = useRef(false); + const playingSourcesRef = useRef([]); + const vadStreamRef = useRef(null); + const scriptProcessorRef = useRef(null); + const silenceTimeoutRef = useRef(null); + const lastRequestTimeRef = useRef(0); + const streamRef = useRef(null); + + // --- Initial Session Creation Effect --- + useEffect(() => { + const startSession = async () => { + setIsBusy(true); + setStatus("Starting new chat session..."); + try { + const session = await createSession(); + setSessionId(session.id); + console.log(`Session created with ID: ${session.id}`); + setStatus("Click the microphone to start recording."); + } catch (err) { + console.error("Error creating session:", err); + setStatus(`Error: Could not start session. ${err.message}`); + setErrorMessage(`Failed to create a chat session: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + } + }; + startSession(); + + return () => { + // Pass the refs to the utility function here + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + }; + }, []); + + // New useEffect hook to automatically scroll to the bottom of the chat history + // The fix: `chatContainerRef` is now included in the dependency array. + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [chatHistory, chatContainerRef]); + + const addMessage = (text, isUser) => { + setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); + }; + + /** + * Plays a stream of audio chunks using the Web Audio API by fetching them from the API. + * This is the orchestrator that uses the stateless streamSpeech API function. + * @param {string} text - The text to be synthesized by the TTS service. + */ + const playStreamingAudio = async (text) => { + setIsBusy(true); + setStatus("Streaming audio..."); + // Pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + + try { + if (!audioContextRef.current) { + audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); + playbackTimeRef.current = audioContextRef.current.currentTime; + } + + const audioContext = audioContextRef.current; + + const onChunkReceived = (rawFloat32Data) => { + // This is the callback that receives processed audio data from apiService. + // It's responsible for using the Web Audio API to play the sound. + const float32Resampled = resampleBuffer( + rawFloat32Data, + 24000, // The model's sample rate is hardcoded to 24000 + audioContext.sampleRate + ); + const audioBuffer = audioContext.createBuffer( + 1, + float32Resampled.length, + audioContext.sampleRate + ); + audioBuffer.copyToChannel(float32Resampled, 0); + + const source = audioContext.createBufferSource(); + source.buffer = audioBuffer; + source.connect(audioContext.destination); + + const currentTime = audioContext.currentTime; + const startTime = Math.max(playbackTimeRef.current, currentTime); + + source.start(startTime); + playbackTimeRef.current = startTime + audioBuffer.duration; + + playingSourcesRef.current.push(source); + source.onended = () => { + playingSourcesRef.current = playingSourcesRef.current.filter( + (s) => s !== source + ); + }; + }; + + const onStreamDone = () => { + // This callback is triggered when the stream finishes. + console.log("TTS Stream complete."); + }; + + // Call the stateless API function, passing the UI-related callbacks + await streamSpeech(text, onChunkReceived, onStreamDone); + + } catch (err) { + console.error("Failed to stream speech:", err); + setStatus(`Error: Failed to stream speech. ${err.message}`); + setErrorMessage(`Failed to stream speech: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + } else { + setStatus("Click to start listening."); + } + } + }; + + + const processConversation = async (audioBlob) => { + console.log("Processing conversation..."); + try { + const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; + if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { + console.log(`Audio too short (${audioDuration.toFixed(2)}ms), skipping.`); + setStatus("Audio was too short. Please speak a little longer."); + lastRequestTimeRef.current = Date.now(); + return; + } + if (audioBlob.size === 0) { + console.warn("Audio blob is empty, skipping STT API call."); + setStatus("Recording stopped, but no audio was captured. Please try again."); + lastRequestTimeRef.current = Date.now(); + return; + } + + setStatus("Transcribing audio..."); + const userText = await transcribeAudio(audioBlob); + addMessage(userText, true); + + setStatus("AI is thinking..."); + const aiText = await chatWithAI(sessionId, userText); + addMessage(aiText, false); + + await playStreamingAudio(aiText); + } catch (error) { + console.error("Conversation processing failed:", error); + setStatus(`Error: ${error.message}`); + setErrorMessage(`An error occurred: ${error.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + // This is the main correction: only stop streams if not in auto-listening mode + if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + } else if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else { + setStatus("Click to start listening."); + } + } + }; + + const startManualRecording = async () => { + if (isRecording) return; + + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + streamRef.current = stream; + mediaRecorderRef.current = new MediaRecorder(stream); + mediaRecorderRef.current.start(); + audioChunksRef.current = []; + + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + + mediaRecorderRef.current.onstop = async () => { + if (streamRef.current) { + streamRef.current.getTracks().forEach(track => track.stop()); + streamRef.current = null; + } + mediaRecorderRef.current = null; + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + }; + setIsRecording(true); + isRecordingRef.current = true; + setStatus("Recording... Click to stop."); + } catch (err) { + console.error("Error accessing microphone:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopManualRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + setIsBusy(true); + setIsRecording(false); + mediaRecorderRef.current.stop(); + } + }; + + const startAutoListening = async () => { + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + vadStreamRef.current = stream; + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const source = audioContext.createMediaStreamSource(stream); + const bufferSize = 4096; + const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); + scriptProcessorRef.current = scriptProcessor; + source.connect(scriptProcessor); + scriptProcessor.connect(audioContext.destination); + + scriptProcessor.onaudioprocess = (event) => { + const inputBuffer = event.inputBuffer.getChannelData(0); + let sum = 0.0; + for (let i = 0; i < inputBuffer.length; i++) { + sum += inputBuffer[i] * inputBuffer[i]; + } + const volume = Math.sqrt(sum / inputBuffer.length); + const isVoiceDetected = volume > VAD_THRESHOLD; + const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; + const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; + + if (isVoiceDetected) { + if (silenceTimeoutRef.current) { + clearTimeout(silenceTimeoutRef.current); + silenceTimeoutRef.current = null; + } + if (!isRecordingRef.current && isCooldownPassed) { + startAutoRecording(stream); + } + } else if (isRecordingRef.current) { + if (!silenceTimeoutRef.current) { + silenceTimeoutRef.current = setTimeout(() => { + stopAutoRecording(); + }, VAD_SILENCE_DURATION); + } + } + }; + setIsAutoListening(true); + setStatus("Listening for voice..."); + } catch (err) { + console.error("Error accessing microphone for VAD:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopAutoListening = () => { + setIsAutoListening(false); + // Pass the refs here to the utility function + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + setStatus("Click to start listening."); + }; + + const startAutoRecording = (stream) => { + if (mediaRecorderRef.current?.state === "recording") return; + mediaRecorderRef.current = new MediaRecorder(stream); + audioChunksRef.current = []; + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + mediaRecorderRef.current.onstop = async () => { + isRecordingRef.current = false; + setIsRecording(false); + if (audioChunksRef.current.length > 0) { + setIsBusy(true); + setStatus("Transcribing audio..."); + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + } else { + setIsBusy(false); + setStatus("Listening for voice..."); + } + }; + mediaRecorderRef.current.start(); + isRecordingRef.current = true; + setIsRecording(true); + setStatus("Recording..."); + }; + + const stopAutoRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + mediaRecorderRef.current.stop(); + } + }; + + const handleMicClick = () => { + // Correctly pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + if (isBusy) return; + + if (isAutoMode) { + if (isAutoListening) { + stopAutoListening(); + } else { + startAutoListening(); + } + } else { + if (isRecording) { + stopManualRecording(); + } else { + startManualRecording(); + } + } + }; + + return { + chatHistory, + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + sessionId, + showErrorModal, + errorMessage, + setIsAutoMode, + handleMicClick, + setShowErrorModal, + }; +}; + +export default useVoiceChat; diff --git a/ui/tts-client-app/src/index.css b/ui/tts-client-app/src/index.css index ec2585e..bd6213e 100644 --- a/ui/tts-client-app/src/index.css +++ b/ui/tts-client-app/src/index.css @@ -1,13 +1,3 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/ui/tts-client-app/src/pages/HomePage.js b/ui/tts-client-app/src/pages/HomePage.js new file mode 100644 index 0000000..648a249 --- /dev/null +++ b/ui/tts-client-app/src/pages/HomePage.js @@ -0,0 +1,75 @@ +// src/pages/HomePage.js +import React, { useRef, useEffect } from "react"; +import ChatWindow from "../components/ChatWindow"; +import Controls from "../components/Controls.js"; +import useVoiceChat from "../hooks/useVoiceChat"; + +const HomePage = () => { + // Chat container ref for scrolling + const chatContainerRef = useRef(null); + + // Custom hook for all voice chat logic + // The 'sessionId' variable has been removed from the destructuring + // because it is not used in this component. + const { + chatHistory, + status, + isRecording, + isBusy, + isAutoMode, + isAutoListening, + showErrorModal, + errorMessage, + setIsAutoMode, + handleMicClick, + setShowErrorModal, + } = useVoiceChat({ chatContainerRef }); + + // Effect to automatically scroll to the bottom of the chat history + // This useEffect is duplicated from the custom hook; it might be redundant, + // but it's kept here to match the provided code's logic. + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [chatHistory]); + + const toggleAutoMode = () => { + setIsAutoMode(!isAutoMode); + }; + + return ( +
+
+ +
+ + + + {showErrorModal && ( +
+
+

Error

+

{errorMessage}

+ +
+
+ )} +
+ ); +}; + +export default HomePage; diff --git a/ui/tts-client-app/src/services/apiService.js b/ui/tts-client-app/src/services/apiService.js new file mode 100644 index 0000000..ed4c4ec --- /dev/null +++ b/ui/tts-client-app/src/services/apiService.js @@ -0,0 +1,126 @@ +// src/services/apiService.js + +// This file handles all communication with your API endpoints. +// It is designed to be stateless and does not use any React hooks. + +import { convertPcmToFloat32 } from "./audioUtils"; + +// Please replace with your actual endpoints +const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; +const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; +const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; +const TTS_ENDPOINT = "http://localhost:8001/speech"; + +/** + * Creates a new chat session with a unique user ID. + * @returns {Promise} The session object from the API response. + */ +export const createSession = async () => { + const generatedUserId = crypto.randomUUID(); + const response = await fetch(SESSIONS_CREATE_ENDPOINT, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ user_id: generatedUserId }), + }); + if (!response.ok) { + throw new Error(`Failed to create session. Status: ${response.status}`); + } + return await response.json(); +}; + +/** + * Sends an audio blob to the STT endpoint for transcription. + * @param {Blob} audioBlob - The recorded audio data. + * @returns {Promise} The transcribed text. + */ +export const transcribeAudio = async (audioBlob) => { + const formData = new FormData(); + formData.append("audio_file", audioBlob, "audio.wav"); + const response = await fetch(STT_ENDPOINT, { + method: "POST", + body: formData, + }); + if (!response.ok) { + throw new Error("STT API failed"); + } + const result = await response.json(); + return result.transcript; +}; + +/** + * Sends a text prompt to the LLM endpoint and gets a text response. + * @param {string} sessionId - The current chat session ID. + * @param {string} prompt - The user's text prompt. + * @returns {Promise} The AI's text response. + */ +export const chatWithAI = async (sessionId, prompt) => { + const response = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ prompt: prompt, model: "gemini" }), + }); + if (!response.ok) { + throw new Error("LLM API failed"); + } + const result = await response.json(); + return result.answer; +}; + +/** + * Streams speech from the TTS endpoint and processes each chunk. + * It uses a callback to pass the processed audio data back to the caller. + * @param {string} text - The text to be synthesized. + * @param {function(Float32Array): void} onData - Callback for each audio chunk. + * @param {function(): void} onDone - Callback to execute when the stream is finished. + * @returns {Promise} + */ +export const streamSpeech = async (text, onData, onDone) => { + try { + const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; + + const response = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ text }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const reader = response.body.getReader(); + let leftover = new Uint8Array(0); + + while (true) { + const { done, value: chunk } = await reader.read(); + if (done) { + if (leftover.length > 0) { + console.warn("Leftover bytes discarded:", leftover.length); + } + break; + } + + let combined = new Uint8Array(leftover.length + chunk.length); + combined.set(leftover); + combined.set(chunk, leftover.length); + + let length = combined.length; + if (length % 2 !== 0) { + length -= 1; + } + + const toConvert = combined.slice(0, length); + leftover = combined.slice(length); + const float32Raw = convertPcmToFloat32(toConvert); + + // Pass the raw float32 data to the caller for resampling + onData(float32Raw); + } + } catch (error) { + console.error("Failed to stream speech:", error); + throw error; + } finally { + // We call the onDone callback to let the hook know the stream has ended. + onDone(); + } +}; diff --git a/ui/tts-client-app/src/services/audioUtils.js b/ui/tts-client-app/src/services/audioUtils.js new file mode 100644 index 0000000..0818779 --- /dev/null +++ b/ui/tts-client-app/src/services/audioUtils.js @@ -0,0 +1,66 @@ +// src/services/audioUtils.js + +// This file should not import React or use any hooks. + +export const stopAllPlayingAudio = (playingSourcesRef, audioContextRef, playbackTimeRef) => { + if (audioContextRef.current) { + playingSourcesRef.current.forEach((source) => { + try { + source.stop(); + } catch (e) { + // Ignore errors from stopping already stopped sources + } + }); + playingSourcesRef.current = []; + playbackTimeRef.current = audioContextRef.current.currentTime; + console.log("All playing audio has been stopped."); + } +}; + +export const stopAllMediaStreams = (vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef) => { + if (vadStreamRef.current) { + vadStreamRef.current.getTracks().forEach((track) => track.stop()); + vadStreamRef.current = null; + } + if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { + mediaRecorderRef.current.stop(); + } + if (scriptProcessorRef.current) { + scriptProcessorRef.current.disconnect(); + scriptProcessorRef.current = null; + } + if (streamRef.current) { + streamRef.current.getTracks().forEach(track => track.stop()); + streamRef.current = null; + } + console.log("All audio streams and timers have been stopped."); +}; + +export const resampleBuffer = (buffer, srcRate, dstRate) => { + if (srcRate === dstRate) return buffer; + const ratio = srcRate / dstRate; + const newLength = Math.round(buffer.length / ratio); + const resampled = new Float32Array(newLength); + for (let i = 0; i < newLength; i++) { + const srcIndex = i * ratio; + const srcIndexFloor = Math.floor(srcIndex); + const srcIndexCeil = Math.min(srcIndexFloor + 1, buffer.length - 1); + const weight = srcIndex - srcIndexFloor; + resampled[i] = + buffer[srcIndexFloor] * (1 - weight) + buffer[srcIndexCeil] * weight; + } + return resampled; +}; + +export const convertPcmToFloat32 = (pcmBytes) => { + const int16Array = new Int16Array( + pcmBytes.buffer, + pcmBytes.byteOffset, + pcmBytes.byteLength / 2 + ); + const float32Array = new Float32Array(int16Array.length); + for (let i = 0; i < int16Array.length; i++) { + float32Array[i] = int16Array[i] / 32768; + } + return float32Array; +}; \ No newline at end of file diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ui/tts-client-app/package-lock.json b/ui/tts-client-app/package-lock.json index 03a0868..6f05481 100644 --- a/ui/tts-client-app/package-lock.json +++ b/ui/tts-client-app/package-lock.json @@ -15,13 +15,15 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } }, "node_modules/@adobe/css-tools": { @@ -3754,6 +3756,11 @@ "tailwindcss": "dist/index.mjs" } }, + "node_modules/@tailwindcss/cli/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -3787,6 +3794,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", @@ -4029,6 +4041,25 @@ "node": ">=8" } }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5458,7 +5489,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -13097,7 +13127,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14788,6 +14817,14 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16791,10 +16828,41 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/ui/tts-client-app/package.json b/ui/tts-client-app/package.json index f07a347..4d75c14 100644 --- a/ui/tts-client-app/package.json +++ b/ui/tts-client-app/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -38,8 +39,9 @@ ] }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } } diff --git a/ui/tts-client-app/public/index.html b/ui/tts-client-app/public/index.html index 8e0f148..3893a7f 100644 --- a/ui/tts-client-app/public/index.html +++ b/ui/tts-client-app/public/index.html @@ -25,7 +25,6 @@ Learn how to configure a non-root public URL by running `npm run build`. --> React App - diff --git a/ui/tts-client-app/src/App.js b/ui/tts-client-app/src/App.js index 6cfd356..74ebba9 100644 --- a/ui/tts-client-app/src/App.js +++ b/ui/tts-client-app/src/App.js @@ -1,781 +1,10 @@ -import React, { useState, useRef, useEffect } from "react"; +// src/App.js +import React from "react"; +import HomePage from "./pages/HomePage"; +import "./styles/main.css"; -// The main application component for the voice chat -const App = () => { - // State for managing the chat history, storing both user and AI messages - const [chatHistory, setChatHistory] = useState([ - { - text: "Hello! I'm an AI assistant. How can I help you today?", - isUser: false, - }, - ]); - // State to manage the UI status, like "Recording...", "Transcribing...", etc. - const [status, setStatus] = useState("Click the microphone to start recording."); - // State to track if the application is currently busy with a request - const [isBusy, setIsBusy] = useState(false); - // State to track the recording status for UI feedback - const [isRecording, setIsRecording] = useState(false); - // State to manage the visibility of the error modal - const [showErrorModal, setShowErrorModal] = useState(false); - // State to hold the error message to be displayed in the modal - const [errorMessage, setErrorMessage] = useState(""); - - // State for managing the chat session - const [sessionId, setSessionId] = useState(null); - // FIX: The userId was being assigned but not used in the rest of the component. - // It's already handled inside the useEffect, so we don't need a state variable for it. - // The previous state variable has been removed. - - // State to toggle between manual and automatic recording modes - const [isAutoMode, setIsAutoMode] = useState(false); - // State to indicate if the app is actively listening for voice in auto mode - const [isAutoListening, setIsAutoListening] = useState(false); - - // Refs for managing Web Audio API and MediaRecorder instances - const mediaRecorderRef = useRef(null); - const audioChunksRef = useRef([]); - const audioContextRef = useRef(null); - const playbackTimeRef = useRef(0); - // Ref to hold the current recording state reliably, to avoid stale closures in event handlers - const isRecordingRef = useRef(false); - - // Keep track of currently playing audio sources so we can stop them on demand - const playingSourcesRef = useRef([]); - - // Refs for managing the VAD (Voice Activity Detection) process - const vadStreamRef = useRef(null); - const scriptProcessorRef = useRef(null); - const silenceTimeoutRef = useRef(null); - - // --- New Refs for Debouncing in Automatic Mode --- - // Ref to track the last time a request was completed. This is used to implement a cooldown. - const lastRequestTimeRef = useRef(0); - // Ref to hold the stream from getUserMedia to be able to stop it later for manual mode - const streamRef = useRef(null); - - // --- New Ref for scrolling to bottom of chat history --- - const chatContainerRef = useRef(null); - - // --- Configuration --- - // Please replace with your actual endpoints - const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; - const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; - const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; - const TTS_ENDPOINT = "http://localhost:8001/speech"; - - // Configuration for Voice Activity Detection - const VAD_THRESHOLD = 0.01; // Adjust this value to control sensitivity (0 to 1) - const VAD_SILENCE_DURATION = 2500; // Duration of silence in ms before stopping recording - - // --- New Configuration for Audio Filtering and Debouncing --- - const MINIMUM_AUDIO_DURATION_MS = 500; // Minimum duration for an audio recording to be processed - const AUTO_MODE_COOLDOWN_MS = 3000; // Cooldown period in milliseconds to prevent rapid re-recordings in automatic mode - - // useEffect hook to create a new session when the component mounts - useEffect(() => { - const createSession = async () => { - setIsBusy(true); - setStatus("Starting new chat session..."); - console.log("Attempting to create a new session."); - try { - const generatedUserId = crypto.randomUUID(); - // FIX: The `setUserId(generatedUserId)` call has been removed - // because the state variable `userId` is no longer needed. - // The `generatedUserId` is still used in the API call. - - const response = await fetch(SESSIONS_CREATE_ENDPOINT, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ user_id: generatedUserId }), - }); - - if (!response.ok) { - throw new Error(`Failed to create session. Status: ${response.status}`); - } - - const session = await response.json(); - setSessionId(session.id); - console.log(`Session created with ID: ${session.id}`); - setStatus("Click the microphone to start recording."); - } catch (err) { - console.error("Error creating session:", err); - setStatus(`Error: Could not start session. ${err.message}`); - setErrorMessage(`Failed to create a chat session: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - } - }; - - createSession(); - - // Cleanup function to stop all streams when the component unmounts - return () => { - stopAllStreams(); - }; - }, []); // Empty dependency array ensures this runs only once on mount - - // New useEffect hook to automatically scroll to the bottom of the chat history - useEffect(() => { - if (chatContainerRef.current) { - chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; - } - }, [chatHistory]); - - /** - * Cleans up all active audio streams and timers. - */ - const stopAllStreams = () => { - if (vadStreamRef.current) { - vadStreamRef.current.getTracks().forEach((track) => track.stop()); - vadStreamRef.current = null; - } - if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { - mediaRecorderRef.current.stop(); - } - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - if (scriptProcessorRef.current) { - scriptProcessorRef.current.disconnect(); - scriptProcessorRef.current = null; - } - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - isRecordingRef.current = false; - setIsRecording(false); - console.log("All audio streams and timers have been stopped."); - }; - - /** - * Adds a new message to the chat history state. The useEffect hook will handle the scrolling. - * @param {string} text - The text content of the message. - * @param {boolean} isUser - True for user messages, false for AI. - */ - const addMessage = (text, isUser) => { - setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); - }; - - /** - * Resamples a Float32Array buffer from a source rate to a destination rate. - * @param {Float32Array} buffer - The input audio buffer. - * @param {number} srcRate - The source sample rate (e.g., 24000). - * @param {number} dstRate - The destination sample rate (AudioContext's). - * @returns {Float32Array} The resampled audio buffer. - */ - const resampleBuffer = (buffer, srcRate, dstRate) => { - if (srcRate === dstRate) return buffer; - const ratio = srcRate / dstRate; - const newLength = Math.round(buffer.length / ratio); - const resampled = new Float32Array(newLength); - for (let i = 0; i < newLength; i++) { - const srcIndex = i * ratio; - const srcIndexFloor = Math.floor(srcIndex); - const srcIndexCeil = Math.min(srcIndexFloor + 1, buffer.length - 1); - const weight = srcIndex - srcIndexFloor; - resampled[i] = - buffer[srcIndexFloor] * (1 - weight) + buffer[srcIndexCeil] * weight; - } - return resampled; - }; - - /** - * Converts raw 16-bit PCM byte data to a normalized Float32Array. - * @param {Uint8Array} pcmBytes - The raw PCM audio data. - * @returns {Float32Array} The converted and normalized audio data. - */ - const convertPcmToFloat32 = (pcmBytes) => { - const int16Array = new Int16Array( - pcmBytes.buffer, - pcmBytes.byteOffset, - pcmBytes.byteLength / 2 - ); - const float32Array = new Float32Array(int16Array.length); - for (let i = 0; i < int16Array.length; i++) { - float32Array[i] = int16Array[i] / 32768; - } - return float32Array; - }; - - /** - * Plays a stream of audio chunks using the Web Audio API. - * @param {string} text - The text to be synthesized by the TTS service. - */ - const playStreamingAudio = async (text) => { - setIsBusy(true); - setStatus("Streaming audio..."); - stopAllPlayingAudio(); // Stop any previous playback - - try { - if (!audioContextRef.current) { - audioContextRef.current = - new (window.AudioContext || window.webkitAudioContext)(); - playbackTimeRef.current = audioContextRef.current.currentTime; - } - - const audioContext = audioContextRef.current; - const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; - - const response = await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ text }), - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const reader = response.body.getReader(); - let leftover = new Uint8Array(0); - - while (true) { - const { done, value: chunk } = await reader.read(); - if (done) { - if (leftover.length > 0) { - console.warn("Leftover bytes discarded:", leftover.length); - } - console.log("TTS Stream complete."); - break; - } - - let combined = new Uint8Array(leftover.length + chunk.length); - combined.set(leftover); - combined.set(chunk, leftover.length); - - let length = combined.length; - if (length % 2 !== 0) { - length -= 1; - } - - const toConvert = combined.slice(0, length); - leftover = combined.slice(length); - const float32Raw = convertPcmToFloat32(toConvert); - const float32Resampled = resampleBuffer( - float32Raw, - 24000, - audioContext.sampleRate - ); - const audioBuffer = audioContext.createBuffer( - 1, - float32Resampled.length, - audioContext.sampleRate - ); - - audioBuffer.copyToChannel(float32Resampled, 0); - - const source = audioContext.createBufferSource(); - source.buffer = audioBuffer; - source.connect(audioContext.destination); - - const currentTime = audioContext.currentTime; - const startTime = Math.max(playbackTimeRef.current, currentTime); - - source.start(startTime); - playbackTimeRef.current = startTime + audioBuffer.duration; - - playingSourcesRef.current.push(source); - source.onended = () => { - playingSourcesRef.current = playingSourcesRef.current.filter( - (s) => s !== source - ); - }; - } - } catch (err) { - console.error("Failed to stream speech:", err); - setStatus(`Error: Failed to stream speech. ${err.message}`); - setErrorMessage(`Failed to stream speech: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Stops all currently playing TTS audio sources immediately. - */ - const stopAllPlayingAudio = () => { - if (audioContextRef.current) { - playingSourcesRef.current.forEach((source) => { - try { - source.stop(); - } catch (e) { - // Ignore errors from stopping already stopped sources - } - }); - playingSourcesRef.current = []; - playbackTimeRef.current = audioContextRef.current.currentTime; - console.log("All playing audio has been stopped."); - } - }; - - /** - * Starts the audio recording process for manual mode. - */ - const startManualRecording = async () => { - if (isRecording) { - console.log("Already recording, ignoring start request."); - return; - } - - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting manual recording..."); - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - streamRef.current = stream; // Store the stream to stop it later - mediaRecorderRef.current = new MediaRecorder(stream); - mediaRecorderRef.current.start(); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // Ensure the stream is stopped after recording has finished. - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - - // Clear the media recorder ref to allow a new recording to be created. - mediaRecorderRef.current = null; - - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - }; - - setIsRecording(true); - isRecordingRef.current = true; - setStatus("Recording... Click to stop."); - } catch (err) { - console.error("Error accessing microphone:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops manual recording and finalizes the audio blob. - */ - const stopManualRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("Stopping manual recording."); - // Set isBusy immediately to prevent multiple stop requests. - setIsBusy(true); - setIsRecording(false); // Update UI immediately - mediaRecorderRef.current.stop(); - // The isRecording state will be set to false by the mediaRecorder.onstop handler after processing. - } - }; - - /** - * Starts the voice activity detection process. - */ - const startAutoListening = async () => { - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting automatic listening (VAD)."); - // Request microphone access for VAD. - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - vadStreamRef.current = stream; - - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const source = audioContext.createMediaStreamSource(stream); - - // Use the maximum buffer size for less frequent onaudioprocess calls - const bufferSize = 4096; - const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); - scriptProcessorRef.current = scriptProcessor; - - source.connect(scriptProcessor); - scriptProcessor.connect(audioContext.destination); - - scriptProcessor.onaudioprocess = (event) => { - const inputBuffer = event.inputBuffer.getChannelData(0); - let sum = 0.0; - for (let i = 0; i < inputBuffer.length; i++) { - sum += inputBuffer[i] * inputBuffer[i]; - } - const volume = Math.sqrt(sum / inputBuffer.length); - - console.log(`Current audio volume: ${volume.toFixed(2)}`); - - const isVoiceDetected = (volume > VAD_THRESHOLD); - - if (isVoiceDetected) { - // Voice is detected, so clear the silence timeout - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - - // --- NEW LOGIC: Check for cooldown before starting recording --- - const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; - const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; - - // Start recording only if it's not already running AND the cooldown has passed - if (!isRecordingRef.current && isCooldownPassed) { - startAutoRecording(stream); - } - } else if (isRecordingRef.current) { - // We are currently recording, but silence is detected - // If a silence timeout is not already running, start one - if (!silenceTimeoutRef.current) { - console.log("Silence detected. Starting timeout."); - silenceTimeoutRef.current = setTimeout(() => { - // If the timeout expires, stop the recording and process the audio - stopAutoRecording(); - }, VAD_SILENCE_DURATION); - } - } - }; - - setIsAutoListening(true); - setStatus("Listening for voice..."); - } catch (err) { - console.error("Error accessing microphone for VAD:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops the VAD process and cleans up resources. - */ - const stopAutoListening = () => { - setIsAutoListening(false); - // We only stop all streams when the user explicitly stops auto mode. - stopAllStreams(); - setStatus("Click to start listening."); - console.log("Stopping automatic listening."); - }; - - /** - * Starts the MediaRecorder for automatic mode. - */ - const startAutoRecording = (stream) => { - // Prevent starting a new recorder if one is already running - if (mediaRecorderRef.current?.state === "recording") { - console.log("Auto recording is already running, ignoring start request."); - return; - } - - console.log("Starting a new MediaRecorder for automatic mode."); - mediaRecorderRef.current = new MediaRecorder(stream); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // This flag is essential to prevent the VAD loop from re-triggering a recording - // before the current one is fully processed. - isRecordingRef.current = false; - setIsRecording(false); - if (audioChunksRef.current.length > 0) { - // Set isBusy to prevent the user from starting a new request while processing - setIsBusy(true); - setStatus("Transcribing audio..."); - console.log("Auto recording stopped. Processing conversation..."); - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - } else { - console.warn("Auto recording stopped but no audio chunks were collected."); - setIsBusy(false); - setStatus("Listening for voice..."); - } - }; - - mediaRecorderRef.current.start(); - isRecordingRef.current = true; - setIsRecording(true); - setStatus("Recording..."); - console.log("MediaRecorder started for automatic mode."); - }; - - /** - * Stops the MediaRecorder for automatic mode. - */ - const stopAutoRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("MediaRecorder stopped for automatic mode."); - mediaRecorderRef.current.stop(); - } - }; - - - /** - * Handles the complete conversation flow: STT -> LLM -> TTS. - * @param {Blob} audioBlob - The recorded audio from the user. - */ - const processConversation = async (audioBlob) => { - console.log("Processing conversation..."); - try { - // Step 1: Filter out audio that is too short to be meaningful - // Get the duration of the audio blob. Assuming a 48kHz sample rate, each byte is ~1/48000th of a second - // For a 'audio/wav' blob, this can be complex. A simple check on blob size is a good heuristic. - // Let's assume an average bitrate and calculate duration. Or even simpler, check the raw size. - // A more robust method would be to analyze the audio header, but for this context, a simple size check is fine. - const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; // Assuming 48kHz, 16-bit mono - if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { - console.log(`Audio is too short (${audioDuration.toFixed(2)}ms), skipping.`); - setStatus("Audio was too short. Please speak a little longer."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; // Exit the function early - } - - setStatus("Transcribing audio..."); - if (audioBlob.size === 0) { - console.warn("Audio blob is empty, skipping STT API call."); - setStatus("Recording stopped, but no audio was captured. Please try again."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; - } - - const formData = new FormData(); - formData.append("audio_file", audioBlob, "audio.wav"); - - const sttResponse = await fetch(STT_ENDPOINT, { - method: "POST", - body: formData, - }); - if (!sttResponse.ok) { - console.error(`STT API failed with status: ${sttResponse.status}`); - throw new Error("STT API failed"); - } - const sttResult = await sttResponse.json(); - const userText = sttResult.transcript; - console.log(`STT transcript received: "${userText}"`); - addMessage(userText, true); - - // Step 2: Text-to-Text (LLM) - setStatus("AI is thinking..."); - const llmResponse = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ prompt: userText, model: "gemini" }), - }); - if (!llmResponse.ok) { - console.error(`LLM API failed with status: ${llmResponse.status}`); - throw new Error("LLM API failed"); - } - const llmResult = await llmResponse.json(); - const aiText = llmResult.answer; - console.log(`LLM response received: "${aiText}"`); - addMessage(aiText, false); - - // Step 3: Text-to-Speech (TTS) - await playStreamingAudio(aiText); - } catch (error) { - console.error("Conversation processing failed:", error); - setStatus(`Error: ${error.message}`); - setErrorMessage(`An error occurred: ${error.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - // Ensure refs are nullified after processing for a clean state - mediaRecorderRef.current = null; - streamRef.current = null; - } else if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Toggles the recording state based on the current mode. - */ - const handleMicClick = () => { - stopAllPlayingAudio(); // Interrupt any ongoing TTS playback immediately - - // Prevent new actions if the app is busy processing a request - if (isBusy) { - console.log("App is busy, ignoring mic click."); - return; - } - - if (isAutoMode) { - if (isAutoListening) { - stopAutoListening(); - } else { - startAutoListening(); - } - } else { // Manual Mode - if (isRecording) { - stopManualRecording(); - } else { - startManualRecording(); - } - } - }; - - // FIX: The microphoneButtonState variable was assigned but never used. - // The logic has been moved directly into the JSX below. - // The previous variable declaration has been removed. - - const micButtonColorClass = isRecording - ? "bg-red-600 hover:bg-red-700 active:bg-red-800" - : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; - - return ( -
-
- {/* Header */} -
-

Voice Chat Assistant

-

- Ask me anything and I'll respond! -

-
- - {/* Chat History */} -
- {chatHistory.map((message, index) => ( -
-
- {message.text} -
-
- ))} -
- - {/* Status and Controls */} -
-
- {status} -
- -
- {/* Microphone Button */} - -
- - {/* Mode Toggle */} -
- -
- -
-
- - {/* Error Modal */} - {showErrorModal && ( -
-
-

Error

-

{errorMessage}

- -
-
- )} -
- ); -}; +function App() { + return ; +} export default App; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/ChatWindow.js b/ui/tts-client-app/src/components/ChatWindow.js new file mode 100644 index 0000000..28397a4 --- /dev/null +++ b/ui/tts-client-app/src/components/ChatWindow.js @@ -0,0 +1,29 @@ +// src/components/ChatWindow.js +import React from "react"; + +const ChatWindow = ({ chatHistory }) => { + return ( +
+ {chatHistory.map((message, index) => ( +
+
+

{message.text}

+
+
+ ))} +
+ ); +}; + +export default ChatWindow; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/Controls.js b/ui/tts-client-app/src/components/Controls.js new file mode 100644 index 0000000..f78e2e1 --- /dev/null +++ b/ui/tts-client-app/src/components/Controls.js @@ -0,0 +1,57 @@ +// src/components/Controls.js +import React from "react"; +import { FaMicrophone, FaRegStopCircle, FaCog } from "react-icons/fa"; + +const Controls = ({ + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + onMicClick, + onToggleAutoMode, +}) => { + const micButtonColorClass = isRecording + ? "bg-red-600 hover:bg-red-700 active:bg-red-800" + : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; + + const micButtonState = + isAutoMode && isAutoListening ? isAutoListening : isRecording; + + return ( +
+
+ {status} +
+
+ + +
+ + +
+
+
+ ); +}; + +export default Controls; \ No newline at end of file diff --git a/ui/tts-client-app/src/hooks/useVoiceChat.js b/ui/tts-client-app/src/hooks/useVoiceChat.js new file mode 100644 index 0000000..4c74745 --- /dev/null +++ b/ui/tts-client-app/src/hooks/useVoiceChat.js @@ -0,0 +1,394 @@ +// src/hooks/useVoiceChat.js + +// This file is a custom React hook that contains all the stateful logic +// and side effects for the voice chat application. + +import { useState, useRef, useEffect } from "react"; +import { + createSession, + transcribeAudio, + chatWithAI, + streamSpeech, +} from "../services/apiService"; +import { + stopAllPlayingAudio, + stopAllMediaStreams, + resampleBuffer, +} from "../services/audioUtils"; + +// Constants for Voice Activity Detection and timing +const VAD_THRESHOLD = 0.01; +const VAD_SILENCE_DURATION = 2500; +const MINIMUM_AUDIO_DURATION_MS = 500; +const AUTO_MODE_COOLDOWN_MS = 3000; + +const useVoiceChat = ({ chatContainerRef }) => { + const [chatHistory, setChatHistory] = useState([ + { + text: "Hello! I'm an AI assistant. How can I help you today?", + isUser: false, + }, + ]); + const [status, setStatus] = useState("Click the microphone to start recording."); + const [isBusy, setIsBusy] = useState(false); + const [isRecording, setIsRecording] = useState(false); + const [showErrorModal, setShowErrorModal] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const [sessionId, setSessionId] = useState(null); + const [isAutoMode, setIsAutoMode] = useState(false); + const [isAutoListening, setIsAutoListening] = useState(false); + + // All refs must be declared here, inside the custom hook. + const mediaRecorderRef = useRef(null); + const audioChunksRef = useRef([]); + const audioContextRef = useRef(null); + const playbackTimeRef = useRef(0); + const isRecordingRef = useRef(false); + const playingSourcesRef = useRef([]); + const vadStreamRef = useRef(null); + const scriptProcessorRef = useRef(null); + const silenceTimeoutRef = useRef(null); + const lastRequestTimeRef = useRef(0); + const streamRef = useRef(null); + + // --- Initial Session Creation Effect --- + useEffect(() => { + const startSession = async () => { + setIsBusy(true); + setStatus("Starting new chat session..."); + try { + const session = await createSession(); + setSessionId(session.id); + console.log(`Session created with ID: ${session.id}`); + setStatus("Click the microphone to start recording."); + } catch (err) { + console.error("Error creating session:", err); + setStatus(`Error: Could not start session. ${err.message}`); + setErrorMessage(`Failed to create a chat session: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + } + }; + startSession(); + + return () => { + // Pass the refs to the utility function here + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + }; + }, []); + + // New useEffect hook to automatically scroll to the bottom of the chat history + // The fix: `chatContainerRef` is now included in the dependency array. + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [chatHistory, chatContainerRef]); + + const addMessage = (text, isUser) => { + setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); + }; + + /** + * Plays a stream of audio chunks using the Web Audio API by fetching them from the API. + * This is the orchestrator that uses the stateless streamSpeech API function. + * @param {string} text - The text to be synthesized by the TTS service. + */ + const playStreamingAudio = async (text) => { + setIsBusy(true); + setStatus("Streaming audio..."); + // Pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + + try { + if (!audioContextRef.current) { + audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); + playbackTimeRef.current = audioContextRef.current.currentTime; + } + + const audioContext = audioContextRef.current; + + const onChunkReceived = (rawFloat32Data) => { + // This is the callback that receives processed audio data from apiService. + // It's responsible for using the Web Audio API to play the sound. + const float32Resampled = resampleBuffer( + rawFloat32Data, + 24000, // The model's sample rate is hardcoded to 24000 + audioContext.sampleRate + ); + const audioBuffer = audioContext.createBuffer( + 1, + float32Resampled.length, + audioContext.sampleRate + ); + audioBuffer.copyToChannel(float32Resampled, 0); + + const source = audioContext.createBufferSource(); + source.buffer = audioBuffer; + source.connect(audioContext.destination); + + const currentTime = audioContext.currentTime; + const startTime = Math.max(playbackTimeRef.current, currentTime); + + source.start(startTime); + playbackTimeRef.current = startTime + audioBuffer.duration; + + playingSourcesRef.current.push(source); + source.onended = () => { + playingSourcesRef.current = playingSourcesRef.current.filter( + (s) => s !== source + ); + }; + }; + + const onStreamDone = () => { + // This callback is triggered when the stream finishes. + console.log("TTS Stream complete."); + }; + + // Call the stateless API function, passing the UI-related callbacks + await streamSpeech(text, onChunkReceived, onStreamDone); + + } catch (err) { + console.error("Failed to stream speech:", err); + setStatus(`Error: Failed to stream speech. ${err.message}`); + setErrorMessage(`Failed to stream speech: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + } else { + setStatus("Click to start listening."); + } + } + }; + + + const processConversation = async (audioBlob) => { + console.log("Processing conversation..."); + try { + const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; + if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { + console.log(`Audio too short (${audioDuration.toFixed(2)}ms), skipping.`); + setStatus("Audio was too short. Please speak a little longer."); + lastRequestTimeRef.current = Date.now(); + return; + } + if (audioBlob.size === 0) { + console.warn("Audio blob is empty, skipping STT API call."); + setStatus("Recording stopped, but no audio was captured. Please try again."); + lastRequestTimeRef.current = Date.now(); + return; + } + + setStatus("Transcribing audio..."); + const userText = await transcribeAudio(audioBlob); + addMessage(userText, true); + + setStatus("AI is thinking..."); + const aiText = await chatWithAI(sessionId, userText); + addMessage(aiText, false); + + await playStreamingAudio(aiText); + } catch (error) { + console.error("Conversation processing failed:", error); + setStatus(`Error: ${error.message}`); + setErrorMessage(`An error occurred: ${error.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + // This is the main correction: only stop streams if not in auto-listening mode + if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + } else if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else { + setStatus("Click to start listening."); + } + } + }; + + const startManualRecording = async () => { + if (isRecording) return; + + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + streamRef.current = stream; + mediaRecorderRef.current = new MediaRecorder(stream); + mediaRecorderRef.current.start(); + audioChunksRef.current = []; + + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + + mediaRecorderRef.current.onstop = async () => { + if (streamRef.current) { + streamRef.current.getTracks().forEach(track => track.stop()); + streamRef.current = null; + } + mediaRecorderRef.current = null; + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + }; + setIsRecording(true); + isRecordingRef.current = true; + setStatus("Recording... Click to stop."); + } catch (err) { + console.error("Error accessing microphone:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopManualRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + setIsBusy(true); + setIsRecording(false); + mediaRecorderRef.current.stop(); + } + }; + + const startAutoListening = async () => { + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + vadStreamRef.current = stream; + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const source = audioContext.createMediaStreamSource(stream); + const bufferSize = 4096; + const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); + scriptProcessorRef.current = scriptProcessor; + source.connect(scriptProcessor); + scriptProcessor.connect(audioContext.destination); + + scriptProcessor.onaudioprocess = (event) => { + const inputBuffer = event.inputBuffer.getChannelData(0); + let sum = 0.0; + for (let i = 0; i < inputBuffer.length; i++) { + sum += inputBuffer[i] * inputBuffer[i]; + } + const volume = Math.sqrt(sum / inputBuffer.length); + const isVoiceDetected = volume > VAD_THRESHOLD; + const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; + const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; + + if (isVoiceDetected) { + if (silenceTimeoutRef.current) { + clearTimeout(silenceTimeoutRef.current); + silenceTimeoutRef.current = null; + } + if (!isRecordingRef.current && isCooldownPassed) { + startAutoRecording(stream); + } + } else if (isRecordingRef.current) { + if (!silenceTimeoutRef.current) { + silenceTimeoutRef.current = setTimeout(() => { + stopAutoRecording(); + }, VAD_SILENCE_DURATION); + } + } + }; + setIsAutoListening(true); + setStatus("Listening for voice..."); + } catch (err) { + console.error("Error accessing microphone for VAD:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopAutoListening = () => { + setIsAutoListening(false); + // Pass the refs here to the utility function + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + setStatus("Click to start listening."); + }; + + const startAutoRecording = (stream) => { + if (mediaRecorderRef.current?.state === "recording") return; + mediaRecorderRef.current = new MediaRecorder(stream); + audioChunksRef.current = []; + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + mediaRecorderRef.current.onstop = async () => { + isRecordingRef.current = false; + setIsRecording(false); + if (audioChunksRef.current.length > 0) { + setIsBusy(true); + setStatus("Transcribing audio..."); + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + } else { + setIsBusy(false); + setStatus("Listening for voice..."); + } + }; + mediaRecorderRef.current.start(); + isRecordingRef.current = true; + setIsRecording(true); + setStatus("Recording..."); + }; + + const stopAutoRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + mediaRecorderRef.current.stop(); + } + }; + + const handleMicClick = () => { + // Correctly pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + if (isBusy) return; + + if (isAutoMode) { + if (isAutoListening) { + stopAutoListening(); + } else { + startAutoListening(); + } + } else { + if (isRecording) { + stopManualRecording(); + } else { + startManualRecording(); + } + } + }; + + return { + chatHistory, + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + sessionId, + showErrorModal, + errorMessage, + setIsAutoMode, + handleMicClick, + setShowErrorModal, + }; +}; + +export default useVoiceChat; diff --git a/ui/tts-client-app/src/index.css b/ui/tts-client-app/src/index.css index ec2585e..bd6213e 100644 --- a/ui/tts-client-app/src/index.css +++ b/ui/tts-client-app/src/index.css @@ -1,13 +1,3 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/ui/tts-client-app/src/pages/HomePage.js b/ui/tts-client-app/src/pages/HomePage.js new file mode 100644 index 0000000..648a249 --- /dev/null +++ b/ui/tts-client-app/src/pages/HomePage.js @@ -0,0 +1,75 @@ +// src/pages/HomePage.js +import React, { useRef, useEffect } from "react"; +import ChatWindow from "../components/ChatWindow"; +import Controls from "../components/Controls.js"; +import useVoiceChat from "../hooks/useVoiceChat"; + +const HomePage = () => { + // Chat container ref for scrolling + const chatContainerRef = useRef(null); + + // Custom hook for all voice chat logic + // The 'sessionId' variable has been removed from the destructuring + // because it is not used in this component. + const { + chatHistory, + status, + isRecording, + isBusy, + isAutoMode, + isAutoListening, + showErrorModal, + errorMessage, + setIsAutoMode, + handleMicClick, + setShowErrorModal, + } = useVoiceChat({ chatContainerRef }); + + // Effect to automatically scroll to the bottom of the chat history + // This useEffect is duplicated from the custom hook; it might be redundant, + // but it's kept here to match the provided code's logic. + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [chatHistory]); + + const toggleAutoMode = () => { + setIsAutoMode(!isAutoMode); + }; + + return ( +
+
+ +
+ + + + {showErrorModal && ( +
+
+

Error

+

{errorMessage}

+ +
+
+ )} +
+ ); +}; + +export default HomePage; diff --git a/ui/tts-client-app/src/services/apiService.js b/ui/tts-client-app/src/services/apiService.js new file mode 100644 index 0000000..ed4c4ec --- /dev/null +++ b/ui/tts-client-app/src/services/apiService.js @@ -0,0 +1,126 @@ +// src/services/apiService.js + +// This file handles all communication with your API endpoints. +// It is designed to be stateless and does not use any React hooks. + +import { convertPcmToFloat32 } from "./audioUtils"; + +// Please replace with your actual endpoints +const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; +const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; +const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; +const TTS_ENDPOINT = "http://localhost:8001/speech"; + +/** + * Creates a new chat session with a unique user ID. + * @returns {Promise} The session object from the API response. + */ +export const createSession = async () => { + const generatedUserId = crypto.randomUUID(); + const response = await fetch(SESSIONS_CREATE_ENDPOINT, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ user_id: generatedUserId }), + }); + if (!response.ok) { + throw new Error(`Failed to create session. Status: ${response.status}`); + } + return await response.json(); +}; + +/** + * Sends an audio blob to the STT endpoint for transcription. + * @param {Blob} audioBlob - The recorded audio data. + * @returns {Promise} The transcribed text. + */ +export const transcribeAudio = async (audioBlob) => { + const formData = new FormData(); + formData.append("audio_file", audioBlob, "audio.wav"); + const response = await fetch(STT_ENDPOINT, { + method: "POST", + body: formData, + }); + if (!response.ok) { + throw new Error("STT API failed"); + } + const result = await response.json(); + return result.transcript; +}; + +/** + * Sends a text prompt to the LLM endpoint and gets a text response. + * @param {string} sessionId - The current chat session ID. + * @param {string} prompt - The user's text prompt. + * @returns {Promise} The AI's text response. + */ +export const chatWithAI = async (sessionId, prompt) => { + const response = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ prompt: prompt, model: "gemini" }), + }); + if (!response.ok) { + throw new Error("LLM API failed"); + } + const result = await response.json(); + return result.answer; +}; + +/** + * Streams speech from the TTS endpoint and processes each chunk. + * It uses a callback to pass the processed audio data back to the caller. + * @param {string} text - The text to be synthesized. + * @param {function(Float32Array): void} onData - Callback for each audio chunk. + * @param {function(): void} onDone - Callback to execute when the stream is finished. + * @returns {Promise} + */ +export const streamSpeech = async (text, onData, onDone) => { + try { + const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; + + const response = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ text }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const reader = response.body.getReader(); + let leftover = new Uint8Array(0); + + while (true) { + const { done, value: chunk } = await reader.read(); + if (done) { + if (leftover.length > 0) { + console.warn("Leftover bytes discarded:", leftover.length); + } + break; + } + + let combined = new Uint8Array(leftover.length + chunk.length); + combined.set(leftover); + combined.set(chunk, leftover.length); + + let length = combined.length; + if (length % 2 !== 0) { + length -= 1; + } + + const toConvert = combined.slice(0, length); + leftover = combined.slice(length); + const float32Raw = convertPcmToFloat32(toConvert); + + // Pass the raw float32 data to the caller for resampling + onData(float32Raw); + } + } catch (error) { + console.error("Failed to stream speech:", error); + throw error; + } finally { + // We call the onDone callback to let the hook know the stream has ended. + onDone(); + } +}; diff --git a/ui/tts-client-app/src/services/audioUtils.js b/ui/tts-client-app/src/services/audioUtils.js new file mode 100644 index 0000000..0818779 --- /dev/null +++ b/ui/tts-client-app/src/services/audioUtils.js @@ -0,0 +1,66 @@ +// src/services/audioUtils.js + +// This file should not import React or use any hooks. + +export const stopAllPlayingAudio = (playingSourcesRef, audioContextRef, playbackTimeRef) => { + if (audioContextRef.current) { + playingSourcesRef.current.forEach((source) => { + try { + source.stop(); + } catch (e) { + // Ignore errors from stopping already stopped sources + } + }); + playingSourcesRef.current = []; + playbackTimeRef.current = audioContextRef.current.currentTime; + console.log("All playing audio has been stopped."); + } +}; + +export const stopAllMediaStreams = (vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef) => { + if (vadStreamRef.current) { + vadStreamRef.current.getTracks().forEach((track) => track.stop()); + vadStreamRef.current = null; + } + if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { + mediaRecorderRef.current.stop(); + } + if (scriptProcessorRef.current) { + scriptProcessorRef.current.disconnect(); + scriptProcessorRef.current = null; + } + if (streamRef.current) { + streamRef.current.getTracks().forEach(track => track.stop()); + streamRef.current = null; + } + console.log("All audio streams and timers have been stopped."); +}; + +export const resampleBuffer = (buffer, srcRate, dstRate) => { + if (srcRate === dstRate) return buffer; + const ratio = srcRate / dstRate; + const newLength = Math.round(buffer.length / ratio); + const resampled = new Float32Array(newLength); + for (let i = 0; i < newLength; i++) { + const srcIndex = i * ratio; + const srcIndexFloor = Math.floor(srcIndex); + const srcIndexCeil = Math.min(srcIndexFloor + 1, buffer.length - 1); + const weight = srcIndex - srcIndexFloor; + resampled[i] = + buffer[srcIndexFloor] * (1 - weight) + buffer[srcIndexCeil] * weight; + } + return resampled; +}; + +export const convertPcmToFloat32 = (pcmBytes) => { + const int16Array = new Int16Array( + pcmBytes.buffer, + pcmBytes.byteOffset, + pcmBytes.byteLength / 2 + ); + const float32Array = new Float32Array(int16Array.length); + for (let i = 0; i < int16Array.length; i++) { + float32Array[i] = int16Array[i] / 32768; + } + return float32Array; +}; \ No newline at end of file diff --git a/ui/tts-client-app/src/styles/main.css b/ui/tts-client-app/src/styles/main.css new file mode 100644 index 0000000..0167e97 --- /dev/null +++ b/ui/tts-client-app/src/styles/main.css @@ -0,0 +1,11 @@ +/* src/styles/main.css */ +/* Content from your original App.css and index.css goes here */ +/* For example: +@tailwind base; +@tailwind components; +@tailwind utilities; + +.App { + text-align: center; +} +*/ \ No newline at end of file diff --git a/ai-hub/app/api/routes/api.py b/ai-hub/app/api/routes/api.py index 69862b0..dba14b6 100644 --- a/ai-hub/app/api/routes/api.py +++ b/ai-hub/app/api/routes/api.py @@ -8,6 +8,9 @@ from .tts import create_tts_router from .stt import create_stt_router +# Import the new workspace router +from .workspace import router as workspace_router + def create_api_router(services: ServiceContainer) -> APIRouter: """ Creates and returns a main APIRouter that includes all sub-routers. @@ -20,5 +23,6 @@ router.include_router(create_documents_router(services)) router.include_router(create_tts_router(services)) router.include_router(create_stt_router(services)) - + router.include_router(workspace_router) + return router \ No newline at end of file diff --git a/ai-hub/app/api/routes/workspace.md b/ai-hub/app/api/routes/workspace.md new file mode 100644 index 0000000..b2b6dd7 --- /dev/null +++ b/ai-hub/app/api/routes/workspace.md @@ -0,0 +1,74 @@ +# AI Hub: Interactive Coding Assistant + +The goal of this project is to extend the existing AI Hub with an interactive coding assistant. This assistant will enable an AI to directly "work" on a user's local codebase, allowing it to understand, debug, and modify a project's files by communicating with a lightweight client running on the user's machine. + +This is a significant step beyond a traditional RESTful API, as it allows for real-time, bi-directional communication, which is essential for the dynamic, back-and-forth nature of a coding session. + +### Core Components + +The project is built on two main components that communicate over a **WebSocket (WSS)** connection: + +* **AI Hub (Server):** The central brain of the operation. This is your existing FastAPI application, which will be extended to include WebSocket endpoints. The server's primary responsibilities are: + * Maintaining the WSS connection with the client. + * Using a **Large Language Model (LLM)** to reason about a user's coding problem. + * Sending structured commands to the client (e.g., "read this file," "execute this command"). + * Receiving and processing command output from the client. + * Generating file changes or new commands based on the current project state. + +* **Local Client:** A new, lightweight application that runs on the user's machine. Its sole purpose is to act as a secure, local intermediary. Its responsibilities are: + * Establishing and maintaining the WSS connection to the AI Hub. + * **Security:** Enforcing a strict sandbox, ensuring the AI can only interact with the user-selected project directory. + * Reading file contents and sending them to the server. + * Executing shell commands and sending the output back to the server. + * Receiving and applying file modifications from the server. + +--- + +### Communication Protocol + +A clear, robust communication schema is the foundation of this project. All communication over the WebSocket will be a JSON object with a consistent structure, using a `type` to identify the command and a `request_id` to link a request to its response. + +#### Server-to-Client Commands + +These are the commands the AI Hub sends to the local client. + +| `type` | Description | Payload | +| :---------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------- | +| `list_files` | Requests a list of files and folders in a specified directory. | `{"path": "./"}` | +| `read_files` | Requests the content of one or more specific files. | `{"files": ["src/main.py", "tests/test.py"]}` | +| `execute_command` | Instructs the client to execute a shell command in the project root directory. | `{"command": "npm test"}` | +| `write_file` | Instructs the client to overwrite a specific file with new content. | `{"path": "src/main.py", "content": "new code..."}` | + +#### Client-to-Server Responses + +These are the responses the local client sends back to the AI Hub. All responses must include the original `request_id`. + +| `type` | Description | Payload | +| :-------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------- | +| `list_files_response` | Returns a list of files and folders. | `{"status": "success", "files": [{"name": "...", "is_dir": true}]}` | +| `read_files_response` | Returns the content of the requested files. | `{"status": "success", "files": {"path": "content"}}` | +| `command_output` | Returns the `stdout` and `stderr` from the executed command. | `{"status": "success", "stdout": "...", "stderr": "..."}` | +| `write_file_response` | Confirms if a file was successfully written or returns an error. | `{"status": "success"}` | + +In the event of an error (e.g., a file not found), the client will always return a `status: "error"` in the payload, along with a human-readable `error_message`. + +--- + +### Kickoff Plan + +We can tackle this project in three phases to ensure a solid foundation. + +#### Phase 1: Establish the Connection +* **Server:** Create the new `workspace.py` route file and define a basic `websocket` endpoint. +* **Client:** Build a minimal proof-of-concept client (e.g., a Python or Node.js script) that can connect to the server's WebSocket endpoint. +* **Goal:** Successfully send a "ping" from the client and receive a "pong" from the server. + +#### Phase 2: Implement Core Functionality +* **Server:** Define the logic for handling incoming client messages (`command_output`, etc.) and for sending commands to the client. +* **Client:** Implement the message handlers for `list_files`, `read_files`, and `execute_command`. This client must include robust file system checks to prevent access outside the designated directory. +* **Goal:** The server can successfully send a `list_files` command to the client, receive a list of files, and then send a `read_files` command for one of those files. + +#### Phase 3: Integrate and Secure +* **Server:** Integrate the new WebSocket communication layer with your existing LLM and RAG pipelines. The LLM's output should be formatted as one of the structured commands. +* **Client:** Implement security features like command validation and a user confirmation UI for potentially destructive commands (e.g., `rm`). +* **Goal:** A user can initiate a conversation with the AI, the AI can ask for a file list and file content, and the client can provide it, all without manual intervention. diff --git a/ai-hub/app/api/routes/workspace.py b/ai-hub/app/api/routes/workspace.py new file mode 100644 index 0000000..87018cf --- /dev/null +++ b/ai-hub/app/api/routes/workspace.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter, WebSocket + +router = APIRouter() + +@router.websocket("/ws/workspace/{session_id}") +async def websocket_endpoint(websocket: WebSocket, session_id: str): + # Your WebSocket logic here + pass \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 66c274f..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1884 +0,0 @@ -{ - "name": "cortex-hub-ui", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - }, - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", - "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "tailwindcss": "4.1.11" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT", - "peer": true - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/browserslist": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", - "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001733", - "electron-to-chromium": "^1.5.199", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001733", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz", - "integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.199", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz", - "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -} diff --git a/ui/package.json b/ui/package.json deleted file mode 100644 index 54a7faa..0000000 --- a/ui/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "devDependencies": { - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" - }, - "dependencies": { - "@tailwindcss/vite": "^4.1.11" - } -} diff --git a/ui/tts-client-app/package-lock.json b/ui/tts-client-app/package-lock.json index 03a0868..6f05481 100644 --- a/ui/tts-client-app/package-lock.json +++ b/ui/tts-client-app/package-lock.json @@ -15,13 +15,15 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } }, "node_modules/@adobe/css-tools": { @@ -3754,6 +3756,11 @@ "tailwindcss": "dist/index.mjs" } }, + "node_modules/@tailwindcss/cli/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -3787,6 +3794,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, "node_modules/@tailwindcss/oxide": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", @@ -4029,6 +4041,25 @@ "node": ">=8" } }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "postcss": "^8.4.41", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -5458,7 +5489,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", @@ -13097,7 +13127,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14788,6 +14817,14 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -16791,10 +16828,41 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "license": "MIT" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.2", diff --git a/ui/tts-client-app/package.json b/ui/tts-client-app/package.json index f07a347..4d75c14 100644 --- a/ui/tts-client-app/package.json +++ b/ui/tts-client-app/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-icons": "^5.5.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -38,8 +39,9 @@ ] }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", - "tailwindcss": "^4.1.11" + "tailwindcss": "^3.4.1" } } diff --git a/ui/tts-client-app/public/index.html b/ui/tts-client-app/public/index.html index 8e0f148..3893a7f 100644 --- a/ui/tts-client-app/public/index.html +++ b/ui/tts-client-app/public/index.html @@ -25,7 +25,6 @@ Learn how to configure a non-root public URL by running `npm run build`. --> React App - diff --git a/ui/tts-client-app/src/App.js b/ui/tts-client-app/src/App.js index 6cfd356..74ebba9 100644 --- a/ui/tts-client-app/src/App.js +++ b/ui/tts-client-app/src/App.js @@ -1,781 +1,10 @@ -import React, { useState, useRef, useEffect } from "react"; +// src/App.js +import React from "react"; +import HomePage from "./pages/HomePage"; +import "./styles/main.css"; -// The main application component for the voice chat -const App = () => { - // State for managing the chat history, storing both user and AI messages - const [chatHistory, setChatHistory] = useState([ - { - text: "Hello! I'm an AI assistant. How can I help you today?", - isUser: false, - }, - ]); - // State to manage the UI status, like "Recording...", "Transcribing...", etc. - const [status, setStatus] = useState("Click the microphone to start recording."); - // State to track if the application is currently busy with a request - const [isBusy, setIsBusy] = useState(false); - // State to track the recording status for UI feedback - const [isRecording, setIsRecording] = useState(false); - // State to manage the visibility of the error modal - const [showErrorModal, setShowErrorModal] = useState(false); - // State to hold the error message to be displayed in the modal - const [errorMessage, setErrorMessage] = useState(""); - - // State for managing the chat session - const [sessionId, setSessionId] = useState(null); - // FIX: The userId was being assigned but not used in the rest of the component. - // It's already handled inside the useEffect, so we don't need a state variable for it. - // The previous state variable has been removed. - - // State to toggle between manual and automatic recording modes - const [isAutoMode, setIsAutoMode] = useState(false); - // State to indicate if the app is actively listening for voice in auto mode - const [isAutoListening, setIsAutoListening] = useState(false); - - // Refs for managing Web Audio API and MediaRecorder instances - const mediaRecorderRef = useRef(null); - const audioChunksRef = useRef([]); - const audioContextRef = useRef(null); - const playbackTimeRef = useRef(0); - // Ref to hold the current recording state reliably, to avoid stale closures in event handlers - const isRecordingRef = useRef(false); - - // Keep track of currently playing audio sources so we can stop them on demand - const playingSourcesRef = useRef([]); - - // Refs for managing the VAD (Voice Activity Detection) process - const vadStreamRef = useRef(null); - const scriptProcessorRef = useRef(null); - const silenceTimeoutRef = useRef(null); - - // --- New Refs for Debouncing in Automatic Mode --- - // Ref to track the last time a request was completed. This is used to implement a cooldown. - const lastRequestTimeRef = useRef(0); - // Ref to hold the stream from getUserMedia to be able to stop it later for manual mode - const streamRef = useRef(null); - - // --- New Ref for scrolling to bottom of chat history --- - const chatContainerRef = useRef(null); - - // --- Configuration --- - // Please replace with your actual endpoints - const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; - const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; - const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; - const TTS_ENDPOINT = "http://localhost:8001/speech"; - - // Configuration for Voice Activity Detection - const VAD_THRESHOLD = 0.01; // Adjust this value to control sensitivity (0 to 1) - const VAD_SILENCE_DURATION = 2500; // Duration of silence in ms before stopping recording - - // --- New Configuration for Audio Filtering and Debouncing --- - const MINIMUM_AUDIO_DURATION_MS = 500; // Minimum duration for an audio recording to be processed - const AUTO_MODE_COOLDOWN_MS = 3000; // Cooldown period in milliseconds to prevent rapid re-recordings in automatic mode - - // useEffect hook to create a new session when the component mounts - useEffect(() => { - const createSession = async () => { - setIsBusy(true); - setStatus("Starting new chat session..."); - console.log("Attempting to create a new session."); - try { - const generatedUserId = crypto.randomUUID(); - // FIX: The `setUserId(generatedUserId)` call has been removed - // because the state variable `userId` is no longer needed. - // The `generatedUserId` is still used in the API call. - - const response = await fetch(SESSIONS_CREATE_ENDPOINT, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ user_id: generatedUserId }), - }); - - if (!response.ok) { - throw new Error(`Failed to create session. Status: ${response.status}`); - } - - const session = await response.json(); - setSessionId(session.id); - console.log(`Session created with ID: ${session.id}`); - setStatus("Click the microphone to start recording."); - } catch (err) { - console.error("Error creating session:", err); - setStatus(`Error: Could not start session. ${err.message}`); - setErrorMessage(`Failed to create a chat session: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - } - }; - - createSession(); - - // Cleanup function to stop all streams when the component unmounts - return () => { - stopAllStreams(); - }; - }, []); // Empty dependency array ensures this runs only once on mount - - // New useEffect hook to automatically scroll to the bottom of the chat history - useEffect(() => { - if (chatContainerRef.current) { - chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; - } - }, [chatHistory]); - - /** - * Cleans up all active audio streams and timers. - */ - const stopAllStreams = () => { - if (vadStreamRef.current) { - vadStreamRef.current.getTracks().forEach((track) => track.stop()); - vadStreamRef.current = null; - } - if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { - mediaRecorderRef.current.stop(); - } - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - if (scriptProcessorRef.current) { - scriptProcessorRef.current.disconnect(); - scriptProcessorRef.current = null; - } - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - isRecordingRef.current = false; - setIsRecording(false); - console.log("All audio streams and timers have been stopped."); - }; - - /** - * Adds a new message to the chat history state. The useEffect hook will handle the scrolling. - * @param {string} text - The text content of the message. - * @param {boolean} isUser - True for user messages, false for AI. - */ - const addMessage = (text, isUser) => { - setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); - }; - - /** - * Resamples a Float32Array buffer from a source rate to a destination rate. - * @param {Float32Array} buffer - The input audio buffer. - * @param {number} srcRate - The source sample rate (e.g., 24000). - * @param {number} dstRate - The destination sample rate (AudioContext's). - * @returns {Float32Array} The resampled audio buffer. - */ - const resampleBuffer = (buffer, srcRate, dstRate) => { - if (srcRate === dstRate) return buffer; - const ratio = srcRate / dstRate; - const newLength = Math.round(buffer.length / ratio); - const resampled = new Float32Array(newLength); - for (let i = 0; i < newLength; i++) { - const srcIndex = i * ratio; - const srcIndexFloor = Math.floor(srcIndex); - const srcIndexCeil = Math.min(srcIndexFloor + 1, buffer.length - 1); - const weight = srcIndex - srcIndexFloor; - resampled[i] = - buffer[srcIndexFloor] * (1 - weight) + buffer[srcIndexCeil] * weight; - } - return resampled; - }; - - /** - * Converts raw 16-bit PCM byte data to a normalized Float32Array. - * @param {Uint8Array} pcmBytes - The raw PCM audio data. - * @returns {Float32Array} The converted and normalized audio data. - */ - const convertPcmToFloat32 = (pcmBytes) => { - const int16Array = new Int16Array( - pcmBytes.buffer, - pcmBytes.byteOffset, - pcmBytes.byteLength / 2 - ); - const float32Array = new Float32Array(int16Array.length); - for (let i = 0; i < int16Array.length; i++) { - float32Array[i] = int16Array[i] / 32768; - } - return float32Array; - }; - - /** - * Plays a stream of audio chunks using the Web Audio API. - * @param {string} text - The text to be synthesized by the TTS service. - */ - const playStreamingAudio = async (text) => { - setIsBusy(true); - setStatus("Streaming audio..."); - stopAllPlayingAudio(); // Stop any previous playback - - try { - if (!audioContextRef.current) { - audioContextRef.current = - new (window.AudioContext || window.webkitAudioContext)(); - playbackTimeRef.current = audioContextRef.current.currentTime; - } - - const audioContext = audioContextRef.current; - const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; - - const response = await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ text }), - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const reader = response.body.getReader(); - let leftover = new Uint8Array(0); - - while (true) { - const { done, value: chunk } = await reader.read(); - if (done) { - if (leftover.length > 0) { - console.warn("Leftover bytes discarded:", leftover.length); - } - console.log("TTS Stream complete."); - break; - } - - let combined = new Uint8Array(leftover.length + chunk.length); - combined.set(leftover); - combined.set(chunk, leftover.length); - - let length = combined.length; - if (length % 2 !== 0) { - length -= 1; - } - - const toConvert = combined.slice(0, length); - leftover = combined.slice(length); - const float32Raw = convertPcmToFloat32(toConvert); - const float32Resampled = resampleBuffer( - float32Raw, - 24000, - audioContext.sampleRate - ); - const audioBuffer = audioContext.createBuffer( - 1, - float32Resampled.length, - audioContext.sampleRate - ); - - audioBuffer.copyToChannel(float32Resampled, 0); - - const source = audioContext.createBufferSource(); - source.buffer = audioBuffer; - source.connect(audioContext.destination); - - const currentTime = audioContext.currentTime; - const startTime = Math.max(playbackTimeRef.current, currentTime); - - source.start(startTime); - playbackTimeRef.current = startTime + audioBuffer.duration; - - playingSourcesRef.current.push(source); - source.onended = () => { - playingSourcesRef.current = playingSourcesRef.current.filter( - (s) => s !== source - ); - }; - } - } catch (err) { - console.error("Failed to stream speech:", err); - setStatus(`Error: Failed to stream speech. ${err.message}`); - setErrorMessage(`Failed to stream speech: ${err.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Stops all currently playing TTS audio sources immediately. - */ - const stopAllPlayingAudio = () => { - if (audioContextRef.current) { - playingSourcesRef.current.forEach((source) => { - try { - source.stop(); - } catch (e) { - // Ignore errors from stopping already stopped sources - } - }); - playingSourcesRef.current = []; - playbackTimeRef.current = audioContextRef.current.currentTime; - console.log("All playing audio has been stopped."); - } - }; - - /** - * Starts the audio recording process for manual mode. - */ - const startManualRecording = async () => { - if (isRecording) { - console.log("Already recording, ignoring start request."); - return; - } - - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting manual recording..."); - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - streamRef.current = stream; // Store the stream to stop it later - mediaRecorderRef.current = new MediaRecorder(stream); - mediaRecorderRef.current.start(); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // Ensure the stream is stopped after recording has finished. - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - streamRef.current = null; - } - - // Clear the media recorder ref to allow a new recording to be created. - mediaRecorderRef.current = null; - - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - }; - - setIsRecording(true); - isRecordingRef.current = true; - setStatus("Recording... Click to stop."); - } catch (err) { - console.error("Error accessing microphone:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops manual recording and finalizes the audio blob. - */ - const stopManualRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("Stopping manual recording."); - // Set isBusy immediately to prevent multiple stop requests. - setIsBusy(true); - setIsRecording(false); // Update UI immediately - mediaRecorderRef.current.stop(); - // The isRecording state will be set to false by the mediaRecorder.onstop handler after processing. - } - }; - - /** - * Starts the voice activity detection process. - */ - const startAutoListening = async () => { - try { - if (!sessionId) { - setErrorMessage("Please wait for the chat session to be initialized."); - setShowErrorModal(true); - return; - } - console.log("Starting automatic listening (VAD)."); - // Request microphone access for VAD. - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - vadStreamRef.current = stream; - - const audioContext = new (window.AudioContext || window.webkitAudioContext)(); - const source = audioContext.createMediaStreamSource(stream); - - // Use the maximum buffer size for less frequent onaudioprocess calls - const bufferSize = 4096; - const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); - scriptProcessorRef.current = scriptProcessor; - - source.connect(scriptProcessor); - scriptProcessor.connect(audioContext.destination); - - scriptProcessor.onaudioprocess = (event) => { - const inputBuffer = event.inputBuffer.getChannelData(0); - let sum = 0.0; - for (let i = 0; i < inputBuffer.length; i++) { - sum += inputBuffer[i] * inputBuffer[i]; - } - const volume = Math.sqrt(sum / inputBuffer.length); - - console.log(`Current audio volume: ${volume.toFixed(2)}`); - - const isVoiceDetected = (volume > VAD_THRESHOLD); - - if (isVoiceDetected) { - // Voice is detected, so clear the silence timeout - if (silenceTimeoutRef.current) { - clearTimeout(silenceTimeoutRef.current); - silenceTimeoutRef.current = null; - } - - // --- NEW LOGIC: Check for cooldown before starting recording --- - const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; - const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; - - // Start recording only if it's not already running AND the cooldown has passed - if (!isRecordingRef.current && isCooldownPassed) { - startAutoRecording(stream); - } - } else if (isRecordingRef.current) { - // We are currently recording, but silence is detected - // If a silence timeout is not already running, start one - if (!silenceTimeoutRef.current) { - console.log("Silence detected. Starting timeout."); - silenceTimeoutRef.current = setTimeout(() => { - // If the timeout expires, stop the recording and process the audio - stopAutoRecording(); - }, VAD_SILENCE_DURATION); - } - } - }; - - setIsAutoListening(true); - setStatus("Listening for voice..."); - } catch (err) { - console.error("Error accessing microphone for VAD:", err); - setStatus("Error: Cannot access microphone."); - setErrorMessage( - "Microphone access has been denied. Please enable it in your browser's site settings to continue." - ); - setShowErrorModal(true); - } - }; - - /** - * Stops the VAD process and cleans up resources. - */ - const stopAutoListening = () => { - setIsAutoListening(false); - // We only stop all streams when the user explicitly stops auto mode. - stopAllStreams(); - setStatus("Click to start listening."); - console.log("Stopping automatic listening."); - }; - - /** - * Starts the MediaRecorder for automatic mode. - */ - const startAutoRecording = (stream) => { - // Prevent starting a new recorder if one is already running - if (mediaRecorderRef.current?.state === "recording") { - console.log("Auto recording is already running, ignoring start request."); - return; - } - - console.log("Starting a new MediaRecorder for automatic mode."); - mediaRecorderRef.current = new MediaRecorder(stream); - audioChunksRef.current = []; - - mediaRecorderRef.current.ondataavailable = (event) => { - audioChunksRef.current.push(event.data); - }; - - mediaRecorderRef.current.onstop = async () => { - // This flag is essential to prevent the VAD loop from re-triggering a recording - // before the current one is fully processed. - isRecordingRef.current = false; - setIsRecording(false); - if (audioChunksRef.current.length > 0) { - // Set isBusy to prevent the user from starting a new request while processing - setIsBusy(true); - setStatus("Transcribing audio..."); - console.log("Auto recording stopped. Processing conversation..."); - const audioBlob = new Blob(audioChunksRef.current, { - type: "audio/wav", - }); - await processConversation(audioBlob); - } else { - console.warn("Auto recording stopped but no audio chunks were collected."); - setIsBusy(false); - setStatus("Listening for voice..."); - } - }; - - mediaRecorderRef.current.start(); - isRecordingRef.current = true; - setIsRecording(true); - setStatus("Recording..."); - console.log("MediaRecorder started for automatic mode."); - }; - - /** - * Stops the MediaRecorder for automatic mode. - */ - const stopAutoRecording = () => { - if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") { - console.log("MediaRecorder stopped for automatic mode."); - mediaRecorderRef.current.stop(); - } - }; - - - /** - * Handles the complete conversation flow: STT -> LLM -> TTS. - * @param {Blob} audioBlob - The recorded audio from the user. - */ - const processConversation = async (audioBlob) => { - console.log("Processing conversation..."); - try { - // Step 1: Filter out audio that is too short to be meaningful - // Get the duration of the audio blob. Assuming a 48kHz sample rate, each byte is ~1/48000th of a second - // For a 'audio/wav' blob, this can be complex. A simple check on blob size is a good heuristic. - // Let's assume an average bitrate and calculate duration. Or even simpler, check the raw size. - // A more robust method would be to analyze the audio header, but for this context, a simple size check is fine. - const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; // Assuming 48kHz, 16-bit mono - if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { - console.log(`Audio is too short (${audioDuration.toFixed(2)}ms), skipping.`); - setStatus("Audio was too short. Please speak a little longer."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; // Exit the function early - } - - setStatus("Transcribing audio..."); - if (audioBlob.size === 0) { - console.warn("Audio blob is empty, skipping STT API call."); - setStatus("Recording stopped, but no audio was captured. Please try again."); - // Update the last request time here so the cooldown still applies. - lastRequestTimeRef.current = Date.now(); - return; - } - - const formData = new FormData(); - formData.append("audio_file", audioBlob, "audio.wav"); - - const sttResponse = await fetch(STT_ENDPOINT, { - method: "POST", - body: formData, - }); - if (!sttResponse.ok) { - console.error(`STT API failed with status: ${sttResponse.status}`); - throw new Error("STT API failed"); - } - const sttResult = await sttResponse.json(); - const userText = sttResult.transcript; - console.log(`STT transcript received: "${userText}"`); - addMessage(userText, true); - - // Step 2: Text-to-Text (LLM) - setStatus("AI is thinking..."); - const llmResponse = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ prompt: userText, model: "gemini" }), - }); - if (!llmResponse.ok) { - console.error(`LLM API failed with status: ${llmResponse.status}`); - throw new Error("LLM API failed"); - } - const llmResult = await llmResponse.json(); - const aiText = llmResult.answer; - console.log(`LLM response received: "${aiText}"`); - addMessage(aiText, false); - - // Step 3: Text-to-Speech (TTS) - await playStreamingAudio(aiText); - } catch (error) { - console.error("Conversation processing failed:", error); - setStatus(`Error: ${error.message}`); - setErrorMessage(`An error occurred: ${error.message}`); - setShowErrorModal(true); - } finally { - setIsBusy(false); - // This is the ideal place to update the last request time - lastRequestTimeRef.current = Date.now(); - if (!isAutoMode) { - setStatus("Click the microphone to start recording."); - // Ensure refs are nullified after processing for a clean state - mediaRecorderRef.current = null; - streamRef.current = null; - } else if (isAutoMode && isAutoListening) { - setStatus("Listening for voice..."); - } else { // This case is when auto mode is on, but we stopped listening. - setStatus("Click to start listening."); - } - } - }; - - /** - * Toggles the recording state based on the current mode. - */ - const handleMicClick = () => { - stopAllPlayingAudio(); // Interrupt any ongoing TTS playback immediately - - // Prevent new actions if the app is busy processing a request - if (isBusy) { - console.log("App is busy, ignoring mic click."); - return; - } - - if (isAutoMode) { - if (isAutoListening) { - stopAutoListening(); - } else { - startAutoListening(); - } - } else { // Manual Mode - if (isRecording) { - stopManualRecording(); - } else { - startManualRecording(); - } - } - }; - - // FIX: The microphoneButtonState variable was assigned but never used. - // The logic has been moved directly into the JSX below. - // The previous variable declaration has been removed. - - const micButtonColorClass = isRecording - ? "bg-red-600 hover:bg-red-700 active:bg-red-800" - : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; - - return ( -
-
- {/* Header */} -
-

Voice Chat Assistant

-

- Ask me anything and I'll respond! -

-
- - {/* Chat History */} -
- {chatHistory.map((message, index) => ( -
-
- {message.text} -
-
- ))} -
- - {/* Status and Controls */} -
-
- {status} -
- -
- {/* Microphone Button */} - -
- - {/* Mode Toggle */} -
- -
- -
-
- - {/* Error Modal */} - {showErrorModal && ( -
-
-

Error

-

{errorMessage}

- -
-
- )} -
- ); -}; +function App() { + return ; +} export default App; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/ChatWindow.js b/ui/tts-client-app/src/components/ChatWindow.js new file mode 100644 index 0000000..28397a4 --- /dev/null +++ b/ui/tts-client-app/src/components/ChatWindow.js @@ -0,0 +1,29 @@ +// src/components/ChatWindow.js +import React from "react"; + +const ChatWindow = ({ chatHistory }) => { + return ( +
+ {chatHistory.map((message, index) => ( +
+
+

{message.text}

+
+
+ ))} +
+ ); +}; + +export default ChatWindow; \ No newline at end of file diff --git a/ui/tts-client-app/src/components/Controls.js b/ui/tts-client-app/src/components/Controls.js new file mode 100644 index 0000000..f78e2e1 --- /dev/null +++ b/ui/tts-client-app/src/components/Controls.js @@ -0,0 +1,57 @@ +// src/components/Controls.js +import React from "react"; +import { FaMicrophone, FaRegStopCircle, FaCog } from "react-icons/fa"; + +const Controls = ({ + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + onMicClick, + onToggleAutoMode, +}) => { + const micButtonColorClass = isRecording + ? "bg-red-600 hover:bg-red-700 active:bg-red-800" + : "bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800"; + + const micButtonState = + isAutoMode && isAutoListening ? isAutoListening : isRecording; + + return ( +
+
+ {status} +
+
+ + +
+ + +
+
+
+ ); +}; + +export default Controls; \ No newline at end of file diff --git a/ui/tts-client-app/src/hooks/useVoiceChat.js b/ui/tts-client-app/src/hooks/useVoiceChat.js new file mode 100644 index 0000000..4c74745 --- /dev/null +++ b/ui/tts-client-app/src/hooks/useVoiceChat.js @@ -0,0 +1,394 @@ +// src/hooks/useVoiceChat.js + +// This file is a custom React hook that contains all the stateful logic +// and side effects for the voice chat application. + +import { useState, useRef, useEffect } from "react"; +import { + createSession, + transcribeAudio, + chatWithAI, + streamSpeech, +} from "../services/apiService"; +import { + stopAllPlayingAudio, + stopAllMediaStreams, + resampleBuffer, +} from "../services/audioUtils"; + +// Constants for Voice Activity Detection and timing +const VAD_THRESHOLD = 0.01; +const VAD_SILENCE_DURATION = 2500; +const MINIMUM_AUDIO_DURATION_MS = 500; +const AUTO_MODE_COOLDOWN_MS = 3000; + +const useVoiceChat = ({ chatContainerRef }) => { + const [chatHistory, setChatHistory] = useState([ + { + text: "Hello! I'm an AI assistant. How can I help you today?", + isUser: false, + }, + ]); + const [status, setStatus] = useState("Click the microphone to start recording."); + const [isBusy, setIsBusy] = useState(false); + const [isRecording, setIsRecording] = useState(false); + const [showErrorModal, setShowErrorModal] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const [sessionId, setSessionId] = useState(null); + const [isAutoMode, setIsAutoMode] = useState(false); + const [isAutoListening, setIsAutoListening] = useState(false); + + // All refs must be declared here, inside the custom hook. + const mediaRecorderRef = useRef(null); + const audioChunksRef = useRef([]); + const audioContextRef = useRef(null); + const playbackTimeRef = useRef(0); + const isRecordingRef = useRef(false); + const playingSourcesRef = useRef([]); + const vadStreamRef = useRef(null); + const scriptProcessorRef = useRef(null); + const silenceTimeoutRef = useRef(null); + const lastRequestTimeRef = useRef(0); + const streamRef = useRef(null); + + // --- Initial Session Creation Effect --- + useEffect(() => { + const startSession = async () => { + setIsBusy(true); + setStatus("Starting new chat session..."); + try { + const session = await createSession(); + setSessionId(session.id); + console.log(`Session created with ID: ${session.id}`); + setStatus("Click the microphone to start recording."); + } catch (err) { + console.error("Error creating session:", err); + setStatus(`Error: Could not start session. ${err.message}`); + setErrorMessage(`Failed to create a chat session: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + } + }; + startSession(); + + return () => { + // Pass the refs to the utility function here + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + }; + }, []); + + // New useEffect hook to automatically scroll to the bottom of the chat history + // The fix: `chatContainerRef` is now included in the dependency array. + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [chatHistory, chatContainerRef]); + + const addMessage = (text, isUser) => { + setChatHistory((prevHistory) => [...prevHistory, { text, isUser }]); + }; + + /** + * Plays a stream of audio chunks using the Web Audio API by fetching them from the API. + * This is the orchestrator that uses the stateless streamSpeech API function. + * @param {string} text - The text to be synthesized by the TTS service. + */ + const playStreamingAudio = async (text) => { + setIsBusy(true); + setStatus("Streaming audio..."); + // Pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + + try { + if (!audioContextRef.current) { + audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); + playbackTimeRef.current = audioContextRef.current.currentTime; + } + + const audioContext = audioContextRef.current; + + const onChunkReceived = (rawFloat32Data) => { + // This is the callback that receives processed audio data from apiService. + // It's responsible for using the Web Audio API to play the sound. + const float32Resampled = resampleBuffer( + rawFloat32Data, + 24000, // The model's sample rate is hardcoded to 24000 + audioContext.sampleRate + ); + const audioBuffer = audioContext.createBuffer( + 1, + float32Resampled.length, + audioContext.sampleRate + ); + audioBuffer.copyToChannel(float32Resampled, 0); + + const source = audioContext.createBufferSource(); + source.buffer = audioBuffer; + source.connect(audioContext.destination); + + const currentTime = audioContext.currentTime; + const startTime = Math.max(playbackTimeRef.current, currentTime); + + source.start(startTime); + playbackTimeRef.current = startTime + audioBuffer.duration; + + playingSourcesRef.current.push(source); + source.onended = () => { + playingSourcesRef.current = playingSourcesRef.current.filter( + (s) => s !== source + ); + }; + }; + + const onStreamDone = () => { + // This callback is triggered when the stream finishes. + console.log("TTS Stream complete."); + }; + + // Call the stateless API function, passing the UI-related callbacks + await streamSpeech(text, onChunkReceived, onStreamDone); + + } catch (err) { + console.error("Failed to stream speech:", err); + setStatus(`Error: Failed to stream speech. ${err.message}`); + setErrorMessage(`Failed to stream speech: ${err.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + } else { + setStatus("Click to start listening."); + } + } + }; + + + const processConversation = async (audioBlob) => { + console.log("Processing conversation..."); + try { + const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; + if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { + console.log(`Audio too short (${audioDuration.toFixed(2)}ms), skipping.`); + setStatus("Audio was too short. Please speak a little longer."); + lastRequestTimeRef.current = Date.now(); + return; + } + if (audioBlob.size === 0) { + console.warn("Audio blob is empty, skipping STT API call."); + setStatus("Recording stopped, but no audio was captured. Please try again."); + lastRequestTimeRef.current = Date.now(); + return; + } + + setStatus("Transcribing audio..."); + const userText = await transcribeAudio(audioBlob); + addMessage(userText, true); + + setStatus("AI is thinking..."); + const aiText = await chatWithAI(sessionId, userText); + addMessage(aiText, false); + + await playStreamingAudio(aiText); + } catch (error) { + console.error("Conversation processing failed:", error); + setStatus(`Error: ${error.message}`); + setErrorMessage(`An error occurred: ${error.message}`); + setShowErrorModal(true); + } finally { + setIsBusy(false); + lastRequestTimeRef.current = Date.now(); + // This is the main correction: only stop streams if not in auto-listening mode + if (!isAutoMode) { + setStatus("Click the microphone to start recording."); + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + } else if (isAutoMode && isAutoListening) { + setStatus("Listening for voice..."); + } else { + setStatus("Click to start listening."); + } + } + }; + + const startManualRecording = async () => { + if (isRecording) return; + + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + streamRef.current = stream; + mediaRecorderRef.current = new MediaRecorder(stream); + mediaRecorderRef.current.start(); + audioChunksRef.current = []; + + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + + mediaRecorderRef.current.onstop = async () => { + if (streamRef.current) { + streamRef.current.getTracks().forEach(track => track.stop()); + streamRef.current = null; + } + mediaRecorderRef.current = null; + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + }; + setIsRecording(true); + isRecordingRef.current = true; + setStatus("Recording... Click to stop."); + } catch (err) { + console.error("Error accessing microphone:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopManualRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + setIsBusy(true); + setIsRecording(false); + mediaRecorderRef.current.stop(); + } + }; + + const startAutoListening = async () => { + try { + if (!sessionId) { + setErrorMessage("Please wait for the chat session to be initialized."); + setShowErrorModal(true); + return; + } + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + vadStreamRef.current = stream; + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const source = audioContext.createMediaStreamSource(stream); + const bufferSize = 4096; + const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); + scriptProcessorRef.current = scriptProcessor; + source.connect(scriptProcessor); + scriptProcessor.connect(audioContext.destination); + + scriptProcessor.onaudioprocess = (event) => { + const inputBuffer = event.inputBuffer.getChannelData(0); + let sum = 0.0; + for (let i = 0; i < inputBuffer.length; i++) { + sum += inputBuffer[i] * inputBuffer[i]; + } + const volume = Math.sqrt(sum / inputBuffer.length); + const isVoiceDetected = volume > VAD_THRESHOLD; + const timeSinceLastRequest = Date.now() - lastRequestTimeRef.current; + const isCooldownPassed = timeSinceLastRequest > AUTO_MODE_COOLDOWN_MS; + + if (isVoiceDetected) { + if (silenceTimeoutRef.current) { + clearTimeout(silenceTimeoutRef.current); + silenceTimeoutRef.current = null; + } + if (!isRecordingRef.current && isCooldownPassed) { + startAutoRecording(stream); + } + } else if (isRecordingRef.current) { + if (!silenceTimeoutRef.current) { + silenceTimeoutRef.current = setTimeout(() => { + stopAutoRecording(); + }, VAD_SILENCE_DURATION); + } + } + }; + setIsAutoListening(true); + setStatus("Listening for voice..."); + } catch (err) { + console.error("Error accessing microphone for VAD:", err); + setStatus("Error: Cannot access microphone."); + setErrorMessage("Microphone access has been denied. Please enable it."); + setShowErrorModal(true); + } + }; + + const stopAutoListening = () => { + setIsAutoListening(false); + // Pass the refs here to the utility function + stopAllMediaStreams(vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef); + setStatus("Click to start listening."); + }; + + const startAutoRecording = (stream) => { + if (mediaRecorderRef.current?.state === "recording") return; + mediaRecorderRef.current = new MediaRecorder(stream); + audioChunksRef.current = []; + mediaRecorderRef.current.ondataavailable = (event) => { + audioChunksRef.current.push(event.data); + }; + mediaRecorderRef.current.onstop = async () => { + isRecordingRef.current = false; + setIsRecording(false); + if (audioChunksRef.current.length > 0) { + setIsBusy(true); + setStatus("Transcribing audio..."); + const audioBlob = new Blob(audioChunksRef.current, { type: "audio/wav" }); + await processConversation(audioBlob); + } else { + setIsBusy(false); + setStatus("Listening for voice..."); + } + }; + mediaRecorderRef.current.start(); + isRecordingRef.current = true; + setIsRecording(true); + setStatus("Recording..."); + }; + + const stopAutoRecording = () => { + if (mediaRecorderRef.current?.state === "recording") { + mediaRecorderRef.current.stop(); + } + }; + + const handleMicClick = () => { + // Correctly pass the refs to the utility function + stopAllPlayingAudio(playingSourcesRef, audioContextRef, playbackTimeRef); + if (isBusy) return; + + if (isAutoMode) { + if (isAutoListening) { + stopAutoListening(); + } else { + startAutoListening(); + } + } else { + if (isRecording) { + stopManualRecording(); + } else { + startManualRecording(); + } + } + }; + + return { + chatHistory, + status, + isBusy, + isRecording, + isAutoMode, + isAutoListening, + sessionId, + showErrorModal, + errorMessage, + setIsAutoMode, + handleMicClick, + setShowErrorModal, + }; +}; + +export default useVoiceChat; diff --git a/ui/tts-client-app/src/index.css b/ui/tts-client-app/src/index.css index ec2585e..bd6213e 100644 --- a/ui/tts-client-app/src/index.css +++ b/ui/tts-client-app/src/index.css @@ -1,13 +1,3 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/ui/tts-client-app/src/pages/HomePage.js b/ui/tts-client-app/src/pages/HomePage.js new file mode 100644 index 0000000..648a249 --- /dev/null +++ b/ui/tts-client-app/src/pages/HomePage.js @@ -0,0 +1,75 @@ +// src/pages/HomePage.js +import React, { useRef, useEffect } from "react"; +import ChatWindow from "../components/ChatWindow"; +import Controls from "../components/Controls.js"; +import useVoiceChat from "../hooks/useVoiceChat"; + +const HomePage = () => { + // Chat container ref for scrolling + const chatContainerRef = useRef(null); + + // Custom hook for all voice chat logic + // The 'sessionId' variable has been removed from the destructuring + // because it is not used in this component. + const { + chatHistory, + status, + isRecording, + isBusy, + isAutoMode, + isAutoListening, + showErrorModal, + errorMessage, + setIsAutoMode, + handleMicClick, + setShowErrorModal, + } = useVoiceChat({ chatContainerRef }); + + // Effect to automatically scroll to the bottom of the chat history + // This useEffect is duplicated from the custom hook; it might be redundant, + // but it's kept here to match the provided code's logic. + useEffect(() => { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [chatHistory]); + + const toggleAutoMode = () => { + setIsAutoMode(!isAutoMode); + }; + + return ( +
+
+ +
+ + + + {showErrorModal && ( +
+
+

Error

+

{errorMessage}

+ +
+
+ )} +
+ ); +}; + +export default HomePage; diff --git a/ui/tts-client-app/src/services/apiService.js b/ui/tts-client-app/src/services/apiService.js new file mode 100644 index 0000000..ed4c4ec --- /dev/null +++ b/ui/tts-client-app/src/services/apiService.js @@ -0,0 +1,126 @@ +// src/services/apiService.js + +// This file handles all communication with your API endpoints. +// It is designed to be stateless and does not use any React hooks. + +import { convertPcmToFloat32 } from "./audioUtils"; + +// Please replace with your actual endpoints +const STT_ENDPOINT = "http://localhost:8001/stt/transcribe"; +const SESSIONS_CREATE_ENDPOINT = "http://localhost:8001/sessions"; +const SESSIONS_CHAT_ENDPOINT = (id) => `http://localhost:8001/sessions/${id}/chat`; +const TTS_ENDPOINT = "http://localhost:8001/speech"; + +/** + * Creates a new chat session with a unique user ID. + * @returns {Promise} The session object from the API response. + */ +export const createSession = async () => { + const generatedUserId = crypto.randomUUID(); + const response = await fetch(SESSIONS_CREATE_ENDPOINT, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ user_id: generatedUserId }), + }); + if (!response.ok) { + throw new Error(`Failed to create session. Status: ${response.status}`); + } + return await response.json(); +}; + +/** + * Sends an audio blob to the STT endpoint for transcription. + * @param {Blob} audioBlob - The recorded audio data. + * @returns {Promise} The transcribed text. + */ +export const transcribeAudio = async (audioBlob) => { + const formData = new FormData(); + formData.append("audio_file", audioBlob, "audio.wav"); + const response = await fetch(STT_ENDPOINT, { + method: "POST", + body: formData, + }); + if (!response.ok) { + throw new Error("STT API failed"); + } + const result = await response.json(); + return result.transcript; +}; + +/** + * Sends a text prompt to the LLM endpoint and gets a text response. + * @param {string} sessionId - The current chat session ID. + * @param {string} prompt - The user's text prompt. + * @returns {Promise} The AI's text response. + */ +export const chatWithAI = async (sessionId, prompt) => { + const response = await fetch(SESSIONS_CHAT_ENDPOINT(sessionId), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ prompt: prompt, model: "gemini" }), + }); + if (!response.ok) { + throw new Error("LLM API failed"); + } + const result = await response.json(); + return result.answer; +}; + +/** + * Streams speech from the TTS endpoint and processes each chunk. + * It uses a callback to pass the processed audio data back to the caller. + * @param {string} text - The text to be synthesized. + * @param {function(Float32Array): void} onData - Callback for each audio chunk. + * @param {function(): void} onDone - Callback to execute when the stream is finished. + * @returns {Promise} + */ +export const streamSpeech = async (text, onData, onDone) => { + try { + const url = `${TTS_ENDPOINT}?stream=true&as_wav=false`; + + const response = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ text }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const reader = response.body.getReader(); + let leftover = new Uint8Array(0); + + while (true) { + const { done, value: chunk } = await reader.read(); + if (done) { + if (leftover.length > 0) { + console.warn("Leftover bytes discarded:", leftover.length); + } + break; + } + + let combined = new Uint8Array(leftover.length + chunk.length); + combined.set(leftover); + combined.set(chunk, leftover.length); + + let length = combined.length; + if (length % 2 !== 0) { + length -= 1; + } + + const toConvert = combined.slice(0, length); + leftover = combined.slice(length); + const float32Raw = convertPcmToFloat32(toConvert); + + // Pass the raw float32 data to the caller for resampling + onData(float32Raw); + } + } catch (error) { + console.error("Failed to stream speech:", error); + throw error; + } finally { + // We call the onDone callback to let the hook know the stream has ended. + onDone(); + } +}; diff --git a/ui/tts-client-app/src/services/audioUtils.js b/ui/tts-client-app/src/services/audioUtils.js new file mode 100644 index 0000000..0818779 --- /dev/null +++ b/ui/tts-client-app/src/services/audioUtils.js @@ -0,0 +1,66 @@ +// src/services/audioUtils.js + +// This file should not import React or use any hooks. + +export const stopAllPlayingAudio = (playingSourcesRef, audioContextRef, playbackTimeRef) => { + if (audioContextRef.current) { + playingSourcesRef.current.forEach((source) => { + try { + source.stop(); + } catch (e) { + // Ignore errors from stopping already stopped sources + } + }); + playingSourcesRef.current = []; + playbackTimeRef.current = audioContextRef.current.currentTime; + console.log("All playing audio has been stopped."); + } +}; + +export const stopAllMediaStreams = (vadStreamRef, mediaRecorderRef, scriptProcessorRef, streamRef) => { + if (vadStreamRef.current) { + vadStreamRef.current.getTracks().forEach((track) => track.stop()); + vadStreamRef.current = null; + } + if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") { + mediaRecorderRef.current.stop(); + } + if (scriptProcessorRef.current) { + scriptProcessorRef.current.disconnect(); + scriptProcessorRef.current = null; + } + if (streamRef.current) { + streamRef.current.getTracks().forEach(track => track.stop()); + streamRef.current = null; + } + console.log("All audio streams and timers have been stopped."); +}; + +export const resampleBuffer = (buffer, srcRate, dstRate) => { + if (srcRate === dstRate) return buffer; + const ratio = srcRate / dstRate; + const newLength = Math.round(buffer.length / ratio); + const resampled = new Float32Array(newLength); + for (let i = 0; i < newLength; i++) { + const srcIndex = i * ratio; + const srcIndexFloor = Math.floor(srcIndex); + const srcIndexCeil = Math.min(srcIndexFloor + 1, buffer.length - 1); + const weight = srcIndex - srcIndexFloor; + resampled[i] = + buffer[srcIndexFloor] * (1 - weight) + buffer[srcIndexCeil] * weight; + } + return resampled; +}; + +export const convertPcmToFloat32 = (pcmBytes) => { + const int16Array = new Int16Array( + pcmBytes.buffer, + pcmBytes.byteOffset, + pcmBytes.byteLength / 2 + ); + const float32Array = new Float32Array(int16Array.length); + for (let i = 0; i < int16Array.length; i++) { + float32Array[i] = int16Array[i] / 32768; + } + return float32Array; +}; \ No newline at end of file diff --git a/ui/tts-client-app/src/styles/main.css b/ui/tts-client-app/src/styles/main.css new file mode 100644 index 0000000..0167e97 --- /dev/null +++ b/ui/tts-client-app/src/styles/main.css @@ -0,0 +1,11 @@ +/* src/styles/main.css */ +/* Content from your original App.css and index.css goes here */ +/* For example: +@tailwind base; +@tailwind components; +@tailwind utilities; + +.App { + text-align: center; +} +*/ \ No newline at end of file diff --git a/ui/tts-client-app/tailwind.config.js b/ui/tts-client-app/tailwind.config.js new file mode 100644 index 0000000..3fbc811 --- /dev/null +++ b/ui/tts-client-app/tailwind.config.js @@ -0,0 +1,11 @@ +module.exports = { + content: [ + "./src/**/*.{js,jsx,ts,tsx}", // React files + "./public/index.html" // (optional, if using CRA) + ], + theme: { + extend: {}, + }, + plugins: [], + }; + \ No newline at end of file