Commit ·
d5079dd
0
Parent(s):
feat: initial Reachy Mini Vibe Coder space
Browse files- Hono backend (Vercel AI SDK + OpenRouter) with HF OAuth middleware
- React frontend with Monaco editor, virtual FS, sandboxed preview iframe
- Embedded build-reachy-voice-app skill loaded at runtime for the agent
- /api/publish route streams createRepo + uploadFiles to a new HF Space
- Docker runtime with envsubst injection of OAuth vars at container start
Made-with: Cursor
- .dockerignore +7 -0
- .gitignore +11 -0
- Dockerfile +67 -0
- README.md +133 -0
- backend/.env.example +12 -0
- backend/package-lock.json +1032 -0
- backend/package.json +26 -0
- backend/src/agent/skill-loader.ts +91 -0
- backend/src/agent/skills/build-reachy-voice-app/SKILL.md +92 -0
- backend/src/agent/skills/build-reachy-voice-app/architecture.md +108 -0
- backend/src/agent/skills/build-reachy-voice-app/deployment.md +196 -0
- backend/src/agent/skills/build-reachy-voice-app/motion-agents.md +129 -0
- backend/src/agent/skills/build-reachy-voice-app/protocols.md +132 -0
- backend/src/agent/skills/build-reachy-voice-app/webrtc-tuning.md +162 -0
- backend/src/agent/system-prompt.ts +114 -0
- backend/src/agent/tools.ts +115 -0
- backend/src/auth.ts +99 -0
- backend/src/routes/chat.ts +87 -0
- backend/src/routes/models.ts +8 -0
- backend/src/routes/publish.ts +144 -0
- backend/src/server.ts +104 -0
- backend/src/types.ts +8 -0
- backend/tsconfig.json +19 -0
- docker-entrypoint.d/10-inject-hf-vars.sh +37 -0
- docker-entrypoint.d/entrypoint.sh +17 -0
- frontend/index.html +49 -0
- frontend/package-lock.json +2220 -0
- frontend/package.json +29 -0
- frontend/src/App.tsx +164 -0
- frontend/src/components/ChatPanel.tsx +219 -0
- frontend/src/components/CodeEditor.tsx +113 -0
- frontend/src/components/HfLogo.tsx +31 -0
- frontend/src/components/PreviewFrame.tsx +104 -0
- frontend/src/components/PublishDialog.tsx +209 -0
- frontend/src/components/SignInScreen.tsx +47 -0
- frontend/src/components/TopBar.tsx +94 -0
- frontend/src/hooks/useAgentChat.ts +186 -0
- frontend/src/hooks/useHfAuth.ts +142 -0
- frontend/src/hooks/usePublish.ts +155 -0
- frontend/src/hooks/useVirtualFS.ts +108 -0
- frontend/src/main.tsx +10 -0
- frontend/src/styles.css +798 -0
- frontend/src/vite-env.d.ts +20 -0
- frontend/tsconfig.app.json +22 -0
- frontend/tsconfig.json +7 -0
- frontend/tsconfig.node.json +15 -0
- frontend/vite.config.ts +24 -0
.dockerignore
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
**/node_modules
|
| 2 |
+
**/dist
|
| 3 |
+
**/.env
|
| 4 |
+
**/.env.local
|
| 5 |
+
.git
|
| 6 |
+
.gitignore
|
| 7 |
+
.DS_Store
|
.gitignore
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules/
|
| 2 |
+
dist/
|
| 3 |
+
.env
|
| 4 |
+
.env.local
|
| 5 |
+
*.log
|
| 6 |
+
*.tsbuildinfo
|
| 7 |
+
.DS_Store
|
| 8 |
+
backend/dist/
|
| 9 |
+
frontend/dist/
|
| 10 |
+
.vscode/
|
| 11 |
+
.idea/
|
Dockerfile
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# syntax=docker/dockerfile:1.7
|
| 2 |
+
# ────────────────────────────────────────────────────────────────────────────
|
| 3 |
+
# Stage 1 · build the React frontend
|
| 4 |
+
# ────────────────────────────────────────────────────────────────────────────
|
| 5 |
+
FROM node:20-slim AS frontend-builder
|
| 6 |
+
|
| 7 |
+
WORKDIR /build/frontend
|
| 8 |
+
|
| 9 |
+
COPY frontend/package.json frontend/package-lock.json* ./
|
| 10 |
+
RUN npm ci || npm install
|
| 11 |
+
|
| 12 |
+
COPY frontend/tsconfig*.json frontend/vite.config.ts frontend/index.html ./
|
| 13 |
+
COPY frontend/src ./src
|
| 14 |
+
|
| 15 |
+
RUN npm run build
|
| 16 |
+
|
| 17 |
+
# ────────────────────────────────────────────────────────────────────────────
|
| 18 |
+
# Stage 2 · build the Hono backend
|
| 19 |
+
# ────────────────────────────────────────────────────────────────────────────
|
| 20 |
+
FROM node:20-slim AS backend-builder
|
| 21 |
+
|
| 22 |
+
WORKDIR /build/backend
|
| 23 |
+
|
| 24 |
+
COPY backend/package.json backend/package-lock.json* ./
|
| 25 |
+
RUN npm ci || npm install
|
| 26 |
+
|
| 27 |
+
COPY backend/tsconfig.json ./
|
| 28 |
+
COPY backend/src ./src
|
| 29 |
+
|
| 30 |
+
RUN npm run build && npm prune --omit=dev
|
| 31 |
+
|
| 32 |
+
# ────────────────────────────────────────────────────────────────────────────
|
| 33 |
+
# Stage 3 · runtime (node + built assets only)
|
| 34 |
+
# ────────────────────────────────────────────────────────────────────────────
|
| 35 |
+
FROM node:20-slim AS runtime
|
| 36 |
+
|
| 37 |
+
# `gettext-base` gives us `envsubst` for the OAuth vars entrypoint.
|
| 38 |
+
RUN apt-get update \
|
| 39 |
+
&& apt-get install -y --no-install-recommends gettext-base tini \
|
| 40 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 41 |
+
|
| 42 |
+
WORKDIR /app
|
| 43 |
+
|
| 44 |
+
# Backend runtime (compiled JS + production node_modules)
|
| 45 |
+
COPY --from=backend-builder /build/backend/dist /app/backend/dist
|
| 46 |
+
COPY --from=backend-builder /build/backend/node_modules /app/backend/node_modules
|
| 47 |
+
COPY --from=backend-builder /build/backend/package.json /app/backend/package.json
|
| 48 |
+
|
| 49 |
+
# Skills are loaded from source markdown at runtime (skill-loader.ts reads
|
| 50 |
+
# them from disk). Ship them alongside the compiled JS.
|
| 51 |
+
COPY backend/src/agent/skills /app/backend/dist/agent/skills
|
| 52 |
+
|
| 53 |
+
# Frontend static build - server.ts detects this folder automatically.
|
| 54 |
+
COPY --from=frontend-builder /build/frontend/dist /app/frontend/dist
|
| 55 |
+
|
| 56 |
+
# HF OAuth variables entrypoint
|
| 57 |
+
COPY docker-entrypoint.d /app/docker-entrypoint.d
|
| 58 |
+
RUN chmod +x /app/docker-entrypoint.d/*.sh
|
| 59 |
+
|
| 60 |
+
ENV NODE_ENV=production \
|
| 61 |
+
PORT=7860 \
|
| 62 |
+
REQUIRE_HF_AUTH=1
|
| 63 |
+
|
| 64 |
+
EXPOSE 7860
|
| 65 |
+
|
| 66 |
+
ENTRYPOINT ["/usr/bin/tini", "--", "/app/docker-entrypoint.d/entrypoint.sh"]
|
| 67 |
+
CMD ["node", "/app/backend/dist/server.js"]
|
README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Reachy Mini Vibe Coder
|
| 3 |
+
emoji: 🤖
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: pink
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
hf_oauth: true
|
| 10 |
+
hf_oauth_scopes:
|
| 11 |
+
- openid
|
| 12 |
+
- profile
|
| 13 |
+
- write-repos
|
| 14 |
+
- manage-repos
|
| 15 |
+
short_description: Vibe-code Reachy Mini voice apps and publish them to HF Spaces.
|
| 16 |
+
tags:
|
| 17 |
+
- reachy_mini
|
| 18 |
+
- agent
|
| 19 |
+
- vibe-coding
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
# Reachy Mini · Vibe Coder
|
| 23 |
+
|
| 24 |
+
A conversational agent that helps you vibe-code browser-based voice apps for
|
| 25 |
+
Reachy Mini robots, then publishes them as Hugging Face Spaces in one click.
|
| 26 |
+
|
| 27 |
+
The agent is grounded in the `build-reachy-voice-app` skill, which documents
|
| 28 |
+
the full architecture (WebRTC 2-hop audio bridge, OpenAI Realtime event
|
| 29 |
+
lifecycle, motion agents, HF OAuth injection, Docker + Nginx deploy). Ask for
|
| 30 |
+
an app, the agent produces the `index.html`, `main.ts`, `style.css`, `Dockerfile`
|
| 31 |
+
and `README.md` that you need, lets you preview them live in a sandboxed
|
| 32 |
+
iframe, and publishes the result to a new HF Space under your account.
|
| 33 |
+
|
| 34 |
+
## Architecture
|
| 35 |
+
|
| 36 |
+
```
|
| 37 |
+
frontend/ Vite + React + @ai-sdk/react
|
| 38 |
+
src/
|
| 39 |
+
components/
|
| 40 |
+
TopBar.tsx HF user avatar, model picker, Publish button
|
| 41 |
+
ChatPanel.tsx Agent conversation
|
| 42 |
+
CodeEditor.tsx Monaco, one tab per virtual-FS file
|
| 43 |
+
PreviewFrame.tsx Sandboxed iframe running the generated app
|
| 44 |
+
hooks/
|
| 45 |
+
useAgentChat.ts useChat + client-side tool execution
|
| 46 |
+
useVirtualFS.ts In-memory file tree
|
| 47 |
+
useHfAuth.ts HF OAuth (hfSpacesOauthLogin)
|
| 48 |
+
|
| 49 |
+
backend/ Hono + Vercel AI SDK (ai + openrouter)
|
| 50 |
+
src/
|
| 51 |
+
server.ts Hono app, mounts /api + serves static frontend
|
| 52 |
+
auth.ts Verifies HF access tokens on /api/*
|
| 53 |
+
agent/
|
| 54 |
+
chat.ts streamText() with editor tools + skill tools
|
| 55 |
+
tools.ts writeFile, editFile, deleteFile, readFile, readSkillDoc
|
| 56 |
+
system-prompt.ts Base prompt + injected SKILL.md
|
| 57 |
+
skill-loader.ts Reads skills/ at runtime
|
| 58 |
+
skills/
|
| 59 |
+
build-reachy-voice-app/
|
| 60 |
+
SKILL.md (copied from reachy-mini-project-folder/.cursor/skills/)
|
| 61 |
+
architecture.md
|
| 62 |
+
deployment.md
|
| 63 |
+
motion-agents.md
|
| 64 |
+
protocols.md
|
| 65 |
+
webrtc-tuning.md
|
| 66 |
+
routes/
|
| 67 |
+
publish.ts @huggingface/hub createRepo + uploadFiles
|
| 68 |
+
|
| 69 |
+
docker-entrypoint.d/
|
| 70 |
+
10-inject-hf-vars.sh envsubst OAUTH_CLIENT_ID / SPACE_* into dist/index.html
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
## Local development
|
| 74 |
+
|
| 75 |
+
### Prerequisites
|
| 76 |
+
|
| 77 |
+
- Node.js 20+
|
| 78 |
+
- An [OpenRouter API key](https://openrouter.ai/keys) (backend)
|
| 79 |
+
- A Hugging Face OAuth app for local dev (see below)
|
| 80 |
+
|
| 81 |
+
### HF OAuth for localhost
|
| 82 |
+
|
| 83 |
+
On a Space the OAuth client ID is injected automatically. For local dev:
|
| 84 |
+
|
| 85 |
+
1. Register an OAuth app at <https://huggingface.co/settings/applications/new>
|
| 86 |
+
2. Fill in:
|
| 87 |
+
- **Homepage URL**: `http://localhost:5173`
|
| 88 |
+
- **Scopes**: `openid`, `profile`, `write-repos`, `manage-repos`
|
| 89 |
+
- **Redirect URIs**: `http://localhost:5173`
|
| 90 |
+
3. Copy the client ID into `frontend/.env.local`:
|
| 91 |
+
```
|
| 92 |
+
VITE_HF_OAUTH_CLIENT_ID=xxxx-xxxx-xxxx
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
### Run
|
| 96 |
+
|
| 97 |
+
```bash
|
| 98 |
+
# Terminal 1 - backend
|
| 99 |
+
cd backend
|
| 100 |
+
cp .env.example .env # fill OPENROUTER_API_KEY
|
| 101 |
+
npm install
|
| 102 |
+
npm run dev
|
| 103 |
+
|
| 104 |
+
# Terminal 2 - frontend
|
| 105 |
+
cd frontend
|
| 106 |
+
npm install
|
| 107 |
+
npm run dev
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
Open <http://localhost:5173>.
|
| 111 |
+
|
| 112 |
+
## Deploy to HF Spaces
|
| 113 |
+
|
| 114 |
+
Push this repo to a Space with `sdk: docker`. The `docker-entrypoint.d`
|
| 115 |
+
script injects the OAuth credentials from the Space runtime into the built
|
| 116 |
+
`index.html` at container start.
|
| 117 |
+
|
| 118 |
+
```bash
|
| 119 |
+
git remote add space https://huggingface.co/spaces/<your-username>/reachy-mini-vibe-coder
|
| 120 |
+
git push space main
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
## Environment variables (Space secrets)
|
| 124 |
+
|
| 125 |
+
| Variable | Required | Description |
|
| 126 |
+
|----------|----------|-------------|
|
| 127 |
+
| `OPENROUTER_API_KEY` | yes | Powers the agent |
|
| 128 |
+
| `OPENROUTER_MODEL` | no | Default `anthropic/claude-sonnet-4-5` |
|
| 129 |
+
|
| 130 |
+
## License
|
| 131 |
+
|
| 132 |
+
MIT. The embedded skill files are derived from the `reachy-mini-project-folder`
|
| 133 |
+
internal docs; the underlying SDKs keep their own licenses.
|
backend/.env.example
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenRouter API key used by the agent
|
| 2 |
+
OPENROUTER_API_KEY=
|
| 3 |
+
|
| 4 |
+
# Default model (can be overridden per-request from the frontend)
|
| 5 |
+
OPENROUTER_MODEL=anthropic/claude-sonnet-4-5
|
| 6 |
+
|
| 7 |
+
# Port Hono listens on (HF Spaces expects 7860)
|
| 8 |
+
PORT=7860
|
| 9 |
+
|
| 10 |
+
# When set, /api/* endpoints require a valid HF access token in the
|
| 11 |
+
# Authorization header. Leave unset during early local dev.
|
| 12 |
+
REQUIRE_HF_AUTH=1
|
backend/package-lock.json
ADDED
|
@@ -0,0 +1,1032 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "reachy-mini-vibe-coder-backend",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "reachy-mini-vibe-coder-backend",
|
| 9 |
+
"version": "0.1.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"@ai-sdk/react": "^2.0.104",
|
| 12 |
+
"@hono/node-server": "^1.19.6",
|
| 13 |
+
"@huggingface/hub": "^2.11.0",
|
| 14 |
+
"@openrouter/ai-sdk-provider": "^2.5.1",
|
| 15 |
+
"ai": "^6.0.158",
|
| 16 |
+
"dotenv": "^17.4.1",
|
| 17 |
+
"hono": "^4.10.5",
|
| 18 |
+
"zod": "^4.3.6"
|
| 19 |
+
},
|
| 20 |
+
"devDependencies": {
|
| 21 |
+
"@types/node": "^22.0.0",
|
| 22 |
+
"tsx": "^4.19.0",
|
| 23 |
+
"typescript": "^5.6.0"
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
"node_modules/@ai-sdk/gateway": {
|
| 27 |
+
"version": "3.0.104",
|
| 28 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.104.tgz",
|
| 29 |
+
"integrity": "sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA==",
|
| 30 |
+
"license": "Apache-2.0",
|
| 31 |
+
"dependencies": {
|
| 32 |
+
"@ai-sdk/provider": "3.0.8",
|
| 33 |
+
"@ai-sdk/provider-utils": "4.0.23",
|
| 34 |
+
"@vercel/oidc": "3.2.0"
|
| 35 |
+
},
|
| 36 |
+
"engines": {
|
| 37 |
+
"node": ">=18"
|
| 38 |
+
},
|
| 39 |
+
"peerDependencies": {
|
| 40 |
+
"zod": "^3.25.76 || ^4.1.8"
|
| 41 |
+
}
|
| 42 |
+
},
|
| 43 |
+
"node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider": {
|
| 44 |
+
"version": "3.0.8",
|
| 45 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz",
|
| 46 |
+
"integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==",
|
| 47 |
+
"license": "Apache-2.0",
|
| 48 |
+
"dependencies": {
|
| 49 |
+
"json-schema": "^0.4.0"
|
| 50 |
+
},
|
| 51 |
+
"engines": {
|
| 52 |
+
"node": ">=18"
|
| 53 |
+
}
|
| 54 |
+
},
|
| 55 |
+
"node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider-utils": {
|
| 56 |
+
"version": "4.0.23",
|
| 57 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.23.tgz",
|
| 58 |
+
"integrity": "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg==",
|
| 59 |
+
"license": "Apache-2.0",
|
| 60 |
+
"dependencies": {
|
| 61 |
+
"@ai-sdk/provider": "3.0.8",
|
| 62 |
+
"@standard-schema/spec": "^1.1.0",
|
| 63 |
+
"eventsource-parser": "^3.0.6"
|
| 64 |
+
},
|
| 65 |
+
"engines": {
|
| 66 |
+
"node": ">=18"
|
| 67 |
+
},
|
| 68 |
+
"peerDependencies": {
|
| 69 |
+
"zod": "^3.25.76 || ^4.1.8"
|
| 70 |
+
}
|
| 71 |
+
},
|
| 72 |
+
"node_modules/@ai-sdk/provider": {
|
| 73 |
+
"version": "2.0.1",
|
| 74 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.1.tgz",
|
| 75 |
+
"integrity": "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng==",
|
| 76 |
+
"license": "Apache-2.0",
|
| 77 |
+
"dependencies": {
|
| 78 |
+
"json-schema": "^0.4.0"
|
| 79 |
+
},
|
| 80 |
+
"engines": {
|
| 81 |
+
"node": ">=18"
|
| 82 |
+
}
|
| 83 |
+
},
|
| 84 |
+
"node_modules/@ai-sdk/provider-utils": {
|
| 85 |
+
"version": "3.0.23",
|
| 86 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.23.tgz",
|
| 87 |
+
"integrity": "sha512-60GYsRj5wIJQRcq5YwYJq4KhwLeStceXEJiZdecP1miiH+6FMmrnc7lZDOJoQ6m9lrudEb+uI4LEwddLz5+rPQ==",
|
| 88 |
+
"license": "Apache-2.0",
|
| 89 |
+
"dependencies": {
|
| 90 |
+
"@ai-sdk/provider": "2.0.1",
|
| 91 |
+
"@standard-schema/spec": "^1.0.0",
|
| 92 |
+
"eventsource-parser": "^3.0.6"
|
| 93 |
+
},
|
| 94 |
+
"engines": {
|
| 95 |
+
"node": ">=18"
|
| 96 |
+
},
|
| 97 |
+
"peerDependencies": {
|
| 98 |
+
"zod": "^3.25.76 || ^4.1.8"
|
| 99 |
+
}
|
| 100 |
+
},
|
| 101 |
+
"node_modules/@ai-sdk/react": {
|
| 102 |
+
"version": "2.0.181",
|
| 103 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.181.tgz",
|
| 104 |
+
"integrity": "sha512-Jay7zyuy8+RHHIZdrM1M4KLi8wAbFzyBAb37HfVSLeIE6HiU43qFN5bI+Xyi9p946AmBNZ7tokuxjXhsUS+V4w==",
|
| 105 |
+
"license": "Apache-2.0",
|
| 106 |
+
"dependencies": {
|
| 107 |
+
"@ai-sdk/provider-utils": "3.0.23",
|
| 108 |
+
"ai": "5.0.179",
|
| 109 |
+
"swr": "^2.2.5",
|
| 110 |
+
"throttleit": "2.1.0"
|
| 111 |
+
},
|
| 112 |
+
"engines": {
|
| 113 |
+
"node": ">=18"
|
| 114 |
+
},
|
| 115 |
+
"peerDependencies": {
|
| 116 |
+
"react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1",
|
| 117 |
+
"zod": "^3.25.76 || ^4.1.8"
|
| 118 |
+
},
|
| 119 |
+
"peerDependenciesMeta": {
|
| 120 |
+
"zod": {
|
| 121 |
+
"optional": true
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
},
|
| 125 |
+
"node_modules/@ai-sdk/react/node_modules/@ai-sdk/gateway": {
|
| 126 |
+
"version": "2.0.82",
|
| 127 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.82.tgz",
|
| 128 |
+
"integrity": "sha512-vtoCSEBGPcxzChI3eqe9C9AJSlc/WUZp92tzpOqVd4B6Tnu4583S+qR7TknB0tPta15TEoOIkK0ENW6D/DgRJQ==",
|
| 129 |
+
"license": "Apache-2.0",
|
| 130 |
+
"dependencies": {
|
| 131 |
+
"@ai-sdk/provider": "2.0.1",
|
| 132 |
+
"@ai-sdk/provider-utils": "3.0.23",
|
| 133 |
+
"@vercel/oidc": "3.1.0"
|
| 134 |
+
},
|
| 135 |
+
"engines": {
|
| 136 |
+
"node": ">=18"
|
| 137 |
+
},
|
| 138 |
+
"peerDependencies": {
|
| 139 |
+
"zod": "^3.25.76 || ^4.1.8"
|
| 140 |
+
}
|
| 141 |
+
},
|
| 142 |
+
"node_modules/@ai-sdk/react/node_modules/@vercel/oidc": {
|
| 143 |
+
"version": "3.1.0",
|
| 144 |
+
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz",
|
| 145 |
+
"integrity": "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==",
|
| 146 |
+
"license": "Apache-2.0",
|
| 147 |
+
"engines": {
|
| 148 |
+
"node": ">= 20"
|
| 149 |
+
}
|
| 150 |
+
},
|
| 151 |
+
"node_modules/@ai-sdk/react/node_modules/ai": {
|
| 152 |
+
"version": "5.0.179",
|
| 153 |
+
"resolved": "https://registry.npmjs.org/ai/-/ai-5.0.179.tgz",
|
| 154 |
+
"integrity": "sha512-tuq/r2FH/pBuY3jo0yHF3UglDV73WONGLhW80DuwgO6w0ftPIqRsAm5p9cE3Bu4LfEuCkMXpiUG/pQRzqKRRaA==",
|
| 155 |
+
"license": "Apache-2.0",
|
| 156 |
+
"dependencies": {
|
| 157 |
+
"@ai-sdk/gateway": "2.0.82",
|
| 158 |
+
"@ai-sdk/provider": "2.0.1",
|
| 159 |
+
"@ai-sdk/provider-utils": "3.0.23",
|
| 160 |
+
"@opentelemetry/api": "1.9.0"
|
| 161 |
+
},
|
| 162 |
+
"engines": {
|
| 163 |
+
"node": ">=18"
|
| 164 |
+
},
|
| 165 |
+
"peerDependencies": {
|
| 166 |
+
"zod": "^3.25.76 || ^4.1.8"
|
| 167 |
+
}
|
| 168 |
+
},
|
| 169 |
+
"node_modules/@esbuild/aix-ppc64": {
|
| 170 |
+
"version": "0.27.7",
|
| 171 |
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
|
| 172 |
+
"integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
|
| 173 |
+
"cpu": [
|
| 174 |
+
"ppc64"
|
| 175 |
+
],
|
| 176 |
+
"dev": true,
|
| 177 |
+
"license": "MIT",
|
| 178 |
+
"optional": true,
|
| 179 |
+
"os": [
|
| 180 |
+
"aix"
|
| 181 |
+
],
|
| 182 |
+
"engines": {
|
| 183 |
+
"node": ">=18"
|
| 184 |
+
}
|
| 185 |
+
},
|
| 186 |
+
"node_modules/@esbuild/android-arm": {
|
| 187 |
+
"version": "0.27.7",
|
| 188 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
|
| 189 |
+
"integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
|
| 190 |
+
"cpu": [
|
| 191 |
+
"arm"
|
| 192 |
+
],
|
| 193 |
+
"dev": true,
|
| 194 |
+
"license": "MIT",
|
| 195 |
+
"optional": true,
|
| 196 |
+
"os": [
|
| 197 |
+
"android"
|
| 198 |
+
],
|
| 199 |
+
"engines": {
|
| 200 |
+
"node": ">=18"
|
| 201 |
+
}
|
| 202 |
+
},
|
| 203 |
+
"node_modules/@esbuild/android-arm64": {
|
| 204 |
+
"version": "0.27.7",
|
| 205 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
|
| 206 |
+
"integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
|
| 207 |
+
"cpu": [
|
| 208 |
+
"arm64"
|
| 209 |
+
],
|
| 210 |
+
"dev": true,
|
| 211 |
+
"license": "MIT",
|
| 212 |
+
"optional": true,
|
| 213 |
+
"os": [
|
| 214 |
+
"android"
|
| 215 |
+
],
|
| 216 |
+
"engines": {
|
| 217 |
+
"node": ">=18"
|
| 218 |
+
}
|
| 219 |
+
},
|
| 220 |
+
"node_modules/@esbuild/android-x64": {
|
| 221 |
+
"version": "0.27.7",
|
| 222 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
|
| 223 |
+
"integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
|
| 224 |
+
"cpu": [
|
| 225 |
+
"x64"
|
| 226 |
+
],
|
| 227 |
+
"dev": true,
|
| 228 |
+
"license": "MIT",
|
| 229 |
+
"optional": true,
|
| 230 |
+
"os": [
|
| 231 |
+
"android"
|
| 232 |
+
],
|
| 233 |
+
"engines": {
|
| 234 |
+
"node": ">=18"
|
| 235 |
+
}
|
| 236 |
+
},
|
| 237 |
+
"node_modules/@esbuild/darwin-arm64": {
|
| 238 |
+
"version": "0.27.7",
|
| 239 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
|
| 240 |
+
"integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
|
| 241 |
+
"cpu": [
|
| 242 |
+
"arm64"
|
| 243 |
+
],
|
| 244 |
+
"dev": true,
|
| 245 |
+
"license": "MIT",
|
| 246 |
+
"optional": true,
|
| 247 |
+
"os": [
|
| 248 |
+
"darwin"
|
| 249 |
+
],
|
| 250 |
+
"engines": {
|
| 251 |
+
"node": ">=18"
|
| 252 |
+
}
|
| 253 |
+
},
|
| 254 |
+
"node_modules/@esbuild/darwin-x64": {
|
| 255 |
+
"version": "0.27.7",
|
| 256 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
|
| 257 |
+
"integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
|
| 258 |
+
"cpu": [
|
| 259 |
+
"x64"
|
| 260 |
+
],
|
| 261 |
+
"dev": true,
|
| 262 |
+
"license": "MIT",
|
| 263 |
+
"optional": true,
|
| 264 |
+
"os": [
|
| 265 |
+
"darwin"
|
| 266 |
+
],
|
| 267 |
+
"engines": {
|
| 268 |
+
"node": ">=18"
|
| 269 |
+
}
|
| 270 |
+
},
|
| 271 |
+
"node_modules/@esbuild/freebsd-arm64": {
|
| 272 |
+
"version": "0.27.7",
|
| 273 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
|
| 274 |
+
"integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
|
| 275 |
+
"cpu": [
|
| 276 |
+
"arm64"
|
| 277 |
+
],
|
| 278 |
+
"dev": true,
|
| 279 |
+
"license": "MIT",
|
| 280 |
+
"optional": true,
|
| 281 |
+
"os": [
|
| 282 |
+
"freebsd"
|
| 283 |
+
],
|
| 284 |
+
"engines": {
|
| 285 |
+
"node": ">=18"
|
| 286 |
+
}
|
| 287 |
+
},
|
| 288 |
+
"node_modules/@esbuild/freebsd-x64": {
|
| 289 |
+
"version": "0.27.7",
|
| 290 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
|
| 291 |
+
"integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
|
| 292 |
+
"cpu": [
|
| 293 |
+
"x64"
|
| 294 |
+
],
|
| 295 |
+
"dev": true,
|
| 296 |
+
"license": "MIT",
|
| 297 |
+
"optional": true,
|
| 298 |
+
"os": [
|
| 299 |
+
"freebsd"
|
| 300 |
+
],
|
| 301 |
+
"engines": {
|
| 302 |
+
"node": ">=18"
|
| 303 |
+
}
|
| 304 |
+
},
|
| 305 |
+
"node_modules/@esbuild/linux-arm": {
|
| 306 |
+
"version": "0.27.7",
|
| 307 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
|
| 308 |
+
"integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
|
| 309 |
+
"cpu": [
|
| 310 |
+
"arm"
|
| 311 |
+
],
|
| 312 |
+
"dev": true,
|
| 313 |
+
"license": "MIT",
|
| 314 |
+
"optional": true,
|
| 315 |
+
"os": [
|
| 316 |
+
"linux"
|
| 317 |
+
],
|
| 318 |
+
"engines": {
|
| 319 |
+
"node": ">=18"
|
| 320 |
+
}
|
| 321 |
+
},
|
| 322 |
+
"node_modules/@esbuild/linux-arm64": {
|
| 323 |
+
"version": "0.27.7",
|
| 324 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
|
| 325 |
+
"integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
|
| 326 |
+
"cpu": [
|
| 327 |
+
"arm64"
|
| 328 |
+
],
|
| 329 |
+
"dev": true,
|
| 330 |
+
"license": "MIT",
|
| 331 |
+
"optional": true,
|
| 332 |
+
"os": [
|
| 333 |
+
"linux"
|
| 334 |
+
],
|
| 335 |
+
"engines": {
|
| 336 |
+
"node": ">=18"
|
| 337 |
+
}
|
| 338 |
+
},
|
| 339 |
+
"node_modules/@esbuild/linux-ia32": {
|
| 340 |
+
"version": "0.27.7",
|
| 341 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
|
| 342 |
+
"integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
|
| 343 |
+
"cpu": [
|
| 344 |
+
"ia32"
|
| 345 |
+
],
|
| 346 |
+
"dev": true,
|
| 347 |
+
"license": "MIT",
|
| 348 |
+
"optional": true,
|
| 349 |
+
"os": [
|
| 350 |
+
"linux"
|
| 351 |
+
],
|
| 352 |
+
"engines": {
|
| 353 |
+
"node": ">=18"
|
| 354 |
+
}
|
| 355 |
+
},
|
| 356 |
+
"node_modules/@esbuild/linux-loong64": {
|
| 357 |
+
"version": "0.27.7",
|
| 358 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
|
| 359 |
+
"integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
|
| 360 |
+
"cpu": [
|
| 361 |
+
"loong64"
|
| 362 |
+
],
|
| 363 |
+
"dev": true,
|
| 364 |
+
"license": "MIT",
|
| 365 |
+
"optional": true,
|
| 366 |
+
"os": [
|
| 367 |
+
"linux"
|
| 368 |
+
],
|
| 369 |
+
"engines": {
|
| 370 |
+
"node": ">=18"
|
| 371 |
+
}
|
| 372 |
+
},
|
| 373 |
+
"node_modules/@esbuild/linux-mips64el": {
|
| 374 |
+
"version": "0.27.7",
|
| 375 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
|
| 376 |
+
"integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
|
| 377 |
+
"cpu": [
|
| 378 |
+
"mips64el"
|
| 379 |
+
],
|
| 380 |
+
"dev": true,
|
| 381 |
+
"license": "MIT",
|
| 382 |
+
"optional": true,
|
| 383 |
+
"os": [
|
| 384 |
+
"linux"
|
| 385 |
+
],
|
| 386 |
+
"engines": {
|
| 387 |
+
"node": ">=18"
|
| 388 |
+
}
|
| 389 |
+
},
|
| 390 |
+
"node_modules/@esbuild/linux-ppc64": {
|
| 391 |
+
"version": "0.27.7",
|
| 392 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
|
| 393 |
+
"integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
|
| 394 |
+
"cpu": [
|
| 395 |
+
"ppc64"
|
| 396 |
+
],
|
| 397 |
+
"dev": true,
|
| 398 |
+
"license": "MIT",
|
| 399 |
+
"optional": true,
|
| 400 |
+
"os": [
|
| 401 |
+
"linux"
|
| 402 |
+
],
|
| 403 |
+
"engines": {
|
| 404 |
+
"node": ">=18"
|
| 405 |
+
}
|
| 406 |
+
},
|
| 407 |
+
"node_modules/@esbuild/linux-riscv64": {
|
| 408 |
+
"version": "0.27.7",
|
| 409 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
|
| 410 |
+
"integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
|
| 411 |
+
"cpu": [
|
| 412 |
+
"riscv64"
|
| 413 |
+
],
|
| 414 |
+
"dev": true,
|
| 415 |
+
"license": "MIT",
|
| 416 |
+
"optional": true,
|
| 417 |
+
"os": [
|
| 418 |
+
"linux"
|
| 419 |
+
],
|
| 420 |
+
"engines": {
|
| 421 |
+
"node": ">=18"
|
| 422 |
+
}
|
| 423 |
+
},
|
| 424 |
+
"node_modules/@esbuild/linux-s390x": {
|
| 425 |
+
"version": "0.27.7",
|
| 426 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
|
| 427 |
+
"integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
|
| 428 |
+
"cpu": [
|
| 429 |
+
"s390x"
|
| 430 |
+
],
|
| 431 |
+
"dev": true,
|
| 432 |
+
"license": "MIT",
|
| 433 |
+
"optional": true,
|
| 434 |
+
"os": [
|
| 435 |
+
"linux"
|
| 436 |
+
],
|
| 437 |
+
"engines": {
|
| 438 |
+
"node": ">=18"
|
| 439 |
+
}
|
| 440 |
+
},
|
| 441 |
+
"node_modules/@esbuild/linux-x64": {
|
| 442 |
+
"version": "0.27.7",
|
| 443 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
|
| 444 |
+
"integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
|
| 445 |
+
"cpu": [
|
| 446 |
+
"x64"
|
| 447 |
+
],
|
| 448 |
+
"dev": true,
|
| 449 |
+
"license": "MIT",
|
| 450 |
+
"optional": true,
|
| 451 |
+
"os": [
|
| 452 |
+
"linux"
|
| 453 |
+
],
|
| 454 |
+
"engines": {
|
| 455 |
+
"node": ">=18"
|
| 456 |
+
}
|
| 457 |
+
},
|
| 458 |
+
"node_modules/@esbuild/netbsd-arm64": {
|
| 459 |
+
"version": "0.27.7",
|
| 460 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
|
| 461 |
+
"integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
|
| 462 |
+
"cpu": [
|
| 463 |
+
"arm64"
|
| 464 |
+
],
|
| 465 |
+
"dev": true,
|
| 466 |
+
"license": "MIT",
|
| 467 |
+
"optional": true,
|
| 468 |
+
"os": [
|
| 469 |
+
"netbsd"
|
| 470 |
+
],
|
| 471 |
+
"engines": {
|
| 472 |
+
"node": ">=18"
|
| 473 |
+
}
|
| 474 |
+
},
|
| 475 |
+
"node_modules/@esbuild/netbsd-x64": {
|
| 476 |
+
"version": "0.27.7",
|
| 477 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
|
| 478 |
+
"integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
|
| 479 |
+
"cpu": [
|
| 480 |
+
"x64"
|
| 481 |
+
],
|
| 482 |
+
"dev": true,
|
| 483 |
+
"license": "MIT",
|
| 484 |
+
"optional": true,
|
| 485 |
+
"os": [
|
| 486 |
+
"netbsd"
|
| 487 |
+
],
|
| 488 |
+
"engines": {
|
| 489 |
+
"node": ">=18"
|
| 490 |
+
}
|
| 491 |
+
},
|
| 492 |
+
"node_modules/@esbuild/openbsd-arm64": {
|
| 493 |
+
"version": "0.27.7",
|
| 494 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
|
| 495 |
+
"integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
|
| 496 |
+
"cpu": [
|
| 497 |
+
"arm64"
|
| 498 |
+
],
|
| 499 |
+
"dev": true,
|
| 500 |
+
"license": "MIT",
|
| 501 |
+
"optional": true,
|
| 502 |
+
"os": [
|
| 503 |
+
"openbsd"
|
| 504 |
+
],
|
| 505 |
+
"engines": {
|
| 506 |
+
"node": ">=18"
|
| 507 |
+
}
|
| 508 |
+
},
|
| 509 |
+
"node_modules/@esbuild/openbsd-x64": {
|
| 510 |
+
"version": "0.27.7",
|
| 511 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
|
| 512 |
+
"integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
|
| 513 |
+
"cpu": [
|
| 514 |
+
"x64"
|
| 515 |
+
],
|
| 516 |
+
"dev": true,
|
| 517 |
+
"license": "MIT",
|
| 518 |
+
"optional": true,
|
| 519 |
+
"os": [
|
| 520 |
+
"openbsd"
|
| 521 |
+
],
|
| 522 |
+
"engines": {
|
| 523 |
+
"node": ">=18"
|
| 524 |
+
}
|
| 525 |
+
},
|
| 526 |
+
"node_modules/@esbuild/openharmony-arm64": {
|
| 527 |
+
"version": "0.27.7",
|
| 528 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
|
| 529 |
+
"integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
|
| 530 |
+
"cpu": [
|
| 531 |
+
"arm64"
|
| 532 |
+
],
|
| 533 |
+
"dev": true,
|
| 534 |
+
"license": "MIT",
|
| 535 |
+
"optional": true,
|
| 536 |
+
"os": [
|
| 537 |
+
"openharmony"
|
| 538 |
+
],
|
| 539 |
+
"engines": {
|
| 540 |
+
"node": ">=18"
|
| 541 |
+
}
|
| 542 |
+
},
|
| 543 |
+
"node_modules/@esbuild/sunos-x64": {
|
| 544 |
+
"version": "0.27.7",
|
| 545 |
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
|
| 546 |
+
"integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
|
| 547 |
+
"cpu": [
|
| 548 |
+
"x64"
|
| 549 |
+
],
|
| 550 |
+
"dev": true,
|
| 551 |
+
"license": "MIT",
|
| 552 |
+
"optional": true,
|
| 553 |
+
"os": [
|
| 554 |
+
"sunos"
|
| 555 |
+
],
|
| 556 |
+
"engines": {
|
| 557 |
+
"node": ">=18"
|
| 558 |
+
}
|
| 559 |
+
},
|
| 560 |
+
"node_modules/@esbuild/win32-arm64": {
|
| 561 |
+
"version": "0.27.7",
|
| 562 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
|
| 563 |
+
"integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
|
| 564 |
+
"cpu": [
|
| 565 |
+
"arm64"
|
| 566 |
+
],
|
| 567 |
+
"dev": true,
|
| 568 |
+
"license": "MIT",
|
| 569 |
+
"optional": true,
|
| 570 |
+
"os": [
|
| 571 |
+
"win32"
|
| 572 |
+
],
|
| 573 |
+
"engines": {
|
| 574 |
+
"node": ">=18"
|
| 575 |
+
}
|
| 576 |
+
},
|
| 577 |
+
"node_modules/@esbuild/win32-ia32": {
|
| 578 |
+
"version": "0.27.7",
|
| 579 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
|
| 580 |
+
"integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
|
| 581 |
+
"cpu": [
|
| 582 |
+
"ia32"
|
| 583 |
+
],
|
| 584 |
+
"dev": true,
|
| 585 |
+
"license": "MIT",
|
| 586 |
+
"optional": true,
|
| 587 |
+
"os": [
|
| 588 |
+
"win32"
|
| 589 |
+
],
|
| 590 |
+
"engines": {
|
| 591 |
+
"node": ">=18"
|
| 592 |
+
}
|
| 593 |
+
},
|
| 594 |
+
"node_modules/@esbuild/win32-x64": {
|
| 595 |
+
"version": "0.27.7",
|
| 596 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
|
| 597 |
+
"integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
|
| 598 |
+
"cpu": [
|
| 599 |
+
"x64"
|
| 600 |
+
],
|
| 601 |
+
"dev": true,
|
| 602 |
+
"license": "MIT",
|
| 603 |
+
"optional": true,
|
| 604 |
+
"os": [
|
| 605 |
+
"win32"
|
| 606 |
+
],
|
| 607 |
+
"engines": {
|
| 608 |
+
"node": ">=18"
|
| 609 |
+
}
|
| 610 |
+
},
|
| 611 |
+
"node_modules/@hono/node-server": {
|
| 612 |
+
"version": "1.19.14",
|
| 613 |
+
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
|
| 614 |
+
"integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
|
| 615 |
+
"license": "MIT",
|
| 616 |
+
"engines": {
|
| 617 |
+
"node": ">=18.14.1"
|
| 618 |
+
},
|
| 619 |
+
"peerDependencies": {
|
| 620 |
+
"hono": "^4"
|
| 621 |
+
}
|
| 622 |
+
},
|
| 623 |
+
"node_modules/@huggingface/hub": {
|
| 624 |
+
"version": "2.11.0",
|
| 625 |
+
"resolved": "https://registry.npmjs.org/@huggingface/hub/-/hub-2.11.0.tgz",
|
| 626 |
+
"integrity": "sha512-WS6QGaXYeBVFlaB4SOn6z4LGUpLB5kRZNL08uUni4izX353KxiwwZMK5+/AWX86MJh8SMZNa/JFcvFCcQsbszQ==",
|
| 627 |
+
"license": "MIT",
|
| 628 |
+
"dependencies": {
|
| 629 |
+
"@huggingface/tasks": "^0.19.90"
|
| 630 |
+
},
|
| 631 |
+
"bin": {
|
| 632 |
+
"hfjs": "dist/cli.js"
|
| 633 |
+
},
|
| 634 |
+
"engines": {
|
| 635 |
+
"node": ">=18"
|
| 636 |
+
},
|
| 637 |
+
"optionalDependencies": {
|
| 638 |
+
"cli-progress": "^3.12.0"
|
| 639 |
+
}
|
| 640 |
+
},
|
| 641 |
+
"node_modules/@huggingface/tasks": {
|
| 642 |
+
"version": "0.19.90",
|
| 643 |
+
"resolved": "https://registry.npmjs.org/@huggingface/tasks/-/tasks-0.19.90.tgz",
|
| 644 |
+
"integrity": "sha512-nfV9luJbvwGQ/5oKXkKhCV9h4X7mwh1YaGG3ORd6UMLDSwr1OFSSatcBX0O9OtBtmNK19aGSjbLFqqgcIR6+IA==",
|
| 645 |
+
"license": "MIT"
|
| 646 |
+
},
|
| 647 |
+
"node_modules/@openrouter/ai-sdk-provider": {
|
| 648 |
+
"version": "2.8.0",
|
| 649 |
+
"resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-2.8.0.tgz",
|
| 650 |
+
"integrity": "sha512-oDDW/0KMqz4suHVloB9sNv0YyKLGNYf1FTevXH6adDkid5dsmbbcYuiEsbIhpZSZtHa6o5AVjK1jEAfePOLxww==",
|
| 651 |
+
"license": "Apache-2.0",
|
| 652 |
+
"engines": {
|
| 653 |
+
"node": ">=18"
|
| 654 |
+
},
|
| 655 |
+
"peerDependencies": {
|
| 656 |
+
"ai": "^6.0.0",
|
| 657 |
+
"zod": "^3.25.0 || ^4.0.0"
|
| 658 |
+
}
|
| 659 |
+
},
|
| 660 |
+
"node_modules/@opentelemetry/api": {
|
| 661 |
+
"version": "1.9.0",
|
| 662 |
+
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
| 663 |
+
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
| 664 |
+
"license": "Apache-2.0",
|
| 665 |
+
"engines": {
|
| 666 |
+
"node": ">=8.0.0"
|
| 667 |
+
}
|
| 668 |
+
},
|
| 669 |
+
"node_modules/@standard-schema/spec": {
|
| 670 |
+
"version": "1.1.0",
|
| 671 |
+
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
| 672 |
+
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
| 673 |
+
"license": "MIT"
|
| 674 |
+
},
|
| 675 |
+
"node_modules/@types/node": {
|
| 676 |
+
"version": "22.19.17",
|
| 677 |
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
|
| 678 |
+
"integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
|
| 679 |
+
"dev": true,
|
| 680 |
+
"license": "MIT",
|
| 681 |
+
"dependencies": {
|
| 682 |
+
"undici-types": "~6.21.0"
|
| 683 |
+
}
|
| 684 |
+
},
|
| 685 |
+
"node_modules/@vercel/oidc": {
|
| 686 |
+
"version": "3.2.0",
|
| 687 |
+
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz",
|
| 688 |
+
"integrity": "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==",
|
| 689 |
+
"license": "Apache-2.0",
|
| 690 |
+
"engines": {
|
| 691 |
+
"node": ">= 20"
|
| 692 |
+
}
|
| 693 |
+
},
|
| 694 |
+
"node_modules/ai": {
|
| 695 |
+
"version": "6.0.168",
|
| 696 |
+
"resolved": "https://registry.npmjs.org/ai/-/ai-6.0.168.tgz",
|
| 697 |
+
"integrity": "sha512-2HqCJuO+1V2aV7vfYs5LFEUfxbkGX+5oa54q/gCCTL7KLTdbxcCu5D7TdLA5kwsrs3Szgjah9q6D9tpjHM3hUQ==",
|
| 698 |
+
"license": "Apache-2.0",
|
| 699 |
+
"peer": true,
|
| 700 |
+
"dependencies": {
|
| 701 |
+
"@ai-sdk/gateway": "3.0.104",
|
| 702 |
+
"@ai-sdk/provider": "3.0.8",
|
| 703 |
+
"@ai-sdk/provider-utils": "4.0.23",
|
| 704 |
+
"@opentelemetry/api": "1.9.0"
|
| 705 |
+
},
|
| 706 |
+
"engines": {
|
| 707 |
+
"node": ">=18"
|
| 708 |
+
},
|
| 709 |
+
"peerDependencies": {
|
| 710 |
+
"zod": "^3.25.76 || ^4.1.8"
|
| 711 |
+
}
|
| 712 |
+
},
|
| 713 |
+
"node_modules/ai/node_modules/@ai-sdk/provider": {
|
| 714 |
+
"version": "3.0.8",
|
| 715 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz",
|
| 716 |
+
"integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==",
|
| 717 |
+
"license": "Apache-2.0",
|
| 718 |
+
"dependencies": {
|
| 719 |
+
"json-schema": "^0.4.0"
|
| 720 |
+
},
|
| 721 |
+
"engines": {
|
| 722 |
+
"node": ">=18"
|
| 723 |
+
}
|
| 724 |
+
},
|
| 725 |
+
"node_modules/ai/node_modules/@ai-sdk/provider-utils": {
|
| 726 |
+
"version": "4.0.23",
|
| 727 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.23.tgz",
|
| 728 |
+
"integrity": "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg==",
|
| 729 |
+
"license": "Apache-2.0",
|
| 730 |
+
"dependencies": {
|
| 731 |
+
"@ai-sdk/provider": "3.0.8",
|
| 732 |
+
"@standard-schema/spec": "^1.1.0",
|
| 733 |
+
"eventsource-parser": "^3.0.6"
|
| 734 |
+
},
|
| 735 |
+
"engines": {
|
| 736 |
+
"node": ">=18"
|
| 737 |
+
},
|
| 738 |
+
"peerDependencies": {
|
| 739 |
+
"zod": "^3.25.76 || ^4.1.8"
|
| 740 |
+
}
|
| 741 |
+
},
|
| 742 |
+
"node_modules/ansi-regex": {
|
| 743 |
+
"version": "5.0.1",
|
| 744 |
+
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
| 745 |
+
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
| 746 |
+
"license": "MIT",
|
| 747 |
+
"optional": true,
|
| 748 |
+
"engines": {
|
| 749 |
+
"node": ">=8"
|
| 750 |
+
}
|
| 751 |
+
},
|
| 752 |
+
"node_modules/cli-progress": {
|
| 753 |
+
"version": "3.12.0",
|
| 754 |
+
"resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz",
|
| 755 |
+
"integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==",
|
| 756 |
+
"license": "MIT",
|
| 757 |
+
"optional": true,
|
| 758 |
+
"dependencies": {
|
| 759 |
+
"string-width": "^4.2.3"
|
| 760 |
+
},
|
| 761 |
+
"engines": {
|
| 762 |
+
"node": ">=4"
|
| 763 |
+
}
|
| 764 |
+
},
|
| 765 |
+
"node_modules/dequal": {
|
| 766 |
+
"version": "2.0.3",
|
| 767 |
+
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
| 768 |
+
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
| 769 |
+
"license": "MIT",
|
| 770 |
+
"engines": {
|
| 771 |
+
"node": ">=6"
|
| 772 |
+
}
|
| 773 |
+
},
|
| 774 |
+
"node_modules/dotenv": {
|
| 775 |
+
"version": "17.4.2",
|
| 776 |
+
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
|
| 777 |
+
"integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
|
| 778 |
+
"license": "BSD-2-Clause",
|
| 779 |
+
"engines": {
|
| 780 |
+
"node": ">=12"
|
| 781 |
+
},
|
| 782 |
+
"funding": {
|
| 783 |
+
"url": "https://dotenvx.com"
|
| 784 |
+
}
|
| 785 |
+
},
|
| 786 |
+
"node_modules/emoji-regex": {
|
| 787 |
+
"version": "8.0.0",
|
| 788 |
+
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
| 789 |
+
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
| 790 |
+
"license": "MIT",
|
| 791 |
+
"optional": true
|
| 792 |
+
},
|
| 793 |
+
"node_modules/esbuild": {
|
| 794 |
+
"version": "0.27.7",
|
| 795 |
+
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
|
| 796 |
+
"integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
|
| 797 |
+
"dev": true,
|
| 798 |
+
"hasInstallScript": true,
|
| 799 |
+
"license": "MIT",
|
| 800 |
+
"bin": {
|
| 801 |
+
"esbuild": "bin/esbuild"
|
| 802 |
+
},
|
| 803 |
+
"engines": {
|
| 804 |
+
"node": ">=18"
|
| 805 |
+
},
|
| 806 |
+
"optionalDependencies": {
|
| 807 |
+
"@esbuild/aix-ppc64": "0.27.7",
|
| 808 |
+
"@esbuild/android-arm": "0.27.7",
|
| 809 |
+
"@esbuild/android-arm64": "0.27.7",
|
| 810 |
+
"@esbuild/android-x64": "0.27.7",
|
| 811 |
+
"@esbuild/darwin-arm64": "0.27.7",
|
| 812 |
+
"@esbuild/darwin-x64": "0.27.7",
|
| 813 |
+
"@esbuild/freebsd-arm64": "0.27.7",
|
| 814 |
+
"@esbuild/freebsd-x64": "0.27.7",
|
| 815 |
+
"@esbuild/linux-arm": "0.27.7",
|
| 816 |
+
"@esbuild/linux-arm64": "0.27.7",
|
| 817 |
+
"@esbuild/linux-ia32": "0.27.7",
|
| 818 |
+
"@esbuild/linux-loong64": "0.27.7",
|
| 819 |
+
"@esbuild/linux-mips64el": "0.27.7",
|
| 820 |
+
"@esbuild/linux-ppc64": "0.27.7",
|
| 821 |
+
"@esbuild/linux-riscv64": "0.27.7",
|
| 822 |
+
"@esbuild/linux-s390x": "0.27.7",
|
| 823 |
+
"@esbuild/linux-x64": "0.27.7",
|
| 824 |
+
"@esbuild/netbsd-arm64": "0.27.7",
|
| 825 |
+
"@esbuild/netbsd-x64": "0.27.7",
|
| 826 |
+
"@esbuild/openbsd-arm64": "0.27.7",
|
| 827 |
+
"@esbuild/openbsd-x64": "0.27.7",
|
| 828 |
+
"@esbuild/openharmony-arm64": "0.27.7",
|
| 829 |
+
"@esbuild/sunos-x64": "0.27.7",
|
| 830 |
+
"@esbuild/win32-arm64": "0.27.7",
|
| 831 |
+
"@esbuild/win32-ia32": "0.27.7",
|
| 832 |
+
"@esbuild/win32-x64": "0.27.7"
|
| 833 |
+
}
|
| 834 |
+
},
|
| 835 |
+
"node_modules/eventsource-parser": {
|
| 836 |
+
"version": "3.0.8",
|
| 837 |
+
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz",
|
| 838 |
+
"integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==",
|
| 839 |
+
"license": "MIT",
|
| 840 |
+
"engines": {
|
| 841 |
+
"node": ">=18.0.0"
|
| 842 |
+
}
|
| 843 |
+
},
|
| 844 |
+
"node_modules/fsevents": {
|
| 845 |
+
"version": "2.3.3",
|
| 846 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 847 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 848 |
+
"dev": true,
|
| 849 |
+
"hasInstallScript": true,
|
| 850 |
+
"license": "MIT",
|
| 851 |
+
"optional": true,
|
| 852 |
+
"os": [
|
| 853 |
+
"darwin"
|
| 854 |
+
],
|
| 855 |
+
"engines": {
|
| 856 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 857 |
+
}
|
| 858 |
+
},
|
| 859 |
+
"node_modules/get-tsconfig": {
|
| 860 |
+
"version": "4.14.0",
|
| 861 |
+
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
|
| 862 |
+
"integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
|
| 863 |
+
"dev": true,
|
| 864 |
+
"license": "MIT",
|
| 865 |
+
"dependencies": {
|
| 866 |
+
"resolve-pkg-maps": "^1.0.0"
|
| 867 |
+
},
|
| 868 |
+
"funding": {
|
| 869 |
+
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
| 870 |
+
}
|
| 871 |
+
},
|
| 872 |
+
"node_modules/hono": {
|
| 873 |
+
"version": "4.12.14",
|
| 874 |
+
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
|
| 875 |
+
"integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
|
| 876 |
+
"license": "MIT",
|
| 877 |
+
"peer": true,
|
| 878 |
+
"engines": {
|
| 879 |
+
"node": ">=16.9.0"
|
| 880 |
+
}
|
| 881 |
+
},
|
| 882 |
+
"node_modules/is-fullwidth-code-point": {
|
| 883 |
+
"version": "3.0.0",
|
| 884 |
+
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
| 885 |
+
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
| 886 |
+
"license": "MIT",
|
| 887 |
+
"optional": true,
|
| 888 |
+
"engines": {
|
| 889 |
+
"node": ">=8"
|
| 890 |
+
}
|
| 891 |
+
},
|
| 892 |
+
"node_modules/json-schema": {
|
| 893 |
+
"version": "0.4.0",
|
| 894 |
+
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
| 895 |
+
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
|
| 896 |
+
"license": "(AFL-2.1 OR BSD-3-Clause)"
|
| 897 |
+
},
|
| 898 |
+
"node_modules/react": {
|
| 899 |
+
"version": "19.2.5",
|
| 900 |
+
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
|
| 901 |
+
"integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
|
| 902 |
+
"license": "MIT",
|
| 903 |
+
"peer": true,
|
| 904 |
+
"engines": {
|
| 905 |
+
"node": ">=0.10.0"
|
| 906 |
+
}
|
| 907 |
+
},
|
| 908 |
+
"node_modules/resolve-pkg-maps": {
|
| 909 |
+
"version": "1.0.0",
|
| 910 |
+
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
| 911 |
+
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
| 912 |
+
"dev": true,
|
| 913 |
+
"license": "MIT",
|
| 914 |
+
"funding": {
|
| 915 |
+
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
| 916 |
+
}
|
| 917 |
+
},
|
| 918 |
+
"node_modules/string-width": {
|
| 919 |
+
"version": "4.2.3",
|
| 920 |
+
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
| 921 |
+
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
| 922 |
+
"license": "MIT",
|
| 923 |
+
"optional": true,
|
| 924 |
+
"dependencies": {
|
| 925 |
+
"emoji-regex": "^8.0.0",
|
| 926 |
+
"is-fullwidth-code-point": "^3.0.0",
|
| 927 |
+
"strip-ansi": "^6.0.1"
|
| 928 |
+
},
|
| 929 |
+
"engines": {
|
| 930 |
+
"node": ">=8"
|
| 931 |
+
}
|
| 932 |
+
},
|
| 933 |
+
"node_modules/strip-ansi": {
|
| 934 |
+
"version": "6.0.1",
|
| 935 |
+
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
| 936 |
+
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
| 937 |
+
"license": "MIT",
|
| 938 |
+
"optional": true,
|
| 939 |
+
"dependencies": {
|
| 940 |
+
"ansi-regex": "^5.0.1"
|
| 941 |
+
},
|
| 942 |
+
"engines": {
|
| 943 |
+
"node": ">=8"
|
| 944 |
+
}
|
| 945 |
+
},
|
| 946 |
+
"node_modules/swr": {
|
| 947 |
+
"version": "2.4.1",
|
| 948 |
+
"resolved": "https://registry.npmjs.org/swr/-/swr-2.4.1.tgz",
|
| 949 |
+
"integrity": "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==",
|
| 950 |
+
"license": "MIT",
|
| 951 |
+
"dependencies": {
|
| 952 |
+
"dequal": "^2.0.3",
|
| 953 |
+
"use-sync-external-store": "^1.6.0"
|
| 954 |
+
},
|
| 955 |
+
"peerDependencies": {
|
| 956 |
+
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 957 |
+
}
|
| 958 |
+
},
|
| 959 |
+
"node_modules/throttleit": {
|
| 960 |
+
"version": "2.1.0",
|
| 961 |
+
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz",
|
| 962 |
+
"integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==",
|
| 963 |
+
"license": "MIT",
|
| 964 |
+
"engines": {
|
| 965 |
+
"node": ">=18"
|
| 966 |
+
},
|
| 967 |
+
"funding": {
|
| 968 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 969 |
+
}
|
| 970 |
+
},
|
| 971 |
+
"node_modules/tsx": {
|
| 972 |
+
"version": "4.21.0",
|
| 973 |
+
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
| 974 |
+
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
| 975 |
+
"dev": true,
|
| 976 |
+
"license": "MIT",
|
| 977 |
+
"dependencies": {
|
| 978 |
+
"esbuild": "~0.27.0",
|
| 979 |
+
"get-tsconfig": "^4.7.5"
|
| 980 |
+
},
|
| 981 |
+
"bin": {
|
| 982 |
+
"tsx": "dist/cli.mjs"
|
| 983 |
+
},
|
| 984 |
+
"engines": {
|
| 985 |
+
"node": ">=18.0.0"
|
| 986 |
+
},
|
| 987 |
+
"optionalDependencies": {
|
| 988 |
+
"fsevents": "~2.3.3"
|
| 989 |
+
}
|
| 990 |
+
},
|
| 991 |
+
"node_modules/typescript": {
|
| 992 |
+
"version": "5.9.3",
|
| 993 |
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
| 994 |
+
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
| 995 |
+
"dev": true,
|
| 996 |
+
"license": "Apache-2.0",
|
| 997 |
+
"bin": {
|
| 998 |
+
"tsc": "bin/tsc",
|
| 999 |
+
"tsserver": "bin/tsserver"
|
| 1000 |
+
},
|
| 1001 |
+
"engines": {
|
| 1002 |
+
"node": ">=14.17"
|
| 1003 |
+
}
|
| 1004 |
+
},
|
| 1005 |
+
"node_modules/undici-types": {
|
| 1006 |
+
"version": "6.21.0",
|
| 1007 |
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
| 1008 |
+
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
| 1009 |
+
"dev": true,
|
| 1010 |
+
"license": "MIT"
|
| 1011 |
+
},
|
| 1012 |
+
"node_modules/use-sync-external-store": {
|
| 1013 |
+
"version": "1.6.0",
|
| 1014 |
+
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
| 1015 |
+
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
| 1016 |
+
"license": "MIT",
|
| 1017 |
+
"peerDependencies": {
|
| 1018 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 1019 |
+
}
|
| 1020 |
+
},
|
| 1021 |
+
"node_modules/zod": {
|
| 1022 |
+
"version": "4.3.6",
|
| 1023 |
+
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
| 1024 |
+
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
| 1025 |
+
"license": "MIT",
|
| 1026 |
+
"peer": true,
|
| 1027 |
+
"funding": {
|
| 1028 |
+
"url": "https://github.com/sponsors/colinhacks"
|
| 1029 |
+
}
|
| 1030 |
+
}
|
| 1031 |
+
}
|
| 1032 |
+
}
|
backend/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "reachy-mini-vibe-coder-backend",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "tsx watch src/server.ts",
|
| 8 |
+
"build": "tsc",
|
| 9 |
+
"start": "node dist/server.js",
|
| 10 |
+
"typecheck": "tsc --noEmit"
|
| 11 |
+
},
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"@hono/node-server": "^1.19.6",
|
| 14 |
+
"@huggingface/hub": "^2.11.0",
|
| 15 |
+
"@openrouter/ai-sdk-provider": "^2.5.1",
|
| 16 |
+
"ai": "^6.0.158",
|
| 17 |
+
"dotenv": "^17.4.1",
|
| 18 |
+
"hono": "^4.10.5",
|
| 19 |
+
"zod": "^4.3.6"
|
| 20 |
+
},
|
| 21 |
+
"devDependencies": {
|
| 22 |
+
"@types/node": "^22.0.0",
|
| 23 |
+
"tsx": "^4.19.0",
|
| 24 |
+
"typescript": "^5.6.0"
|
| 25 |
+
}
|
| 26 |
+
}
|
backend/src/agent/skill-loader.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
| 2 |
+
import path from "node:path";
|
| 3 |
+
import { fileURLToPath } from "node:url";
|
| 4 |
+
|
| 5 |
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
| 6 |
+
|
| 7 |
+
/**
|
| 8 |
+
* Skills are markdown files grouped per topic under `agent/skills/<id>/`.
|
| 9 |
+
* Each folder must contain a `SKILL.md` (the hub) and optionally other
|
| 10 |
+
* `.md` support files (referenced by relative links from SKILL.md).
|
| 11 |
+
*
|
| 12 |
+
* Loaded at runtime: tweak the .md files and restart the server, no rebuild.
|
| 13 |
+
*/
|
| 14 |
+
|
| 15 |
+
const SKILLS_DIR = path.resolve(__dirname, "./skills");
|
| 16 |
+
|
| 17 |
+
export interface SkillDoc {
|
| 18 |
+
filename: string;
|
| 19 |
+
title: string;
|
| 20 |
+
content: string;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
export interface Skill {
|
| 24 |
+
id: string;
|
| 25 |
+
hub: SkillDoc;
|
| 26 |
+
docs: SkillDoc[];
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
function extractTitle(markdown: string, fallback: string): string {
|
| 30 |
+
const match = markdown.match(/^#\s+(.+)$/m);
|
| 31 |
+
return match ? match[1].trim() : fallback;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
export function loadSkill(id: string): Skill | null {
|
| 35 |
+
const dir = path.join(SKILLS_DIR, id);
|
| 36 |
+
if (!existsSync(dir)) return null;
|
| 37 |
+
|
| 38 |
+
const files = readdirSync(dir).filter((f) => f.endsWith(".md"));
|
| 39 |
+
if (files.length === 0) return null;
|
| 40 |
+
|
| 41 |
+
const hubFile = files.find((f) => f.toLowerCase() === "skill.md");
|
| 42 |
+
if (!hubFile) return null;
|
| 43 |
+
|
| 44 |
+
const toDoc = (filename: string): SkillDoc => {
|
| 45 |
+
const content = readFileSync(path.join(dir, filename), "utf-8");
|
| 46 |
+
return {
|
| 47 |
+
filename,
|
| 48 |
+
title: extractTitle(content, filename.replace(/\.md$/, "")),
|
| 49 |
+
content,
|
| 50 |
+
};
|
| 51 |
+
};
|
| 52 |
+
|
| 53 |
+
return {
|
| 54 |
+
id,
|
| 55 |
+
hub: toDoc(hubFile),
|
| 56 |
+
docs: files.filter((f) => f !== hubFile).map(toDoc),
|
| 57 |
+
};
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
export const PRIMARY_SKILL_ID = "build-reachy-voice-app";
|
| 61 |
+
|
| 62 |
+
let cached: Skill | null | undefined;
|
| 63 |
+
export function getPrimarySkill(): Skill | null {
|
| 64 |
+
if (cached === undefined) {
|
| 65 |
+
cached = loadSkill(PRIMARY_SKILL_ID);
|
| 66 |
+
if (cached) {
|
| 67 |
+
const docNames = cached.docs.map((d) => d.filename).join(", ");
|
| 68 |
+
console.log(
|
| 69 |
+
`[skill-loader] Loaded skill "${cached.id}" (hub: ${cached.hub.filename}, docs: ${docNames || "none"})`,
|
| 70 |
+
);
|
| 71 |
+
} else {
|
| 72 |
+
console.warn(
|
| 73 |
+
`[skill-loader] Skill "${PRIMARY_SKILL_ID}" not found under ${SKILLS_DIR}`,
|
| 74 |
+
);
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
return cached;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
export function listSkillDocs(skill: Skill): string[] {
|
| 81 |
+
return skill.docs.map((d) => d.filename.replace(/\.md$/, ""));
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
export function getSkillDoc(skill: Skill, topic: string): SkillDoc | null {
|
| 85 |
+
const normalized = topic.toLowerCase().replace(/\.md$/, "");
|
| 86 |
+
return (
|
| 87 |
+
skill.docs.find(
|
| 88 |
+
(d) => d.filename.toLowerCase().replace(/\.md$/, "") === normalized,
|
| 89 |
+
) ?? null
|
| 90 |
+
);
|
| 91 |
+
}
|
backend/src/agent/skills/build-reachy-voice-app/SKILL.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: build-reachy-voice-app
|
| 3 |
+
description: Build a browser-based voice conversation app that connects a Reachy Mini robot to a real-time speech model (OpenAI Realtime API) via WebRTC. Use when the user wants to create, clone, port, or extend a web app that relays audio between Reachy Mini and a voice LLM, manages motion agents (head wobble, antenna oscillation, dances), exposes tool calls from the model, and deploys to Hugging Face Spaces.
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
# Build a Reachy Mini voice conversation web app
|
| 7 |
+
|
| 8 |
+
Reference implementation: [`reachy_mini_minimal_conversation/`](../../../reachy_mini_minimal_conversation/).
|
| 9 |
+
Python reference (runs on the robot, use as source of truth for behavior): [`reachy_mini_conversation_app/`](../../../reachy_mini_conversation_app/).
|
| 10 |
+
|
| 11 |
+
## Architecture: the browser is an audio hub
|
| 12 |
+
|
| 13 |
+
The browser holds **two independent `RTCPeerConnection`s** and bridges their audio tracks:
|
| 14 |
+
|
| 15 |
+
```
|
| 16 |
+
Robot mic ──WebRTC──▶ Browser ──WebRTC──▶ OpenAI Realtime
|
| 17 |
+
│
|
| 18 |
+
Robot speaker ◀──WebRTC── Browser ◀──WebRTC──────┘
|
| 19 |
+
│
|
| 20 |
+
data channel ◀─────┘
|
| 21 |
+
(tool calls)
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
- **PC1 (Robot)**: ReachyMini JS SDK, signaling through a HF-hosted server + HF OAuth. Exposes audio tracks + a data channel for JSON commands (`set_full_target`, `play_sound`, ...).
|
| 25 |
+
- **PC2 (OpenAI)**: Direct WebRTC to `api.openai.com/v1/realtime`. One POST with SDP, no renegotiation, data channel for events/tools/transcripts.
|
| 26 |
+
- **Audio relay**: the robot mic track becomes the OpenAI sender input; the OpenAI output track is pushed back to the robot speaker sender via `replaceTrack()` (the robot sender already exists, you do not add a new one).
|
| 27 |
+
|
| 28 |
+
This is fundamentally different from the Python app (which runs ON the robot and talks to the daemon via local Python APIs). Most Python features must be **reimplemented client-side**, not "proxied".
|
| 29 |
+
|
| 30 |
+
## Stack
|
| 31 |
+
|
| 32 |
+
- Vite + TypeScript, vanilla DOM (no framework).
|
| 33 |
+
- ReachyMini JS SDK loaded from `jsdelivr` as an ES module in `index.html`, exposed as `window.ReachyMini`. Not an npm dep.
|
| 34 |
+
- Deploy target: **Hugging Face Spaces with Docker SDK** (not Static). Nginx serves `dist/` and proxies `/openai/*` -> `api.openai.com` to bypass CORS.
|
| 35 |
+
|
| 36 |
+
## Critical gotchas (read first, save hours)
|
| 37 |
+
|
| 38 |
+
1. **OpenAI has no CORS**. Direct `fetch('https://api.openai.com/v1/realtime', ...)` from a browser always fails. Use a same-origin proxy: Vite dev server proxy + Nginx `/openai/` location in prod. See [deployment.md](deployment.md).
|
| 39 |
+
2. **HF OAuth behaves differently local vs Space**:
|
| 40 |
+
- Local: the user must create an OAuth app manually and paste the `clientId` in your Settings UI. `localhost` requires an explicit clientId.
|
| 41 |
+
- On a Space with `hf_oauth: true` in `README.md` frontmatter: the clientId is available as env vars **server-side only** (`OAUTH_CLIENT_ID`, `SPACE_HOST`, ...). The JS runtime does NOT receive them through `window.huggingface.variables` unless you inject them. Solution: a `docker-entrypoint.d` script that `envsubst`-s placeholders like `__OAUTH_CLIENT_ID__` into `index.html` at container start. See [deployment.md](deployment.md).
|
| 42 |
+
3. **Robot mic track is not exposed by the SDK**. You must reach into `robot._pc.getReceivers()` and pick the audio receiver's track. Capture it right after `startSession()`.
|
| 43 |
+
4. **Robot speaker = `replaceTrack()` on an existing sender**, not `addTrack()`. Grab the audio sender from `robot._pc.getSenders()` and swap its track when OpenAI's `ontrack` fires.
|
| 44 |
+
5. **The daemon cannot play dances by name over WebRTC**. `PlayMoveTaskRequest not yet implemented over WS`. Dances must be evaluated client-side and streamed as `set_full_target` frames. See [protocols.md](protocols.md) and [motion-agents.md](motion-agents.md).
|
| 45 |
+
6. **`set_full_target` uses flat arrays**, not nested: `head` is 16 floats row-major (4x4 homogeneous), `antennas` is `[right, left]` in radians, `body_yaw` is a scalar radian.
|
| 46 |
+
7. **Three motion sources fight each other** (wobbler, antennas, tool-driven poses/dances). Serialize them with explicit guards or dances will jitter.
|
| 47 |
+
8. **WebRTC over WiFi cuts voice** unless you tune Opus FEC, bitrate, and the jitter buffer. See [webrtc-tuning.md](webrtc-tuning.md).
|
| 48 |
+
|
| 49 |
+
## Minimal build order
|
| 50 |
+
|
| 51 |
+
Follow this order. Each step is independently testable.
|
| 52 |
+
|
| 53 |
+
1. **Scaffold**: Vite + TS, `index.html` loading ReachyMini SDK from CDN, a single full-screen button.
|
| 54 |
+
2. **Robot only**: login (HF OAuth), list robots, start session, play a canned head pose via `setHeadPose` to confirm commands work.
|
| 55 |
+
3. **OpenAI only**: input key in UI (stored in `localStorage`), create PC, `addTransceiver('audio', { direction: 'sendrecv' })`, attach `getUserMedia` mic, POST SDP via your proxy, hear the model reply through default speakers.
|
| 56 |
+
4. **Bridge**: replace `getUserMedia` with the robot mic track, and route OpenAI `ontrack` to the robot speaker sender.
|
| 57 |
+
5. **UI state machine**: `signed-out | connected | ready | listening | user-speaking | processing | ai-speaking | error`. Drive CSS animations from states + an `--audio-level` var fed by an `AnalyserNode`.
|
| 58 |
+
6. **Motion agents** (head wobbler, antennas) reading RMS from the OpenAI output track. See [motion-agents.md](motion-agents.md).
|
| 59 |
+
7. **Tools**: declare `move_head` and `play_dance` in `session.update`, handle `response.function_call_arguments.done`, reply with `conversation.item.create` (function_call_output) + `response.create`.
|
| 60 |
+
8. **Reliability** knobs: SDP munging, `playoutDelayHint`, `sender.setParameters`, ICE grace period, silent reconnect, transport pill. See [webrtc-tuning.md](webrtc-tuning.md).
|
| 61 |
+
9. **Deploy**: Docker + Nginx + OAuth envsubst. See [deployment.md](deployment.md).
|
| 62 |
+
|
| 63 |
+
## OpenAI Realtime event lifecycle
|
| 64 |
+
|
| 65 |
+
The state machine is driven by events on the OpenAI data channel, not by audio analysis:
|
| 66 |
+
|
| 67 |
+
| OpenAI event | UI state | Motion agents |
|
| 68 |
+
|---|---|---|
|
| 69 |
+
| `session.created` | `connected` -> `ready` after `session.update` ack | idle |
|
| 70 |
+
| `input_audio_buffer.speech_started` | `user-speaking` | freeze antennas, stop wobbler |
|
| 71 |
+
| `input_audio_buffer.speech_stopped` | `processing` | still frozen |
|
| 72 |
+
| `response.audio.delta` (first one) | `ai-speaking` | enable wobbler, antennas resume |
|
| 73 |
+
| `response.done` | `listening` | back to idle |
|
| 74 |
+
| `response.function_call_arguments.done` | (transient, stay in current) | run tool, reply |
|
| 75 |
+
|
| 76 |
+
Register tools **once** on `session.created` via `session.update`. Do not send `session.update` on every turn, it resets transcription.
|
| 77 |
+
|
| 78 |
+
## Reference files
|
| 79 |
+
|
| 80 |
+
- [architecture.md](architecture.md): detailed 2-hop topology, audio track plumbing code, state machine diagram.
|
| 81 |
+
- [protocols.md](protocols.md): daemon WebRTC data channel commands (what works, what doesn't), OpenAI Realtime events we care about, tool-call envelope format.
|
| 82 |
+
- [motion-agents.md](motion-agents.md): head wobbler math, antenna oscillation, why dances are procedural and how to port them from the Python lib.
|
| 83 |
+
- [webrtc-tuning.md](webrtc-tuning.md): SDP munging for Opus, bitrate caps, jitter buffer hint, ICE handling, auto-reconnect, transport path detection.
|
| 84 |
+
- [deployment.md](deployment.md): Dockerfile + Nginx config, OAuth env injection, HF Space YAML frontmatter, dev proxy.
|
| 85 |
+
|
| 86 |
+
## Things you should NOT do
|
| 87 |
+
|
| 88 |
+
- Do **not** proxy the Python conversation app logic: it runs on the robot, uses a Python SDK with direct ZMQ/ws access to the daemon, and has features (dance library, `play_move`) that are simply not exposed over the public WebRTC data channel.
|
| 89 |
+
- Do **not** rebuild the ReachyMini JS SDK; it handles HF auth, signaling and WebRTC negotiation. Import it from jsdelivr; its internals (`_pc`, `sendRaw`) are stable enough to use for low-level access.
|
| 90 |
+
- Do **not** fetch dance JSONs from random HF datasets; the real dances are procedural Python functions. If you want a quick win, pre-bake the evaluation offline, but prefer porting the `rhythmic_motion.py` primitives to TS.
|
| 91 |
+
- Do **not** use `addTrack` on the robot PC to play TTS audio; the sender already exists, `replaceTrack` is the only path that works.
|
| 92 |
+
- Do **not** rely on `window.huggingface.variables` for OAuth on Spaces; it is not populated automatically.
|
backend/src/agent/skills/build-reachy-voice-app/architecture.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architecture details
|
| 2 |
+
|
| 3 |
+
## Two independent peer connections
|
| 4 |
+
|
| 5 |
+
```
|
| 6 |
+
┌──────────────────────────────────────────────────────────────┐
|
| 7 |
+
│ Browser │
|
| 8 |
+
│ │
|
| 9 |
+
│ ┌─────────────┐ ┌──────────────┐ │
|
| 10 |
+
│ │ PC1 │ │ PC2 │ │
|
| 11 |
+
│ │ (Robot) │ │ (OpenAI) │ │
|
| 12 |
+
│ │ │ │ │ │
|
| 13 |
+
│ │ mic track ─┼─────────────────────────┼─► sender │ │
|
| 14 |
+
│ │ │ (audio relay) │ │ │
|
| 15 |
+
│ │ spk sender ◄─────────────────────────┼── recv track │ │
|
| 16 |
+
│ │ │ │ │ │
|
| 17 |
+
│ │ datachan │ │ datachan │ │
|
| 18 |
+
│ │ (JSON) │ │ (events + │ │
|
| 19 |
+
│ │ │ │ tools) │ │
|
| 20 |
+
│ └──────┬──────┘ └──────┬───────┘ │
|
| 21 |
+
│ │ │ │
|
| 22 |
+
└──────────┼───────────────────────────────────────┼───────────┘
|
| 23 |
+
│ WebRTC WebRTC │
|
| 24 |
+
▼ ▼
|
| 25 |
+
Reachy daemon OpenAI Realtime
|
| 26 |
+
(ws signaling (single POST
|
| 27 |
+
via HF server) SDP handshake)
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
Both PCs are completely independent. Failure of one must not tear down the other. The audio relay is the only thing that links them.
|
| 31 |
+
|
| 32 |
+
## Audio plumbing
|
| 33 |
+
|
| 34 |
+
### Robot mic -> OpenAI
|
| 35 |
+
|
| 36 |
+
After `robot.startSession()`:
|
| 37 |
+
|
| 38 |
+
```ts
|
| 39 |
+
const robotPc: RTCPeerConnection = robot._pc;
|
| 40 |
+
const micReceiver = robotPc.getReceivers().find(r => r.track?.kind === "audio");
|
| 41 |
+
const robotMicTrack = micReceiver!.track;
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
Pass this track to the OpenAI client and `addTrack(robotMicTrack, new MediaStream([robotMicTrack]))` on the OpenAI PC before `createOffer()`. Also add a `RTCRtpTransceiver('audio', { direction: 'sendrecv' })` so the model can send audio back.
|
| 45 |
+
|
| 46 |
+
### OpenAI TTS -> Robot speaker
|
| 47 |
+
|
| 48 |
+
On OpenAI `pc.ontrack`:
|
| 49 |
+
|
| 50 |
+
```ts
|
| 51 |
+
const spkSender = robotPc.getSenders().find(s => s.track?.kind === "audio");
|
| 52 |
+
await spkSender!.replaceTrack(event.track);
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
Do not `addTrack`, the robot already has an audio sender wired to the robot's speaker. Replacing its track is what actually routes audio to the robot.
|
| 56 |
+
|
| 57 |
+
Also attach a silent `<audio autoplay>` element to the OpenAI track so the browser keeps the receiver "alive" (some browsers stop decoding when no sink is attached).
|
| 58 |
+
|
| 59 |
+
## UI state machine
|
| 60 |
+
|
| 61 |
+
Drive the central animated circle from a single `state` variable:
|
| 62 |
+
|
| 63 |
+
```
|
| 64 |
+
signed-out ─login─▶ authenticated ─pickRobot─▶ connected ─start─▶ ready
|
| 65 |
+
│
|
| 66 |
+
(barge-in)
|
| 67 |
+
▼
|
| 68 |
+
listening ◀──response.done── ai-speaking ◀─ processing
|
| 69 |
+
│ ▲ ▲
|
| 70 |
+
▼ │ │
|
| 71 |
+
user-speaking ──speech_stopped──────┴──────────────┘
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
Use `data-state="..."` on the root and express all animations in CSS. Never mutate inline styles for state changes; only for the per-frame `--audio-level` var.
|
| 75 |
+
|
| 76 |
+
## Audio level for UI
|
| 77 |
+
|
| 78 |
+
One `AnalyserNode` per source of interest (robot mic for user speaking glow, OpenAI output for AI speaking glow and motion agents). Compute RMS in a `requestAnimationFrame` loop, smooth with a leaky integrator, write to a CSS var:
|
| 79 |
+
|
| 80 |
+
```ts
|
| 81 |
+
const smoothed = alpha * previous + (1 - alpha) * rms;
|
| 82 |
+
document.documentElement.style.setProperty("--audio-level", smoothed.toFixed(3));
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
## Motion agent coordination
|
| 86 |
+
|
| 87 |
+
Three animation sources can drive the head / antennas simultaneously:
|
| 88 |
+
|
| 89 |
+
1. **Head wobbler**: low-amplitude continuous sway while `ai-speaking`.
|
| 90 |
+
2. **Antennas oscillator**: slow breathing while idle, freeze on user speech, medium amplitude during AI speech.
|
| 91 |
+
3. **Tool-driven**: `move_head` (static pose for N seconds) or `play_dance` (procedural stream at 100 Hz).
|
| 92 |
+
|
| 93 |
+
Resolution rules:
|
| 94 |
+
- When a tool fires, set a `toolLock` boolean. Wobbler and antennas become no-ops while true.
|
| 95 |
+
- Dances take exclusive control at 100 Hz via `set_full_target`. On dance end, restore idle pose before releasing the lock.
|
| 96 |
+
- Never interleave `setHeadPose` calls from different agents in the same tick. The daemon's last-write-wins semantics will cause visible jitter.
|
| 97 |
+
|
| 98 |
+
## Error and reconnect boundaries
|
| 99 |
+
|
| 100 |
+
Distinguish three classes of errors:
|
| 101 |
+
|
| 102 |
+
| Source | Retry? | UX |
|
| 103 |
+
|---|---|---|
|
| 104 |
+
| Robot PC fails | No (fatal) | show error, require full restart |
|
| 105 |
+
| OpenAI PC fails | Yes, 1 silent retry | stay in current state, log warn, escalate to fatal on 2nd failure |
|
| 106 |
+
| Daemon command fails (JSON error on data chan) | No (log) | continue, it's a transient robot issue |
|
| 107 |
+
|
| 108 |
+
Reset the retry counter on `connected`. Reset all timers and locks in the teardown path.
|
backend/src/agent/skills/build-reachy-voice-app/deployment.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Deployment on Hugging Face Spaces
|
| 2 |
+
|
| 3 |
+
## Why Docker, not Static
|
| 4 |
+
|
| 5 |
+
You need:
|
| 6 |
+
1. A same-origin proxy for `api.openai.com` (CORS).
|
| 7 |
+
2. Runtime env-var injection for the HF OAuth clientId.
|
| 8 |
+
|
| 9 |
+
Both require a real server. Use Spaces **Docker SDK** with Nginx; `Static` SDK cannot proxy and has no entrypoint hook.
|
| 10 |
+
|
| 11 |
+
## `README.md` frontmatter
|
| 12 |
+
|
| 13 |
+
```yaml
|
| 14 |
+
---
|
| 15 |
+
title: Reachy Voice Demo
|
| 16 |
+
emoji: 🤖
|
| 17 |
+
colorFrom: indigo
|
| 18 |
+
colorTo: pink
|
| 19 |
+
sdk: docker
|
| 20 |
+
app_port: 8080
|
| 21 |
+
hf_oauth: true
|
| 22 |
+
hf_oauth_scopes:
|
| 23 |
+
- read-repos
|
| 24 |
+
pinned: false
|
| 25 |
+
---
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
Key bits:
|
| 29 |
+
- `sdk: docker`, NOT `static`.
|
| 30 |
+
- `app_port: 8080` (match your Nginx `listen`).
|
| 31 |
+
- `hf_oauth: true` makes `OAUTH_CLIENT_ID`, `SPACE_HOST` and friends available as env vars inside the container.
|
| 32 |
+
|
| 33 |
+
## Dockerfile
|
| 34 |
+
|
| 35 |
+
```dockerfile
|
| 36 |
+
FROM node:20-alpine AS build
|
| 37 |
+
WORKDIR /app
|
| 38 |
+
COPY package*.json ./
|
| 39 |
+
RUN npm ci
|
| 40 |
+
COPY . .
|
| 41 |
+
RUN npm run build
|
| 42 |
+
|
| 43 |
+
FROM nginx:1.27-alpine
|
| 44 |
+
RUN apk add --no-cache bash gettext
|
| 45 |
+
COPY nginx.conf /etc/nginx/nginx.conf
|
| 46 |
+
COPY --from=build /app/dist /usr/share/nginx/html
|
| 47 |
+
COPY docker-entrypoint.d/ /docker-entrypoint.d/
|
| 48 |
+
RUN chmod +x /docker-entrypoint.d/*.sh
|
| 49 |
+
EXPOSE 8080
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
The base `nginx` image already runs `/docker-entrypoint.d/*.sh` before starting Nginx.
|
| 53 |
+
|
| 54 |
+
## Nginx config
|
| 55 |
+
|
| 56 |
+
```nginx
|
| 57 |
+
worker_processes 1;
|
| 58 |
+
events { worker_connections 1024; }
|
| 59 |
+
|
| 60 |
+
http {
|
| 61 |
+
include /etc/nginx/mime.types;
|
| 62 |
+
default_type application/octet-stream;
|
| 63 |
+
sendfile on;
|
| 64 |
+
keepalive_timeout 65;
|
| 65 |
+
|
| 66 |
+
# Hide version
|
| 67 |
+
server_tokens off;
|
| 68 |
+
|
| 69 |
+
server {
|
| 70 |
+
listen 8080 default_server;
|
| 71 |
+
|
| 72 |
+
root /usr/share/nginx/html;
|
| 73 |
+
index index.html;
|
| 74 |
+
|
| 75 |
+
# SPA fallback
|
| 76 |
+
location / {
|
| 77 |
+
try_files $uri $uri/ /index.html;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
# Same-origin proxy to OpenAI (bypasses CORS)
|
| 81 |
+
location /openai/ {
|
| 82 |
+
proxy_pass https://api.openai.com/;
|
| 83 |
+
proxy_set_header Host api.openai.com;
|
| 84 |
+
proxy_ssl_server_name on;
|
| 85 |
+
proxy_ssl_name api.openai.com;
|
| 86 |
+
proxy_http_version 1.1;
|
| 87 |
+
proxy_set_header Connection "";
|
| 88 |
+
proxy_read_timeout 600s;
|
| 89 |
+
proxy_send_timeout 600s;
|
| 90 |
+
|
| 91 |
+
# Preserve client Authorization header
|
| 92 |
+
proxy_pass_request_headers on;
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
Common pitfalls:
|
| 99 |
+
- **421 Misdirected Request**: you forgot `proxy_set_header Host` or `proxy_ssl_name`. OpenAI refuses requests whose SNI/host doesn't match.
|
| 100 |
+
- **500 from Nginx**: missing `resolver` when upstream is HTTPS. Fix: either add `resolver 8.8.8.8 valid=30s;` or ensure `proxy_pass https://api.openai.com/` (without variable) is resolved at boot.
|
| 101 |
+
- **401 pass-through**: good, your proxy works; the key is wrong.
|
| 102 |
+
|
| 103 |
+
## OAuth env injection
|
| 104 |
+
|
| 105 |
+
HF sets env vars server-side but your bundle is static JS. Inject them at container start with `envsubst`.
|
| 106 |
+
|
| 107 |
+
### `index.html` placeholders
|
| 108 |
+
|
| 109 |
+
```html
|
| 110 |
+
<meta name="oauth-client-id" content="__OAUTH_CLIENT_ID__" />
|
| 111 |
+
<meta name="oauth-scopes" content="__OAUTH_SCOPES__" />
|
| 112 |
+
<meta name="space-host" content="__SPACE_HOST__" />
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
Read them in JS:
|
| 116 |
+
|
| 117 |
+
```ts
|
| 118 |
+
function meta(name: string, fallback = "") {
|
| 119 |
+
const el = document.querySelector(`meta[name="${name}"]`) as HTMLMetaElement | null;
|
| 120 |
+
const v = el?.content ?? "";
|
| 121 |
+
// the placeholder survives in dev where envsubst hasn't run
|
| 122 |
+
return v.startsWith("__") && v.endsWith("__") ? fallback : v;
|
| 123 |
+
}
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
### `docker-entrypoint.d/10-envsubst.sh`
|
| 127 |
+
|
| 128 |
+
```bash
|
| 129 |
+
#!/bin/bash
|
| 130 |
+
set -euo pipefail
|
| 131 |
+
|
| 132 |
+
TARGET=/usr/share/nginx/html/index.html
|
| 133 |
+
|
| 134 |
+
: "${OAUTH_CLIENT_ID:=}"
|
| 135 |
+
: "${OAUTH_SCOPES:=read-repos}"
|
| 136 |
+
: "${SPACE_HOST:=}"
|
| 137 |
+
|
| 138 |
+
export OAUTH_CLIENT_ID OAUTH_SCOPES SPACE_HOST
|
| 139 |
+
|
| 140 |
+
envsubst '${OAUTH_CLIENT_ID} ${OAUTH_SCOPES} ${SPACE_HOST}' \
|
| 141 |
+
< "$TARGET" > "${TARGET}.tmp" && mv "${TARGET}.tmp" "$TARGET"
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
Pass the variable list to `envsubst` explicitly; without it, Nginx config `$uri` would also be substituted.
|
| 145 |
+
|
| 146 |
+
## Dev mode (Vite)
|
| 147 |
+
|
| 148 |
+
Skip Nginx. Use Vite's proxy in `vite.config.ts`:
|
| 149 |
+
|
| 150 |
+
```ts
|
| 151 |
+
export default defineConfig({
|
| 152 |
+
server: {
|
| 153 |
+
proxy: {
|
| 154 |
+
"/openai": {
|
| 155 |
+
target: "https://api.openai.com",
|
| 156 |
+
changeOrigin: true,
|
| 157 |
+
rewrite: (p) => p.replace(/^\/openai/, ""),
|
| 158 |
+
secure: true,
|
| 159 |
+
},
|
| 160 |
+
},
|
| 161 |
+
},
|
| 162 |
+
});
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
Local OAuth does NOT work out of the box because HF requires a registered redirect URI. Either:
|
| 166 |
+
- register `http://localhost:5173` as a redirect in your own HF OAuth app and paste the clientId in a Settings UI (store in `localStorage`);
|
| 167 |
+
- or skip OAuth locally and hardcode a robot URL for dev.
|
| 168 |
+
|
| 169 |
+
## Build + push
|
| 170 |
+
|
| 171 |
+
The repo should have two remotes (see `git-remotes-safety.mdc` in this workspace):
|
| 172 |
+
- `github` (public upstream): never force-push.
|
| 173 |
+
- `space` (HF Space): force-push on your own namespace is fine.
|
| 174 |
+
|
| 175 |
+
```bash
|
| 176 |
+
npm run build # sanity check
|
| 177 |
+
git add -A && git commit -m "..."
|
| 178 |
+
git push space feat/new-ui:main --force-with-lease
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
If the Space doesn't rebuild, push an empty commit:
|
| 182 |
+
|
| 183 |
+
```bash
|
| 184 |
+
git commit --allow-empty -m "chore: trigger rebuild"
|
| 185 |
+
git push space feat/new-ui:main --force-with-lease
|
| 186 |
+
```
|
| 187 |
+
|
| 188 |
+
## Post-deploy smoke test
|
| 189 |
+
|
| 190 |
+
- Open the Space, sign in via HF: should NOT say "Missing clientId".
|
| 191 |
+
- List robots: should show your robot (requires the robot to be online and authed with the same HF account).
|
| 192 |
+
- Start session: the central circle should animate `listening`.
|
| 193 |
+
- Speak: state -> `user-speaking` -> `processing` -> `ai-speaking`. Voice reaches the robot's speakers.
|
| 194 |
+
- Say "nod yes": tool fires, head nods.
|
| 195 |
+
- Say "dance dizzy spin": tool fires, dance plays at 100 Hz.
|
| 196 |
+
- Transport pill shows `LAN` on local network, `Relay` through a mobile hotspot.
|
backend/src/agent/skills/build-reachy-voice-app/motion-agents.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Motion agents
|
| 2 |
+
|
| 3 |
+
Three concurrent agents animate the robot. All of them ultimately write to the daemon via `set_full_target` (or equivalent) and must not fight.
|
| 4 |
+
|
| 5 |
+
## Head wobbler
|
| 6 |
+
|
| 7 |
+
Goal: gentle head sway while the AI is speaking, amplitude proportional to TTS loudness.
|
| 8 |
+
|
| 9 |
+
Recipe:
|
| 10 |
+
- `AnalyserNode` on the OpenAI output track, read time-domain data in a `requestAnimationFrame` loop, compute RMS.
|
| 11 |
+
- Smooth with a leaky integrator (`alpha ~0.12`). Clip to [0, 1].
|
| 12 |
+
- Drive two lissajous-style sinusoids with independent frequencies for roll/pitch, amplitude modulated by the smoothed RMS.
|
| 13 |
+
- Ship a head pose via `robot.setHeadPose(matrix)` every frame, or stop if `audioLevel < epsilon`.
|
| 14 |
+
|
| 15 |
+
```ts
|
| 16 |
+
const roll = maxRoll * level * Math.sin(2 * Math.PI * 0.9 * t + 0.3);
|
| 17 |
+
const pitch = maxPitch * level * Math.sin(2 * Math.PI * 1.3 * t + 1.1);
|
| 18 |
+
// yaw = 0, small translation optional
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
Pause (emit identity pose or skip emission) when:
|
| 22 |
+
- tool lock is active
|
| 23 |
+
- dance is playing
|
| 24 |
+
- state is `user-speaking` (barge-in)
|
| 25 |
+
|
| 26 |
+
## Antennas oscillator
|
| 27 |
+
|
| 28 |
+
Goal: idle "breathing" + reactive motion during AI speech.
|
| 29 |
+
|
| 30 |
+
Two modes:
|
| 31 |
+
- **Idle**: slow symmetric oscillation, `antennas = [A*sin(w*t), A*sin(w*t + pi)]` with small `A` (0.1 rad), period ~4 s.
|
| 32 |
+
- **Speaking**: faster, audio-reactive amplitude, optional asymmetry.
|
| 33 |
+
|
| 34 |
+
Freeze to `[0, 0]` during `user-speaking`. Release tool lock by restoring idle mode gradually over ~300 ms.
|
| 35 |
+
|
| 36 |
+
## Dances (tool-driven)
|
| 37 |
+
|
| 38 |
+
Dances in the Reachy ecosystem are **procedural Python functions**, not recorded keyframes. They live in `reachy_mini_dances_library/`:
|
| 39 |
+
|
| 40 |
+
- `rhythmic_motion.py`: primitives
|
| 41 |
+
- `oscillation_motion(t_beats, {amplitude, subcycles_per_beat, phase_offset, waveform})` with waveforms `sin | cos | square | triangle | sawtooth`
|
| 42 |
+
- `transient_motion(t_beats, {amplitude, duration, delay, repeat_every})` with smoothstep easing
|
| 43 |
+
- atomic helpers: `atomic_x_pos`, `atomic_roll`, ..., `atomic_antennas_wiggle`, ...
|
| 44 |
+
- `combine_offsets([...])`: sum of `MoveOffsets`
|
| 45 |
+
- `collection/dance.py`: ~19 named dances composed from the primitives (`move_simple_nod`, `move_yeah_nod`, `move_dizzy_spin`, `move_jackson_square`, ...).
|
| 46 |
+
- `dance_move.py`: wraps a move function with its parameters, `evaluate(t_seconds)` converts `t_seconds` to `t_beats` (with a global tempo, typically ~1.9 beats/s) and returns a 4x4 head matrix + antennas + body_yaw via `create_head_pose`.
|
| 47 |
+
|
| 48 |
+
### Port strategy (recommended)
|
| 49 |
+
|
| 50 |
+
Do not fetch JSON. Port the primitives + collection to TypeScript. Then:
|
| 51 |
+
|
| 52 |
+
```ts
|
| 53 |
+
class DancePlayer {
|
| 54 |
+
async play(name: string) {
|
| 55 |
+
const move = AVAILABLE_MOVES[name];
|
| 56 |
+
const durationS = (1 / TEMPO_BPS) * move.defaultDurationBeats;
|
| 57 |
+
const start = performance.now();
|
| 58 |
+
const tick = () => {
|
| 59 |
+
const t = (performance.now() - start) / 1000;
|
| 60 |
+
if (t >= durationS) return this.stop();
|
| 61 |
+
const tBeats = t * TEMPO_BPS;
|
| 62 |
+
const offsets = move.fn(tBeats, move.params);
|
| 63 |
+
const head = createHeadPose(offsets.x, offsets.y, offsets.z,
|
| 64 |
+
offsets.roll, offsets.pitch, offsets.yaw);
|
| 65 |
+
robot.sendRaw({
|
| 66 |
+
type: "set_full_target",
|
| 67 |
+
head: Array.from(head), // 16 floats
|
| 68 |
+
antennas: [offsets.antennas[0], offsets.antennas[1]],
|
| 69 |
+
body_yaw: 0,
|
| 70 |
+
});
|
| 71 |
+
this.rafId = requestAnimationFrame(tick);
|
| 72 |
+
};
|
| 73 |
+
tick();
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
Ship at 100 Hz either via `setInterval(10)` or via a `requestAnimationFrame` loop with a 10 ms check. 60 Hz is acceptable but feels less smooth on fast dances.
|
| 79 |
+
|
| 80 |
+
### createHeadPose (port of scipy Euler -> matrix)
|
| 81 |
+
|
| 82 |
+
`scipy.spatial.transform.Rotation.from_euler("xyz", [roll, pitch, yaw])` builds `Rx @ Ry @ Rz` with intrinsic rotations. In TS:
|
| 83 |
+
|
| 84 |
+
```ts
|
| 85 |
+
function createHeadPose(x: number, y: number, z: number,
|
| 86 |
+
roll: number, pitch: number, yaw: number): Float32Array {
|
| 87 |
+
const cr = Math.cos(roll), sr = Math.sin(roll);
|
| 88 |
+
const cp = Math.cos(pitch), sp = Math.sin(pitch);
|
| 89 |
+
const cy = Math.cos(yaw), sy = Math.sin(yaw);
|
| 90 |
+
// R = Rx(roll) @ Ry(pitch) @ Rz(yaw)
|
| 91 |
+
const r00 = cp * cy;
|
| 92 |
+
const r01 = -cp * sy;
|
| 93 |
+
const r02 = sp;
|
| 94 |
+
const r10 = cr * sy + sr * sp * cy;
|
| 95 |
+
const r11 = cr * cy - sr * sp * sy;
|
| 96 |
+
const r12 = -sr * cp;
|
| 97 |
+
const r20 = sr * sy - cr * sp * cy;
|
| 98 |
+
const r21 = sr * cy + cr * sp * sy;
|
| 99 |
+
const r22 = cr * cp;
|
| 100 |
+
const m = new Float32Array(16);
|
| 101 |
+
m[0]=r00; m[1]=r01; m[2]=r02; m[3]=x;
|
| 102 |
+
m[4]=r10; m[5]=r11; m[6]=r12; m[7]=y;
|
| 103 |
+
m[8]=r20; m[9]=r21; m[10]=r22; m[11]=z;
|
| 104 |
+
m[12]=0; m[13]=0; m[14]=0; m[15]=1;
|
| 105 |
+
return m;
|
| 106 |
+
}
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
Cross-check against Python: pick a few `(x,y,z,r,p,y)` triplets, compute in Python, paste the expected matrix into a unit test.
|
| 110 |
+
|
| 111 |
+
### Alternative: pre-baked JSON
|
| 112 |
+
|
| 113 |
+
If porting feels heavy, run the Python evaluator offline for each dance at 100 Hz, dump to JSON (`[{head: number[16], antennas: [r,l], body_yaw}, ...]`), ship them statically. Simpler, but you lose parameterisation.
|
| 114 |
+
|
| 115 |
+
## Tool-driven poses (`move_head`)
|
| 116 |
+
|
| 117 |
+
Tiny tool, much simpler than dances. Keep a dict of named poses:
|
| 118 |
+
|
| 119 |
+
```ts
|
| 120 |
+
const HEAD_POSES = {
|
| 121 |
+
nod_yes: { x: 0, y: 0, z: 0, roll: 0, pitch: 0.3, yaw: 0 },
|
| 122 |
+
nod_no: { x: 0, y: 0, z: 0, roll: 0, pitch: 0, yaw: 0.4 },
|
| 123 |
+
look_left: { x: 0, y: 0, z: 0, roll: 0, pitch: 0, yaw: 0.5 },
|
| 124 |
+
look_right: { x: 0, y: 0, z: 0, roll: 0, pitch: 0, yaw:-0.5 },
|
| 125 |
+
tilt: { x: 0, y: 0, z: 0, roll: 0.4, pitch: 0, yaw: 0 },
|
| 126 |
+
};
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
Set tool lock, emit pose, wait `duration_seconds`, restore identity pose, release lock. Reply the tool call once the motion completes so the model can reference it in the next turn.
|
backend/src/agent/skills/build-reachy-voice-app/protocols.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Protocol details
|
| 2 |
+
|
| 3 |
+
## Reachy Mini daemon data channel
|
| 4 |
+
|
| 5 |
+
Send JSON messages through the robot's data channel. The SDK wraps this as `robot.sendRaw(obj)`. The daemon accepts (verified in `reachy_mini/io/protocol.py` and `ws_server.py`):
|
| 6 |
+
|
| 7 |
+
| Command | Payload | Notes |
|
| 8 |
+
|---|---|---|
|
| 9 |
+
| `set_target` | `{head, antennas, body_yaw}` keyframe | interpolated by daemon |
|
| 10 |
+
| `set_full_target` | `{head: float[16], antennas: [r,l], body_yaw}` | **this is the fast path**, no interpolation, ship at 100 Hz for smooth motion |
|
| 11 |
+
| `set_head_joints` | per-joint angles | rarely needed |
|
| 12 |
+
| `set_body_yaw` | scalar radians | body turntable |
|
| 13 |
+
| `set_antennas` | `[right, left]` radians | |
|
| 14 |
+
| `goto_target` | keyframe + duration | daemon blends |
|
| 15 |
+
| `wake_up` / `goto_sleep` | `{}` | safe boot/shutdown poses |
|
| 16 |
+
| `play_sound` | base64 WAV or URL | daemon speaker out |
|
| 17 |
+
| `set_motor_mode` / `set_torque` | per motor | |
|
| 18 |
+
|
| 19 |
+
### Not supported over the data channel
|
| 20 |
+
|
| 21 |
+
- **`PlayMoveTaskRequest`** (dances by name): the daemon logs `"PlayMoveTaskRequest not yet implemented over WS"`. Never rely on this.
|
| 22 |
+
- Any task-style long-running request. The data channel is for state updates, not RPC with awaitable results.
|
| 23 |
+
|
| 24 |
+
### `set_full_target` format
|
| 25 |
+
|
| 26 |
+
Head is a 4x4 homogeneous transformation matrix in **row-major** order, flattened to 16 floats. Construct it with the same convention as `reachy_mini.utils.create_head_pose`:
|
| 27 |
+
|
| 28 |
+
```
|
| 29 |
+
T = Translation(x, y, z) @ Rx(roll) @ Ry(pitch) @ Rz(yaw)
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
In TypeScript, compose three 3x3 rotations with scipy's intrinsic-XYZ convention, insert into the top-left of a 4x4, translation in the last column, `[0,0,0,1]` at the bottom row, then flatten row-major.
|
| 33 |
+
|
| 34 |
+
Units: meters for translation, radians for rotations and antennas.
|
| 35 |
+
|
| 36 |
+
## OpenAI Realtime events we actually use
|
| 37 |
+
|
| 38 |
+
Over the OpenAI data channel:
|
| 39 |
+
|
| 40 |
+
### Outbound (we send)
|
| 41 |
+
|
| 42 |
+
- `session.update` with `{type: "realtime", model, voice, instructions, tools, turn_detection, input_audio_transcription}` **once** on `session.created`. Re-sending this mid-conversation resets transcription.
|
| 43 |
+
- `conversation.item.create` with a `function_call_output` item, to reply to a tool call.
|
| 44 |
+
- `response.create` right after, to let the model speak again.
|
| 45 |
+
|
| 46 |
+
### Inbound (we listen)
|
| 47 |
+
|
| 48 |
+
| Event | Action |
|
| 49 |
+
|---|---|
|
| 50 |
+
| `session.created` | send `session.update`, set state `connected` |
|
| 51 |
+
| `session.updated` | state `ready` |
|
| 52 |
+
| `input_audio_buffer.speech_started` | state `user-speaking`, freeze motion |
|
| 53 |
+
| `input_audio_buffer.speech_stopped` | state `processing` |
|
| 54 |
+
| `response.audio.delta` (first) | state `ai-speaking`, enable wobbler |
|
| 55 |
+
| `response.audio_transcript.delta` | optional: live caption |
|
| 56 |
+
| `response.function_call_arguments.delta` | accumulate into `toolCalls[callId].arguments` |
|
| 57 |
+
| `response.function_call_arguments.done` | parse JSON args, execute tool, reply |
|
| 58 |
+
| `response.done` | state `listening` |
|
| 59 |
+
| `error` | log; do not tear down unless data channel closes |
|
| 60 |
+
|
| 61 |
+
### Tool call envelope
|
| 62 |
+
|
| 63 |
+
Declare tools in `session.update`:
|
| 64 |
+
|
| 65 |
+
```ts
|
| 66 |
+
{
|
| 67 |
+
type: "function",
|
| 68 |
+
name: "move_head",
|
| 69 |
+
description: "Move the robot head to a named pose for N seconds",
|
| 70 |
+
parameters: {
|
| 71 |
+
type: "object",
|
| 72 |
+
properties: {
|
| 73 |
+
pose: { type: "string", enum: ["nod_yes", "nod_no", "look_left", "look_right", "tilt"] },
|
| 74 |
+
duration_seconds: { type: "number", minimum: 0.3, maximum: 5 }
|
| 75 |
+
},
|
| 76 |
+
required: ["pose"]
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
Handle:
|
| 82 |
+
|
| 83 |
+
```ts
|
| 84 |
+
dc.addEventListener("message", (e) => {
|
| 85 |
+
const msg = JSON.parse(e.data);
|
| 86 |
+
if (msg.type === "response.function_call_arguments.done") {
|
| 87 |
+
const args = JSON.parse(msg.arguments);
|
| 88 |
+
const result = await executeTool(msg.name, args);
|
| 89 |
+
dc.send(JSON.stringify({
|
| 90 |
+
type: "conversation.item.create",
|
| 91 |
+
item: {
|
| 92 |
+
type: "function_call_output",
|
| 93 |
+
call_id: msg.call_id,
|
| 94 |
+
output: JSON.stringify(result),
|
| 95 |
+
}
|
| 96 |
+
}));
|
| 97 |
+
dc.send(JSON.stringify({ type: "response.create" }));
|
| 98 |
+
}
|
| 99 |
+
});
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
`call_id` **must** match. Always respond, even on error (set `output: JSON.stringify({ error: "..." })`), otherwise the model stalls.
|
| 103 |
+
|
| 104 |
+
## OpenAI Realtime WebRTC handshake
|
| 105 |
+
|
| 106 |
+
One POST, SDP as body, not JSON:
|
| 107 |
+
|
| 108 |
+
```ts
|
| 109 |
+
const res = await fetch(`${proxy}/openai/v1/realtime?model=gpt-realtime`, {
|
| 110 |
+
method: "POST",
|
| 111 |
+
headers: {
|
| 112 |
+
"Authorization": `Bearer ${apiKey}`,
|
| 113 |
+
"Content-Type": "application/sdp",
|
| 114 |
+
},
|
| 115 |
+
body: offer.sdp,
|
| 116 |
+
});
|
| 117 |
+
const answerSdp = await res.text();
|
| 118 |
+
await pc.setRemoteDescription({ type: "answer", sdp: answerSdp });
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
- `proxy` is `""` in prod (same origin, Nginx strips `/openai/`) and `""` in dev (Vite proxy rewrites).
|
| 122 |
+
- Status codes seen in the wild: 401 (bad key), 421 (wrong host header via proxy, check your Nginx config), 500 (Nginx upstream issue), 400 (bad SDP, usually m-line order).
|
| 123 |
+
|
| 124 |
+
## Robot daemon signaling
|
| 125 |
+
|
| 126 |
+
You never touch this directly. The ReachyMini SDK handles:
|
| 127 |
+
- listing robots via HF-hosted server
|
| 128 |
+
- OAuth token exchange
|
| 129 |
+
- WebRTC offer/answer and ICE through the HF signaling relay
|
| 130 |
+
- creating the PC, the data channel, and the initial audio transceivers
|
| 131 |
+
|
| 132 |
+
You only consume `robot._pc`, `robot.startSession()`, `robot.setHeadPose(...)`, `robot.setAntennas(...)`, `robot.sendRaw(...)`, `robot.login(...)`, `robot.listRobots()`.
|
backend/src/agent/skills/build-reachy-voice-app/webrtc-tuning.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# WebRTC reliability tuning
|
| 2 |
+
|
| 3 |
+
Problem: we ship voice over WiFi with the browser as an audio hub. Default WebRTC settings produce audible cutouts when signal dips. The following four knobs, applied together, eliminate most mid-sentence cutouts on typical home WiFi. All four are applied on the OpenAI PC; the robot PC is local to the network (low RTT, no tuning needed).
|
| 4 |
+
|
| 5 |
+
## 1. SDP munging: Opus FEC + DTX + bitrate
|
| 6 |
+
|
| 7 |
+
Patch the `a=fmtp:<pt>` line for Opus in **both** the local offer and the remote answer (OpenAI's SDP defaults are not great for lossy links).
|
| 8 |
+
|
| 9 |
+
```ts
|
| 10 |
+
const OPUS_FMTP_OVERRIDES = {
|
| 11 |
+
minptime: "10",
|
| 12 |
+
useinbandfec: "1", // in-band FEC, costs ~10% bitrate, recovers single-packet loss
|
| 13 |
+
usedtx: "0", // disable discontinuous transmission, TTS hates silence gaps
|
| 14 |
+
stereo: "0",
|
| 15 |
+
"sprop-stereo": "0",
|
| 16 |
+
maxaveragebitrate: "32000",
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
function patchOpusFmtp(sdp: string): string {
|
| 20 |
+
// find opus payload type via "a=rtpmap:<pt> opus/48000/2"
|
| 21 |
+
// rewrite the matching "a=fmtp:<pt> ..." line with OPUS_FMTP_OVERRIDES
|
| 22 |
+
// if no fmtp line exists, inject one right after the rtpmap
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
const rawOffer = await pc.createOffer();
|
| 26 |
+
await pc.setLocalDescription({ type: "offer", sdp: patchOpusFmtp(rawOffer.sdp!) });
|
| 27 |
+
// ... POST to OpenAI ...
|
| 28 |
+
const rawAnswer = await res.text();
|
| 29 |
+
await pc.setRemoteDescription({ type: "answer", sdp: patchOpusFmtp(rawAnswer) });
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
Both patches matter. Patching the offer tells OpenAI what we accept; patching the answer forces the decoder and our encoder into the expected mode.
|
| 33 |
+
|
| 34 |
+
## 2. `playoutDelayHint` on inbound receivers
|
| 35 |
+
|
| 36 |
+
Increases the jitter buffer from ~40 ms default to 150 ms. Adds some latency but absorbs WiFi burst losses.
|
| 37 |
+
|
| 38 |
+
```ts
|
| 39 |
+
pc.ontrack = (event) => {
|
| 40 |
+
try {
|
| 41 |
+
(event.receiver as any).playoutDelayHint = 0.15;
|
| 42 |
+
} catch {}
|
| 43 |
+
// ... route to robot speaker ...
|
| 44 |
+
};
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
150 ms is a good trade-off for voice chat; anything past 300 ms feels laggy.
|
| 48 |
+
|
| 49 |
+
## 3. `sender.setParameters` bitrate cap
|
| 50 |
+
|
| 51 |
+
Caps the outgoing audio bitrate to prevent WiFi saturation when several devices share the AP. Has to be done after `setLocalDescription`.
|
| 52 |
+
|
| 53 |
+
```ts
|
| 54 |
+
async function capSenderBitrate(pc: RTCPeerConnection, maxBitrate: number) {
|
| 55 |
+
const sender = pc.getSenders().find(s => s.track?.kind === "audio");
|
| 56 |
+
if (!sender) return;
|
| 57 |
+
const params = sender.getParameters();
|
| 58 |
+
if (!params.encodings || !params.encodings.length) {
|
| 59 |
+
params.encodings = [{}];
|
| 60 |
+
}
|
| 61 |
+
params.encodings[0].maxBitrate = maxBitrate;
|
| 62 |
+
await sender.setParameters(params);
|
| 63 |
+
}
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
Call with `32000` to match the Opus `maxaveragebitrate`.
|
| 67 |
+
|
| 68 |
+
## 4. Robust ICE state handling
|
| 69 |
+
|
| 70 |
+
Default behavior: `iceConnectionState === "disconnected"` fires on any 2-second gap and panics. Over WiFi this happens all the time and auto-heals.
|
| 71 |
+
|
| 72 |
+
```ts
|
| 73 |
+
const ICE_DISCONNECT_GRACE_MS = 5000;
|
| 74 |
+
let iceGraceTimer: number | null = null;
|
| 75 |
+
|
| 76 |
+
pc.oniceconnectionstatechange = () => {
|
| 77 |
+
const s = pc.iceConnectionState;
|
| 78 |
+
if (s === "connected" || s === "completed") {
|
| 79 |
+
if (iceGraceTimer !== null) { clearTimeout(iceGraceTimer); iceGraceTimer = null; }
|
| 80 |
+
return;
|
| 81 |
+
}
|
| 82 |
+
if (s === "failed") {
|
| 83 |
+
if (iceGraceTimer !== null) { clearTimeout(iceGraceTimer); iceGraceTimer = null; }
|
| 84 |
+
escalateToError(new Error("ICE failed"));
|
| 85 |
+
return;
|
| 86 |
+
}
|
| 87 |
+
if (s === "disconnected") {
|
| 88 |
+
if (iceGraceTimer !== null) return;
|
| 89 |
+
iceGraceTimer = window.setTimeout(() => {
|
| 90 |
+
iceGraceTimer = null;
|
| 91 |
+
if (pc.iceConnectionState === "disconnected") {
|
| 92 |
+
escalateToError(new Error("ICE disconnected beyond grace"));
|
| 93 |
+
}
|
| 94 |
+
}, ICE_DISCONNECT_GRACE_MS);
|
| 95 |
+
}
|
| 96 |
+
};
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
## Silent auto-reconnect
|
| 100 |
+
|
| 101 |
+
If ICE escalates, close the OpenAI PC (not the robot PC) and retry once with the same robot mic track. If the second attempt also fails, bubble the error.
|
| 102 |
+
|
| 103 |
+
```ts
|
| 104 |
+
let reconnecting = false, reconnectAttempts = 0;
|
| 105 |
+
|
| 106 |
+
async function tryReconnectOpenai(robotMicTrack: MediaStreamTrack, cause: Error) {
|
| 107 |
+
if (reconnecting) return;
|
| 108 |
+
if (reconnectAttempts >= 1) { onFatalError(cause); return; }
|
| 109 |
+
reconnecting = true;
|
| 110 |
+
reconnectAttempts += 1;
|
| 111 |
+
pauseMotionAgents();
|
| 112 |
+
openai.close();
|
| 113 |
+
await new Promise(r => setTimeout(r, 500));
|
| 114 |
+
try {
|
| 115 |
+
await connectOpenai(robotMicTrack);
|
| 116 |
+
// reset attempts on `connected`
|
| 117 |
+
} catch (err) {
|
| 118 |
+
onFatalError(err as Error);
|
| 119 |
+
} finally {
|
| 120 |
+
reconnecting = false;
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
Reset `reconnectAttempts = 0` whenever the new PC reaches `connected`. Reset everything in the `teardown()` path.
|
| 126 |
+
|
| 127 |
+
## Transport path detection (LAN / Direct / Relay pill)
|
| 128 |
+
|
| 129 |
+
Useful to diagnose WiFi issues: tells the user whether the robot PC went peer-to-peer on the LAN or got relayed through a TURN server.
|
| 130 |
+
|
| 131 |
+
Poll `robot._pc.getStats()` every 3 seconds and read the selected candidate pair:
|
| 132 |
+
|
| 133 |
+
```ts
|
| 134 |
+
type TransportKind = "lan" | "direct" | "relay" | "checking";
|
| 135 |
+
|
| 136 |
+
function classify(stats: RTCStatsReport): TransportKind {
|
| 137 |
+
let pair: any;
|
| 138 |
+
stats.forEach(r => {
|
| 139 |
+
if (r.type === "candidate-pair" && (r.nominated || r.state === "succeeded") && r.selected !== false) {
|
| 140 |
+
pair = r;
|
| 141 |
+
}
|
| 142 |
+
});
|
| 143 |
+
if (!pair) return "checking";
|
| 144 |
+
const local = stats.get(pair.localCandidateId);
|
| 145 |
+
const remote = stats.get(pair.remoteCandidateId);
|
| 146 |
+
if (!local || !remote) return "checking";
|
| 147 |
+
if (local.candidateType === "relay" || remote.candidateType === "relay") return "relay";
|
| 148 |
+
if (local.candidateType === "host" && remote.candidateType === "host") return "lan";
|
| 149 |
+
return "direct"; // srflx, prflx
|
| 150 |
+
}
|
| 151 |
+
```
|
| 152 |
+
|
| 153 |
+
Drive a small header pill with CSS classes `transport-{lan|direct|relay|checking}` (green / blue / amber / blinking).
|
| 154 |
+
|
| 155 |
+
Start the monitor after `startSession()` resolves (give ICE ~600 ms to pick a pair), stop it in teardown.
|
| 156 |
+
|
| 157 |
+
## What NOT to bother with
|
| 158 |
+
|
| 159 |
+
- Custom TURN servers: the HF signaling relay already provides one; switching gains nothing unless you self-host.
|
| 160 |
+
- Disabling audio-level event updates: they are cheap.
|
| 161 |
+
- `RTCPeerConnection.restartIce()`: tested, does not fix ICE failures in this setup; rebuilding the PC is cleaner.
|
| 162 |
+
- FEC on the robot PC: it's on the local network, RTT < 10 ms, losses are zero. Only tune the OpenAI PC.
|
backend/src/agent/system-prompt.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getPrimarySkill, listSkillDocs } from "./skill-loader.js";
|
| 2 |
+
|
| 3 |
+
const BASE_PROMPT = `You are **Reachy Vibe Coder**, an agent that helps users build browser-based
|
| 4 |
+
voice conversation apps for the Reachy Mini robot. The user describes what
|
| 5 |
+
they want, you produce the source files, the user previews them live in a
|
| 6 |
+
sandboxed iframe and can publish the result to a Hugging Face Space in one
|
| 7 |
+
click.
|
| 8 |
+
|
| 9 |
+
## How you work
|
| 10 |
+
|
| 11 |
+
You maintain a small **virtual file system** for the current project. Every
|
| 12 |
+
file mutation MUST go through one of these tools:
|
| 13 |
+
|
| 14 |
+
- **write_file(path, content)** — Create or fully overwrite a file. Use for
|
| 15 |
+
new files and when a rewrite is cleaner than a surgical patch.
|
| 16 |
+
- **edit_file(path, oldString, newString)** — Replace an *exact* substring.
|
| 17 |
+
Use for small targeted edits. If \`oldString\` is not unique the edit
|
| 18 |
+
fails - provide more context.
|
| 19 |
+
- **delete_file(path)** — Remove a file.
|
| 20 |
+
- **read_file(path)** — Inspect a file before editing.
|
| 21 |
+
- **list_files()** — List the files currently in the virtual FS.
|
| 22 |
+
- **read_skill_doc(topic)** — Load a detailed skill reference page. Use
|
| 23 |
+
*before* writing code whenever the topic is non-trivial.
|
| 24 |
+
|
| 25 |
+
The virtual FS is rendered as a file tree in the UI and each file is shown
|
| 26 |
+
in a Monaco editor. A sandboxed iframe re-runs the app whenever
|
| 27 |
+
\`index.html\` changes.
|
| 28 |
+
|
| 29 |
+
## Target project shape
|
| 30 |
+
|
| 31 |
+
A typical Reachy Mini voice app produced by you looks like:
|
| 32 |
+
|
| 33 |
+
\`\`\`
|
| 34 |
+
index.html ReachyMini SDK from CDN + OAuth injection placeholders
|
| 35 |
+
src/main.ts entry point (PC1 robot + PC2 OpenAI + state machine)
|
| 36 |
+
src/style.css
|
| 37 |
+
src/<module>.ts motion agents, OpenAI glue, etc.
|
| 38 |
+
package.json vite + TypeScript, no framework deps
|
| 39 |
+
vite.config.ts dev proxy for /openai/*
|
| 40 |
+
nginx.conf prod proxy for /openai/* (same shape as dev)
|
| 41 |
+
Dockerfile multi-stage Vite build + Nginx runtime
|
| 42 |
+
docker-entrypoint.d/
|
| 43 |
+
10-inject-hf-vars.sh envsubst OAuth vars at container start
|
| 44 |
+
README.md HF Space frontmatter (sdk: docker, hf_oauth: true)
|
| 45 |
+
\`\`\`
|
| 46 |
+
|
| 47 |
+
The reference implementation is \`reachy_mini_minimal_conversation/\` and the
|
| 48 |
+
full conventions live in the embedded **build-reachy-voice-app** skill (see
|
| 49 |
+
below - call \`read_skill_doc\` for the chapter you need).
|
| 50 |
+
|
| 51 |
+
## Working loop
|
| 52 |
+
|
| 53 |
+
1. Start with a \`<thinking>\` block: what is the user asking, what files are
|
| 54 |
+
already there (call \`list_files\` if uncertain), which skill docs do you
|
| 55 |
+
need to re-read before editing.
|
| 56 |
+
2. Call \`read_skill_doc\` for every relevant chapter (architecture, webrtc
|
| 57 |
+
tuning, deployment, …) *before* writing non-trivial code.
|
| 58 |
+
3. Mutate the virtual FS through the tools. Prefer \`edit_file\` over
|
| 59 |
+
\`write_file\` for small changes.
|
| 60 |
+
4. Finish with a short plain-text summary of what you did and what the user
|
| 61 |
+
should test next.
|
| 62 |
+
|
| 63 |
+
## Non-negotiables
|
| 64 |
+
|
| 65 |
+
- OpenAI's Realtime API has no CORS - every fetch to \`api.openai.com\` MUST
|
| 66 |
+
go through a same-origin proxy (Vite dev + Nginx prod). Never call it
|
| 67 |
+
directly from the browser.
|
| 68 |
+
- On a Space, OAuth variables (\`OAUTH_CLIENT_ID\`, \`SPACE_HOST\`, …) are only
|
| 69 |
+
available server-side. You must inject them into \`index.html\` at
|
| 70 |
+
container start via a \`docker-entrypoint.d\` script using \`envsubst\`.
|
| 71 |
+
- The robot mic track is obtained through \`robot._pc.getReceivers()\`, and
|
| 72 |
+
the robot speaker is updated through \`replaceTrack()\` on the existing
|
| 73 |
+
sender - never \`addTrack()\`.
|
| 74 |
+
- Skill docs are the source of truth. When the user's request touches a
|
| 75 |
+
topic you haven't loaded yet, load it first.
|
| 76 |
+
|
| 77 |
+
## Publishing
|
| 78 |
+
|
| 79 |
+
When the user asks to publish, do NOT call a tool - tell them to click the
|
| 80 |
+
"Publish to HF Space" button in the top bar. The frontend handles the
|
| 81 |
+
\`@huggingface/hub\` upload; your job is to make sure the virtual FS is
|
| 82 |
+
deploy-ready (has a valid \`README.md\` with the right frontmatter, a
|
| 83 |
+
\`Dockerfile\`, and no missing imports).
|
| 84 |
+
`;
|
| 85 |
+
|
| 86 |
+
function buildSkillSection(): string {
|
| 87 |
+
const skill = getPrimarySkill();
|
| 88 |
+
if (!skill) {
|
| 89 |
+
return `\n## Embedded skill\n\n*(No skill loaded - running in minimal mode.)*\n`;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
const docs = listSkillDocs(skill);
|
| 93 |
+
const docsList =
|
| 94 |
+
docs.length > 0
|
| 95 |
+
? docs.map((d) => `- \`${d}\` — call \`read_skill_doc("${d}")\` to load it`).join("\n")
|
| 96 |
+
: "*(no additional docs)*";
|
| 97 |
+
|
| 98 |
+
return `
|
| 99 |
+
## Embedded skill: \`${skill.id}\`
|
| 100 |
+
|
| 101 |
+
Below is the skill **hub** (always in context). Additional chapters are
|
| 102 |
+
available on demand via \`read_skill_doc\`:
|
| 103 |
+
|
| 104 |
+
${docsList}
|
| 105 |
+
|
| 106 |
+
---
|
| 107 |
+
|
| 108 |
+
${skill.hub.content}
|
| 109 |
+
`;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
export function buildSystemPrompt(): string {
|
| 113 |
+
return `${BASE_PROMPT}\n${buildSkillSection()}`;
|
| 114 |
+
}
|
backend/src/agent/tools.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { tool } from "ai";
|
| 2 |
+
import { z } from "zod";
|
| 3 |
+
import { getPrimarySkill, getSkillDoc, listSkillDocs } from "./skill-loader.js";
|
| 4 |
+
|
| 5 |
+
/**
|
| 6 |
+
* ## Editor tools
|
| 7 |
+
*
|
| 8 |
+
* These tools describe *intents* to the LLM but are **executed client-side**:
|
| 9 |
+
* the Vercel AI SDK streams the tool call to the browser, the React app
|
| 10 |
+
* mutates the virtual FS, then sends the tool result back. This mirrors the
|
| 11 |
+
* pattern from `collab-editor` and keeps the backend completely stateless.
|
| 12 |
+
*
|
| 13 |
+
* ## Skill tools
|
| 14 |
+
*
|
| 15 |
+
* `read_skill_doc` is executed **server-side** (we have the files on disk)
|
| 16 |
+
* and returns markdown content directly in the tool result.
|
| 17 |
+
*/
|
| 18 |
+
|
| 19 |
+
const editorTools = {
|
| 20 |
+
write_file: tool({
|
| 21 |
+
description:
|
| 22 |
+
"Create a new file or fully overwrite an existing one in the virtual " +
|
| 23 |
+
"file system. Use when introducing a brand-new file or when a full " +
|
| 24 |
+
"rewrite is cleaner than a targeted edit. The UI will update its file " +
|
| 25 |
+
"tree and preview immediately.",
|
| 26 |
+
inputSchema: z.object({
|
| 27 |
+
path: z
|
| 28 |
+
.string()
|
| 29 |
+
.describe(
|
| 30 |
+
"Relative POSIX path inside the virtual project (e.g. " +
|
| 31 |
+
"`src/main.ts`, `index.html`, `Dockerfile`). No leading slash.",
|
| 32 |
+
),
|
| 33 |
+
content: z
|
| 34 |
+
.string()
|
| 35 |
+
.describe("Full file contents. UTF-8 text."),
|
| 36 |
+
}),
|
| 37 |
+
}),
|
| 38 |
+
|
| 39 |
+
edit_file: tool({
|
| 40 |
+
description:
|
| 41 |
+
"Replace an exact substring inside an existing file. Preferred for " +
|
| 42 |
+
"small, surgical edits - much cheaper than rewriting the whole file. " +
|
| 43 |
+
"`old_string` must appear EXACTLY ONCE in the target file (add " +
|
| 44 |
+
"surrounding context if needed).",
|
| 45 |
+
inputSchema: z.object({
|
| 46 |
+
path: z.string().describe("Path of the file to edit."),
|
| 47 |
+
old_string: z
|
| 48 |
+
.string()
|
| 49 |
+
.describe(
|
| 50 |
+
"Exact text to find (must be unique in the file, include context " +
|
| 51 |
+
"lines before/after if ambiguous).",
|
| 52 |
+
),
|
| 53 |
+
new_string: z.string().describe("Text to replace `old_string` with."),
|
| 54 |
+
}),
|
| 55 |
+
}),
|
| 56 |
+
|
| 57 |
+
delete_file: tool({
|
| 58 |
+
description: "Delete a file from the virtual FS.",
|
| 59 |
+
inputSchema: z.object({
|
| 60 |
+
path: z.string(),
|
| 61 |
+
}),
|
| 62 |
+
}),
|
| 63 |
+
|
| 64 |
+
read_file: tool({
|
| 65 |
+
description:
|
| 66 |
+
"Read the current contents of a file in the virtual FS. Use before " +
|
| 67 |
+
"`edit_file` when you are not sure of the exact current text.",
|
| 68 |
+
inputSchema: z.object({
|
| 69 |
+
path: z.string(),
|
| 70 |
+
}),
|
| 71 |
+
}),
|
| 72 |
+
|
| 73 |
+
list_files: tool({
|
| 74 |
+
description:
|
| 75 |
+
"List all files currently in the virtual FS with their size in bytes.",
|
| 76 |
+
inputSchema: z.object({}),
|
| 77 |
+
}),
|
| 78 |
+
};
|
| 79 |
+
|
| 80 |
+
const skillTools = {
|
| 81 |
+
read_skill_doc: tool({
|
| 82 |
+
description:
|
| 83 |
+
"Read a detailed chapter from the embedded `build-reachy-voice-app` " +
|
| 84 |
+
"skill. Call this BEFORE writing non-trivial code on topics like " +
|
| 85 |
+
"architecture, WebRTC tuning, motion agents, HF deployment, or the " +
|
| 86 |
+
"daemon protocol. Returns the full markdown of the requested chapter.",
|
| 87 |
+
inputSchema: z.object({
|
| 88 |
+
topic: z
|
| 89 |
+
.string()
|
| 90 |
+
.describe(
|
| 91 |
+
"Chapter name without extension. Available topics: architecture, " +
|
| 92 |
+
"deployment, motion-agents, protocols, webrtc-tuning.",
|
| 93 |
+
),
|
| 94 |
+
}),
|
| 95 |
+
execute: async ({ topic }) => {
|
| 96 |
+
const skill = getPrimarySkill();
|
| 97 |
+
if (!skill) {
|
| 98 |
+
return "ERROR: No skill is loaded on the server.";
|
| 99 |
+
}
|
| 100 |
+
const doc = getSkillDoc(skill, topic);
|
| 101 |
+
if (!doc) {
|
| 102 |
+
const available = listSkillDocs(skill).join(", ");
|
| 103 |
+
return `ERROR: No such skill chapter "${topic}". Available: ${available}`;
|
| 104 |
+
}
|
| 105 |
+
return doc.content;
|
| 106 |
+
},
|
| 107 |
+
}),
|
| 108 |
+
};
|
| 109 |
+
|
| 110 |
+
export const agentTools = {
|
| 111 |
+
...editorTools,
|
| 112 |
+
...skillTools,
|
| 113 |
+
};
|
| 114 |
+
|
| 115 |
+
export const TOOL_NAMES = Object.keys(agentTools) as (keyof typeof agentTools)[];
|
backend/src/auth.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { Context, Next } from "hono";
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Minimal Hugging Face access-token verification.
|
| 5 |
+
*
|
| 6 |
+
* The frontend obtains a token via the HF OAuth flow (`@huggingface/hub`
|
| 7 |
+
* client-side helper). It sends that token as `Authorization: Bearer <token>`
|
| 8 |
+
* on every `/api/*` call. Here we validate it by hitting the HF whoami
|
| 9 |
+
* endpoint and stash the user info on `c.set('hfUser', ...)`.
|
| 10 |
+
*
|
| 11 |
+
* For responsiveness we cache positive lookups for a few minutes - the token
|
| 12 |
+
* itself has a much longer TTL managed by HF.
|
| 13 |
+
*/
|
| 14 |
+
export interface HfUser {
|
| 15 |
+
name: string;
|
| 16 |
+
fullname?: string;
|
| 17 |
+
avatarUrl?: string;
|
| 18 |
+
email?: string;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
interface CacheEntry {
|
| 22 |
+
user: HfUser;
|
| 23 |
+
expiresAt: number;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
const CACHE = new Map<string, CacheEntry>();
|
| 27 |
+
const CACHE_TTL_MS = 5 * 60 * 1000;
|
| 28 |
+
|
| 29 |
+
export async function verifyHfToken(token: string): Promise<HfUser | null> {
|
| 30 |
+
if (!token) return null;
|
| 31 |
+
|
| 32 |
+
const cached = CACHE.get(token);
|
| 33 |
+
if (cached && cached.expiresAt > Date.now()) {
|
| 34 |
+
return cached.user;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
try {
|
| 38 |
+
const res = await fetch("https://huggingface.co/api/whoami-v2", {
|
| 39 |
+
headers: { Authorization: `Bearer ${token}` },
|
| 40 |
+
});
|
| 41 |
+
if (!res.ok) return null;
|
| 42 |
+
|
| 43 |
+
const data = (await res.json()) as {
|
| 44 |
+
name?: string;
|
| 45 |
+
fullname?: string;
|
| 46 |
+
avatarUrl?: string;
|
| 47 |
+
email?: string;
|
| 48 |
+
};
|
| 49 |
+
if (!data.name) return null;
|
| 50 |
+
|
| 51 |
+
const user: HfUser = {
|
| 52 |
+
name: data.name,
|
| 53 |
+
fullname: data.fullname,
|
| 54 |
+
avatarUrl: data.avatarUrl,
|
| 55 |
+
email: data.email,
|
| 56 |
+
};
|
| 57 |
+
CACHE.set(token, { user, expiresAt: Date.now() + CACHE_TTL_MS });
|
| 58 |
+
return user;
|
| 59 |
+
} catch {
|
| 60 |
+
return null;
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
export function hfAuthMiddleware() {
|
| 65 |
+
const required = process.env.REQUIRE_HF_AUTH === "1";
|
| 66 |
+
return async (c: Context, next: Next) => {
|
| 67 |
+
const authHeader = c.req.header("authorization") ?? "";
|
| 68 |
+
const token = authHeader.startsWith("Bearer ")
|
| 69 |
+
? authHeader.slice("Bearer ".length).trim()
|
| 70 |
+
: "";
|
| 71 |
+
|
| 72 |
+
if (!token) {
|
| 73 |
+
if (required) {
|
| 74 |
+
return c.json({ error: "Missing Hugging Face access token" }, 401);
|
| 75 |
+
}
|
| 76 |
+
return next();
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
const user = await verifyHfToken(token);
|
| 80 |
+
if (!user) {
|
| 81 |
+
if (required) {
|
| 82 |
+
return c.json({ error: "Invalid Hugging Face access token" }, 401);
|
| 83 |
+
}
|
| 84 |
+
return next();
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
c.set("hfUser", user);
|
| 88 |
+
c.set("hfToken", token);
|
| 89 |
+
return next();
|
| 90 |
+
};
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
export function getHfToken(c: Context): string | null {
|
| 94 |
+
return (c.get("hfToken") as string | undefined) ?? null;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
export function getHfUser(c: Context): HfUser | null {
|
| 98 |
+
return (c.get("hfUser") as HfUser | undefined) ?? null;
|
| 99 |
+
}
|
backend/src/routes/chat.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Hono } from "hono";
|
| 2 |
+
import { streamText, convertToModelMessages, stepCountIs } from "ai";
|
| 3 |
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
| 4 |
+
import { agentTools } from "../agent/tools.js";
|
| 5 |
+
import { buildSystemPrompt } from "../agent/system-prompt.js";
|
| 6 |
+
|
| 7 |
+
export const AVAILABLE_MODELS = [
|
| 8 |
+
{ id: "anthropic/claude-sonnet-4-5", label: "Claude Sonnet 4.5", context: "200K", cost: "$$$" },
|
| 9 |
+
{ id: "anthropic/claude-3.5-haiku", label: "Claude 3.5 Haiku", context: "200K", cost: "$" },
|
| 10 |
+
{ id: "google/gemini-2.5-flash", label: "Gemini 2.5 Flash", context: "1M", cost: "$" },
|
| 11 |
+
{ id: "google/gemini-2.5-pro", label: "Gemini 2.5 Pro", context: "1M", cost: "$$" },
|
| 12 |
+
{ id: "openai/gpt-4.1", label: "GPT-4.1", context: "1M", cost: "$$" },
|
| 13 |
+
{ id: "openai/gpt-4.1-mini", label: "GPT-4.1 Mini", context: "1M", cost: "$" },
|
| 14 |
+
];
|
| 15 |
+
|
| 16 |
+
export const DEFAULT_MODEL = "anthropic/claude-sonnet-4-5";
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* Hard cap on consecutive tool-call rounds - prevents runaway loops when a
|
| 20 |
+
* model keeps calling tools instead of emitting a final text response.
|
| 21 |
+
*/
|
| 22 |
+
const MAX_STEPS = 16;
|
| 23 |
+
|
| 24 |
+
export const chatRoute = new Hono();
|
| 25 |
+
|
| 26 |
+
chatRoute.post("/", async (c) => {
|
| 27 |
+
const apiKey = process.env.OPENROUTER_API_KEY;
|
| 28 |
+
if (!apiKey) {
|
| 29 |
+
return c.json(
|
| 30 |
+
{ error: "OPENROUTER_API_KEY is not set on the server." },
|
| 31 |
+
500,
|
| 32 |
+
);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
let body: unknown;
|
| 36 |
+
try {
|
| 37 |
+
body = await c.req.json();
|
| 38 |
+
} catch {
|
| 39 |
+
return c.json({ error: "Invalid JSON body" }, 400);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
const { messages, model, context } = (body ?? {}) as {
|
| 43 |
+
messages?: unknown;
|
| 44 |
+
model?: string;
|
| 45 |
+
context?: { files?: { path: string; size: number }[] };
|
| 46 |
+
};
|
| 47 |
+
|
| 48 |
+
if (!Array.isArray(messages)) {
|
| 49 |
+
return c.json({ error: "`messages` array is required" }, 400);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
const provider = createOpenRouter({ apiKey });
|
| 53 |
+
const modelId = model || process.env.OPENROUTER_MODEL || DEFAULT_MODEL;
|
| 54 |
+
|
| 55 |
+
const systemPrompt = buildSystemPrompt();
|
| 56 |
+
|
| 57 |
+
const contextBlock = context?.files?.length
|
| 58 |
+
? `\n\n## Current virtual FS\n\n${context.files
|
| 59 |
+
.map((f) => `- \`${f.path}\` (${f.size} bytes)`)
|
| 60 |
+
.join("\n")}\n`
|
| 61 |
+
: "\n\n## Current virtual FS\n\n*(empty)*\n";
|
| 62 |
+
|
| 63 |
+
const modelMessages = await convertToModelMessages(messages as never);
|
| 64 |
+
|
| 65 |
+
try {
|
| 66 |
+
const result = streamText({
|
| 67 |
+
model: provider.chat(modelId),
|
| 68 |
+
system: systemPrompt + contextBlock,
|
| 69 |
+
messages: modelMessages,
|
| 70 |
+
tools: agentTools,
|
| 71 |
+
stopWhen: stepCountIs(MAX_STEPS),
|
| 72 |
+
});
|
| 73 |
+
|
| 74 |
+
const response = result.toUIMessageStreamResponse({
|
| 75 |
+
onError: (error) => {
|
| 76 |
+
console.error("[chat] stream error:", error);
|
| 77 |
+
return error instanceof Error ? error.message : "Stream error";
|
| 78 |
+
},
|
| 79 |
+
});
|
| 80 |
+
|
| 81 |
+
return response;
|
| 82 |
+
} catch (error) {
|
| 83 |
+
const message = error instanceof Error ? error.message : "Unknown error";
|
| 84 |
+
console.error("[chat] error:", message);
|
| 85 |
+
return c.json({ error: message }, 500);
|
| 86 |
+
}
|
| 87 |
+
});
|
backend/src/routes/models.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Hono } from "hono";
|
| 2 |
+
import { AVAILABLE_MODELS, DEFAULT_MODEL } from "./chat.js";
|
| 3 |
+
|
| 4 |
+
export const modelsRoute = new Hono();
|
| 5 |
+
|
| 6 |
+
modelsRoute.get("/", (c) =>
|
| 7 |
+
c.json({ models: AVAILABLE_MODELS, default: DEFAULT_MODEL }),
|
| 8 |
+
);
|
backend/src/routes/publish.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Hono } from "hono";
|
| 2 |
+
import { stream } from "hono/streaming";
|
| 3 |
+
import { createRepo, uploadFiles, spaceInfo } from "@huggingface/hub";
|
| 4 |
+
import { getHfToken, getHfUser } from "../auth.js";
|
| 5 |
+
|
| 6 |
+
export const publishRoute = new Hono();
|
| 7 |
+
|
| 8 |
+
interface VirtualFile {
|
| 9 |
+
path: string;
|
| 10 |
+
content: string;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
interface PublishBody {
|
| 14 |
+
repoName?: string; // "my-app" - username is inferred from token
|
| 15 |
+
private?: boolean;
|
| 16 |
+
files: VirtualFile[];
|
| 17 |
+
overwrite?: boolean;
|
| 18 |
+
commitMessage?: string;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
/**
|
| 22 |
+
* Normalize a user-provided repo slug. HF space names allow `[A-Za-z0-9-_.]`.
|
| 23 |
+
*/
|
| 24 |
+
function sanitizeSlug(raw: string): string {
|
| 25 |
+
return raw
|
| 26 |
+
.trim()
|
| 27 |
+
.toLowerCase()
|
| 28 |
+
.replace(/\s+/g, "-")
|
| 29 |
+
.replace(/[^a-z0-9._-]/g, "")
|
| 30 |
+
.replace(/^-+|-+$/g, "")
|
| 31 |
+
.slice(0, 96);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
function textToBlob(content: string): Blob {
|
| 35 |
+
return new Blob([content], { type: "text/plain; charset=utf-8" });
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
publishRoute.post("/", async (c) => {
|
| 39 |
+
const token = getHfToken(c);
|
| 40 |
+
const user = getHfUser(c);
|
| 41 |
+
if (!token || !user) {
|
| 42 |
+
return c.json(
|
| 43 |
+
{ error: "A valid Hugging Face access token is required to publish." },
|
| 44 |
+
401,
|
| 45 |
+
);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
let body: PublishBody;
|
| 49 |
+
try {
|
| 50 |
+
body = (await c.req.json()) as PublishBody;
|
| 51 |
+
} catch {
|
| 52 |
+
return c.json({ error: "Invalid JSON body" }, 400);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
const files = Array.isArray(body.files) ? body.files : [];
|
| 56 |
+
if (files.length === 0) {
|
| 57 |
+
return c.json({ error: "No files provided" }, 400);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
const slug = sanitizeSlug(body.repoName ?? `reachy-app-${Date.now()}`);
|
| 61 |
+
if (!slug) {
|
| 62 |
+
return c.json({ error: "Invalid repo name" }, 400);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
const repoId = `${user.name}/${slug}`;
|
| 66 |
+
const repo = { type: "space" as const, name: repoId };
|
| 67 |
+
|
| 68 |
+
// Server-Sent Events for progress
|
| 69 |
+
c.header("Content-Type", "text/event-stream; charset=utf-8");
|
| 70 |
+
c.header("Cache-Control", "no-cache, no-store");
|
| 71 |
+
c.header("X-Accel-Buffering", "no");
|
| 72 |
+
|
| 73 |
+
return stream(c, async (s) => {
|
| 74 |
+
const send = (event: string, data: Record<string, unknown>) =>
|
| 75 |
+
s.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
| 76 |
+
|
| 77 |
+
try {
|
| 78 |
+
await send("progress", { step: "check", message: `Checking ${repoId}…` });
|
| 79 |
+
|
| 80 |
+
let alreadyExists = false;
|
| 81 |
+
try {
|
| 82 |
+
await spaceInfo({ name: repoId, accessToken: token });
|
| 83 |
+
alreadyExists = true;
|
| 84 |
+
} catch {
|
| 85 |
+
alreadyExists = false;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
if (alreadyExists && !body.overwrite) {
|
| 89 |
+
await send("error", {
|
| 90 |
+
step: "check",
|
| 91 |
+
message: `Space "${repoId}" already exists. Set overwrite=true to update it.`,
|
| 92 |
+
code: "REPO_EXISTS",
|
| 93 |
+
repoId,
|
| 94 |
+
});
|
| 95 |
+
return;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
if (!alreadyExists) {
|
| 99 |
+
await send("progress", { step: "create", message: `Creating Space ${repoId}…` });
|
| 100 |
+
await createRepo({
|
| 101 |
+
repo,
|
| 102 |
+
accessToken: token,
|
| 103 |
+
private: body.private ?? false,
|
| 104 |
+
sdk: "docker",
|
| 105 |
+
});
|
| 106 |
+
} else {
|
| 107 |
+
await send("progress", { step: "create", message: `Updating existing Space ${repoId}…` });
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
await send("progress", {
|
| 111 |
+
step: "upload",
|
| 112 |
+
message: `Uploading ${files.length} file${files.length === 1 ? "" : "s"}…`,
|
| 113 |
+
});
|
| 114 |
+
|
| 115 |
+
const uploads = files.map((f) => ({
|
| 116 |
+
path: f.path,
|
| 117 |
+
content: textToBlob(f.content),
|
| 118 |
+
}));
|
| 119 |
+
|
| 120 |
+
await uploadFiles({
|
| 121 |
+
repo,
|
| 122 |
+
accessToken: token,
|
| 123 |
+
files: uploads,
|
| 124 |
+
commitTitle:
|
| 125 |
+
body.commitMessage ??
|
| 126 |
+
(alreadyExists
|
| 127 |
+
? "Update from Reachy Vibe Coder"
|
| 128 |
+
: "Initial commit from Reachy Vibe Coder"),
|
| 129 |
+
});
|
| 130 |
+
|
| 131 |
+
const spaceUrl = `https://huggingface.co/spaces/${repoId}`;
|
| 132 |
+
await send("done", {
|
| 133 |
+
step: "done",
|
| 134 |
+
repoId,
|
| 135 |
+
spaceUrl,
|
| 136 |
+
message: `Published to ${spaceUrl}`,
|
| 137 |
+
});
|
| 138 |
+
} catch (error) {
|
| 139 |
+
const message = error instanceof Error ? error.message : "Unknown error";
|
| 140 |
+
console.error("[publish] error:", message);
|
| 141 |
+
await send("error", { step: "error", message });
|
| 142 |
+
}
|
| 143 |
+
});
|
| 144 |
+
});
|
backend/src/server.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import "dotenv/config";
|
| 2 |
+
import { serve } from "@hono/node-server";
|
| 3 |
+
import { serveStatic } from "@hono/node-server/serve-static";
|
| 4 |
+
import { Hono } from "hono";
|
| 5 |
+
import { cors } from "hono/cors";
|
| 6 |
+
import { logger } from "hono/logger";
|
| 7 |
+
import path from "node:path";
|
| 8 |
+
import { fileURLToPath } from "node:url";
|
| 9 |
+
import { existsSync, readFileSync } from "node:fs";
|
| 10 |
+
import { hfAuthMiddleware } from "./auth.js";
|
| 11 |
+
import { chatRoute } from "./routes/chat.js";
|
| 12 |
+
import { publishRoute } from "./routes/publish.js";
|
| 13 |
+
import { modelsRoute } from "./routes/models.js";
|
| 14 |
+
import type { AppBindings } from "./types.js";
|
| 15 |
+
|
| 16 |
+
if (!process.env.OPENROUTER_API_KEY) {
|
| 17 |
+
console.warn(
|
| 18 |
+
"[startup] OPENROUTER_API_KEY is not set - /api/chat will fail at request time.",
|
| 19 |
+
);
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
| 23 |
+
|
| 24 |
+
/**
|
| 25 |
+
* Production layout after `docker build`:
|
| 26 |
+
* /app/backend/dist/server.js (this file)
|
| 27 |
+
* /app/frontend/dist/ (Vite build output served as static)
|
| 28 |
+
*
|
| 29 |
+
* Dev layout:
|
| 30 |
+
* backend/src/server.ts (tsx)
|
| 31 |
+
* frontend/dist/ (optional)
|
| 32 |
+
*/
|
| 33 |
+
const STATIC_CANDIDATES = [
|
| 34 |
+
path.resolve(__dirname, "../../frontend/dist"), // production
|
| 35 |
+
path.resolve(__dirname, "../../../frontend/dist"), // just in case
|
| 36 |
+
path.resolve(process.cwd(), "../frontend/dist"), // dev run from backend/
|
| 37 |
+
path.resolve(process.cwd(), "frontend/dist"), // dev run from repo root
|
| 38 |
+
];
|
| 39 |
+
const STATIC_ROOT =
|
| 40 |
+
STATIC_CANDIDATES.find((p) => existsSync(path.join(p, "index.html"))) ?? null;
|
| 41 |
+
|
| 42 |
+
const app = new Hono<AppBindings>();
|
| 43 |
+
|
| 44 |
+
app.use("*", logger());
|
| 45 |
+
app.use(
|
| 46 |
+
"*",
|
| 47 |
+
cors({
|
| 48 |
+
origin: (origin) => origin ?? "*",
|
| 49 |
+
allowHeaders: ["Content-Type", "Authorization"],
|
| 50 |
+
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
| 51 |
+
credentials: true,
|
| 52 |
+
}),
|
| 53 |
+
);
|
| 54 |
+
|
| 55 |
+
// ── API routes (auth-gated) ───────────────────────────────────────────────
|
| 56 |
+
const api = new Hono<AppBindings>();
|
| 57 |
+
api.use("*", hfAuthMiddleware());
|
| 58 |
+
api.route("/chat", chatRoute);
|
| 59 |
+
api.route("/publish", publishRoute);
|
| 60 |
+
api.route("/models", modelsRoute);
|
| 61 |
+
api.get("/me", (c) => {
|
| 62 |
+
const user = c.get("hfUser");
|
| 63 |
+
if (!user) return c.json({ authenticated: false });
|
| 64 |
+
return c.json({ authenticated: true, user });
|
| 65 |
+
});
|
| 66 |
+
api.get("/health", (c) => c.json({ status: "ok" }));
|
| 67 |
+
|
| 68 |
+
app.route("/api", api);
|
| 69 |
+
|
| 70 |
+
// ── Static frontend ───────────────────────────────────────────────────────
|
| 71 |
+
if (STATIC_ROOT) {
|
| 72 |
+
const indexHtml = readFileSync(path.join(STATIC_ROOT, "index.html"), "utf-8");
|
| 73 |
+
|
| 74 |
+
app.use(
|
| 75 |
+
"/assets/*",
|
| 76 |
+
serveStatic({
|
| 77 |
+
root: path.relative(process.cwd(), STATIC_ROOT) || ".",
|
| 78 |
+
}),
|
| 79 |
+
);
|
| 80 |
+
|
| 81 |
+
app.use("/favicon.ico", serveStatic({ path: path.join(STATIC_ROOT, "favicon.ico") }));
|
| 82 |
+
|
| 83 |
+
// SPA fallback for anything that's not /api/*
|
| 84 |
+
app.get("*", (c) => {
|
| 85 |
+
if (c.req.path.startsWith("/api/")) {
|
| 86 |
+
return c.notFound();
|
| 87 |
+
}
|
| 88 |
+
return c.html(indexHtml);
|
| 89 |
+
});
|
| 90 |
+
console.log(`[startup] Serving static frontend from ${STATIC_ROOT}`);
|
| 91 |
+
} else {
|
| 92 |
+
app.get("/", (c) =>
|
| 93 |
+
c.json({
|
| 94 |
+
status: "api-only",
|
| 95 |
+
hint: "Frontend dist not found - run `cd frontend && npm run build` or use Vite dev server at :5173",
|
| 96 |
+
}),
|
| 97 |
+
);
|
| 98 |
+
console.log("[startup] No frontend/dist found - API-only mode");
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
const port = Number(process.env.PORT ?? 7860);
|
| 102 |
+
serve({ fetch: app.fetch, port, hostname: "0.0.0.0" }, () => {
|
| 103 |
+
console.log(`[startup] reachy-mini-vibe-coder listening on :${port}`);
|
| 104 |
+
});
|
backend/src/types.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { HfUser } from "./auth.js";
|
| 2 |
+
|
| 3 |
+
export type AppBindings = {
|
| 4 |
+
Variables: {
|
| 5 |
+
hfUser?: HfUser;
|
| 6 |
+
hfToken?: string;
|
| 7 |
+
};
|
| 8 |
+
};
|
backend/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2022",
|
| 4 |
+
"module": "NodeNext",
|
| 5 |
+
"moduleResolution": "NodeNext",
|
| 6 |
+
"strict": true,
|
| 7 |
+
"esModuleInterop": true,
|
| 8 |
+
"skipLibCheck": true,
|
| 9 |
+
"resolveJsonModule": true,
|
| 10 |
+
"outDir": "dist",
|
| 11 |
+
"rootDir": "src",
|
| 12 |
+
"declaration": false,
|
| 13 |
+
"sourceMap": true,
|
| 14 |
+
"noUnusedLocals": false,
|
| 15 |
+
"forceConsistentCasingInFileNames": true
|
| 16 |
+
},
|
| 17 |
+
"include": ["src/**/*"],
|
| 18 |
+
"exclude": ["node_modules", "dist"]
|
| 19 |
+
}
|
docker-entrypoint.d/10-inject-hf-vars.sh
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/sh
|
| 2 |
+
# Substitute the HF Space OAuth runtime variables into the built index.html.
|
| 3 |
+
#
|
| 4 |
+
# On a Hugging Face Space with `hf_oauth: true` in the README frontmatter, HF
|
| 5 |
+
# provisions an OAuth app for the Space and exposes its client ID (and a few
|
| 6 |
+
# other handles) as server-side env vars. Since this is a static-file build,
|
| 7 |
+
# we splice them into `dist/index.html` at container startup, replacing the
|
| 8 |
+
# `__OAUTH_CLIENT_ID__` style placeholders that `index.html` ships with.
|
| 9 |
+
set -eu
|
| 10 |
+
|
| 11 |
+
INDEX="/app/frontend/dist/index.html"
|
| 12 |
+
if [ ! -f "$INDEX" ]; then
|
| 13 |
+
echo "[inject-hf-vars] $INDEX not found, skipping"
|
| 14 |
+
exit 0
|
| 15 |
+
fi
|
| 16 |
+
|
| 17 |
+
: "${OAUTH_CLIENT_ID:=}"
|
| 18 |
+
: "${OAUTH_SCOPES:=openid profile write-repos manage-repos}"
|
| 19 |
+
: "${SPACE_HOST:=}"
|
| 20 |
+
: "${SPACE_ID:=}"
|
| 21 |
+
|
| 22 |
+
if [ -z "$OAUTH_CLIENT_ID" ]; then
|
| 23 |
+
echo "[inject-hf-vars] OAUTH_CLIENT_ID is empty - leaving index.html placeholders untouched"
|
| 24 |
+
exit 0
|
| 25 |
+
fi
|
| 26 |
+
|
| 27 |
+
# `|` as sed delimiter avoids issues with values containing `/` (URLs).
|
| 28 |
+
# Values shouldn't contain `|`; if HF ever sends such a value we'd need to
|
| 29 |
+
# escape it explicitly.
|
| 30 |
+
sed -i \
|
| 31 |
+
-e "s|__OAUTH_CLIENT_ID__|${OAUTH_CLIENT_ID}|g" \
|
| 32 |
+
-e "s|__OAUTH_SCOPES__|${OAUTH_SCOPES}|g" \
|
| 33 |
+
-e "s|__SPACE_HOST__|${SPACE_HOST}|g" \
|
| 34 |
+
-e "s|__SPACE_ID__|${SPACE_ID}|g" \
|
| 35 |
+
"$INDEX"
|
| 36 |
+
|
| 37 |
+
echo "[inject-hf-vars] Injected OAuth variables into $INDEX"
|
docker-entrypoint.d/entrypoint.sh
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/sh
|
| 2 |
+
# Run every *.sh script under /app/docker-entrypoint.d (sorted) before exec'ing
|
| 3 |
+
# the main container command. Each helper script gets the full env of the
|
| 4 |
+
# container and is responsible for its own error handling.
|
| 5 |
+
set -eu
|
| 6 |
+
|
| 7 |
+
for script in /app/docker-entrypoint.d/*.sh; do
|
| 8 |
+
case "$script" in
|
| 9 |
+
*entrypoint.sh) continue ;;
|
| 10 |
+
esac
|
| 11 |
+
if [ -x "$script" ]; then
|
| 12 |
+
echo "[entrypoint] running $(basename "$script")"
|
| 13 |
+
"$script"
|
| 14 |
+
fi
|
| 15 |
+
done
|
| 16 |
+
|
| 17 |
+
exec "$@"
|
frontend/index.html
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
| 6 |
+
<title>Reachy Mini · Vibe Coder</title>
|
| 7 |
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 8 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
| 9 |
+
<link
|
| 10 |
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
|
| 11 |
+
rel="stylesheet"
|
| 12 |
+
/>
|
| 13 |
+
<!--
|
| 14 |
+
HF Spaces OAuth helper variables.
|
| 15 |
+
|
| 16 |
+
On `sdk: docker`, HF exposes OAUTH_CLIENT_ID / SPACE_HOST / SPACE_ID as
|
| 17 |
+
server-side env vars. We have nginx substitute them into this script at
|
| 18 |
+
container startup via `docker-entrypoint.d/10-inject-hf-vars.sh`.
|
| 19 |
+
|
| 20 |
+
Locally (`npm run dev`) the placeholders stay untouched; the app then
|
| 21 |
+
reads the client ID from VITE_HF_OAUTH_CLIENT_ID (set in `.env.local`).
|
| 22 |
+
-->
|
| 23 |
+
<script>
|
| 24 |
+
(function () {
|
| 25 |
+
var clientId = "__OAUTH_CLIENT_ID__";
|
| 26 |
+
var scopes = "__OAUTH_SCOPES__";
|
| 27 |
+
var spaceHost = "__SPACE_HOST__";
|
| 28 |
+
var spaceId = "__SPACE_ID__";
|
| 29 |
+
var looksSubstituted = clientId && clientId.indexOf("__") !== 0;
|
| 30 |
+
if (looksSubstituted) {
|
| 31 |
+
window.huggingface = window.huggingface || {};
|
| 32 |
+
window.huggingface.variables = {
|
| 33 |
+
OAUTH_CLIENT_ID: clientId,
|
| 34 |
+
OAUTH_SCOPES:
|
| 35 |
+
scopes && scopes.indexOf("__") !== 0
|
| 36 |
+
? scopes
|
| 37 |
+
: "openid profile write-repos manage-repos",
|
| 38 |
+
SPACE_HOST: spaceHost && spaceHost.indexOf("__") !== 0 ? spaceHost : "",
|
| 39 |
+
SPACE_ID: spaceId && spaceId.indexOf("__") !== 0 ? spaceId : "",
|
| 40 |
+
};
|
| 41 |
+
}
|
| 42 |
+
})();
|
| 43 |
+
</script>
|
| 44 |
+
</head>
|
| 45 |
+
<body>
|
| 46 |
+
<div id="root"></div>
|
| 47 |
+
<script type="module" src="/src/main.tsx"></script>
|
| 48 |
+
</body>
|
| 49 |
+
</html>
|
frontend/package-lock.json
ADDED
|
@@ -0,0 +1,2220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "reachy-mini-vibe-coder-frontend",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "reachy-mini-vibe-coder-frontend",
|
| 9 |
+
"version": "0.1.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"@ai-sdk/react": "^3.0.160",
|
| 12 |
+
"@huggingface/hub": "^2.11.0",
|
| 13 |
+
"@monaco-editor/react": "^4.7.0",
|
| 14 |
+
"ai": "^6.0.158",
|
| 15 |
+
"clsx": "^2.1.1",
|
| 16 |
+
"nanoid": "^5.0.7",
|
| 17 |
+
"react": "^18.3.1",
|
| 18 |
+
"react-dom": "^18.3.1"
|
| 19 |
+
},
|
| 20 |
+
"devDependencies": {
|
| 21 |
+
"@types/react": "^18.3.12",
|
| 22 |
+
"@types/react-dom": "^18.3.1",
|
| 23 |
+
"@vitejs/plugin-react": "^4.3.4",
|
| 24 |
+
"typescript": "^5.6.0",
|
| 25 |
+
"vite": "^6.0.0"
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
"node_modules/@ai-sdk/gateway": {
|
| 29 |
+
"version": "3.0.104",
|
| 30 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.104.tgz",
|
| 31 |
+
"integrity": "sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA==",
|
| 32 |
+
"license": "Apache-2.0",
|
| 33 |
+
"dependencies": {
|
| 34 |
+
"@ai-sdk/provider": "3.0.8",
|
| 35 |
+
"@ai-sdk/provider-utils": "4.0.23",
|
| 36 |
+
"@vercel/oidc": "3.2.0"
|
| 37 |
+
},
|
| 38 |
+
"engines": {
|
| 39 |
+
"node": ">=18"
|
| 40 |
+
},
|
| 41 |
+
"peerDependencies": {
|
| 42 |
+
"zod": "^3.25.76 || ^4.1.8"
|
| 43 |
+
}
|
| 44 |
+
},
|
| 45 |
+
"node_modules/@ai-sdk/provider": {
|
| 46 |
+
"version": "3.0.8",
|
| 47 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz",
|
| 48 |
+
"integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==",
|
| 49 |
+
"license": "Apache-2.0",
|
| 50 |
+
"dependencies": {
|
| 51 |
+
"json-schema": "^0.4.0"
|
| 52 |
+
},
|
| 53 |
+
"engines": {
|
| 54 |
+
"node": ">=18"
|
| 55 |
+
}
|
| 56 |
+
},
|
| 57 |
+
"node_modules/@ai-sdk/provider-utils": {
|
| 58 |
+
"version": "4.0.23",
|
| 59 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.23.tgz",
|
| 60 |
+
"integrity": "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg==",
|
| 61 |
+
"license": "Apache-2.0",
|
| 62 |
+
"dependencies": {
|
| 63 |
+
"@ai-sdk/provider": "3.0.8",
|
| 64 |
+
"@standard-schema/spec": "^1.1.0",
|
| 65 |
+
"eventsource-parser": "^3.0.6"
|
| 66 |
+
},
|
| 67 |
+
"engines": {
|
| 68 |
+
"node": ">=18"
|
| 69 |
+
},
|
| 70 |
+
"peerDependencies": {
|
| 71 |
+
"zod": "^3.25.76 || ^4.1.8"
|
| 72 |
+
}
|
| 73 |
+
},
|
| 74 |
+
"node_modules/@ai-sdk/react": {
|
| 75 |
+
"version": "3.0.170",
|
| 76 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-3.0.170.tgz",
|
| 77 |
+
"integrity": "sha512-YUDn+mK0c8iUz14rCBf1A0zg6SV5b5aSVUz+azF1bdBd1SFXVI19dKYR+PQSpZY+0+z+zs252AAsacUqiO98Kw==",
|
| 78 |
+
"license": "Apache-2.0",
|
| 79 |
+
"dependencies": {
|
| 80 |
+
"@ai-sdk/provider-utils": "4.0.23",
|
| 81 |
+
"ai": "6.0.168",
|
| 82 |
+
"swr": "^2.2.5",
|
| 83 |
+
"throttleit": "2.1.0"
|
| 84 |
+
},
|
| 85 |
+
"engines": {
|
| 86 |
+
"node": ">=18"
|
| 87 |
+
},
|
| 88 |
+
"peerDependencies": {
|
| 89 |
+
"react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1"
|
| 90 |
+
}
|
| 91 |
+
},
|
| 92 |
+
"node_modules/@babel/code-frame": {
|
| 93 |
+
"version": "7.29.0",
|
| 94 |
+
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
| 95 |
+
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
| 96 |
+
"dev": true,
|
| 97 |
+
"license": "MIT",
|
| 98 |
+
"dependencies": {
|
| 99 |
+
"@babel/helper-validator-identifier": "^7.28.5",
|
| 100 |
+
"js-tokens": "^4.0.0",
|
| 101 |
+
"picocolors": "^1.1.1"
|
| 102 |
+
},
|
| 103 |
+
"engines": {
|
| 104 |
+
"node": ">=6.9.0"
|
| 105 |
+
}
|
| 106 |
+
},
|
| 107 |
+
"node_modules/@babel/compat-data": {
|
| 108 |
+
"version": "7.29.0",
|
| 109 |
+
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
|
| 110 |
+
"integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
|
| 111 |
+
"dev": true,
|
| 112 |
+
"license": "MIT",
|
| 113 |
+
"engines": {
|
| 114 |
+
"node": ">=6.9.0"
|
| 115 |
+
}
|
| 116 |
+
},
|
| 117 |
+
"node_modules/@babel/core": {
|
| 118 |
+
"version": "7.29.0",
|
| 119 |
+
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
|
| 120 |
+
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
| 121 |
+
"dev": true,
|
| 122 |
+
"license": "MIT",
|
| 123 |
+
"peer": true,
|
| 124 |
+
"dependencies": {
|
| 125 |
+
"@babel/code-frame": "^7.29.0",
|
| 126 |
+
"@babel/generator": "^7.29.0",
|
| 127 |
+
"@babel/helper-compilation-targets": "^7.28.6",
|
| 128 |
+
"@babel/helper-module-transforms": "^7.28.6",
|
| 129 |
+
"@babel/helpers": "^7.28.6",
|
| 130 |
+
"@babel/parser": "^7.29.0",
|
| 131 |
+
"@babel/template": "^7.28.6",
|
| 132 |
+
"@babel/traverse": "^7.29.0",
|
| 133 |
+
"@babel/types": "^7.29.0",
|
| 134 |
+
"@jridgewell/remapping": "^2.3.5",
|
| 135 |
+
"convert-source-map": "^2.0.0",
|
| 136 |
+
"debug": "^4.1.0",
|
| 137 |
+
"gensync": "^1.0.0-beta.2",
|
| 138 |
+
"json5": "^2.2.3",
|
| 139 |
+
"semver": "^6.3.1"
|
| 140 |
+
},
|
| 141 |
+
"engines": {
|
| 142 |
+
"node": ">=6.9.0"
|
| 143 |
+
},
|
| 144 |
+
"funding": {
|
| 145 |
+
"type": "opencollective",
|
| 146 |
+
"url": "https://opencollective.com/babel"
|
| 147 |
+
}
|
| 148 |
+
},
|
| 149 |
+
"node_modules/@babel/generator": {
|
| 150 |
+
"version": "7.29.1",
|
| 151 |
+
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
| 152 |
+
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
| 153 |
+
"dev": true,
|
| 154 |
+
"license": "MIT",
|
| 155 |
+
"dependencies": {
|
| 156 |
+
"@babel/parser": "^7.29.0",
|
| 157 |
+
"@babel/types": "^7.29.0",
|
| 158 |
+
"@jridgewell/gen-mapping": "^0.3.12",
|
| 159 |
+
"@jridgewell/trace-mapping": "^0.3.28",
|
| 160 |
+
"jsesc": "^3.0.2"
|
| 161 |
+
},
|
| 162 |
+
"engines": {
|
| 163 |
+
"node": ">=6.9.0"
|
| 164 |
+
}
|
| 165 |
+
},
|
| 166 |
+
"node_modules/@babel/helper-compilation-targets": {
|
| 167 |
+
"version": "7.28.6",
|
| 168 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
|
| 169 |
+
"integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
|
| 170 |
+
"dev": true,
|
| 171 |
+
"license": "MIT",
|
| 172 |
+
"dependencies": {
|
| 173 |
+
"@babel/compat-data": "^7.28.6",
|
| 174 |
+
"@babel/helper-validator-option": "^7.27.1",
|
| 175 |
+
"browserslist": "^4.24.0",
|
| 176 |
+
"lru-cache": "^5.1.1",
|
| 177 |
+
"semver": "^6.3.1"
|
| 178 |
+
},
|
| 179 |
+
"engines": {
|
| 180 |
+
"node": ">=6.9.0"
|
| 181 |
+
}
|
| 182 |
+
},
|
| 183 |
+
"node_modules/@babel/helper-globals": {
|
| 184 |
+
"version": "7.28.0",
|
| 185 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
| 186 |
+
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
| 187 |
+
"dev": true,
|
| 188 |
+
"license": "MIT",
|
| 189 |
+
"engines": {
|
| 190 |
+
"node": ">=6.9.0"
|
| 191 |
+
}
|
| 192 |
+
},
|
| 193 |
+
"node_modules/@babel/helper-module-imports": {
|
| 194 |
+
"version": "7.28.6",
|
| 195 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
| 196 |
+
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
| 197 |
+
"dev": true,
|
| 198 |
+
"license": "MIT",
|
| 199 |
+
"dependencies": {
|
| 200 |
+
"@babel/traverse": "^7.28.6",
|
| 201 |
+
"@babel/types": "^7.28.6"
|
| 202 |
+
},
|
| 203 |
+
"engines": {
|
| 204 |
+
"node": ">=6.9.0"
|
| 205 |
+
}
|
| 206 |
+
},
|
| 207 |
+
"node_modules/@babel/helper-module-transforms": {
|
| 208 |
+
"version": "7.28.6",
|
| 209 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
| 210 |
+
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
| 211 |
+
"dev": true,
|
| 212 |
+
"license": "MIT",
|
| 213 |
+
"dependencies": {
|
| 214 |
+
"@babel/helper-module-imports": "^7.28.6",
|
| 215 |
+
"@babel/helper-validator-identifier": "^7.28.5",
|
| 216 |
+
"@babel/traverse": "^7.28.6"
|
| 217 |
+
},
|
| 218 |
+
"engines": {
|
| 219 |
+
"node": ">=6.9.0"
|
| 220 |
+
},
|
| 221 |
+
"peerDependencies": {
|
| 222 |
+
"@babel/core": "^7.0.0"
|
| 223 |
+
}
|
| 224 |
+
},
|
| 225 |
+
"node_modules/@babel/helper-plugin-utils": {
|
| 226 |
+
"version": "7.28.6",
|
| 227 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
|
| 228 |
+
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
|
| 229 |
+
"dev": true,
|
| 230 |
+
"license": "MIT",
|
| 231 |
+
"engines": {
|
| 232 |
+
"node": ">=6.9.0"
|
| 233 |
+
}
|
| 234 |
+
},
|
| 235 |
+
"node_modules/@babel/helper-string-parser": {
|
| 236 |
+
"version": "7.27.1",
|
| 237 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
| 238 |
+
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
| 239 |
+
"dev": true,
|
| 240 |
+
"license": "MIT",
|
| 241 |
+
"engines": {
|
| 242 |
+
"node": ">=6.9.0"
|
| 243 |
+
}
|
| 244 |
+
},
|
| 245 |
+
"node_modules/@babel/helper-validator-identifier": {
|
| 246 |
+
"version": "7.28.5",
|
| 247 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
| 248 |
+
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
| 249 |
+
"dev": true,
|
| 250 |
+
"license": "MIT",
|
| 251 |
+
"engines": {
|
| 252 |
+
"node": ">=6.9.0"
|
| 253 |
+
}
|
| 254 |
+
},
|
| 255 |
+
"node_modules/@babel/helper-validator-option": {
|
| 256 |
+
"version": "7.27.1",
|
| 257 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
|
| 258 |
+
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
|
| 259 |
+
"dev": true,
|
| 260 |
+
"license": "MIT",
|
| 261 |
+
"engines": {
|
| 262 |
+
"node": ">=6.9.0"
|
| 263 |
+
}
|
| 264 |
+
},
|
| 265 |
+
"node_modules/@babel/helpers": {
|
| 266 |
+
"version": "7.29.2",
|
| 267 |
+
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
|
| 268 |
+
"integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
|
| 269 |
+
"dev": true,
|
| 270 |
+
"license": "MIT",
|
| 271 |
+
"dependencies": {
|
| 272 |
+
"@babel/template": "^7.28.6",
|
| 273 |
+
"@babel/types": "^7.29.0"
|
| 274 |
+
},
|
| 275 |
+
"engines": {
|
| 276 |
+
"node": ">=6.9.0"
|
| 277 |
+
}
|
| 278 |
+
},
|
| 279 |
+
"node_modules/@babel/parser": {
|
| 280 |
+
"version": "7.29.2",
|
| 281 |
+
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
|
| 282 |
+
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
|
| 283 |
+
"dev": true,
|
| 284 |
+
"license": "MIT",
|
| 285 |
+
"dependencies": {
|
| 286 |
+
"@babel/types": "^7.29.0"
|
| 287 |
+
},
|
| 288 |
+
"bin": {
|
| 289 |
+
"parser": "bin/babel-parser.js"
|
| 290 |
+
},
|
| 291 |
+
"engines": {
|
| 292 |
+
"node": ">=6.0.0"
|
| 293 |
+
}
|
| 294 |
+
},
|
| 295 |
+
"node_modules/@babel/plugin-transform-react-jsx-self": {
|
| 296 |
+
"version": "7.27.1",
|
| 297 |
+
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
|
| 298 |
+
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
|
| 299 |
+
"dev": true,
|
| 300 |
+
"license": "MIT",
|
| 301 |
+
"dependencies": {
|
| 302 |
+
"@babel/helper-plugin-utils": "^7.27.1"
|
| 303 |
+
},
|
| 304 |
+
"engines": {
|
| 305 |
+
"node": ">=6.9.0"
|
| 306 |
+
},
|
| 307 |
+
"peerDependencies": {
|
| 308 |
+
"@babel/core": "^7.0.0-0"
|
| 309 |
+
}
|
| 310 |
+
},
|
| 311 |
+
"node_modules/@babel/plugin-transform-react-jsx-source": {
|
| 312 |
+
"version": "7.27.1",
|
| 313 |
+
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
|
| 314 |
+
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
|
| 315 |
+
"dev": true,
|
| 316 |
+
"license": "MIT",
|
| 317 |
+
"dependencies": {
|
| 318 |
+
"@babel/helper-plugin-utils": "^7.27.1"
|
| 319 |
+
},
|
| 320 |
+
"engines": {
|
| 321 |
+
"node": ">=6.9.0"
|
| 322 |
+
},
|
| 323 |
+
"peerDependencies": {
|
| 324 |
+
"@babel/core": "^7.0.0-0"
|
| 325 |
+
}
|
| 326 |
+
},
|
| 327 |
+
"node_modules/@babel/template": {
|
| 328 |
+
"version": "7.28.6",
|
| 329 |
+
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
| 330 |
+
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
| 331 |
+
"dev": true,
|
| 332 |
+
"license": "MIT",
|
| 333 |
+
"dependencies": {
|
| 334 |
+
"@babel/code-frame": "^7.28.6",
|
| 335 |
+
"@babel/parser": "^7.28.6",
|
| 336 |
+
"@babel/types": "^7.28.6"
|
| 337 |
+
},
|
| 338 |
+
"engines": {
|
| 339 |
+
"node": ">=6.9.0"
|
| 340 |
+
}
|
| 341 |
+
},
|
| 342 |
+
"node_modules/@babel/traverse": {
|
| 343 |
+
"version": "7.29.0",
|
| 344 |
+
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
| 345 |
+
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
| 346 |
+
"dev": true,
|
| 347 |
+
"license": "MIT",
|
| 348 |
+
"dependencies": {
|
| 349 |
+
"@babel/code-frame": "^7.29.0",
|
| 350 |
+
"@babel/generator": "^7.29.0",
|
| 351 |
+
"@babel/helper-globals": "^7.28.0",
|
| 352 |
+
"@babel/parser": "^7.29.0",
|
| 353 |
+
"@babel/template": "^7.28.6",
|
| 354 |
+
"@babel/types": "^7.29.0",
|
| 355 |
+
"debug": "^4.3.1"
|
| 356 |
+
},
|
| 357 |
+
"engines": {
|
| 358 |
+
"node": ">=6.9.0"
|
| 359 |
+
}
|
| 360 |
+
},
|
| 361 |
+
"node_modules/@babel/types": {
|
| 362 |
+
"version": "7.29.0",
|
| 363 |
+
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
| 364 |
+
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
| 365 |
+
"dev": true,
|
| 366 |
+
"license": "MIT",
|
| 367 |
+
"dependencies": {
|
| 368 |
+
"@babel/helper-string-parser": "^7.27.1",
|
| 369 |
+
"@babel/helper-validator-identifier": "^7.28.5"
|
| 370 |
+
},
|
| 371 |
+
"engines": {
|
| 372 |
+
"node": ">=6.9.0"
|
| 373 |
+
}
|
| 374 |
+
},
|
| 375 |
+
"node_modules/@esbuild/aix-ppc64": {
|
| 376 |
+
"version": "0.25.12",
|
| 377 |
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
| 378 |
+
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
|
| 379 |
+
"cpu": [
|
| 380 |
+
"ppc64"
|
| 381 |
+
],
|
| 382 |
+
"dev": true,
|
| 383 |
+
"license": "MIT",
|
| 384 |
+
"optional": true,
|
| 385 |
+
"os": [
|
| 386 |
+
"aix"
|
| 387 |
+
],
|
| 388 |
+
"engines": {
|
| 389 |
+
"node": ">=18"
|
| 390 |
+
}
|
| 391 |
+
},
|
| 392 |
+
"node_modules/@esbuild/android-arm": {
|
| 393 |
+
"version": "0.25.12",
|
| 394 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
|
| 395 |
+
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
|
| 396 |
+
"cpu": [
|
| 397 |
+
"arm"
|
| 398 |
+
],
|
| 399 |
+
"dev": true,
|
| 400 |
+
"license": "MIT",
|
| 401 |
+
"optional": true,
|
| 402 |
+
"os": [
|
| 403 |
+
"android"
|
| 404 |
+
],
|
| 405 |
+
"engines": {
|
| 406 |
+
"node": ">=18"
|
| 407 |
+
}
|
| 408 |
+
},
|
| 409 |
+
"node_modules/@esbuild/android-arm64": {
|
| 410 |
+
"version": "0.25.12",
|
| 411 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
|
| 412 |
+
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
|
| 413 |
+
"cpu": [
|
| 414 |
+
"arm64"
|
| 415 |
+
],
|
| 416 |
+
"dev": true,
|
| 417 |
+
"license": "MIT",
|
| 418 |
+
"optional": true,
|
| 419 |
+
"os": [
|
| 420 |
+
"android"
|
| 421 |
+
],
|
| 422 |
+
"engines": {
|
| 423 |
+
"node": ">=18"
|
| 424 |
+
}
|
| 425 |
+
},
|
| 426 |
+
"node_modules/@esbuild/android-x64": {
|
| 427 |
+
"version": "0.25.12",
|
| 428 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
|
| 429 |
+
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
|
| 430 |
+
"cpu": [
|
| 431 |
+
"x64"
|
| 432 |
+
],
|
| 433 |
+
"dev": true,
|
| 434 |
+
"license": "MIT",
|
| 435 |
+
"optional": true,
|
| 436 |
+
"os": [
|
| 437 |
+
"android"
|
| 438 |
+
],
|
| 439 |
+
"engines": {
|
| 440 |
+
"node": ">=18"
|
| 441 |
+
}
|
| 442 |
+
},
|
| 443 |
+
"node_modules/@esbuild/darwin-arm64": {
|
| 444 |
+
"version": "0.25.12",
|
| 445 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
|
| 446 |
+
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
|
| 447 |
+
"cpu": [
|
| 448 |
+
"arm64"
|
| 449 |
+
],
|
| 450 |
+
"dev": true,
|
| 451 |
+
"license": "MIT",
|
| 452 |
+
"optional": true,
|
| 453 |
+
"os": [
|
| 454 |
+
"darwin"
|
| 455 |
+
],
|
| 456 |
+
"engines": {
|
| 457 |
+
"node": ">=18"
|
| 458 |
+
}
|
| 459 |
+
},
|
| 460 |
+
"node_modules/@esbuild/darwin-x64": {
|
| 461 |
+
"version": "0.25.12",
|
| 462 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
|
| 463 |
+
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
|
| 464 |
+
"cpu": [
|
| 465 |
+
"x64"
|
| 466 |
+
],
|
| 467 |
+
"dev": true,
|
| 468 |
+
"license": "MIT",
|
| 469 |
+
"optional": true,
|
| 470 |
+
"os": [
|
| 471 |
+
"darwin"
|
| 472 |
+
],
|
| 473 |
+
"engines": {
|
| 474 |
+
"node": ">=18"
|
| 475 |
+
}
|
| 476 |
+
},
|
| 477 |
+
"node_modules/@esbuild/freebsd-arm64": {
|
| 478 |
+
"version": "0.25.12",
|
| 479 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
|
| 480 |
+
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
|
| 481 |
+
"cpu": [
|
| 482 |
+
"arm64"
|
| 483 |
+
],
|
| 484 |
+
"dev": true,
|
| 485 |
+
"license": "MIT",
|
| 486 |
+
"optional": true,
|
| 487 |
+
"os": [
|
| 488 |
+
"freebsd"
|
| 489 |
+
],
|
| 490 |
+
"engines": {
|
| 491 |
+
"node": ">=18"
|
| 492 |
+
}
|
| 493 |
+
},
|
| 494 |
+
"node_modules/@esbuild/freebsd-x64": {
|
| 495 |
+
"version": "0.25.12",
|
| 496 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
|
| 497 |
+
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
|
| 498 |
+
"cpu": [
|
| 499 |
+
"x64"
|
| 500 |
+
],
|
| 501 |
+
"dev": true,
|
| 502 |
+
"license": "MIT",
|
| 503 |
+
"optional": true,
|
| 504 |
+
"os": [
|
| 505 |
+
"freebsd"
|
| 506 |
+
],
|
| 507 |
+
"engines": {
|
| 508 |
+
"node": ">=18"
|
| 509 |
+
}
|
| 510 |
+
},
|
| 511 |
+
"node_modules/@esbuild/linux-arm": {
|
| 512 |
+
"version": "0.25.12",
|
| 513 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
|
| 514 |
+
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
|
| 515 |
+
"cpu": [
|
| 516 |
+
"arm"
|
| 517 |
+
],
|
| 518 |
+
"dev": true,
|
| 519 |
+
"license": "MIT",
|
| 520 |
+
"optional": true,
|
| 521 |
+
"os": [
|
| 522 |
+
"linux"
|
| 523 |
+
],
|
| 524 |
+
"engines": {
|
| 525 |
+
"node": ">=18"
|
| 526 |
+
}
|
| 527 |
+
},
|
| 528 |
+
"node_modules/@esbuild/linux-arm64": {
|
| 529 |
+
"version": "0.25.12",
|
| 530 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
|
| 531 |
+
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
|
| 532 |
+
"cpu": [
|
| 533 |
+
"arm64"
|
| 534 |
+
],
|
| 535 |
+
"dev": true,
|
| 536 |
+
"license": "MIT",
|
| 537 |
+
"optional": true,
|
| 538 |
+
"os": [
|
| 539 |
+
"linux"
|
| 540 |
+
],
|
| 541 |
+
"engines": {
|
| 542 |
+
"node": ">=18"
|
| 543 |
+
}
|
| 544 |
+
},
|
| 545 |
+
"node_modules/@esbuild/linux-ia32": {
|
| 546 |
+
"version": "0.25.12",
|
| 547 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
|
| 548 |
+
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
|
| 549 |
+
"cpu": [
|
| 550 |
+
"ia32"
|
| 551 |
+
],
|
| 552 |
+
"dev": true,
|
| 553 |
+
"license": "MIT",
|
| 554 |
+
"optional": true,
|
| 555 |
+
"os": [
|
| 556 |
+
"linux"
|
| 557 |
+
],
|
| 558 |
+
"engines": {
|
| 559 |
+
"node": ">=18"
|
| 560 |
+
}
|
| 561 |
+
},
|
| 562 |
+
"node_modules/@esbuild/linux-loong64": {
|
| 563 |
+
"version": "0.25.12",
|
| 564 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
|
| 565 |
+
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
|
| 566 |
+
"cpu": [
|
| 567 |
+
"loong64"
|
| 568 |
+
],
|
| 569 |
+
"dev": true,
|
| 570 |
+
"license": "MIT",
|
| 571 |
+
"optional": true,
|
| 572 |
+
"os": [
|
| 573 |
+
"linux"
|
| 574 |
+
],
|
| 575 |
+
"engines": {
|
| 576 |
+
"node": ">=18"
|
| 577 |
+
}
|
| 578 |
+
},
|
| 579 |
+
"node_modules/@esbuild/linux-mips64el": {
|
| 580 |
+
"version": "0.25.12",
|
| 581 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
|
| 582 |
+
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
|
| 583 |
+
"cpu": [
|
| 584 |
+
"mips64el"
|
| 585 |
+
],
|
| 586 |
+
"dev": true,
|
| 587 |
+
"license": "MIT",
|
| 588 |
+
"optional": true,
|
| 589 |
+
"os": [
|
| 590 |
+
"linux"
|
| 591 |
+
],
|
| 592 |
+
"engines": {
|
| 593 |
+
"node": ">=18"
|
| 594 |
+
}
|
| 595 |
+
},
|
| 596 |
+
"node_modules/@esbuild/linux-ppc64": {
|
| 597 |
+
"version": "0.25.12",
|
| 598 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
|
| 599 |
+
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
|
| 600 |
+
"cpu": [
|
| 601 |
+
"ppc64"
|
| 602 |
+
],
|
| 603 |
+
"dev": true,
|
| 604 |
+
"license": "MIT",
|
| 605 |
+
"optional": true,
|
| 606 |
+
"os": [
|
| 607 |
+
"linux"
|
| 608 |
+
],
|
| 609 |
+
"engines": {
|
| 610 |
+
"node": ">=18"
|
| 611 |
+
}
|
| 612 |
+
},
|
| 613 |
+
"node_modules/@esbuild/linux-riscv64": {
|
| 614 |
+
"version": "0.25.12",
|
| 615 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
|
| 616 |
+
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
|
| 617 |
+
"cpu": [
|
| 618 |
+
"riscv64"
|
| 619 |
+
],
|
| 620 |
+
"dev": true,
|
| 621 |
+
"license": "MIT",
|
| 622 |
+
"optional": true,
|
| 623 |
+
"os": [
|
| 624 |
+
"linux"
|
| 625 |
+
],
|
| 626 |
+
"engines": {
|
| 627 |
+
"node": ">=18"
|
| 628 |
+
}
|
| 629 |
+
},
|
| 630 |
+
"node_modules/@esbuild/linux-s390x": {
|
| 631 |
+
"version": "0.25.12",
|
| 632 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
|
| 633 |
+
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
|
| 634 |
+
"cpu": [
|
| 635 |
+
"s390x"
|
| 636 |
+
],
|
| 637 |
+
"dev": true,
|
| 638 |
+
"license": "MIT",
|
| 639 |
+
"optional": true,
|
| 640 |
+
"os": [
|
| 641 |
+
"linux"
|
| 642 |
+
],
|
| 643 |
+
"engines": {
|
| 644 |
+
"node": ">=18"
|
| 645 |
+
}
|
| 646 |
+
},
|
| 647 |
+
"node_modules/@esbuild/linux-x64": {
|
| 648 |
+
"version": "0.25.12",
|
| 649 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
|
| 650 |
+
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
|
| 651 |
+
"cpu": [
|
| 652 |
+
"x64"
|
| 653 |
+
],
|
| 654 |
+
"dev": true,
|
| 655 |
+
"license": "MIT",
|
| 656 |
+
"optional": true,
|
| 657 |
+
"os": [
|
| 658 |
+
"linux"
|
| 659 |
+
],
|
| 660 |
+
"engines": {
|
| 661 |
+
"node": ">=18"
|
| 662 |
+
}
|
| 663 |
+
},
|
| 664 |
+
"node_modules/@esbuild/netbsd-arm64": {
|
| 665 |
+
"version": "0.25.12",
|
| 666 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
|
| 667 |
+
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
|
| 668 |
+
"cpu": [
|
| 669 |
+
"arm64"
|
| 670 |
+
],
|
| 671 |
+
"dev": true,
|
| 672 |
+
"license": "MIT",
|
| 673 |
+
"optional": true,
|
| 674 |
+
"os": [
|
| 675 |
+
"netbsd"
|
| 676 |
+
],
|
| 677 |
+
"engines": {
|
| 678 |
+
"node": ">=18"
|
| 679 |
+
}
|
| 680 |
+
},
|
| 681 |
+
"node_modules/@esbuild/netbsd-x64": {
|
| 682 |
+
"version": "0.25.12",
|
| 683 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
|
| 684 |
+
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
|
| 685 |
+
"cpu": [
|
| 686 |
+
"x64"
|
| 687 |
+
],
|
| 688 |
+
"dev": true,
|
| 689 |
+
"license": "MIT",
|
| 690 |
+
"optional": true,
|
| 691 |
+
"os": [
|
| 692 |
+
"netbsd"
|
| 693 |
+
],
|
| 694 |
+
"engines": {
|
| 695 |
+
"node": ">=18"
|
| 696 |
+
}
|
| 697 |
+
},
|
| 698 |
+
"node_modules/@esbuild/openbsd-arm64": {
|
| 699 |
+
"version": "0.25.12",
|
| 700 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
|
| 701 |
+
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
|
| 702 |
+
"cpu": [
|
| 703 |
+
"arm64"
|
| 704 |
+
],
|
| 705 |
+
"dev": true,
|
| 706 |
+
"license": "MIT",
|
| 707 |
+
"optional": true,
|
| 708 |
+
"os": [
|
| 709 |
+
"openbsd"
|
| 710 |
+
],
|
| 711 |
+
"engines": {
|
| 712 |
+
"node": ">=18"
|
| 713 |
+
}
|
| 714 |
+
},
|
| 715 |
+
"node_modules/@esbuild/openbsd-x64": {
|
| 716 |
+
"version": "0.25.12",
|
| 717 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
|
| 718 |
+
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
|
| 719 |
+
"cpu": [
|
| 720 |
+
"x64"
|
| 721 |
+
],
|
| 722 |
+
"dev": true,
|
| 723 |
+
"license": "MIT",
|
| 724 |
+
"optional": true,
|
| 725 |
+
"os": [
|
| 726 |
+
"openbsd"
|
| 727 |
+
],
|
| 728 |
+
"engines": {
|
| 729 |
+
"node": ">=18"
|
| 730 |
+
}
|
| 731 |
+
},
|
| 732 |
+
"node_modules/@esbuild/openharmony-arm64": {
|
| 733 |
+
"version": "0.25.12",
|
| 734 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
|
| 735 |
+
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
|
| 736 |
+
"cpu": [
|
| 737 |
+
"arm64"
|
| 738 |
+
],
|
| 739 |
+
"dev": true,
|
| 740 |
+
"license": "MIT",
|
| 741 |
+
"optional": true,
|
| 742 |
+
"os": [
|
| 743 |
+
"openharmony"
|
| 744 |
+
],
|
| 745 |
+
"engines": {
|
| 746 |
+
"node": ">=18"
|
| 747 |
+
}
|
| 748 |
+
},
|
| 749 |
+
"node_modules/@esbuild/sunos-x64": {
|
| 750 |
+
"version": "0.25.12",
|
| 751 |
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
|
| 752 |
+
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
|
| 753 |
+
"cpu": [
|
| 754 |
+
"x64"
|
| 755 |
+
],
|
| 756 |
+
"dev": true,
|
| 757 |
+
"license": "MIT",
|
| 758 |
+
"optional": true,
|
| 759 |
+
"os": [
|
| 760 |
+
"sunos"
|
| 761 |
+
],
|
| 762 |
+
"engines": {
|
| 763 |
+
"node": ">=18"
|
| 764 |
+
}
|
| 765 |
+
},
|
| 766 |
+
"node_modules/@esbuild/win32-arm64": {
|
| 767 |
+
"version": "0.25.12",
|
| 768 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
|
| 769 |
+
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
|
| 770 |
+
"cpu": [
|
| 771 |
+
"arm64"
|
| 772 |
+
],
|
| 773 |
+
"dev": true,
|
| 774 |
+
"license": "MIT",
|
| 775 |
+
"optional": true,
|
| 776 |
+
"os": [
|
| 777 |
+
"win32"
|
| 778 |
+
],
|
| 779 |
+
"engines": {
|
| 780 |
+
"node": ">=18"
|
| 781 |
+
}
|
| 782 |
+
},
|
| 783 |
+
"node_modules/@esbuild/win32-ia32": {
|
| 784 |
+
"version": "0.25.12",
|
| 785 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
|
| 786 |
+
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
|
| 787 |
+
"cpu": [
|
| 788 |
+
"ia32"
|
| 789 |
+
],
|
| 790 |
+
"dev": true,
|
| 791 |
+
"license": "MIT",
|
| 792 |
+
"optional": true,
|
| 793 |
+
"os": [
|
| 794 |
+
"win32"
|
| 795 |
+
],
|
| 796 |
+
"engines": {
|
| 797 |
+
"node": ">=18"
|
| 798 |
+
}
|
| 799 |
+
},
|
| 800 |
+
"node_modules/@esbuild/win32-x64": {
|
| 801 |
+
"version": "0.25.12",
|
| 802 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
|
| 803 |
+
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
|
| 804 |
+
"cpu": [
|
| 805 |
+
"x64"
|
| 806 |
+
],
|
| 807 |
+
"dev": true,
|
| 808 |
+
"license": "MIT",
|
| 809 |
+
"optional": true,
|
| 810 |
+
"os": [
|
| 811 |
+
"win32"
|
| 812 |
+
],
|
| 813 |
+
"engines": {
|
| 814 |
+
"node": ">=18"
|
| 815 |
+
}
|
| 816 |
+
},
|
| 817 |
+
"node_modules/@huggingface/hub": {
|
| 818 |
+
"version": "2.11.0",
|
| 819 |
+
"resolved": "https://registry.npmjs.org/@huggingface/hub/-/hub-2.11.0.tgz",
|
| 820 |
+
"integrity": "sha512-WS6QGaXYeBVFlaB4SOn6z4LGUpLB5kRZNL08uUni4izX353KxiwwZMK5+/AWX86MJh8SMZNa/JFcvFCcQsbszQ==",
|
| 821 |
+
"license": "MIT",
|
| 822 |
+
"dependencies": {
|
| 823 |
+
"@huggingface/tasks": "^0.19.90"
|
| 824 |
+
},
|
| 825 |
+
"bin": {
|
| 826 |
+
"hfjs": "dist/cli.js"
|
| 827 |
+
},
|
| 828 |
+
"engines": {
|
| 829 |
+
"node": ">=18"
|
| 830 |
+
},
|
| 831 |
+
"optionalDependencies": {
|
| 832 |
+
"cli-progress": "^3.12.0"
|
| 833 |
+
}
|
| 834 |
+
},
|
| 835 |
+
"node_modules/@huggingface/tasks": {
|
| 836 |
+
"version": "0.19.90",
|
| 837 |
+
"resolved": "https://registry.npmjs.org/@huggingface/tasks/-/tasks-0.19.90.tgz",
|
| 838 |
+
"integrity": "sha512-nfV9luJbvwGQ/5oKXkKhCV9h4X7mwh1YaGG3ORd6UMLDSwr1OFSSatcBX0O9OtBtmNK19aGSjbLFqqgcIR6+IA==",
|
| 839 |
+
"license": "MIT"
|
| 840 |
+
},
|
| 841 |
+
"node_modules/@jridgewell/gen-mapping": {
|
| 842 |
+
"version": "0.3.13",
|
| 843 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
| 844 |
+
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
| 845 |
+
"dev": true,
|
| 846 |
+
"license": "MIT",
|
| 847 |
+
"dependencies": {
|
| 848 |
+
"@jridgewell/sourcemap-codec": "^1.5.0",
|
| 849 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 850 |
+
}
|
| 851 |
+
},
|
| 852 |
+
"node_modules/@jridgewell/remapping": {
|
| 853 |
+
"version": "2.3.5",
|
| 854 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
| 855 |
+
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
| 856 |
+
"dev": true,
|
| 857 |
+
"license": "MIT",
|
| 858 |
+
"dependencies": {
|
| 859 |
+
"@jridgewell/gen-mapping": "^0.3.5",
|
| 860 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 861 |
+
}
|
| 862 |
+
},
|
| 863 |
+
"node_modules/@jridgewell/resolve-uri": {
|
| 864 |
+
"version": "3.1.2",
|
| 865 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 866 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 867 |
+
"dev": true,
|
| 868 |
+
"license": "MIT",
|
| 869 |
+
"engines": {
|
| 870 |
+
"node": ">=6.0.0"
|
| 871 |
+
}
|
| 872 |
+
},
|
| 873 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 874 |
+
"version": "1.5.5",
|
| 875 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 876 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 877 |
+
"dev": true,
|
| 878 |
+
"license": "MIT"
|
| 879 |
+
},
|
| 880 |
+
"node_modules/@jridgewell/trace-mapping": {
|
| 881 |
+
"version": "0.3.31",
|
| 882 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
| 883 |
+
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
| 884 |
+
"dev": true,
|
| 885 |
+
"license": "MIT",
|
| 886 |
+
"dependencies": {
|
| 887 |
+
"@jridgewell/resolve-uri": "^3.1.0",
|
| 888 |
+
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 889 |
+
}
|
| 890 |
+
},
|
| 891 |
+
"node_modules/@monaco-editor/loader": {
|
| 892 |
+
"version": "1.7.0",
|
| 893 |
+
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz",
|
| 894 |
+
"integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==",
|
| 895 |
+
"license": "MIT",
|
| 896 |
+
"dependencies": {
|
| 897 |
+
"state-local": "^1.0.6"
|
| 898 |
+
}
|
| 899 |
+
},
|
| 900 |
+
"node_modules/@monaco-editor/react": {
|
| 901 |
+
"version": "4.7.0",
|
| 902 |
+
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz",
|
| 903 |
+
"integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==",
|
| 904 |
+
"license": "MIT",
|
| 905 |
+
"dependencies": {
|
| 906 |
+
"@monaco-editor/loader": "^1.5.0"
|
| 907 |
+
},
|
| 908 |
+
"peerDependencies": {
|
| 909 |
+
"monaco-editor": ">= 0.25.0 < 1",
|
| 910 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
| 911 |
+
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 912 |
+
}
|
| 913 |
+
},
|
| 914 |
+
"node_modules/@opentelemetry/api": {
|
| 915 |
+
"version": "1.9.0",
|
| 916 |
+
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
| 917 |
+
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
| 918 |
+
"license": "Apache-2.0",
|
| 919 |
+
"engines": {
|
| 920 |
+
"node": ">=8.0.0"
|
| 921 |
+
}
|
| 922 |
+
},
|
| 923 |
+
"node_modules/@rolldown/pluginutils": {
|
| 924 |
+
"version": "1.0.0-beta.27",
|
| 925 |
+
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
| 926 |
+
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
|
| 927 |
+
"dev": true,
|
| 928 |
+
"license": "MIT"
|
| 929 |
+
},
|
| 930 |
+
"node_modules/@rollup/rollup-android-arm-eabi": {
|
| 931 |
+
"version": "4.60.2",
|
| 932 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz",
|
| 933 |
+
"integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==",
|
| 934 |
+
"cpu": [
|
| 935 |
+
"arm"
|
| 936 |
+
],
|
| 937 |
+
"dev": true,
|
| 938 |
+
"license": "MIT",
|
| 939 |
+
"optional": true,
|
| 940 |
+
"os": [
|
| 941 |
+
"android"
|
| 942 |
+
]
|
| 943 |
+
},
|
| 944 |
+
"node_modules/@rollup/rollup-android-arm64": {
|
| 945 |
+
"version": "4.60.2",
|
| 946 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz",
|
| 947 |
+
"integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==",
|
| 948 |
+
"cpu": [
|
| 949 |
+
"arm64"
|
| 950 |
+
],
|
| 951 |
+
"dev": true,
|
| 952 |
+
"license": "MIT",
|
| 953 |
+
"optional": true,
|
| 954 |
+
"os": [
|
| 955 |
+
"android"
|
| 956 |
+
]
|
| 957 |
+
},
|
| 958 |
+
"node_modules/@rollup/rollup-darwin-arm64": {
|
| 959 |
+
"version": "4.60.2",
|
| 960 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz",
|
| 961 |
+
"integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==",
|
| 962 |
+
"cpu": [
|
| 963 |
+
"arm64"
|
| 964 |
+
],
|
| 965 |
+
"dev": true,
|
| 966 |
+
"license": "MIT",
|
| 967 |
+
"optional": true,
|
| 968 |
+
"os": [
|
| 969 |
+
"darwin"
|
| 970 |
+
]
|
| 971 |
+
},
|
| 972 |
+
"node_modules/@rollup/rollup-darwin-x64": {
|
| 973 |
+
"version": "4.60.2",
|
| 974 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz",
|
| 975 |
+
"integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==",
|
| 976 |
+
"cpu": [
|
| 977 |
+
"x64"
|
| 978 |
+
],
|
| 979 |
+
"dev": true,
|
| 980 |
+
"license": "MIT",
|
| 981 |
+
"optional": true,
|
| 982 |
+
"os": [
|
| 983 |
+
"darwin"
|
| 984 |
+
]
|
| 985 |
+
},
|
| 986 |
+
"node_modules/@rollup/rollup-freebsd-arm64": {
|
| 987 |
+
"version": "4.60.2",
|
| 988 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz",
|
| 989 |
+
"integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==",
|
| 990 |
+
"cpu": [
|
| 991 |
+
"arm64"
|
| 992 |
+
],
|
| 993 |
+
"dev": true,
|
| 994 |
+
"license": "MIT",
|
| 995 |
+
"optional": true,
|
| 996 |
+
"os": [
|
| 997 |
+
"freebsd"
|
| 998 |
+
]
|
| 999 |
+
},
|
| 1000 |
+
"node_modules/@rollup/rollup-freebsd-x64": {
|
| 1001 |
+
"version": "4.60.2",
|
| 1002 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz",
|
| 1003 |
+
"integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==",
|
| 1004 |
+
"cpu": [
|
| 1005 |
+
"x64"
|
| 1006 |
+
],
|
| 1007 |
+
"dev": true,
|
| 1008 |
+
"license": "MIT",
|
| 1009 |
+
"optional": true,
|
| 1010 |
+
"os": [
|
| 1011 |
+
"freebsd"
|
| 1012 |
+
]
|
| 1013 |
+
},
|
| 1014 |
+
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
| 1015 |
+
"version": "4.60.2",
|
| 1016 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz",
|
| 1017 |
+
"integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==",
|
| 1018 |
+
"cpu": [
|
| 1019 |
+
"arm"
|
| 1020 |
+
],
|
| 1021 |
+
"dev": true,
|
| 1022 |
+
"license": "MIT",
|
| 1023 |
+
"optional": true,
|
| 1024 |
+
"os": [
|
| 1025 |
+
"linux"
|
| 1026 |
+
]
|
| 1027 |
+
},
|
| 1028 |
+
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
| 1029 |
+
"version": "4.60.2",
|
| 1030 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz",
|
| 1031 |
+
"integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==",
|
| 1032 |
+
"cpu": [
|
| 1033 |
+
"arm"
|
| 1034 |
+
],
|
| 1035 |
+
"dev": true,
|
| 1036 |
+
"license": "MIT",
|
| 1037 |
+
"optional": true,
|
| 1038 |
+
"os": [
|
| 1039 |
+
"linux"
|
| 1040 |
+
]
|
| 1041 |
+
},
|
| 1042 |
+
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
| 1043 |
+
"version": "4.60.2",
|
| 1044 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz",
|
| 1045 |
+
"integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==",
|
| 1046 |
+
"cpu": [
|
| 1047 |
+
"arm64"
|
| 1048 |
+
],
|
| 1049 |
+
"dev": true,
|
| 1050 |
+
"license": "MIT",
|
| 1051 |
+
"optional": true,
|
| 1052 |
+
"os": [
|
| 1053 |
+
"linux"
|
| 1054 |
+
]
|
| 1055 |
+
},
|
| 1056 |
+
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
| 1057 |
+
"version": "4.60.2",
|
| 1058 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz",
|
| 1059 |
+
"integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==",
|
| 1060 |
+
"cpu": [
|
| 1061 |
+
"arm64"
|
| 1062 |
+
],
|
| 1063 |
+
"dev": true,
|
| 1064 |
+
"license": "MIT",
|
| 1065 |
+
"optional": true,
|
| 1066 |
+
"os": [
|
| 1067 |
+
"linux"
|
| 1068 |
+
]
|
| 1069 |
+
},
|
| 1070 |
+
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
| 1071 |
+
"version": "4.60.2",
|
| 1072 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz",
|
| 1073 |
+
"integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==",
|
| 1074 |
+
"cpu": [
|
| 1075 |
+
"loong64"
|
| 1076 |
+
],
|
| 1077 |
+
"dev": true,
|
| 1078 |
+
"license": "MIT",
|
| 1079 |
+
"optional": true,
|
| 1080 |
+
"os": [
|
| 1081 |
+
"linux"
|
| 1082 |
+
]
|
| 1083 |
+
},
|
| 1084 |
+
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
| 1085 |
+
"version": "4.60.2",
|
| 1086 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz",
|
| 1087 |
+
"integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==",
|
| 1088 |
+
"cpu": [
|
| 1089 |
+
"loong64"
|
| 1090 |
+
],
|
| 1091 |
+
"dev": true,
|
| 1092 |
+
"license": "MIT",
|
| 1093 |
+
"optional": true,
|
| 1094 |
+
"os": [
|
| 1095 |
+
"linux"
|
| 1096 |
+
]
|
| 1097 |
+
},
|
| 1098 |
+
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
| 1099 |
+
"version": "4.60.2",
|
| 1100 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz",
|
| 1101 |
+
"integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==",
|
| 1102 |
+
"cpu": [
|
| 1103 |
+
"ppc64"
|
| 1104 |
+
],
|
| 1105 |
+
"dev": true,
|
| 1106 |
+
"license": "MIT",
|
| 1107 |
+
"optional": true,
|
| 1108 |
+
"os": [
|
| 1109 |
+
"linux"
|
| 1110 |
+
]
|
| 1111 |
+
},
|
| 1112 |
+
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
| 1113 |
+
"version": "4.60.2",
|
| 1114 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz",
|
| 1115 |
+
"integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==",
|
| 1116 |
+
"cpu": [
|
| 1117 |
+
"ppc64"
|
| 1118 |
+
],
|
| 1119 |
+
"dev": true,
|
| 1120 |
+
"license": "MIT",
|
| 1121 |
+
"optional": true,
|
| 1122 |
+
"os": [
|
| 1123 |
+
"linux"
|
| 1124 |
+
]
|
| 1125 |
+
},
|
| 1126 |
+
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
| 1127 |
+
"version": "4.60.2",
|
| 1128 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz",
|
| 1129 |
+
"integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==",
|
| 1130 |
+
"cpu": [
|
| 1131 |
+
"riscv64"
|
| 1132 |
+
],
|
| 1133 |
+
"dev": true,
|
| 1134 |
+
"license": "MIT",
|
| 1135 |
+
"optional": true,
|
| 1136 |
+
"os": [
|
| 1137 |
+
"linux"
|
| 1138 |
+
]
|
| 1139 |
+
},
|
| 1140 |
+
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
| 1141 |
+
"version": "4.60.2",
|
| 1142 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz",
|
| 1143 |
+
"integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==",
|
| 1144 |
+
"cpu": [
|
| 1145 |
+
"riscv64"
|
| 1146 |
+
],
|
| 1147 |
+
"dev": true,
|
| 1148 |
+
"license": "MIT",
|
| 1149 |
+
"optional": true,
|
| 1150 |
+
"os": [
|
| 1151 |
+
"linux"
|
| 1152 |
+
]
|
| 1153 |
+
},
|
| 1154 |
+
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
| 1155 |
+
"version": "4.60.2",
|
| 1156 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz",
|
| 1157 |
+
"integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==",
|
| 1158 |
+
"cpu": [
|
| 1159 |
+
"s390x"
|
| 1160 |
+
],
|
| 1161 |
+
"dev": true,
|
| 1162 |
+
"license": "MIT",
|
| 1163 |
+
"optional": true,
|
| 1164 |
+
"os": [
|
| 1165 |
+
"linux"
|
| 1166 |
+
]
|
| 1167 |
+
},
|
| 1168 |
+
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
| 1169 |
+
"version": "4.60.2",
|
| 1170 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz",
|
| 1171 |
+
"integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==",
|
| 1172 |
+
"cpu": [
|
| 1173 |
+
"x64"
|
| 1174 |
+
],
|
| 1175 |
+
"dev": true,
|
| 1176 |
+
"license": "MIT",
|
| 1177 |
+
"optional": true,
|
| 1178 |
+
"os": [
|
| 1179 |
+
"linux"
|
| 1180 |
+
]
|
| 1181 |
+
},
|
| 1182 |
+
"node_modules/@rollup/rollup-linux-x64-musl": {
|
| 1183 |
+
"version": "4.60.2",
|
| 1184 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz",
|
| 1185 |
+
"integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==",
|
| 1186 |
+
"cpu": [
|
| 1187 |
+
"x64"
|
| 1188 |
+
],
|
| 1189 |
+
"dev": true,
|
| 1190 |
+
"license": "MIT",
|
| 1191 |
+
"optional": true,
|
| 1192 |
+
"os": [
|
| 1193 |
+
"linux"
|
| 1194 |
+
]
|
| 1195 |
+
},
|
| 1196 |
+
"node_modules/@rollup/rollup-openbsd-x64": {
|
| 1197 |
+
"version": "4.60.2",
|
| 1198 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz",
|
| 1199 |
+
"integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==",
|
| 1200 |
+
"cpu": [
|
| 1201 |
+
"x64"
|
| 1202 |
+
],
|
| 1203 |
+
"dev": true,
|
| 1204 |
+
"license": "MIT",
|
| 1205 |
+
"optional": true,
|
| 1206 |
+
"os": [
|
| 1207 |
+
"openbsd"
|
| 1208 |
+
]
|
| 1209 |
+
},
|
| 1210 |
+
"node_modules/@rollup/rollup-openharmony-arm64": {
|
| 1211 |
+
"version": "4.60.2",
|
| 1212 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz",
|
| 1213 |
+
"integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==",
|
| 1214 |
+
"cpu": [
|
| 1215 |
+
"arm64"
|
| 1216 |
+
],
|
| 1217 |
+
"dev": true,
|
| 1218 |
+
"license": "MIT",
|
| 1219 |
+
"optional": true,
|
| 1220 |
+
"os": [
|
| 1221 |
+
"openharmony"
|
| 1222 |
+
]
|
| 1223 |
+
},
|
| 1224 |
+
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
| 1225 |
+
"version": "4.60.2",
|
| 1226 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz",
|
| 1227 |
+
"integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==",
|
| 1228 |
+
"cpu": [
|
| 1229 |
+
"arm64"
|
| 1230 |
+
],
|
| 1231 |
+
"dev": true,
|
| 1232 |
+
"license": "MIT",
|
| 1233 |
+
"optional": true,
|
| 1234 |
+
"os": [
|
| 1235 |
+
"win32"
|
| 1236 |
+
]
|
| 1237 |
+
},
|
| 1238 |
+
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
| 1239 |
+
"version": "4.60.2",
|
| 1240 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz",
|
| 1241 |
+
"integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==",
|
| 1242 |
+
"cpu": [
|
| 1243 |
+
"ia32"
|
| 1244 |
+
],
|
| 1245 |
+
"dev": true,
|
| 1246 |
+
"license": "MIT",
|
| 1247 |
+
"optional": true,
|
| 1248 |
+
"os": [
|
| 1249 |
+
"win32"
|
| 1250 |
+
]
|
| 1251 |
+
},
|
| 1252 |
+
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
| 1253 |
+
"version": "4.60.2",
|
| 1254 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz",
|
| 1255 |
+
"integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==",
|
| 1256 |
+
"cpu": [
|
| 1257 |
+
"x64"
|
| 1258 |
+
],
|
| 1259 |
+
"dev": true,
|
| 1260 |
+
"license": "MIT",
|
| 1261 |
+
"optional": true,
|
| 1262 |
+
"os": [
|
| 1263 |
+
"win32"
|
| 1264 |
+
]
|
| 1265 |
+
},
|
| 1266 |
+
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
| 1267 |
+
"version": "4.60.2",
|
| 1268 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz",
|
| 1269 |
+
"integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==",
|
| 1270 |
+
"cpu": [
|
| 1271 |
+
"x64"
|
| 1272 |
+
],
|
| 1273 |
+
"dev": true,
|
| 1274 |
+
"license": "MIT",
|
| 1275 |
+
"optional": true,
|
| 1276 |
+
"os": [
|
| 1277 |
+
"win32"
|
| 1278 |
+
]
|
| 1279 |
+
},
|
| 1280 |
+
"node_modules/@standard-schema/spec": {
|
| 1281 |
+
"version": "1.1.0",
|
| 1282 |
+
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
| 1283 |
+
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
| 1284 |
+
"license": "MIT"
|
| 1285 |
+
},
|
| 1286 |
+
"node_modules/@types/babel__core": {
|
| 1287 |
+
"version": "7.20.5",
|
| 1288 |
+
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
| 1289 |
+
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
|
| 1290 |
+
"dev": true,
|
| 1291 |
+
"license": "MIT",
|
| 1292 |
+
"dependencies": {
|
| 1293 |
+
"@babel/parser": "^7.20.7",
|
| 1294 |
+
"@babel/types": "^7.20.7",
|
| 1295 |
+
"@types/babel__generator": "*",
|
| 1296 |
+
"@types/babel__template": "*",
|
| 1297 |
+
"@types/babel__traverse": "*"
|
| 1298 |
+
}
|
| 1299 |
+
},
|
| 1300 |
+
"node_modules/@types/babel__generator": {
|
| 1301 |
+
"version": "7.27.0",
|
| 1302 |
+
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
|
| 1303 |
+
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
|
| 1304 |
+
"dev": true,
|
| 1305 |
+
"license": "MIT",
|
| 1306 |
+
"dependencies": {
|
| 1307 |
+
"@babel/types": "^7.0.0"
|
| 1308 |
+
}
|
| 1309 |
+
},
|
| 1310 |
+
"node_modules/@types/babel__template": {
|
| 1311 |
+
"version": "7.4.4",
|
| 1312 |
+
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
|
| 1313 |
+
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
|
| 1314 |
+
"dev": true,
|
| 1315 |
+
"license": "MIT",
|
| 1316 |
+
"dependencies": {
|
| 1317 |
+
"@babel/parser": "^7.1.0",
|
| 1318 |
+
"@babel/types": "^7.0.0"
|
| 1319 |
+
}
|
| 1320 |
+
},
|
| 1321 |
+
"node_modules/@types/babel__traverse": {
|
| 1322 |
+
"version": "7.28.0",
|
| 1323 |
+
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
|
| 1324 |
+
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
|
| 1325 |
+
"dev": true,
|
| 1326 |
+
"license": "MIT",
|
| 1327 |
+
"dependencies": {
|
| 1328 |
+
"@babel/types": "^7.28.2"
|
| 1329 |
+
}
|
| 1330 |
+
},
|
| 1331 |
+
"node_modules/@types/estree": {
|
| 1332 |
+
"version": "1.0.8",
|
| 1333 |
+
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
| 1334 |
+
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
| 1335 |
+
"dev": true,
|
| 1336 |
+
"license": "MIT"
|
| 1337 |
+
},
|
| 1338 |
+
"node_modules/@types/prop-types": {
|
| 1339 |
+
"version": "15.7.15",
|
| 1340 |
+
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
| 1341 |
+
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
| 1342 |
+
"dev": true,
|
| 1343 |
+
"license": "MIT"
|
| 1344 |
+
},
|
| 1345 |
+
"node_modules/@types/react": {
|
| 1346 |
+
"version": "18.3.28",
|
| 1347 |
+
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
|
| 1348 |
+
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
|
| 1349 |
+
"dev": true,
|
| 1350 |
+
"license": "MIT",
|
| 1351 |
+
"peer": true,
|
| 1352 |
+
"dependencies": {
|
| 1353 |
+
"@types/prop-types": "*",
|
| 1354 |
+
"csstype": "^3.2.2"
|
| 1355 |
+
}
|
| 1356 |
+
},
|
| 1357 |
+
"node_modules/@types/react-dom": {
|
| 1358 |
+
"version": "18.3.7",
|
| 1359 |
+
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
| 1360 |
+
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
| 1361 |
+
"dev": true,
|
| 1362 |
+
"license": "MIT",
|
| 1363 |
+
"peerDependencies": {
|
| 1364 |
+
"@types/react": "^18.0.0"
|
| 1365 |
+
}
|
| 1366 |
+
},
|
| 1367 |
+
"node_modules/@types/trusted-types": {
|
| 1368 |
+
"version": "2.0.7",
|
| 1369 |
+
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
| 1370 |
+
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
| 1371 |
+
"license": "MIT",
|
| 1372 |
+
"optional": true
|
| 1373 |
+
},
|
| 1374 |
+
"node_modules/@vercel/oidc": {
|
| 1375 |
+
"version": "3.2.0",
|
| 1376 |
+
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz",
|
| 1377 |
+
"integrity": "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==",
|
| 1378 |
+
"license": "Apache-2.0",
|
| 1379 |
+
"engines": {
|
| 1380 |
+
"node": ">= 20"
|
| 1381 |
+
}
|
| 1382 |
+
},
|
| 1383 |
+
"node_modules/@vitejs/plugin-react": {
|
| 1384 |
+
"version": "4.7.0",
|
| 1385 |
+
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
| 1386 |
+
"integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
|
| 1387 |
+
"dev": true,
|
| 1388 |
+
"license": "MIT",
|
| 1389 |
+
"dependencies": {
|
| 1390 |
+
"@babel/core": "^7.28.0",
|
| 1391 |
+
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
|
| 1392 |
+
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
|
| 1393 |
+
"@rolldown/pluginutils": "1.0.0-beta.27",
|
| 1394 |
+
"@types/babel__core": "^7.20.5",
|
| 1395 |
+
"react-refresh": "^0.17.0"
|
| 1396 |
+
},
|
| 1397 |
+
"engines": {
|
| 1398 |
+
"node": "^14.18.0 || >=16.0.0"
|
| 1399 |
+
},
|
| 1400 |
+
"peerDependencies": {
|
| 1401 |
+
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
| 1402 |
+
}
|
| 1403 |
+
},
|
| 1404 |
+
"node_modules/ai": {
|
| 1405 |
+
"version": "6.0.168",
|
| 1406 |
+
"resolved": "https://registry.npmjs.org/ai/-/ai-6.0.168.tgz",
|
| 1407 |
+
"integrity": "sha512-2HqCJuO+1V2aV7vfYs5LFEUfxbkGX+5oa54q/gCCTL7KLTdbxcCu5D7TdLA5kwsrs3Szgjah9q6D9tpjHM3hUQ==",
|
| 1408 |
+
"license": "Apache-2.0",
|
| 1409 |
+
"dependencies": {
|
| 1410 |
+
"@ai-sdk/gateway": "3.0.104",
|
| 1411 |
+
"@ai-sdk/provider": "3.0.8",
|
| 1412 |
+
"@ai-sdk/provider-utils": "4.0.23",
|
| 1413 |
+
"@opentelemetry/api": "1.9.0"
|
| 1414 |
+
},
|
| 1415 |
+
"engines": {
|
| 1416 |
+
"node": ">=18"
|
| 1417 |
+
},
|
| 1418 |
+
"peerDependencies": {
|
| 1419 |
+
"zod": "^3.25.76 || ^4.1.8"
|
| 1420 |
+
}
|
| 1421 |
+
},
|
| 1422 |
+
"node_modules/ansi-regex": {
|
| 1423 |
+
"version": "5.0.1",
|
| 1424 |
+
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
| 1425 |
+
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
| 1426 |
+
"license": "MIT",
|
| 1427 |
+
"optional": true,
|
| 1428 |
+
"engines": {
|
| 1429 |
+
"node": ">=8"
|
| 1430 |
+
}
|
| 1431 |
+
},
|
| 1432 |
+
"node_modules/baseline-browser-mapping": {
|
| 1433 |
+
"version": "2.10.20",
|
| 1434 |
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz",
|
| 1435 |
+
"integrity": "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==",
|
| 1436 |
+
"dev": true,
|
| 1437 |
+
"license": "Apache-2.0",
|
| 1438 |
+
"bin": {
|
| 1439 |
+
"baseline-browser-mapping": "dist/cli.cjs"
|
| 1440 |
+
},
|
| 1441 |
+
"engines": {
|
| 1442 |
+
"node": ">=6.0.0"
|
| 1443 |
+
}
|
| 1444 |
+
},
|
| 1445 |
+
"node_modules/browserslist": {
|
| 1446 |
+
"version": "4.28.2",
|
| 1447 |
+
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
|
| 1448 |
+
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
|
| 1449 |
+
"dev": true,
|
| 1450 |
+
"funding": [
|
| 1451 |
+
{
|
| 1452 |
+
"type": "opencollective",
|
| 1453 |
+
"url": "https://opencollective.com/browserslist"
|
| 1454 |
+
},
|
| 1455 |
+
{
|
| 1456 |
+
"type": "tidelift",
|
| 1457 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 1458 |
+
},
|
| 1459 |
+
{
|
| 1460 |
+
"type": "github",
|
| 1461 |
+
"url": "https://github.com/sponsors/ai"
|
| 1462 |
+
}
|
| 1463 |
+
],
|
| 1464 |
+
"license": "MIT",
|
| 1465 |
+
"peer": true,
|
| 1466 |
+
"dependencies": {
|
| 1467 |
+
"baseline-browser-mapping": "^2.10.12",
|
| 1468 |
+
"caniuse-lite": "^1.0.30001782",
|
| 1469 |
+
"electron-to-chromium": "^1.5.328",
|
| 1470 |
+
"node-releases": "^2.0.36",
|
| 1471 |
+
"update-browserslist-db": "^1.2.3"
|
| 1472 |
+
},
|
| 1473 |
+
"bin": {
|
| 1474 |
+
"browserslist": "cli.js"
|
| 1475 |
+
},
|
| 1476 |
+
"engines": {
|
| 1477 |
+
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 1478 |
+
}
|
| 1479 |
+
},
|
| 1480 |
+
"node_modules/caniuse-lite": {
|
| 1481 |
+
"version": "1.0.30001790",
|
| 1482 |
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz",
|
| 1483 |
+
"integrity": "sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==",
|
| 1484 |
+
"dev": true,
|
| 1485 |
+
"funding": [
|
| 1486 |
+
{
|
| 1487 |
+
"type": "opencollective",
|
| 1488 |
+
"url": "https://opencollective.com/browserslist"
|
| 1489 |
+
},
|
| 1490 |
+
{
|
| 1491 |
+
"type": "tidelift",
|
| 1492 |
+
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
| 1493 |
+
},
|
| 1494 |
+
{
|
| 1495 |
+
"type": "github",
|
| 1496 |
+
"url": "https://github.com/sponsors/ai"
|
| 1497 |
+
}
|
| 1498 |
+
],
|
| 1499 |
+
"license": "CC-BY-4.0"
|
| 1500 |
+
},
|
| 1501 |
+
"node_modules/cli-progress": {
|
| 1502 |
+
"version": "3.12.0",
|
| 1503 |
+
"resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz",
|
| 1504 |
+
"integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==",
|
| 1505 |
+
"license": "MIT",
|
| 1506 |
+
"optional": true,
|
| 1507 |
+
"dependencies": {
|
| 1508 |
+
"string-width": "^4.2.3"
|
| 1509 |
+
},
|
| 1510 |
+
"engines": {
|
| 1511 |
+
"node": ">=4"
|
| 1512 |
+
}
|
| 1513 |
+
},
|
| 1514 |
+
"node_modules/clsx": {
|
| 1515 |
+
"version": "2.1.1",
|
| 1516 |
+
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
| 1517 |
+
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
| 1518 |
+
"license": "MIT",
|
| 1519 |
+
"engines": {
|
| 1520 |
+
"node": ">=6"
|
| 1521 |
+
}
|
| 1522 |
+
},
|
| 1523 |
+
"node_modules/convert-source-map": {
|
| 1524 |
+
"version": "2.0.0",
|
| 1525 |
+
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
| 1526 |
+
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
| 1527 |
+
"dev": true,
|
| 1528 |
+
"license": "MIT"
|
| 1529 |
+
},
|
| 1530 |
+
"node_modules/csstype": {
|
| 1531 |
+
"version": "3.2.3",
|
| 1532 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
| 1533 |
+
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
| 1534 |
+
"dev": true,
|
| 1535 |
+
"license": "MIT"
|
| 1536 |
+
},
|
| 1537 |
+
"node_modules/debug": {
|
| 1538 |
+
"version": "4.4.3",
|
| 1539 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 1540 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 1541 |
+
"dev": true,
|
| 1542 |
+
"license": "MIT",
|
| 1543 |
+
"dependencies": {
|
| 1544 |
+
"ms": "^2.1.3"
|
| 1545 |
+
},
|
| 1546 |
+
"engines": {
|
| 1547 |
+
"node": ">=6.0"
|
| 1548 |
+
},
|
| 1549 |
+
"peerDependenciesMeta": {
|
| 1550 |
+
"supports-color": {
|
| 1551 |
+
"optional": true
|
| 1552 |
+
}
|
| 1553 |
+
}
|
| 1554 |
+
},
|
| 1555 |
+
"node_modules/dequal": {
|
| 1556 |
+
"version": "2.0.3",
|
| 1557 |
+
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
| 1558 |
+
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
| 1559 |
+
"license": "MIT",
|
| 1560 |
+
"engines": {
|
| 1561 |
+
"node": ">=6"
|
| 1562 |
+
}
|
| 1563 |
+
},
|
| 1564 |
+
"node_modules/dompurify": {
|
| 1565 |
+
"version": "3.2.7",
|
| 1566 |
+
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
|
| 1567 |
+
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
|
| 1568 |
+
"license": "(MPL-2.0 OR Apache-2.0)",
|
| 1569 |
+
"optionalDependencies": {
|
| 1570 |
+
"@types/trusted-types": "^2.0.7"
|
| 1571 |
+
}
|
| 1572 |
+
},
|
| 1573 |
+
"node_modules/electron-to-chromium": {
|
| 1574 |
+
"version": "1.5.343",
|
| 1575 |
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.343.tgz",
|
| 1576 |
+
"integrity": "sha512-YHnQ3MXI08icvL9ZKnEBy05F2EQ8ob01UaMOuMbM8l+4UcAq6MPPbBTJBbsBUg3H8JeZNt+O4fjsoWth3p6IFg==",
|
| 1577 |
+
"dev": true,
|
| 1578 |
+
"license": "ISC"
|
| 1579 |
+
},
|
| 1580 |
+
"node_modules/emoji-regex": {
|
| 1581 |
+
"version": "8.0.0",
|
| 1582 |
+
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
| 1583 |
+
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
| 1584 |
+
"license": "MIT",
|
| 1585 |
+
"optional": true
|
| 1586 |
+
},
|
| 1587 |
+
"node_modules/esbuild": {
|
| 1588 |
+
"version": "0.25.12",
|
| 1589 |
+
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
| 1590 |
+
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
| 1591 |
+
"dev": true,
|
| 1592 |
+
"hasInstallScript": true,
|
| 1593 |
+
"license": "MIT",
|
| 1594 |
+
"bin": {
|
| 1595 |
+
"esbuild": "bin/esbuild"
|
| 1596 |
+
},
|
| 1597 |
+
"engines": {
|
| 1598 |
+
"node": ">=18"
|
| 1599 |
+
},
|
| 1600 |
+
"optionalDependencies": {
|
| 1601 |
+
"@esbuild/aix-ppc64": "0.25.12",
|
| 1602 |
+
"@esbuild/android-arm": "0.25.12",
|
| 1603 |
+
"@esbuild/android-arm64": "0.25.12",
|
| 1604 |
+
"@esbuild/android-x64": "0.25.12",
|
| 1605 |
+
"@esbuild/darwin-arm64": "0.25.12",
|
| 1606 |
+
"@esbuild/darwin-x64": "0.25.12",
|
| 1607 |
+
"@esbuild/freebsd-arm64": "0.25.12",
|
| 1608 |
+
"@esbuild/freebsd-x64": "0.25.12",
|
| 1609 |
+
"@esbuild/linux-arm": "0.25.12",
|
| 1610 |
+
"@esbuild/linux-arm64": "0.25.12",
|
| 1611 |
+
"@esbuild/linux-ia32": "0.25.12",
|
| 1612 |
+
"@esbuild/linux-loong64": "0.25.12",
|
| 1613 |
+
"@esbuild/linux-mips64el": "0.25.12",
|
| 1614 |
+
"@esbuild/linux-ppc64": "0.25.12",
|
| 1615 |
+
"@esbuild/linux-riscv64": "0.25.12",
|
| 1616 |
+
"@esbuild/linux-s390x": "0.25.12",
|
| 1617 |
+
"@esbuild/linux-x64": "0.25.12",
|
| 1618 |
+
"@esbuild/netbsd-arm64": "0.25.12",
|
| 1619 |
+
"@esbuild/netbsd-x64": "0.25.12",
|
| 1620 |
+
"@esbuild/openbsd-arm64": "0.25.12",
|
| 1621 |
+
"@esbuild/openbsd-x64": "0.25.12",
|
| 1622 |
+
"@esbuild/openharmony-arm64": "0.25.12",
|
| 1623 |
+
"@esbuild/sunos-x64": "0.25.12",
|
| 1624 |
+
"@esbuild/win32-arm64": "0.25.12",
|
| 1625 |
+
"@esbuild/win32-ia32": "0.25.12",
|
| 1626 |
+
"@esbuild/win32-x64": "0.25.12"
|
| 1627 |
+
}
|
| 1628 |
+
},
|
| 1629 |
+
"node_modules/escalade": {
|
| 1630 |
+
"version": "3.2.0",
|
| 1631 |
+
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
| 1632 |
+
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
| 1633 |
+
"dev": true,
|
| 1634 |
+
"license": "MIT",
|
| 1635 |
+
"engines": {
|
| 1636 |
+
"node": ">=6"
|
| 1637 |
+
}
|
| 1638 |
+
},
|
| 1639 |
+
"node_modules/eventsource-parser": {
|
| 1640 |
+
"version": "3.0.8",
|
| 1641 |
+
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz",
|
| 1642 |
+
"integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==",
|
| 1643 |
+
"license": "MIT",
|
| 1644 |
+
"engines": {
|
| 1645 |
+
"node": ">=18.0.0"
|
| 1646 |
+
}
|
| 1647 |
+
},
|
| 1648 |
+
"node_modules/fdir": {
|
| 1649 |
+
"version": "6.5.0",
|
| 1650 |
+
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 1651 |
+
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
| 1652 |
+
"dev": true,
|
| 1653 |
+
"license": "MIT",
|
| 1654 |
+
"engines": {
|
| 1655 |
+
"node": ">=12.0.0"
|
| 1656 |
+
},
|
| 1657 |
+
"peerDependencies": {
|
| 1658 |
+
"picomatch": "^3 || ^4"
|
| 1659 |
+
},
|
| 1660 |
+
"peerDependenciesMeta": {
|
| 1661 |
+
"picomatch": {
|
| 1662 |
+
"optional": true
|
| 1663 |
+
}
|
| 1664 |
+
}
|
| 1665 |
+
},
|
| 1666 |
+
"node_modules/fsevents": {
|
| 1667 |
+
"version": "2.3.3",
|
| 1668 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1669 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1670 |
+
"dev": true,
|
| 1671 |
+
"hasInstallScript": true,
|
| 1672 |
+
"license": "MIT",
|
| 1673 |
+
"optional": true,
|
| 1674 |
+
"os": [
|
| 1675 |
+
"darwin"
|
| 1676 |
+
],
|
| 1677 |
+
"engines": {
|
| 1678 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1679 |
+
}
|
| 1680 |
+
},
|
| 1681 |
+
"node_modules/gensync": {
|
| 1682 |
+
"version": "1.0.0-beta.2",
|
| 1683 |
+
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
| 1684 |
+
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
| 1685 |
+
"dev": true,
|
| 1686 |
+
"license": "MIT",
|
| 1687 |
+
"engines": {
|
| 1688 |
+
"node": ">=6.9.0"
|
| 1689 |
+
}
|
| 1690 |
+
},
|
| 1691 |
+
"node_modules/is-fullwidth-code-point": {
|
| 1692 |
+
"version": "3.0.0",
|
| 1693 |
+
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
| 1694 |
+
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
| 1695 |
+
"license": "MIT",
|
| 1696 |
+
"optional": true,
|
| 1697 |
+
"engines": {
|
| 1698 |
+
"node": ">=8"
|
| 1699 |
+
}
|
| 1700 |
+
},
|
| 1701 |
+
"node_modules/js-tokens": {
|
| 1702 |
+
"version": "4.0.0",
|
| 1703 |
+
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
| 1704 |
+
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
| 1705 |
+
"license": "MIT"
|
| 1706 |
+
},
|
| 1707 |
+
"node_modules/jsesc": {
|
| 1708 |
+
"version": "3.1.0",
|
| 1709 |
+
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
| 1710 |
+
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
| 1711 |
+
"dev": true,
|
| 1712 |
+
"license": "MIT",
|
| 1713 |
+
"bin": {
|
| 1714 |
+
"jsesc": "bin/jsesc"
|
| 1715 |
+
},
|
| 1716 |
+
"engines": {
|
| 1717 |
+
"node": ">=6"
|
| 1718 |
+
}
|
| 1719 |
+
},
|
| 1720 |
+
"node_modules/json-schema": {
|
| 1721 |
+
"version": "0.4.0",
|
| 1722 |
+
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
| 1723 |
+
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
|
| 1724 |
+
"license": "(AFL-2.1 OR BSD-3-Clause)"
|
| 1725 |
+
},
|
| 1726 |
+
"node_modules/json5": {
|
| 1727 |
+
"version": "2.2.3",
|
| 1728 |
+
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
| 1729 |
+
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
| 1730 |
+
"dev": true,
|
| 1731 |
+
"license": "MIT",
|
| 1732 |
+
"bin": {
|
| 1733 |
+
"json5": "lib/cli.js"
|
| 1734 |
+
},
|
| 1735 |
+
"engines": {
|
| 1736 |
+
"node": ">=6"
|
| 1737 |
+
}
|
| 1738 |
+
},
|
| 1739 |
+
"node_modules/loose-envify": {
|
| 1740 |
+
"version": "1.4.0",
|
| 1741 |
+
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
| 1742 |
+
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
| 1743 |
+
"license": "MIT",
|
| 1744 |
+
"dependencies": {
|
| 1745 |
+
"js-tokens": "^3.0.0 || ^4.0.0"
|
| 1746 |
+
},
|
| 1747 |
+
"bin": {
|
| 1748 |
+
"loose-envify": "cli.js"
|
| 1749 |
+
}
|
| 1750 |
+
},
|
| 1751 |
+
"node_modules/lru-cache": {
|
| 1752 |
+
"version": "5.1.1",
|
| 1753 |
+
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
| 1754 |
+
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
| 1755 |
+
"dev": true,
|
| 1756 |
+
"license": "ISC",
|
| 1757 |
+
"dependencies": {
|
| 1758 |
+
"yallist": "^3.0.2"
|
| 1759 |
+
}
|
| 1760 |
+
},
|
| 1761 |
+
"node_modules/marked": {
|
| 1762 |
+
"version": "14.0.0",
|
| 1763 |
+
"resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
|
| 1764 |
+
"integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
|
| 1765 |
+
"license": "MIT",
|
| 1766 |
+
"bin": {
|
| 1767 |
+
"marked": "bin/marked.js"
|
| 1768 |
+
},
|
| 1769 |
+
"engines": {
|
| 1770 |
+
"node": ">= 18"
|
| 1771 |
+
}
|
| 1772 |
+
},
|
| 1773 |
+
"node_modules/monaco-editor": {
|
| 1774 |
+
"version": "0.55.1",
|
| 1775 |
+
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
|
| 1776 |
+
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
|
| 1777 |
+
"license": "MIT",
|
| 1778 |
+
"peer": true,
|
| 1779 |
+
"dependencies": {
|
| 1780 |
+
"dompurify": "3.2.7",
|
| 1781 |
+
"marked": "14.0.0"
|
| 1782 |
+
}
|
| 1783 |
+
},
|
| 1784 |
+
"node_modules/ms": {
|
| 1785 |
+
"version": "2.1.3",
|
| 1786 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1787 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 1788 |
+
"dev": true,
|
| 1789 |
+
"license": "MIT"
|
| 1790 |
+
},
|
| 1791 |
+
"node_modules/nanoid": {
|
| 1792 |
+
"version": "5.1.9",
|
| 1793 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.9.tgz",
|
| 1794 |
+
"integrity": "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==",
|
| 1795 |
+
"funding": [
|
| 1796 |
+
{
|
| 1797 |
+
"type": "github",
|
| 1798 |
+
"url": "https://github.com/sponsors/ai"
|
| 1799 |
+
}
|
| 1800 |
+
],
|
| 1801 |
+
"license": "MIT",
|
| 1802 |
+
"bin": {
|
| 1803 |
+
"nanoid": "bin/nanoid.js"
|
| 1804 |
+
},
|
| 1805 |
+
"engines": {
|
| 1806 |
+
"node": "^18 || >=20"
|
| 1807 |
+
}
|
| 1808 |
+
},
|
| 1809 |
+
"node_modules/node-releases": {
|
| 1810 |
+
"version": "2.0.38",
|
| 1811 |
+
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz",
|
| 1812 |
+
"integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==",
|
| 1813 |
+
"dev": true,
|
| 1814 |
+
"license": "MIT"
|
| 1815 |
+
},
|
| 1816 |
+
"node_modules/picocolors": {
|
| 1817 |
+
"version": "1.1.1",
|
| 1818 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 1819 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 1820 |
+
"dev": true,
|
| 1821 |
+
"license": "ISC"
|
| 1822 |
+
},
|
| 1823 |
+
"node_modules/picomatch": {
|
| 1824 |
+
"version": "4.0.4",
|
| 1825 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
| 1826 |
+
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
| 1827 |
+
"dev": true,
|
| 1828 |
+
"license": "MIT",
|
| 1829 |
+
"peer": true,
|
| 1830 |
+
"engines": {
|
| 1831 |
+
"node": ">=12"
|
| 1832 |
+
},
|
| 1833 |
+
"funding": {
|
| 1834 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 1835 |
+
}
|
| 1836 |
+
},
|
| 1837 |
+
"node_modules/postcss": {
|
| 1838 |
+
"version": "8.5.10",
|
| 1839 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
|
| 1840 |
+
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
|
| 1841 |
+
"dev": true,
|
| 1842 |
+
"funding": [
|
| 1843 |
+
{
|
| 1844 |
+
"type": "opencollective",
|
| 1845 |
+
"url": "https://opencollective.com/postcss/"
|
| 1846 |
+
},
|
| 1847 |
+
{
|
| 1848 |
+
"type": "tidelift",
|
| 1849 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 1850 |
+
},
|
| 1851 |
+
{
|
| 1852 |
+
"type": "github",
|
| 1853 |
+
"url": "https://github.com/sponsors/ai"
|
| 1854 |
+
}
|
| 1855 |
+
],
|
| 1856 |
+
"license": "MIT",
|
| 1857 |
+
"dependencies": {
|
| 1858 |
+
"nanoid": "^3.3.11",
|
| 1859 |
+
"picocolors": "^1.1.1",
|
| 1860 |
+
"source-map-js": "^1.2.1"
|
| 1861 |
+
},
|
| 1862 |
+
"engines": {
|
| 1863 |
+
"node": "^10 || ^12 || >=14"
|
| 1864 |
+
}
|
| 1865 |
+
},
|
| 1866 |
+
"node_modules/postcss/node_modules/nanoid": {
|
| 1867 |
+
"version": "3.3.11",
|
| 1868 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
| 1869 |
+
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
| 1870 |
+
"dev": true,
|
| 1871 |
+
"funding": [
|
| 1872 |
+
{
|
| 1873 |
+
"type": "github",
|
| 1874 |
+
"url": "https://github.com/sponsors/ai"
|
| 1875 |
+
}
|
| 1876 |
+
],
|
| 1877 |
+
"license": "MIT",
|
| 1878 |
+
"bin": {
|
| 1879 |
+
"nanoid": "bin/nanoid.cjs"
|
| 1880 |
+
},
|
| 1881 |
+
"engines": {
|
| 1882 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 1883 |
+
}
|
| 1884 |
+
},
|
| 1885 |
+
"node_modules/react": {
|
| 1886 |
+
"version": "18.3.1",
|
| 1887 |
+
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
| 1888 |
+
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
| 1889 |
+
"license": "MIT",
|
| 1890 |
+
"peer": true,
|
| 1891 |
+
"dependencies": {
|
| 1892 |
+
"loose-envify": "^1.1.0"
|
| 1893 |
+
},
|
| 1894 |
+
"engines": {
|
| 1895 |
+
"node": ">=0.10.0"
|
| 1896 |
+
}
|
| 1897 |
+
},
|
| 1898 |
+
"node_modules/react-dom": {
|
| 1899 |
+
"version": "18.3.1",
|
| 1900 |
+
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
| 1901 |
+
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
| 1902 |
+
"license": "MIT",
|
| 1903 |
+
"peer": true,
|
| 1904 |
+
"dependencies": {
|
| 1905 |
+
"loose-envify": "^1.1.0",
|
| 1906 |
+
"scheduler": "^0.23.2"
|
| 1907 |
+
},
|
| 1908 |
+
"peerDependencies": {
|
| 1909 |
+
"react": "^18.3.1"
|
| 1910 |
+
}
|
| 1911 |
+
},
|
| 1912 |
+
"node_modules/react-refresh": {
|
| 1913 |
+
"version": "0.17.0",
|
| 1914 |
+
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
| 1915 |
+
"integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
|
| 1916 |
+
"dev": true,
|
| 1917 |
+
"license": "MIT",
|
| 1918 |
+
"engines": {
|
| 1919 |
+
"node": ">=0.10.0"
|
| 1920 |
+
}
|
| 1921 |
+
},
|
| 1922 |
+
"node_modules/rollup": {
|
| 1923 |
+
"version": "4.60.2",
|
| 1924 |
+
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz",
|
| 1925 |
+
"integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==",
|
| 1926 |
+
"dev": true,
|
| 1927 |
+
"license": "MIT",
|
| 1928 |
+
"dependencies": {
|
| 1929 |
+
"@types/estree": "1.0.8"
|
| 1930 |
+
},
|
| 1931 |
+
"bin": {
|
| 1932 |
+
"rollup": "dist/bin/rollup"
|
| 1933 |
+
},
|
| 1934 |
+
"engines": {
|
| 1935 |
+
"node": ">=18.0.0",
|
| 1936 |
+
"npm": ">=8.0.0"
|
| 1937 |
+
},
|
| 1938 |
+
"optionalDependencies": {
|
| 1939 |
+
"@rollup/rollup-android-arm-eabi": "4.60.2",
|
| 1940 |
+
"@rollup/rollup-android-arm64": "4.60.2",
|
| 1941 |
+
"@rollup/rollup-darwin-arm64": "4.60.2",
|
| 1942 |
+
"@rollup/rollup-darwin-x64": "4.60.2",
|
| 1943 |
+
"@rollup/rollup-freebsd-arm64": "4.60.2",
|
| 1944 |
+
"@rollup/rollup-freebsd-x64": "4.60.2",
|
| 1945 |
+
"@rollup/rollup-linux-arm-gnueabihf": "4.60.2",
|
| 1946 |
+
"@rollup/rollup-linux-arm-musleabihf": "4.60.2",
|
| 1947 |
+
"@rollup/rollup-linux-arm64-gnu": "4.60.2",
|
| 1948 |
+
"@rollup/rollup-linux-arm64-musl": "4.60.2",
|
| 1949 |
+
"@rollup/rollup-linux-loong64-gnu": "4.60.2",
|
| 1950 |
+
"@rollup/rollup-linux-loong64-musl": "4.60.2",
|
| 1951 |
+
"@rollup/rollup-linux-ppc64-gnu": "4.60.2",
|
| 1952 |
+
"@rollup/rollup-linux-ppc64-musl": "4.60.2",
|
| 1953 |
+
"@rollup/rollup-linux-riscv64-gnu": "4.60.2",
|
| 1954 |
+
"@rollup/rollup-linux-riscv64-musl": "4.60.2",
|
| 1955 |
+
"@rollup/rollup-linux-s390x-gnu": "4.60.2",
|
| 1956 |
+
"@rollup/rollup-linux-x64-gnu": "4.60.2",
|
| 1957 |
+
"@rollup/rollup-linux-x64-musl": "4.60.2",
|
| 1958 |
+
"@rollup/rollup-openbsd-x64": "4.60.2",
|
| 1959 |
+
"@rollup/rollup-openharmony-arm64": "4.60.2",
|
| 1960 |
+
"@rollup/rollup-win32-arm64-msvc": "4.60.2",
|
| 1961 |
+
"@rollup/rollup-win32-ia32-msvc": "4.60.2",
|
| 1962 |
+
"@rollup/rollup-win32-x64-gnu": "4.60.2",
|
| 1963 |
+
"@rollup/rollup-win32-x64-msvc": "4.60.2",
|
| 1964 |
+
"fsevents": "~2.3.2"
|
| 1965 |
+
}
|
| 1966 |
+
},
|
| 1967 |
+
"node_modules/scheduler": {
|
| 1968 |
+
"version": "0.23.2",
|
| 1969 |
+
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
| 1970 |
+
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
| 1971 |
+
"license": "MIT",
|
| 1972 |
+
"dependencies": {
|
| 1973 |
+
"loose-envify": "^1.1.0"
|
| 1974 |
+
}
|
| 1975 |
+
},
|
| 1976 |
+
"node_modules/semver": {
|
| 1977 |
+
"version": "6.3.1",
|
| 1978 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
| 1979 |
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
| 1980 |
+
"dev": true,
|
| 1981 |
+
"license": "ISC",
|
| 1982 |
+
"bin": {
|
| 1983 |
+
"semver": "bin/semver.js"
|
| 1984 |
+
}
|
| 1985 |
+
},
|
| 1986 |
+
"node_modules/source-map-js": {
|
| 1987 |
+
"version": "1.2.1",
|
| 1988 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 1989 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 1990 |
+
"dev": true,
|
| 1991 |
+
"license": "BSD-3-Clause",
|
| 1992 |
+
"engines": {
|
| 1993 |
+
"node": ">=0.10.0"
|
| 1994 |
+
}
|
| 1995 |
+
},
|
| 1996 |
+
"node_modules/state-local": {
|
| 1997 |
+
"version": "1.0.7",
|
| 1998 |
+
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
|
| 1999 |
+
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
|
| 2000 |
+
"license": "MIT"
|
| 2001 |
+
},
|
| 2002 |
+
"node_modules/string-width": {
|
| 2003 |
+
"version": "4.2.3",
|
| 2004 |
+
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
| 2005 |
+
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
| 2006 |
+
"license": "MIT",
|
| 2007 |
+
"optional": true,
|
| 2008 |
+
"dependencies": {
|
| 2009 |
+
"emoji-regex": "^8.0.0",
|
| 2010 |
+
"is-fullwidth-code-point": "^3.0.0",
|
| 2011 |
+
"strip-ansi": "^6.0.1"
|
| 2012 |
+
},
|
| 2013 |
+
"engines": {
|
| 2014 |
+
"node": ">=8"
|
| 2015 |
+
}
|
| 2016 |
+
},
|
| 2017 |
+
"node_modules/strip-ansi": {
|
| 2018 |
+
"version": "6.0.1",
|
| 2019 |
+
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
| 2020 |
+
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
| 2021 |
+
"license": "MIT",
|
| 2022 |
+
"optional": true,
|
| 2023 |
+
"dependencies": {
|
| 2024 |
+
"ansi-regex": "^5.0.1"
|
| 2025 |
+
},
|
| 2026 |
+
"engines": {
|
| 2027 |
+
"node": ">=8"
|
| 2028 |
+
}
|
| 2029 |
+
},
|
| 2030 |
+
"node_modules/swr": {
|
| 2031 |
+
"version": "2.4.1",
|
| 2032 |
+
"resolved": "https://registry.npmjs.org/swr/-/swr-2.4.1.tgz",
|
| 2033 |
+
"integrity": "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==",
|
| 2034 |
+
"license": "MIT",
|
| 2035 |
+
"dependencies": {
|
| 2036 |
+
"dequal": "^2.0.3",
|
| 2037 |
+
"use-sync-external-store": "^1.6.0"
|
| 2038 |
+
},
|
| 2039 |
+
"peerDependencies": {
|
| 2040 |
+
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 2041 |
+
}
|
| 2042 |
+
},
|
| 2043 |
+
"node_modules/throttleit": {
|
| 2044 |
+
"version": "2.1.0",
|
| 2045 |
+
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz",
|
| 2046 |
+
"integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==",
|
| 2047 |
+
"license": "MIT",
|
| 2048 |
+
"engines": {
|
| 2049 |
+
"node": ">=18"
|
| 2050 |
+
},
|
| 2051 |
+
"funding": {
|
| 2052 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 2053 |
+
}
|
| 2054 |
+
},
|
| 2055 |
+
"node_modules/tinyglobby": {
|
| 2056 |
+
"version": "0.2.16",
|
| 2057 |
+
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
| 2058 |
+
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
| 2059 |
+
"dev": true,
|
| 2060 |
+
"license": "MIT",
|
| 2061 |
+
"dependencies": {
|
| 2062 |
+
"fdir": "^6.5.0",
|
| 2063 |
+
"picomatch": "^4.0.4"
|
| 2064 |
+
},
|
| 2065 |
+
"engines": {
|
| 2066 |
+
"node": ">=12.0.0"
|
| 2067 |
+
},
|
| 2068 |
+
"funding": {
|
| 2069 |
+
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 2070 |
+
}
|
| 2071 |
+
},
|
| 2072 |
+
"node_modules/typescript": {
|
| 2073 |
+
"version": "5.9.3",
|
| 2074 |
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
| 2075 |
+
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
| 2076 |
+
"dev": true,
|
| 2077 |
+
"license": "Apache-2.0",
|
| 2078 |
+
"bin": {
|
| 2079 |
+
"tsc": "bin/tsc",
|
| 2080 |
+
"tsserver": "bin/tsserver"
|
| 2081 |
+
},
|
| 2082 |
+
"engines": {
|
| 2083 |
+
"node": ">=14.17"
|
| 2084 |
+
}
|
| 2085 |
+
},
|
| 2086 |
+
"node_modules/update-browserslist-db": {
|
| 2087 |
+
"version": "1.2.3",
|
| 2088 |
+
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
| 2089 |
+
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
| 2090 |
+
"dev": true,
|
| 2091 |
+
"funding": [
|
| 2092 |
+
{
|
| 2093 |
+
"type": "opencollective",
|
| 2094 |
+
"url": "https://opencollective.com/browserslist"
|
| 2095 |
+
},
|
| 2096 |
+
{
|
| 2097 |
+
"type": "tidelift",
|
| 2098 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 2099 |
+
},
|
| 2100 |
+
{
|
| 2101 |
+
"type": "github",
|
| 2102 |
+
"url": "https://github.com/sponsors/ai"
|
| 2103 |
+
}
|
| 2104 |
+
],
|
| 2105 |
+
"license": "MIT",
|
| 2106 |
+
"dependencies": {
|
| 2107 |
+
"escalade": "^3.2.0",
|
| 2108 |
+
"picocolors": "^1.1.1"
|
| 2109 |
+
},
|
| 2110 |
+
"bin": {
|
| 2111 |
+
"update-browserslist-db": "cli.js"
|
| 2112 |
+
},
|
| 2113 |
+
"peerDependencies": {
|
| 2114 |
+
"browserslist": ">= 4.21.0"
|
| 2115 |
+
}
|
| 2116 |
+
},
|
| 2117 |
+
"node_modules/use-sync-external-store": {
|
| 2118 |
+
"version": "1.6.0",
|
| 2119 |
+
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
| 2120 |
+
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
| 2121 |
+
"license": "MIT",
|
| 2122 |
+
"peerDependencies": {
|
| 2123 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 2124 |
+
}
|
| 2125 |
+
},
|
| 2126 |
+
"node_modules/vite": {
|
| 2127 |
+
"version": "6.4.2",
|
| 2128 |
+
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
|
| 2129 |
+
"integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
|
| 2130 |
+
"dev": true,
|
| 2131 |
+
"license": "MIT",
|
| 2132 |
+
"peer": true,
|
| 2133 |
+
"dependencies": {
|
| 2134 |
+
"esbuild": "^0.25.0",
|
| 2135 |
+
"fdir": "^6.4.4",
|
| 2136 |
+
"picomatch": "^4.0.2",
|
| 2137 |
+
"postcss": "^8.5.3",
|
| 2138 |
+
"rollup": "^4.34.9",
|
| 2139 |
+
"tinyglobby": "^0.2.13"
|
| 2140 |
+
},
|
| 2141 |
+
"bin": {
|
| 2142 |
+
"vite": "bin/vite.js"
|
| 2143 |
+
},
|
| 2144 |
+
"engines": {
|
| 2145 |
+
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
| 2146 |
+
},
|
| 2147 |
+
"funding": {
|
| 2148 |
+
"url": "https://github.com/vitejs/vite?sponsor=1"
|
| 2149 |
+
},
|
| 2150 |
+
"optionalDependencies": {
|
| 2151 |
+
"fsevents": "~2.3.3"
|
| 2152 |
+
},
|
| 2153 |
+
"peerDependencies": {
|
| 2154 |
+
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
| 2155 |
+
"jiti": ">=1.21.0",
|
| 2156 |
+
"less": "*",
|
| 2157 |
+
"lightningcss": "^1.21.0",
|
| 2158 |
+
"sass": "*",
|
| 2159 |
+
"sass-embedded": "*",
|
| 2160 |
+
"stylus": "*",
|
| 2161 |
+
"sugarss": "*",
|
| 2162 |
+
"terser": "^5.16.0",
|
| 2163 |
+
"tsx": "^4.8.1",
|
| 2164 |
+
"yaml": "^2.4.2"
|
| 2165 |
+
},
|
| 2166 |
+
"peerDependenciesMeta": {
|
| 2167 |
+
"@types/node": {
|
| 2168 |
+
"optional": true
|
| 2169 |
+
},
|
| 2170 |
+
"jiti": {
|
| 2171 |
+
"optional": true
|
| 2172 |
+
},
|
| 2173 |
+
"less": {
|
| 2174 |
+
"optional": true
|
| 2175 |
+
},
|
| 2176 |
+
"lightningcss": {
|
| 2177 |
+
"optional": true
|
| 2178 |
+
},
|
| 2179 |
+
"sass": {
|
| 2180 |
+
"optional": true
|
| 2181 |
+
},
|
| 2182 |
+
"sass-embedded": {
|
| 2183 |
+
"optional": true
|
| 2184 |
+
},
|
| 2185 |
+
"stylus": {
|
| 2186 |
+
"optional": true
|
| 2187 |
+
},
|
| 2188 |
+
"sugarss": {
|
| 2189 |
+
"optional": true
|
| 2190 |
+
},
|
| 2191 |
+
"terser": {
|
| 2192 |
+
"optional": true
|
| 2193 |
+
},
|
| 2194 |
+
"tsx": {
|
| 2195 |
+
"optional": true
|
| 2196 |
+
},
|
| 2197 |
+
"yaml": {
|
| 2198 |
+
"optional": true
|
| 2199 |
+
}
|
| 2200 |
+
}
|
| 2201 |
+
},
|
| 2202 |
+
"node_modules/yallist": {
|
| 2203 |
+
"version": "3.1.1",
|
| 2204 |
+
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
| 2205 |
+
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
| 2206 |
+
"dev": true,
|
| 2207 |
+
"license": "ISC"
|
| 2208 |
+
},
|
| 2209 |
+
"node_modules/zod": {
|
| 2210 |
+
"version": "4.3.6",
|
| 2211 |
+
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
| 2212 |
+
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
| 2213 |
+
"license": "MIT",
|
| 2214 |
+
"peer": true,
|
| 2215 |
+
"funding": {
|
| 2216 |
+
"url": "https://github.com/sponsors/colinhacks"
|
| 2217 |
+
}
|
| 2218 |
+
}
|
| 2219 |
+
}
|
| 2220 |
+
}
|
frontend/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "reachy-mini-vibe-coder-frontend",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "tsc -b && vite build",
|
| 9 |
+
"preview": "vite preview",
|
| 10 |
+
"typecheck": "tsc -b --noEmit"
|
| 11 |
+
},
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"@ai-sdk/react": "^3.0.160",
|
| 14 |
+
"@huggingface/hub": "^2.11.0",
|
| 15 |
+
"@monaco-editor/react": "^4.7.0",
|
| 16 |
+
"ai": "^6.0.158",
|
| 17 |
+
"clsx": "^2.1.1",
|
| 18 |
+
"nanoid": "^5.0.7",
|
| 19 |
+
"react": "^18.3.1",
|
| 20 |
+
"react-dom": "^18.3.1"
|
| 21 |
+
},
|
| 22 |
+
"devDependencies": {
|
| 23 |
+
"@types/react": "^18.3.12",
|
| 24 |
+
"@types/react-dom": "^18.3.1",
|
| 25 |
+
"@vitejs/plugin-react": "^4.3.4",
|
| 26 |
+
"typescript": "^5.6.0",
|
| 27 |
+
"vite": "^6.0.0"
|
| 28 |
+
}
|
| 29 |
+
}
|
frontend/src/App.tsx
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useCallback, useEffect, useRef, useState } from "react";
|
| 2 |
+
import { useHfAuth } from "./hooks/useHfAuth";
|
| 3 |
+
import { useVirtualFS } from "./hooks/useVirtualFS";
|
| 4 |
+
import { useAgentChat } from "./hooks/useAgentChat";
|
| 5 |
+
import { SignInScreen } from "./components/SignInScreen";
|
| 6 |
+
import { TopBar } from "./components/TopBar";
|
| 7 |
+
import { ChatPanel } from "./components/ChatPanel";
|
| 8 |
+
import { CodeEditor } from "./components/CodeEditor";
|
| 9 |
+
import { PreviewFrame } from "./components/PreviewFrame";
|
| 10 |
+
import { PublishDialog } from "./components/PublishDialog";
|
| 11 |
+
|
| 12 |
+
interface Model {
|
| 13 |
+
id: string;
|
| 14 |
+
label: string;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
type Tab = "code" | "preview";
|
| 18 |
+
|
| 19 |
+
export function App() {
|
| 20 |
+
const auth = useHfAuth();
|
| 21 |
+
const vfs = useVirtualFS();
|
| 22 |
+
|
| 23 |
+
const [models, setModels] = useState<Model[]>([]);
|
| 24 |
+
const [selectedModel, setSelectedModel] = useState<string | null>(null);
|
| 25 |
+
const modelRef = useRef<string | null>(selectedModel);
|
| 26 |
+
modelRef.current = selectedModel;
|
| 27 |
+
|
| 28 |
+
const [activeTab, setActiveTab] = useState<Tab>("code");
|
| 29 |
+
const [publishOpen, setPublishOpen] = useState(false);
|
| 30 |
+
|
| 31 |
+
const chat = useAgentChat({
|
| 32 |
+
accessToken: auth.accessToken,
|
| 33 |
+
vfs,
|
| 34 |
+
modelRef,
|
| 35 |
+
});
|
| 36 |
+
|
| 37 |
+
useEffect(() => {
|
| 38 |
+
if (!auth.isSignedIn || !auth.accessToken) return;
|
| 39 |
+
let cancelled = false;
|
| 40 |
+
fetch("/api/models", {
|
| 41 |
+
headers: { Authorization: `Bearer ${auth.accessToken}` },
|
| 42 |
+
})
|
| 43 |
+
.then((r) => (r.ok ? r.json() : null))
|
| 44 |
+
.then((data) => {
|
| 45 |
+
if (cancelled || !data) return;
|
| 46 |
+
setModels(data.models ?? []);
|
| 47 |
+
if (data.default && !selectedModel) setSelectedModel(data.default);
|
| 48 |
+
})
|
| 49 |
+
.catch(() => {});
|
| 50 |
+
return () => {
|
| 51 |
+
cancelled = true;
|
| 52 |
+
};
|
| 53 |
+
}, [auth.isSignedIn, auth.accessToken, selectedModel]);
|
| 54 |
+
|
| 55 |
+
const handleReset = useCallback(() => {
|
| 56 |
+
if (
|
| 57 |
+
!confirm(
|
| 58 |
+
"Clear the chat history and all files in the virtual FS? This cannot be undone.",
|
| 59 |
+
)
|
| 60 |
+
) {
|
| 61 |
+
return;
|
| 62 |
+
}
|
| 63 |
+
chat.clearMessages();
|
| 64 |
+
vfs.clear();
|
| 65 |
+
}, [chat, vfs]);
|
| 66 |
+
|
| 67 |
+
const handleFileChange = useCallback(
|
| 68 |
+
(path: string, content: string) => {
|
| 69 |
+
vfs.writeFile(path, content);
|
| 70 |
+
},
|
| 71 |
+
[vfs],
|
| 72 |
+
);
|
| 73 |
+
|
| 74 |
+
if (!auth.isReady) {
|
| 75 |
+
return (
|
| 76 |
+
<div className="app-shell">
|
| 77 |
+
<div style={{ margin: "auto", color: "var(--text-faint)" }}>
|
| 78 |
+
Loading…
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
if (!auth.isSignedIn) {
|
| 85 |
+
return (
|
| 86 |
+
<div className="app-shell">
|
| 87 |
+
<SignInScreen
|
| 88 |
+
onSignIn={auth.signIn}
|
| 89 |
+
isSigningIn={auth.isSigningIn}
|
| 90 |
+
error={auth.error}
|
| 91 |
+
hasClientId={!!auth.clientId}
|
| 92 |
+
/>
|
| 93 |
+
</div>
|
| 94 |
+
);
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
const canPublish = vfs.files.length > 0;
|
| 98 |
+
|
| 99 |
+
return (
|
| 100 |
+
<div className="app-shell">
|
| 101 |
+
<TopBar
|
| 102 |
+
user={auth.user}
|
| 103 |
+
models={models}
|
| 104 |
+
selectedModel={selectedModel}
|
| 105 |
+
onModelChange={setSelectedModel}
|
| 106 |
+
onPublish={() => setPublishOpen(true)}
|
| 107 |
+
onSignOut={auth.signOut}
|
| 108 |
+
onReset={handleReset}
|
| 109 |
+
canPublish={canPublish}
|
| 110 |
+
/>
|
| 111 |
+
|
| 112 |
+
<div className="app-main">
|
| 113 |
+
<ChatPanel
|
| 114 |
+
messages={chat.messages}
|
| 115 |
+
isLoading={chat.isLoading}
|
| 116 |
+
input={chat.input}
|
| 117 |
+
setInput={chat.setInput}
|
| 118 |
+
onSend={chat.sendMessage}
|
| 119 |
+
onStop={chat.stop}
|
| 120 |
+
error={chat.error ?? null}
|
| 121 |
+
/>
|
| 122 |
+
|
| 123 |
+
<div className="right-pane">
|
| 124 |
+
<div className="tab-bar">
|
| 125 |
+
<button
|
| 126 |
+
type="button"
|
| 127 |
+
className={`tab ${activeTab === "code" ? "active" : ""}`}
|
| 128 |
+
onClick={() => setActiveTab("code")}
|
| 129 |
+
>
|
| 130 |
+
Code · {vfs.files.length}
|
| 131 |
+
</button>
|
| 132 |
+
<button
|
| 133 |
+
type="button"
|
| 134 |
+
className={`tab ${activeTab === "preview" ? "active" : ""}`}
|
| 135 |
+
onClick={() => setActiveTab("preview")}
|
| 136 |
+
>
|
| 137 |
+
Preview
|
| 138 |
+
</button>
|
| 139 |
+
<div className="tab-spacer" />
|
| 140 |
+
</div>
|
| 141 |
+
|
| 142 |
+
<div
|
| 143 |
+
className={`pane-content ${activeTab === "code" ? "" : "hidden"}`}
|
| 144 |
+
>
|
| 145 |
+
<CodeEditor files={vfs.files} onChange={handleFileChange} />
|
| 146 |
+
</div>
|
| 147 |
+
<div
|
| 148 |
+
className={`pane-content ${activeTab === "preview" ? "" : "hidden"}`}
|
| 149 |
+
>
|
| 150 |
+
<PreviewFrame files={vfs.files} version={vfs.version} />
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
<PublishDialog
|
| 156 |
+
open={publishOpen}
|
| 157 |
+
onClose={() => setPublishOpen(false)}
|
| 158 |
+
files={vfs.files}
|
| 159 |
+
accessToken={auth.accessToken}
|
| 160 |
+
defaultUsername={auth.user?.preferred_username ?? null}
|
| 161 |
+
/>
|
| 162 |
+
</div>
|
| 163 |
+
);
|
| 164 |
+
}
|
frontend/src/components/ChatPanel.tsx
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { UIMessage } from "ai";
|
| 2 |
+
import { useEffect, useRef } from "react";
|
| 3 |
+
|
| 4 |
+
interface ChatPanelProps {
|
| 5 |
+
messages: UIMessage[];
|
| 6 |
+
isLoading: boolean;
|
| 7 |
+
input: string;
|
| 8 |
+
setInput: (v: string) => void;
|
| 9 |
+
onSend: (text: string) => void;
|
| 10 |
+
onStop: () => void;
|
| 11 |
+
error: Error | null;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
const EXAMPLE_PROMPTS = [
|
| 15 |
+
"Create a minimal Reachy Mini voice app that just connects to OpenAI Realtime and lets me talk.",
|
| 16 |
+
"Add a head-wobbler motion agent that reacts to the AI voice's RMS level.",
|
| 17 |
+
"Add a play_dance tool call that streams a procedural happy dance to the robot.",
|
| 18 |
+
];
|
| 19 |
+
|
| 20 |
+
function renderMessagePart(
|
| 21 |
+
part: UIMessage["parts"][number],
|
| 22 |
+
idx: number,
|
| 23 |
+
): React.ReactNode {
|
| 24 |
+
const type = part.type;
|
| 25 |
+
|
| 26 |
+
if (type === "text") {
|
| 27 |
+
const text = (part as { text: string }).text;
|
| 28 |
+
if (!text.trim()) return null;
|
| 29 |
+
return (
|
| 30 |
+
<div key={idx} className="bubble-agent-text">
|
| 31 |
+
{text}
|
| 32 |
+
</div>
|
| 33 |
+
);
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
if (typeof type === "string" && type.startsWith("tool-")) {
|
| 37 |
+
const toolPart = part as {
|
| 38 |
+
type: string;
|
| 39 |
+
state?: string;
|
| 40 |
+
input?: Record<string, unknown>;
|
| 41 |
+
output?: unknown;
|
| 42 |
+
errorText?: string;
|
| 43 |
+
};
|
| 44 |
+
const toolName = type.slice("tool-".length);
|
| 45 |
+
const state = toolPart.state ?? "unknown";
|
| 46 |
+
const statusClass =
|
| 47 |
+
state === "output-available" || state === "result"
|
| 48 |
+
? "status-done"
|
| 49 |
+
: state === "output-error" || state === "error"
|
| 50 |
+
? "status-error"
|
| 51 |
+
: "";
|
| 52 |
+
|
| 53 |
+
let detail = "";
|
| 54 |
+
if (toolPart.input) {
|
| 55 |
+
const input = toolPart.input;
|
| 56 |
+
if (toolName === "write_file" && typeof input.path === "string") {
|
| 57 |
+
detail = `${input.path}${typeof input.content === "string" ? ` · ${input.content.length} chars` : ""}`;
|
| 58 |
+
} else if (toolName === "edit_file" && typeof input.path === "string") {
|
| 59 |
+
detail = String(input.path);
|
| 60 |
+
} else if (
|
| 61 |
+
(toolName === "read_file" ||
|
| 62 |
+
toolName === "delete_file") &&
|
| 63 |
+
typeof input.path === "string"
|
| 64 |
+
) {
|
| 65 |
+
detail = String(input.path);
|
| 66 |
+
} else if (toolName === "read_skill_doc" && typeof input.topic === "string") {
|
| 67 |
+
detail = String(input.topic);
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
return (
|
| 72 |
+
<div key={idx} className={`bubble-tool ${statusClass}`}>
|
| 73 |
+
<div className="bubble-tool-header">
|
| 74 |
+
<span className="dot" />
|
| 75 |
+
<span>{toolLabel(toolName)}</span>
|
| 76 |
+
{detail && <span className="mono" style={{ color: "var(--text-faint)" }}>· {detail}</span>}
|
| 77 |
+
</div>
|
| 78 |
+
{toolPart.errorText && (
|
| 79 |
+
<div className="bubble-tool-detail">{toolPart.errorText}</div>
|
| 80 |
+
)}
|
| 81 |
+
</div>
|
| 82 |
+
);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
return null;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
function toolLabel(name: string): string {
|
| 89 |
+
const map: Record<string, string> = {
|
| 90 |
+
write_file: "Writing file",
|
| 91 |
+
edit_file: "Editing file",
|
| 92 |
+
delete_file: "Deleting file",
|
| 93 |
+
read_file: "Reading file",
|
| 94 |
+
list_files: "Listing files",
|
| 95 |
+
read_skill_doc: "Loading skill chapter",
|
| 96 |
+
};
|
| 97 |
+
return map[name] ?? name;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
export function ChatPanel({
|
| 101 |
+
messages,
|
| 102 |
+
isLoading,
|
| 103 |
+
input,
|
| 104 |
+
setInput,
|
| 105 |
+
onSend,
|
| 106 |
+
onStop,
|
| 107 |
+
error,
|
| 108 |
+
}: ChatPanelProps) {
|
| 109 |
+
const scrollRef = useRef<HTMLDivElement>(null);
|
| 110 |
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
| 111 |
+
|
| 112 |
+
useEffect(() => {
|
| 113 |
+
scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight });
|
| 114 |
+
}, [messages]);
|
| 115 |
+
|
| 116 |
+
const handleSubmit = (e?: React.FormEvent) => {
|
| 117 |
+
e?.preventDefault();
|
| 118 |
+
if (isLoading) {
|
| 119 |
+
onStop();
|
| 120 |
+
return;
|
| 121 |
+
}
|
| 122 |
+
const text = input.trim();
|
| 123 |
+
if (!text) return;
|
| 124 |
+
onSend(text);
|
| 125 |
+
};
|
| 126 |
+
|
| 127 |
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
| 128 |
+
if (e.key === "Enter" && !e.shiftKey) {
|
| 129 |
+
e.preventDefault();
|
| 130 |
+
handleSubmit();
|
| 131 |
+
}
|
| 132 |
+
};
|
| 133 |
+
|
| 134 |
+
const showEmpty = messages.length === 0;
|
| 135 |
+
|
| 136 |
+
return (
|
| 137 |
+
<aside className="chat-panel">
|
| 138 |
+
<div className="chat-messages" ref={scrollRef}>
|
| 139 |
+
{showEmpty && (
|
| 140 |
+
<div className="chat-empty">
|
| 141 |
+
<p>
|
| 142 |
+
Describe the Reachy Mini voice app you want to build. The agent
|
| 143 |
+
knows the full <code>build-reachy-voice-app</code> skill (WebRTC
|
| 144 |
+
2-hop, OpenAI Realtime, motion agents, HF deployment) and will
|
| 145 |
+
create the files in the virtual FS on the right.
|
| 146 |
+
</p>
|
| 147 |
+
<div className="chat-empty-examples">
|
| 148 |
+
{EXAMPLE_PROMPTS.map((p) => (
|
| 149 |
+
<button
|
| 150 |
+
key={p}
|
| 151 |
+
type="button"
|
| 152 |
+
className="chat-empty-example"
|
| 153 |
+
onClick={() => setInput(p)}
|
| 154 |
+
>
|
| 155 |
+
{p}
|
| 156 |
+
</button>
|
| 157 |
+
))}
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
)}
|
| 161 |
+
|
| 162 |
+
{messages.map((m) =>
|
| 163 |
+
m.role === "user" ? (
|
| 164 |
+
<div key={m.id} className="bubble-user">
|
| 165 |
+
{m.parts
|
| 166 |
+
.map((p) => ((p as { type: string }).type === "text" ? (p as { text: string }).text : ""))
|
| 167 |
+
.join("")}
|
| 168 |
+
</div>
|
| 169 |
+
) : (
|
| 170 |
+
<div key={m.id} className="bubble-agent">
|
| 171 |
+
{m.parts.map((p, i) => renderMessagePart(p, i))}
|
| 172 |
+
</div>
|
| 173 |
+
),
|
| 174 |
+
)}
|
| 175 |
+
|
| 176 |
+
{error && (
|
| 177 |
+
<div className="signin-error" style={{ alignSelf: "stretch" }}>
|
| 178 |
+
{error.message}
|
| 179 |
+
</div>
|
| 180 |
+
)}
|
| 181 |
+
</div>
|
| 182 |
+
|
| 183 |
+
<form className="chat-composer" onSubmit={handleSubmit}>
|
| 184 |
+
<div className="chat-composer-field">
|
| 185 |
+
<textarea
|
| 186 |
+
ref={textareaRef}
|
| 187 |
+
value={input}
|
| 188 |
+
onChange={(e) => setInput(e.target.value)}
|
| 189 |
+
onKeyDown={handleKeyDown}
|
| 190 |
+
placeholder={
|
| 191 |
+
isLoading
|
| 192 |
+
? "Agent is working…"
|
| 193 |
+
: "Describe what to build or change…"
|
| 194 |
+
}
|
| 195 |
+
rows={1}
|
| 196 |
+
/>
|
| 197 |
+
<button
|
| 198 |
+
type="submit"
|
| 199 |
+
className="chat-send"
|
| 200 |
+
disabled={!isLoading && input.trim().length === 0}
|
| 201 |
+
title={isLoading ? "Stop" : "Send"}
|
| 202 |
+
>
|
| 203 |
+
{isLoading ? "■" : "↑"}
|
| 204 |
+
</button>
|
| 205 |
+
</div>
|
| 206 |
+
<div className="chat-status">
|
| 207 |
+
{isLoading ? (
|
| 208 |
+
<>
|
| 209 |
+
<span className="spinner" />
|
| 210 |
+
<span>Agent is thinking…</span>
|
| 211 |
+
</>
|
| 212 |
+
) : (
|
| 213 |
+
<span>Enter to send · Shift+Enter for newline</span>
|
| 214 |
+
)}
|
| 215 |
+
</div>
|
| 216 |
+
</form>
|
| 217 |
+
</aside>
|
| 218 |
+
);
|
| 219 |
+
}
|
frontend/src/components/CodeEditor.tsx
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Editor from "@monaco-editor/react";
|
| 2 |
+
import { useEffect, useMemo, useState } from "react";
|
| 3 |
+
import type { VirtualFile } from "../hooks/useVirtualFS";
|
| 4 |
+
|
| 5 |
+
interface CodeEditorProps {
|
| 6 |
+
files: VirtualFile[];
|
| 7 |
+
onChange: (path: string, content: string) => void;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
function guessLanguage(path: string): string {
|
| 11 |
+
const ext = path.split(".").pop()?.toLowerCase() ?? "";
|
| 12 |
+
switch (ext) {
|
| 13 |
+
case "ts":
|
| 14 |
+
case "tsx":
|
| 15 |
+
return "typescript";
|
| 16 |
+
case "js":
|
| 17 |
+
case "jsx":
|
| 18 |
+
return "javascript";
|
| 19 |
+
case "html":
|
| 20 |
+
return "html";
|
| 21 |
+
case "css":
|
| 22 |
+
return "css";
|
| 23 |
+
case "json":
|
| 24 |
+
return "json";
|
| 25 |
+
case "md":
|
| 26 |
+
return "markdown";
|
| 27 |
+
case "yml":
|
| 28 |
+
case "yaml":
|
| 29 |
+
return "yaml";
|
| 30 |
+
case "sh":
|
| 31 |
+
return "shell";
|
| 32 |
+
default:
|
| 33 |
+
return "plaintext";
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
export function CodeEditor({ files, onChange }: CodeEditorProps) {
|
| 38 |
+
const [activePath, setActivePath] = useState<string | null>(null);
|
| 39 |
+
|
| 40 |
+
useEffect(() => {
|
| 41 |
+
if (!activePath && files.length > 0) {
|
| 42 |
+
const preferred = files.find(
|
| 43 |
+
(f) => f.path === "index.html" || f.path === "src/main.ts",
|
| 44 |
+
);
|
| 45 |
+
setActivePath(preferred?.path ?? files[0].path);
|
| 46 |
+
return;
|
| 47 |
+
}
|
| 48 |
+
if (activePath && !files.some((f) => f.path === activePath)) {
|
| 49 |
+
setActivePath(files[0]?.path ?? null);
|
| 50 |
+
}
|
| 51 |
+
}, [files, activePath]);
|
| 52 |
+
|
| 53 |
+
const activeFile = useMemo(
|
| 54 |
+
() => files.find((f) => f.path === activePath) ?? null,
|
| 55 |
+
[files, activePath],
|
| 56 |
+
);
|
| 57 |
+
|
| 58 |
+
if (files.length === 0) {
|
| 59 |
+
return (
|
| 60 |
+
<div className="code-layout">
|
| 61 |
+
<div className="file-tree">
|
| 62 |
+
<div className="file-tree-empty">No files yet.</div>
|
| 63 |
+
</div>
|
| 64 |
+
<div className="editor-area">
|
| 65 |
+
<div className="editor-placeholder">
|
| 66 |
+
The agent will write files here. Ask it to create an app.
|
| 67 |
+
</div>
|
| 68 |
+
</div>
|
| 69 |
+
</div>
|
| 70 |
+
);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
return (
|
| 74 |
+
<div className="code-layout">
|
| 75 |
+
<div className="file-tree">
|
| 76 |
+
{files.map((f) => (
|
| 77 |
+
<div
|
| 78 |
+
key={f.path}
|
| 79 |
+
className={`file-tree-item ${activePath === f.path ? "active" : ""}`}
|
| 80 |
+
onClick={() => setActivePath(f.path)}
|
| 81 |
+
>
|
| 82 |
+
<span>{f.path}</span>
|
| 83 |
+
<span className="file-tree-size">{f.content.length}B</span>
|
| 84 |
+
</div>
|
| 85 |
+
))}
|
| 86 |
+
</div>
|
| 87 |
+
<div className="editor-area">
|
| 88 |
+
{activeFile ? (
|
| 89 |
+
<Editor
|
| 90 |
+
key={activeFile.path}
|
| 91 |
+
height="100%"
|
| 92 |
+
theme="vs-dark"
|
| 93 |
+
language={guessLanguage(activeFile.path)}
|
| 94 |
+
value={activeFile.content}
|
| 95 |
+
onChange={(v) => onChange(activeFile.path, v ?? "")}
|
| 96 |
+
options={{
|
| 97 |
+
fontSize: 12,
|
| 98 |
+
fontFamily:
|
| 99 |
+
'"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace',
|
| 100 |
+
minimap: { enabled: false },
|
| 101 |
+
scrollBeyondLastLine: false,
|
| 102 |
+
tabSize: 2,
|
| 103 |
+
wordWrap: "on",
|
| 104 |
+
automaticLayout: true,
|
| 105 |
+
}}
|
| 106 |
+
/>
|
| 107 |
+
) : (
|
| 108 |
+
<div className="editor-placeholder">Select a file from the tree.</div>
|
| 109 |
+
)}
|
| 110 |
+
</div>
|
| 111 |
+
</div>
|
| 112 |
+
);
|
| 113 |
+
}
|
frontend/src/components/HfLogo.tsx
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* The official Hugging Face emoji logo. Drop-in SVG; the container controls the
|
| 3 |
+
* size via font-size or an explicit width/height prop.
|
| 4 |
+
*/
|
| 5 |
+
export function HfLogo({ size = 20 }: { size?: number }) {
|
| 6 |
+
return (
|
| 7 |
+
<svg
|
| 8 |
+
viewBox="0 0 96 96"
|
| 9 |
+
width={size}
|
| 10 |
+
height={size}
|
| 11 |
+
aria-hidden="true"
|
| 12 |
+
style={{ display: "inline-block" }}
|
| 13 |
+
>
|
| 14 |
+
<defs>
|
| 15 |
+
<radialGradient id="hf-face" cx="0.5" cy="0.5" r="0.5">
|
| 16 |
+
<stop offset="0%" stopColor="#FFD21E" />
|
| 17 |
+
<stop offset="100%" stopColor="#FFAC03" />
|
| 18 |
+
</radialGradient>
|
| 19 |
+
</defs>
|
| 20 |
+
<circle cx="48" cy="48" r="44" fill="url(#hf-face)" stroke="#1A1A1A" strokeWidth="4" />
|
| 21 |
+
<circle cx="32" cy="42" r="6" fill="#1A1A1A" />
|
| 22 |
+
<circle cx="64" cy="42" r="6" fill="#1A1A1A" />
|
| 23 |
+
<path
|
| 24 |
+
d="M30 60 Q48 76 66 60 Q66 70 48 72 Q30 70 30 60 Z"
|
| 25 |
+
fill="#1A1A1A"
|
| 26 |
+
/>
|
| 27 |
+
<circle cx="22" cy="56" r="5" fill="#FF7979" opacity="0.8" />
|
| 28 |
+
<circle cx="74" cy="56" r="5" fill="#FF7979" opacity="0.8" />
|
| 29 |
+
</svg>
|
| 30 |
+
);
|
| 31 |
+
}
|
frontend/src/components/PreviewFrame.tsx
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useEffect, useMemo, useRef, useState } from "react";
|
| 2 |
+
import type { VirtualFile } from "../hooks/useVirtualFS";
|
| 3 |
+
|
| 4 |
+
interface PreviewFrameProps {
|
| 5 |
+
files: VirtualFile[];
|
| 6 |
+
version: number;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
/**
|
| 10 |
+
* Render the virtual FS inside a sandboxed iframe.
|
| 11 |
+
*
|
| 12 |
+
* Strategy: we build a single HTML document by taking `index.html` from the
|
| 13 |
+
* virtual FS and inlining every other text asset (CSS + JS/TS). This is
|
| 14 |
+
* deliberately simple and works for the kind of minimal Vite apps the agent
|
| 15 |
+
* produces.
|
| 16 |
+
*
|
| 17 |
+
* Notes / limitations:
|
| 18 |
+
* - `.ts` files are shipped as `type="module"` with an esbuild-free path:
|
| 19 |
+
* they run in the iframe as-is, so only ES2022-compatible TS (= "TypeScript
|
| 20 |
+
* lite") works. Real Vite builds are what we ship to HF Spaces.
|
| 21 |
+
* - WebRTC/OpenAI calls will fail in the preview sandbox because of CORS and
|
| 22 |
+
* missing OAuth. The preview is there to catch JS errors and see the UI.
|
| 23 |
+
* - We strip the `<script type="module" src="/src/main.ts">` tag when the
|
| 24 |
+
* corresponding file is inlined just above it.
|
| 25 |
+
*/
|
| 26 |
+
function buildPreviewDoc(files: VirtualFile[]): string | null {
|
| 27 |
+
const fileMap = new Map(files.map((f) => [f.path, f.content]));
|
| 28 |
+
|
| 29 |
+
const rawHtml = fileMap.get("index.html");
|
| 30 |
+
if (!rawHtml) return null;
|
| 31 |
+
|
| 32 |
+
// Inline every local text asset referenced through href="/..." or src="/..."
|
| 33 |
+
const inlineAsset = (_match: string, attr: string, _q: string, path: string): string => {
|
| 34 |
+
const normalized = path.replace(/^\//, "");
|
| 35 |
+
const content = fileMap.get(normalized);
|
| 36 |
+
if (content === undefined) return _match;
|
| 37 |
+
|
| 38 |
+
if (attr === "href" && normalized.endsWith(".css")) {
|
| 39 |
+
return `<style data-inlined="${normalized}">\n${content}\n</style>`;
|
| 40 |
+
}
|
| 41 |
+
if (
|
| 42 |
+
attr === "src" &&
|
| 43 |
+
(normalized.endsWith(".js") ||
|
| 44 |
+
normalized.endsWith(".mjs") ||
|
| 45 |
+
normalized.endsWith(".ts") ||
|
| 46 |
+
normalized.endsWith(".tsx"))
|
| 47 |
+
) {
|
| 48 |
+
return `<script type="module" data-inlined="${normalized}">\n${content}\n</script>`;
|
| 49 |
+
}
|
| 50 |
+
return _match;
|
| 51 |
+
};
|
| 52 |
+
|
| 53 |
+
const inlined = rawHtml.replace(
|
| 54 |
+
/<(?:link|script)[^>]*\b(href|src)\s*=\s*(["'])(\/[^"']+)\2[^>]*>(?:<\/script>)?/gi,
|
| 55 |
+
inlineAsset,
|
| 56 |
+
);
|
| 57 |
+
|
| 58 |
+
const bannerDoc = `<!-- Preview generated by reachy-mini-vibe-coder -->`;
|
| 59 |
+
return `${bannerDoc}\n${inlined}`;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
export function PreviewFrame({ files, version }: PreviewFrameProps) {
|
| 63 |
+
const [docHtml, setDocHtml] = useState<string | null>(null);
|
| 64 |
+
const timerRef = useRef<number | null>(null);
|
| 65 |
+
|
| 66 |
+
const hasIndex = useMemo(
|
| 67 |
+
() => files.some((f) => f.path === "index.html"),
|
| 68 |
+
[files],
|
| 69 |
+
);
|
| 70 |
+
|
| 71 |
+
useEffect(() => {
|
| 72 |
+
if (timerRef.current) window.clearTimeout(timerRef.current);
|
| 73 |
+
timerRef.current = window.setTimeout(() => {
|
| 74 |
+
const doc = buildPreviewDoc(files);
|
| 75 |
+
setDocHtml(doc);
|
| 76 |
+
}, 300);
|
| 77 |
+
return () => {
|
| 78 |
+
if (timerRef.current) window.clearTimeout(timerRef.current);
|
| 79 |
+
};
|
| 80 |
+
}, [files, version]);
|
| 81 |
+
|
| 82 |
+
if (!hasIndex) {
|
| 83 |
+
return (
|
| 84 |
+
<div className="preview-empty">
|
| 85 |
+
<p>No <code>index.html</code> in the virtual FS yet.</p>
|
| 86 |
+
<p style={{ marginTop: 8 }}>Ask the agent to create one.</p>
|
| 87 |
+
</div>
|
| 88 |
+
);
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
if (docHtml === null) {
|
| 92 |
+
return <div className="preview-empty">Building preview…</div>;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
return (
|
| 96 |
+
<iframe
|
| 97 |
+
key={version}
|
| 98 |
+
className="preview-frame"
|
| 99 |
+
title="App preview"
|
| 100 |
+
srcDoc={docHtml}
|
| 101 |
+
sandbox="allow-scripts allow-same-origin allow-forms"
|
| 102 |
+
/>
|
| 103 |
+
);
|
| 104 |
+
}
|
frontend/src/components/PublishDialog.tsx
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useEffect, useState } from "react";
|
| 2 |
+
import type { VirtualFile } from "../hooks/useVirtualFS";
|
| 3 |
+
import { usePublish } from "../hooks/usePublish";
|
| 4 |
+
|
| 5 |
+
interface PublishDialogProps {
|
| 6 |
+
open: boolean;
|
| 7 |
+
onClose: () => void;
|
| 8 |
+
files: VirtualFile[];
|
| 9 |
+
accessToken: string | null;
|
| 10 |
+
defaultUsername: string | null;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
function suggestRepoName(username: string | null): string {
|
| 14 |
+
const stamp = new Date()
|
| 15 |
+
.toISOString()
|
| 16 |
+
.slice(0, 10)
|
| 17 |
+
.replace(/-/g, "");
|
| 18 |
+
return `reachy-voice-${stamp}`;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
export function PublishDialog({
|
| 22 |
+
open,
|
| 23 |
+
onClose,
|
| 24 |
+
files,
|
| 25 |
+
accessToken,
|
| 26 |
+
defaultUsername,
|
| 27 |
+
}: PublishDialogProps) {
|
| 28 |
+
const { status, steps, result, error, errorCode, publish, reset } =
|
| 29 |
+
usePublish(accessToken);
|
| 30 |
+
|
| 31 |
+
const [repoName, setRepoName] = useState("");
|
| 32 |
+
const [isPrivate, setIsPrivate] = useState(false);
|
| 33 |
+
const [overwrite, setOverwrite] = useState(false);
|
| 34 |
+
const [commitMessage, setCommitMessage] = useState("");
|
| 35 |
+
|
| 36 |
+
useEffect(() => {
|
| 37 |
+
if (open) {
|
| 38 |
+
setRepoName((prev) => prev || suggestRepoName(defaultUsername));
|
| 39 |
+
if (status !== "idle") reset();
|
| 40 |
+
}
|
| 41 |
+
}, [open, defaultUsername, reset, status]);
|
| 42 |
+
|
| 43 |
+
if (!open) return null;
|
| 44 |
+
|
| 45 |
+
const handlePublish = () => {
|
| 46 |
+
if (status === "publishing") return;
|
| 47 |
+
publish({
|
| 48 |
+
repoName,
|
| 49 |
+
isPrivate,
|
| 50 |
+
overwrite,
|
| 51 |
+
commitMessage: commitMessage.trim() || undefined,
|
| 52 |
+
files,
|
| 53 |
+
});
|
| 54 |
+
};
|
| 55 |
+
|
| 56 |
+
const handleClose = () => {
|
| 57 |
+
if (status === "publishing") return;
|
| 58 |
+
reset();
|
| 59 |
+
onClose();
|
| 60 |
+
};
|
| 61 |
+
|
| 62 |
+
return (
|
| 63 |
+
<div className="dialog-backdrop" onClick={handleClose}>
|
| 64 |
+
<div className="dialog" onClick={(e) => e.stopPropagation()}>
|
| 65 |
+
<header className="dialog-header">
|
| 66 |
+
<span>Publish to Hugging Face Space</span>
|
| 67 |
+
<button
|
| 68 |
+
type="button"
|
| 69 |
+
className="btn-ghost"
|
| 70 |
+
onClick={handleClose}
|
| 71 |
+
disabled={status === "publishing"}
|
| 72 |
+
style={{ padding: "4px 8px" }}
|
| 73 |
+
>
|
| 74 |
+
✕
|
| 75 |
+
</button>
|
| 76 |
+
</header>
|
| 77 |
+
|
| 78 |
+
<div className="dialog-body">
|
| 79 |
+
<div className="field">
|
| 80 |
+
<label>Space name</label>
|
| 81 |
+
<input
|
| 82 |
+
type="text"
|
| 83 |
+
value={repoName}
|
| 84 |
+
onChange={(e) => setRepoName(e.target.value)}
|
| 85 |
+
placeholder="my-reachy-app"
|
| 86 |
+
disabled={status === "publishing"}
|
| 87 |
+
/>
|
| 88 |
+
{defaultUsername && (
|
| 89 |
+
<div className="field-hint">
|
| 90 |
+
Will be published as <code>{defaultUsername}/{repoName || "<name>"}</code>
|
| 91 |
+
</div>
|
| 92 |
+
)}
|
| 93 |
+
</div>
|
| 94 |
+
|
| 95 |
+
<div className="field">
|
| 96 |
+
<label>Commit message (optional)</label>
|
| 97 |
+
<input
|
| 98 |
+
type="text"
|
| 99 |
+
value={commitMessage}
|
| 100 |
+
onChange={(e) => setCommitMessage(e.target.value)}
|
| 101 |
+
placeholder="Initial commit from Reachy Vibe Coder"
|
| 102 |
+
disabled={status === "publishing"}
|
| 103 |
+
/>
|
| 104 |
+
</div>
|
| 105 |
+
|
| 106 |
+
<div className="field-row">
|
| 107 |
+
<input
|
| 108 |
+
id="publish-private"
|
| 109 |
+
type="checkbox"
|
| 110 |
+
checked={isPrivate}
|
| 111 |
+
onChange={(e) => setIsPrivate(e.target.checked)}
|
| 112 |
+
disabled={status === "publishing"}
|
| 113 |
+
/>
|
| 114 |
+
<label htmlFor="publish-private" style={{ cursor: "pointer" }}>
|
| 115 |
+
Make Space private
|
| 116 |
+
</label>
|
| 117 |
+
</div>
|
| 118 |
+
|
| 119 |
+
<div className="field-row">
|
| 120 |
+
<input
|
| 121 |
+
id="publish-overwrite"
|
| 122 |
+
type="checkbox"
|
| 123 |
+
checked={overwrite}
|
| 124 |
+
onChange={(e) => setOverwrite(e.target.checked)}
|
| 125 |
+
disabled={status === "publishing"}
|
| 126 |
+
/>
|
| 127 |
+
<label htmlFor="publish-overwrite" style={{ cursor: "pointer" }}>
|
| 128 |
+
Overwrite if Space already exists
|
| 129 |
+
</label>
|
| 130 |
+
</div>
|
| 131 |
+
|
| 132 |
+
<div className="field">
|
| 133 |
+
<label>Files to upload ({files.length})</label>
|
| 134 |
+
<div className="publish-log">
|
| 135 |
+
{files.map((f) => (
|
| 136 |
+
<div key={f.path} className="publish-log-step">
|
| 137 |
+
{f.path} <span style={{ color: "var(--text-faint)" }}>· {f.content.length}B</span>
|
| 138 |
+
</div>
|
| 139 |
+
))}
|
| 140 |
+
</div>
|
| 141 |
+
</div>
|
| 142 |
+
|
| 143 |
+
{steps.length > 0 && (
|
| 144 |
+
<div className="field">
|
| 145 |
+
<label>Progress</label>
|
| 146 |
+
<div className="publish-log">
|
| 147 |
+
{steps.map((s, i) => (
|
| 148 |
+
<div
|
| 149 |
+
key={i}
|
| 150 |
+
className={`publish-log-step ${s.step === "done" ? "done" : ""}`}
|
| 151 |
+
>
|
| 152 |
+
[{s.step}] {s.message}
|
| 153 |
+
</div>
|
| 154 |
+
))}
|
| 155 |
+
</div>
|
| 156 |
+
</div>
|
| 157 |
+
)}
|
| 158 |
+
|
| 159 |
+
{status === "done" && result.spaceUrl && (
|
| 160 |
+
<div className="publish-success">
|
| 161 |
+
<div>✓ Published!</div>
|
| 162 |
+
<div>
|
| 163 |
+
<a href={result.spaceUrl} target="_blank" rel="noopener">
|
| 164 |
+
{result.spaceUrl}
|
| 165 |
+
</a>
|
| 166 |
+
</div>
|
| 167 |
+
</div>
|
| 168 |
+
)}
|
| 169 |
+
|
| 170 |
+
{status === "error" && error && (
|
| 171 |
+
<div className="publish-error">
|
| 172 |
+
{error}
|
| 173 |
+
{errorCode === "REPO_EXISTS" && (
|
| 174 |
+
<div style={{ marginTop: 6 }}>
|
| 175 |
+
Enable "Overwrite if Space already exists" and try again.
|
| 176 |
+
</div>
|
| 177 |
+
)}
|
| 178 |
+
</div>
|
| 179 |
+
)}
|
| 180 |
+
</div>
|
| 181 |
+
|
| 182 |
+
<footer className="dialog-footer">
|
| 183 |
+
<button
|
| 184 |
+
type="button"
|
| 185 |
+
className="btn btn-ghost"
|
| 186 |
+
onClick={handleClose}
|
| 187 |
+
disabled={status === "publishing"}
|
| 188 |
+
>
|
| 189 |
+
{status === "done" ? "Close" : "Cancel"}
|
| 190 |
+
</button>
|
| 191 |
+
{status !== "done" && (
|
| 192 |
+
<button
|
| 193 |
+
type="button"
|
| 194 |
+
className="btn btn-primary"
|
| 195 |
+
onClick={handlePublish}
|
| 196 |
+
disabled={
|
| 197 |
+
status === "publishing" ||
|
| 198 |
+
repoName.trim().length === 0 ||
|
| 199 |
+
files.length === 0
|
| 200 |
+
}
|
| 201 |
+
>
|
| 202 |
+
{status === "publishing" ? "Publishing…" : "Publish"}
|
| 203 |
+
</button>
|
| 204 |
+
)}
|
| 205 |
+
</footer>
|
| 206 |
+
</div>
|
| 207 |
+
</div>
|
| 208 |
+
);
|
| 209 |
+
}
|
frontend/src/components/SignInScreen.tsx
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { HfLogo } from "./HfLogo";
|
| 2 |
+
|
| 3 |
+
interface SignInScreenProps {
|
| 4 |
+
onSignIn: () => void;
|
| 5 |
+
isSigningIn: boolean;
|
| 6 |
+
error: string | null;
|
| 7 |
+
hasClientId: boolean;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
export function SignInScreen({
|
| 11 |
+
onSignIn,
|
| 12 |
+
isSigningIn,
|
| 13 |
+
error,
|
| 14 |
+
hasClientId,
|
| 15 |
+
}: SignInScreenProps) {
|
| 16 |
+
return (
|
| 17 |
+
<div className="signin-screen">
|
| 18 |
+
<div className="signin-emoji">🤖</div>
|
| 19 |
+
<h1 className="signin-title">Reachy Mini · Vibe Coder</h1>
|
| 20 |
+
<p className="signin-subtitle">
|
| 21 |
+
Sign in with your Hugging Face account to start vibe-coding browser-based
|
| 22 |
+
voice apps for Reachy Mini. You'll be able to preview the app live and
|
| 23 |
+
publish it as a Hugging Face Space in one click.
|
| 24 |
+
</p>
|
| 25 |
+
<button
|
| 26 |
+
type="button"
|
| 27 |
+
className="signin-button"
|
| 28 |
+
onClick={onSignIn}
|
| 29 |
+
disabled={isSigningIn || !hasClientId}
|
| 30 |
+
>
|
| 31 |
+
<HfLogo size={20} />
|
| 32 |
+
{isSigningIn ? "Redirecting…" : "Sign in with Hugging Face"}
|
| 33 |
+
</button>
|
| 34 |
+
|
| 35 |
+
{!hasClientId && (
|
| 36 |
+
<p className="signin-hint">
|
| 37 |
+
No OAuth client ID detected. On a Hugging Face Space this is injected
|
| 38 |
+
automatically via the Docker entrypoint. For local dev, create an
|
| 39 |
+
OAuth app and set <code>VITE_HF_OAUTH_CLIENT_ID</code> in{" "}
|
| 40 |
+
<code>frontend/.env.local</code>.
|
| 41 |
+
</p>
|
| 42 |
+
)}
|
| 43 |
+
|
| 44 |
+
{error && <div className="signin-error">{error}</div>}
|
| 45 |
+
</div>
|
| 46 |
+
);
|
| 47 |
+
}
|
frontend/src/components/TopBar.tsx
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { OAuthResult } from "@huggingface/hub";
|
| 2 |
+
import { HfLogo } from "./HfLogo";
|
| 3 |
+
|
| 4 |
+
interface Model {
|
| 5 |
+
id: string;
|
| 6 |
+
label: string;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
interface TopBarProps {
|
| 10 |
+
user: OAuthResult["userInfo"] | null;
|
| 11 |
+
models: Model[];
|
| 12 |
+
selectedModel: string | null;
|
| 13 |
+
onModelChange: (id: string) => void;
|
| 14 |
+
onPublish: () => void;
|
| 15 |
+
onSignOut: () => void;
|
| 16 |
+
onReset: () => void;
|
| 17 |
+
canPublish: boolean;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
export function TopBar({
|
| 21 |
+
user,
|
| 22 |
+
models,
|
| 23 |
+
selectedModel,
|
| 24 |
+
onModelChange,
|
| 25 |
+
onPublish,
|
| 26 |
+
onSignOut,
|
| 27 |
+
onReset,
|
| 28 |
+
canPublish,
|
| 29 |
+
}: TopBarProps) {
|
| 30 |
+
return (
|
| 31 |
+
<header className="topbar">
|
| 32 |
+
<div className="topbar-brand">
|
| 33 |
+
<HfLogo size={26} />
|
| 34 |
+
<div className="topbar-title">
|
| 35 |
+
<span className="topbar-title-main">Reachy Mini · Vibe Coder</span>
|
| 36 |
+
<span className="topbar-title-sub">Powered by Hugging Face</span>
|
| 37 |
+
</div>
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<div className="topbar-spacer" />
|
| 41 |
+
|
| 42 |
+
{models.length > 0 && (
|
| 43 |
+
<div className="topbar-model">
|
| 44 |
+
<span>Model</span>
|
| 45 |
+
<select
|
| 46 |
+
value={selectedModel ?? ""}
|
| 47 |
+
onChange={(e) => onModelChange(e.target.value)}
|
| 48 |
+
>
|
| 49 |
+
{models.map((m) => (
|
| 50 |
+
<option key={m.id} value={m.id}>
|
| 51 |
+
{m.label}
|
| 52 |
+
</option>
|
| 53 |
+
))}
|
| 54 |
+
</select>
|
| 55 |
+
</div>
|
| 56 |
+
)}
|
| 57 |
+
|
| 58 |
+
<button type="button" className="btn btn-ghost" onClick={onReset} title="Reset virtual FS and chat">
|
| 59 |
+
Reset
|
| 60 |
+
</button>
|
| 61 |
+
|
| 62 |
+
<button
|
| 63 |
+
type="button"
|
| 64 |
+
className="btn btn-primary"
|
| 65 |
+
onClick={onPublish}
|
| 66 |
+
disabled={!canPublish}
|
| 67 |
+
title={canPublish ? "Publish to Hugging Face Space" : "Add files before publishing"}
|
| 68 |
+
>
|
| 69 |
+
Publish to HF Space
|
| 70 |
+
</button>
|
| 71 |
+
|
| 72 |
+
{user && (
|
| 73 |
+
<div className="topbar-user" title={user.preferred_username}>
|
| 74 |
+
{user.picture ? (
|
| 75 |
+
<img src={user.picture} alt={user.preferred_username} />
|
| 76 |
+
) : (
|
| 77 |
+
<span className="avatar-fallback">
|
| 78 |
+
{user.preferred_username.slice(0, 1).toUpperCase()}
|
| 79 |
+
</span>
|
| 80 |
+
)}
|
| 81 |
+
<span className="topbar-user-name">{user.preferred_username}</span>
|
| 82 |
+
<button
|
| 83 |
+
type="button"
|
| 84 |
+
className="btn-ghost topbar-user-menu"
|
| 85 |
+
onClick={onSignOut}
|
| 86 |
+
title="Sign out"
|
| 87 |
+
>
|
| 88 |
+
✕
|
| 89 |
+
</button>
|
| 90 |
+
</div>
|
| 91 |
+
)}
|
| 92 |
+
</header>
|
| 93 |
+
);
|
| 94 |
+
}
|
frontend/src/hooks/useAgentChat.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useChat } from "@ai-sdk/react";
|
| 2 |
+
import {
|
| 3 |
+
DefaultChatTransport,
|
| 4 |
+
lastAssistantMessageIsCompleteWithToolCalls,
|
| 5 |
+
} from "ai";
|
| 6 |
+
import type { UIMessage } from "ai";
|
| 7 |
+
import { useCallback, useMemo, useRef, useState } from "react";
|
| 8 |
+
import type { VirtualFSHandle } from "./useVirtualFS";
|
| 9 |
+
|
| 10 |
+
const MAX_TOOL_ROUNDS = 12;
|
| 11 |
+
|
| 12 |
+
interface UseAgentChatOptions {
|
| 13 |
+
accessToken: string | null;
|
| 14 |
+
vfs: VirtualFSHandle;
|
| 15 |
+
modelRef: React.RefObject<string | null>;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
interface ToolCall {
|
| 19 |
+
toolName: string;
|
| 20 |
+
args: unknown;
|
| 21 |
+
toolCallId: string;
|
| 22 |
+
dynamic?: boolean;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
function executeEditorToolCall(
|
| 26 |
+
tc: ToolCall,
|
| 27 |
+
vfs: VirtualFSHandle,
|
| 28 |
+
): string | { error: string } {
|
| 29 |
+
const { toolName, args } = tc;
|
| 30 |
+
|
| 31 |
+
switch (toolName) {
|
| 32 |
+
case "write_file": {
|
| 33 |
+
const { path, content } = args as { path: string; content: string };
|
| 34 |
+
if (!path) return { error: "Missing `path`" };
|
| 35 |
+
if (typeof content !== "string") return { error: "Missing `content`" };
|
| 36 |
+
vfs.writeFile(path, content);
|
| 37 |
+
return `Wrote ${content.length} chars to ${path}`;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
case "edit_file": {
|
| 41 |
+
const { path, old_string, new_string } = args as {
|
| 42 |
+
path: string;
|
| 43 |
+
old_string: string;
|
| 44 |
+
new_string: string;
|
| 45 |
+
};
|
| 46 |
+
const res = vfs.editFile(path, old_string, new_string);
|
| 47 |
+
if (!res.ok) return { error: res.error ?? "Edit failed" };
|
| 48 |
+
return `Edited ${path}`;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
case "delete_file": {
|
| 52 |
+
const { path } = args as { path: string };
|
| 53 |
+
const res = vfs.deleteFile(path);
|
| 54 |
+
if (!res.ok) return { error: res.error ?? "Delete failed" };
|
| 55 |
+
return `Deleted ${path}`;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
case "read_file": {
|
| 59 |
+
const { path } = args as { path: string };
|
| 60 |
+
const file = vfs.readFile(path);
|
| 61 |
+
if (!file) return { error: `File "${path}" does not exist` };
|
| 62 |
+
return file.content;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
case "list_files": {
|
| 66 |
+
const list = vfs.listFiles();
|
| 67 |
+
if (list.length === 0) return "(virtual FS is empty)";
|
| 68 |
+
return list
|
| 69 |
+
.map((f) => `- ${f.path} (${f.content.length} bytes)`)
|
| 70 |
+
.join("\n");
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
default:
|
| 74 |
+
return { error: `Unknown client-side tool: ${toolName}` };
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
export function useAgentChat({ accessToken, vfs, modelRef }: UseAgentChatOptions) {
|
| 79 |
+
const vfsRef = useRef(vfs);
|
| 80 |
+
vfsRef.current = vfs;
|
| 81 |
+
|
| 82 |
+
const [input, setInput] = useState("");
|
| 83 |
+
|
| 84 |
+
const transport = useMemo(
|
| 85 |
+
() =>
|
| 86 |
+
new DefaultChatTransport({
|
| 87 |
+
api: "/api/chat",
|
| 88 |
+
headers: (): Record<string, string> =>
|
| 89 |
+
accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
|
| 90 |
+
}),
|
| 91 |
+
[accessToken],
|
| 92 |
+
);
|
| 93 |
+
|
| 94 |
+
const { addToolResult, ...chat } = useChat({
|
| 95 |
+
transport,
|
| 96 |
+
sendAutomaticallyWhen: ({ messages }) => {
|
| 97 |
+
let toolRounds = 0;
|
| 98 |
+
for (let i = messages.length - 1; i >= 0; i--) {
|
| 99 |
+
const m = messages[i];
|
| 100 |
+
if (m.role !== "assistant") break;
|
| 101 |
+
const hasToolPart = (m.parts ?? []).some(
|
| 102 |
+
(p) => typeof p.type === "string" && p.type.startsWith("tool-"),
|
| 103 |
+
);
|
| 104 |
+
if (!hasToolPart) break;
|
| 105 |
+
toolRounds += 1;
|
| 106 |
+
}
|
| 107 |
+
if (toolRounds >= MAX_TOOL_ROUNDS) return false;
|
| 108 |
+
return lastAssistantMessageIsCompleteWithToolCalls({ messages });
|
| 109 |
+
},
|
| 110 |
+
|
| 111 |
+
async onToolCall({ toolCall }) {
|
| 112 |
+
if (toolCall.dynamic) return;
|
| 113 |
+
|
| 114 |
+
// `read_skill_doc` is executed server-side (see backend tools.ts).
|
| 115 |
+
// Only the editor tools are handled here.
|
| 116 |
+
const clientSideTools = new Set([
|
| 117 |
+
"write_file",
|
| 118 |
+
"edit_file",
|
| 119 |
+
"delete_file",
|
| 120 |
+
"read_file",
|
| 121 |
+
"list_files",
|
| 122 |
+
]);
|
| 123 |
+
if (!clientSideTools.has(toolCall.toolName as string)) return;
|
| 124 |
+
|
| 125 |
+
const result = executeEditorToolCall(
|
| 126 |
+
{
|
| 127 |
+
toolName: toolCall.toolName as string,
|
| 128 |
+
args: toolCall.input,
|
| 129 |
+
toolCallId: toolCall.toolCallId,
|
| 130 |
+
},
|
| 131 |
+
vfsRef.current,
|
| 132 |
+
);
|
| 133 |
+
|
| 134 |
+
const output = typeof result === "string" ? result : `ERROR: ${result.error}`;
|
| 135 |
+
|
| 136 |
+
(addToolResult as (args: {
|
| 137 |
+
tool: string;
|
| 138 |
+
toolCallId: string;
|
| 139 |
+
output: unknown;
|
| 140 |
+
}) => void)({
|
| 141 |
+
tool: toolCall.toolName as string,
|
| 142 |
+
toolCallId: toolCall.toolCallId,
|
| 143 |
+
output,
|
| 144 |
+
});
|
| 145 |
+
},
|
| 146 |
+
|
| 147 |
+
onError(error) {
|
| 148 |
+
console.error("[agent] chat error:", error);
|
| 149 |
+
},
|
| 150 |
+
});
|
| 151 |
+
|
| 152 |
+
const sendMessage = useCallback(
|
| 153 |
+
(text: string) => {
|
| 154 |
+
const trimmed = text.trim();
|
| 155 |
+
if (!trimmed) return;
|
| 156 |
+
const context = {
|
| 157 |
+
files: vfsRef.current.getAll().map((f) => ({
|
| 158 |
+
path: f.path,
|
| 159 |
+
size: f.content.length,
|
| 160 |
+
})),
|
| 161 |
+
};
|
| 162 |
+
chat.sendMessage(
|
| 163 |
+
{ text: trimmed },
|
| 164 |
+
{ body: { context, model: modelRef.current } },
|
| 165 |
+
);
|
| 166 |
+
setInput("");
|
| 167 |
+
},
|
| 168 |
+
[chat, modelRef],
|
| 169 |
+
);
|
| 170 |
+
|
| 171 |
+
const clearMessages = useCallback(() => {
|
| 172 |
+
chat.setMessages([]);
|
| 173 |
+
}, [chat]);
|
| 174 |
+
|
| 175 |
+
return {
|
| 176 |
+
messages: chat.messages as UIMessage[],
|
| 177 |
+
status: chat.status,
|
| 178 |
+
isLoading: chat.status === "streaming" || chat.status === "submitted",
|
| 179 |
+
error: chat.error,
|
| 180 |
+
stop: chat.stop,
|
| 181 |
+
sendMessage,
|
| 182 |
+
clearMessages,
|
| 183 |
+
input,
|
| 184 |
+
setInput,
|
| 185 |
+
};
|
| 186 |
+
}
|
frontend/src/hooks/useHfAuth.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useCallback, useEffect, useRef, useState } from "react";
|
| 2 |
+
import {
|
| 3 |
+
oauthHandleRedirectIfPresent,
|
| 4 |
+
oauthLoginUrl,
|
| 5 |
+
type OAuthResult,
|
| 6 |
+
} from "@huggingface/hub";
|
| 7 |
+
|
| 8 |
+
/**
|
| 9 |
+
* HF OAuth flow, reused from `reachy_mini_minimal_conversation/src/main.ts`.
|
| 10 |
+
*
|
| 11 |
+
* - On a Space: the client ID is injected into `window.huggingface.variables`
|
| 12 |
+
* by `docker-entrypoint.d/10-inject-hf-vars.sh` at container start.
|
| 13 |
+
* - Locally: we read it from `VITE_HF_OAUTH_CLIENT_ID` (`.env.local`).
|
| 14 |
+
*/
|
| 15 |
+
|
| 16 |
+
const STORAGE_KEY = "hf-oauth-result-v1";
|
| 17 |
+
|
| 18 |
+
function readClientId(): string | null {
|
| 19 |
+
const injected = window.huggingface?.variables?.OAUTH_CLIENT_ID;
|
| 20 |
+
if (injected) return injected;
|
| 21 |
+
return import.meta.env.VITE_HF_OAUTH_CLIENT_ID ?? null;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
function readScopes(): string {
|
| 25 |
+
return (
|
| 26 |
+
window.huggingface?.variables?.OAUTH_SCOPES ??
|
| 27 |
+
"openid profile write-repos manage-repos"
|
| 28 |
+
);
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
function loadStoredResult(): OAuthResult | null {
|
| 32 |
+
try {
|
| 33 |
+
const raw = sessionStorage.getItem(STORAGE_KEY);
|
| 34 |
+
if (!raw) return null;
|
| 35 |
+
const parsed = JSON.parse(raw) as OAuthResult;
|
| 36 |
+
const expiresAt = parsed.accessTokenExpiresAt
|
| 37 |
+
? new Date(parsed.accessTokenExpiresAt).getTime()
|
| 38 |
+
: 0;
|
| 39 |
+
if (expiresAt && expiresAt < Date.now()) {
|
| 40 |
+
sessionStorage.removeItem(STORAGE_KEY);
|
| 41 |
+
return null;
|
| 42 |
+
}
|
| 43 |
+
parsed.accessTokenExpiresAt = new Date(parsed.accessTokenExpiresAt);
|
| 44 |
+
return parsed;
|
| 45 |
+
} catch {
|
| 46 |
+
return null;
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
function storeResult(result: OAuthResult): void {
|
| 51 |
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(result));
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
export interface HfAuthState {
|
| 55 |
+
isReady: boolean;
|
| 56 |
+
isSignedIn: boolean;
|
| 57 |
+
isSigningIn: boolean;
|
| 58 |
+
user: OAuthResult["userInfo"] | null;
|
| 59 |
+
accessToken: string | null;
|
| 60 |
+
error: string | null;
|
| 61 |
+
clientId: string | null;
|
| 62 |
+
signIn: () => Promise<void>;
|
| 63 |
+
signOut: () => void;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
export function useHfAuth(): HfAuthState {
|
| 67 |
+
const [result, setResult] = useState<OAuthResult | null>(null);
|
| 68 |
+
const [isReady, setIsReady] = useState(false);
|
| 69 |
+
const [isSigningIn, setIsSigningIn] = useState(false);
|
| 70 |
+
const [error, setError] = useState<string | null>(null);
|
| 71 |
+
const clientIdRef = useRef<string | null>(readClientId());
|
| 72 |
+
|
| 73 |
+
useEffect(() => {
|
| 74 |
+
let cancelled = false;
|
| 75 |
+
(async () => {
|
| 76 |
+
try {
|
| 77 |
+
const redirectResult = await oauthHandleRedirectIfPresent();
|
| 78 |
+
if (cancelled) return;
|
| 79 |
+
if (redirectResult) {
|
| 80 |
+
storeResult(redirectResult);
|
| 81 |
+
setResult(redirectResult);
|
| 82 |
+
const url = new URL(window.location.href);
|
| 83 |
+
url.searchParams.delete("code");
|
| 84 |
+
url.searchParams.delete("state");
|
| 85 |
+
window.history.replaceState({}, "", url.toString());
|
| 86 |
+
setIsReady(true);
|
| 87 |
+
return;
|
| 88 |
+
}
|
| 89 |
+
const stored = loadStoredResult();
|
| 90 |
+
if (stored) setResult(stored);
|
| 91 |
+
setIsReady(true);
|
| 92 |
+
} catch (e) {
|
| 93 |
+
if (cancelled) return;
|
| 94 |
+
setError(e instanceof Error ? e.message : "OAuth error");
|
| 95 |
+
setIsReady(true);
|
| 96 |
+
}
|
| 97 |
+
})();
|
| 98 |
+
return () => {
|
| 99 |
+
cancelled = true;
|
| 100 |
+
};
|
| 101 |
+
}, []);
|
| 102 |
+
|
| 103 |
+
const signIn = useCallback(async () => {
|
| 104 |
+
setError(null);
|
| 105 |
+
const clientId = clientIdRef.current;
|
| 106 |
+
if (!clientId) {
|
| 107 |
+
setError(
|
| 108 |
+
"No Hugging Face OAuth client ID configured. On a Space this should be auto-injected; locally set VITE_HF_OAUTH_CLIENT_ID in frontend/.env.local.",
|
| 109 |
+
);
|
| 110 |
+
return;
|
| 111 |
+
}
|
| 112 |
+
setIsSigningIn(true);
|
| 113 |
+
try {
|
| 114 |
+
const url = await oauthLoginUrl({
|
| 115 |
+
clientId,
|
| 116 |
+
scopes: readScopes(),
|
| 117 |
+
redirectUrl: window.location.origin + window.location.pathname,
|
| 118 |
+
});
|
| 119 |
+
window.location.href = url;
|
| 120 |
+
} catch (e) {
|
| 121 |
+
setIsSigningIn(false);
|
| 122 |
+
setError(e instanceof Error ? e.message : "Failed to start OAuth flow");
|
| 123 |
+
}
|
| 124 |
+
}, []);
|
| 125 |
+
|
| 126 |
+
const signOut = useCallback(() => {
|
| 127 |
+
sessionStorage.removeItem(STORAGE_KEY);
|
| 128 |
+
setResult(null);
|
| 129 |
+
}, []);
|
| 130 |
+
|
| 131 |
+
return {
|
| 132 |
+
isReady,
|
| 133 |
+
isSignedIn: !!result?.accessToken,
|
| 134 |
+
isSigningIn,
|
| 135 |
+
user: result?.userInfo ?? null,
|
| 136 |
+
accessToken: result?.accessToken ?? null,
|
| 137 |
+
error,
|
| 138 |
+
clientId: clientIdRef.current,
|
| 139 |
+
signIn,
|
| 140 |
+
signOut,
|
| 141 |
+
};
|
| 142 |
+
}
|
frontend/src/hooks/usePublish.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useCallback, useRef, useState } from "react";
|
| 2 |
+
import type { VirtualFile } from "./useVirtualFS";
|
| 3 |
+
|
| 4 |
+
export type PublishStatus = "idle" | "publishing" | "done" | "error";
|
| 5 |
+
|
| 6 |
+
export interface PublishStep {
|
| 7 |
+
step: string;
|
| 8 |
+
message: string;
|
| 9 |
+
timestamp: number;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
interface PublishArgs {
|
| 13 |
+
repoName: string;
|
| 14 |
+
isPrivate: boolean;
|
| 15 |
+
overwrite: boolean;
|
| 16 |
+
files: VirtualFile[];
|
| 17 |
+
commitMessage?: string;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
interface PublishResult {
|
| 21 |
+
repoId?: string;
|
| 22 |
+
spaceUrl?: string;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
export function usePublish(accessToken: string | null) {
|
| 26 |
+
const [status, setStatus] = useState<PublishStatus>("idle");
|
| 27 |
+
const [steps, setSteps] = useState<PublishStep[]>([]);
|
| 28 |
+
const [result, setResult] = useState<PublishResult>({});
|
| 29 |
+
const [error, setError] = useState<string | null>(null);
|
| 30 |
+
const [errorCode, setErrorCode] = useState<string | null>(null);
|
| 31 |
+
const abortRef = useRef<AbortController | null>(null);
|
| 32 |
+
|
| 33 |
+
const reset = useCallback(() => {
|
| 34 |
+
setStatus("idle");
|
| 35 |
+
setSteps([]);
|
| 36 |
+
setResult({});
|
| 37 |
+
setError(null);
|
| 38 |
+
setErrorCode(null);
|
| 39 |
+
}, []);
|
| 40 |
+
|
| 41 |
+
const publish = useCallback(
|
| 42 |
+
async (args: PublishArgs) => {
|
| 43 |
+
if (!accessToken) {
|
| 44 |
+
setError("You must sign in with Hugging Face before publishing.");
|
| 45 |
+
setStatus("error");
|
| 46 |
+
return;
|
| 47 |
+
}
|
| 48 |
+
reset();
|
| 49 |
+
setStatus("publishing");
|
| 50 |
+
|
| 51 |
+
const controller = new AbortController();
|
| 52 |
+
abortRef.current = controller;
|
| 53 |
+
|
| 54 |
+
const pushStep = (step: string, message: string) =>
|
| 55 |
+
setSteps((prev) => [
|
| 56 |
+
...prev,
|
| 57 |
+
{ step, message, timestamp: Date.now() },
|
| 58 |
+
]);
|
| 59 |
+
|
| 60 |
+
try {
|
| 61 |
+
const response = await fetch("/api/publish", {
|
| 62 |
+
method: "POST",
|
| 63 |
+
headers: {
|
| 64 |
+
"Content-Type": "application/json",
|
| 65 |
+
Authorization: `Bearer ${accessToken}`,
|
| 66 |
+
},
|
| 67 |
+
body: JSON.stringify({
|
| 68 |
+
repoName: args.repoName,
|
| 69 |
+
private: args.isPrivate,
|
| 70 |
+
overwrite: args.overwrite,
|
| 71 |
+
commitMessage: args.commitMessage,
|
| 72 |
+
files: args.files.map((f) => ({ path: f.path, content: f.content })),
|
| 73 |
+
}),
|
| 74 |
+
signal: controller.signal,
|
| 75 |
+
});
|
| 76 |
+
|
| 77 |
+
if (!response.ok || !response.body) {
|
| 78 |
+
const text = await response.text().catch(() => "");
|
| 79 |
+
throw new Error(
|
| 80 |
+
text || `Publish request failed (${response.status})`,
|
| 81 |
+
);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
const reader = response.body.getReader();
|
| 85 |
+
const decoder = new TextDecoder();
|
| 86 |
+
let buffer = "";
|
| 87 |
+
|
| 88 |
+
while (true) {
|
| 89 |
+
const { done, value } = await reader.read();
|
| 90 |
+
if (done) break;
|
| 91 |
+
buffer += decoder.decode(value, { stream: true });
|
| 92 |
+
|
| 93 |
+
let eventEnd;
|
| 94 |
+
while ((eventEnd = buffer.indexOf("\n\n")) !== -1) {
|
| 95 |
+
const raw = buffer.slice(0, eventEnd);
|
| 96 |
+
buffer = buffer.slice(eventEnd + 2);
|
| 97 |
+
const lines = raw.split("\n");
|
| 98 |
+
let eventName = "message";
|
| 99 |
+
let dataStr = "";
|
| 100 |
+
for (const line of lines) {
|
| 101 |
+
if (line.startsWith("event:")) eventName = line.slice(6).trim();
|
| 102 |
+
else if (line.startsWith("data:")) dataStr += line.slice(5).trim();
|
| 103 |
+
}
|
| 104 |
+
if (!dataStr) continue;
|
| 105 |
+
let data: Record<string, unknown>;
|
| 106 |
+
try {
|
| 107 |
+
data = JSON.parse(dataStr);
|
| 108 |
+
} catch {
|
| 109 |
+
continue;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
if (eventName === "progress") {
|
| 113 |
+
pushStep(String(data.step ?? "?"), String(data.message ?? ""));
|
| 114 |
+
} else if (eventName === "done") {
|
| 115 |
+
pushStep("done", String(data.message ?? "Published"));
|
| 116 |
+
setResult({
|
| 117 |
+
repoId: data.repoId as string | undefined,
|
| 118 |
+
spaceUrl: data.spaceUrl as string | undefined,
|
| 119 |
+
});
|
| 120 |
+
setStatus("done");
|
| 121 |
+
} else if (eventName === "error") {
|
| 122 |
+
setError(String(data.message ?? "Publish failed"));
|
| 123 |
+
setErrorCode((data.code as string | undefined) ?? null);
|
| 124 |
+
setStatus("error");
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
} catch (e) {
|
| 129 |
+
if ((e as { name?: string })?.name === "AbortError") return;
|
| 130 |
+
setError(e instanceof Error ? e.message : "Unknown error");
|
| 131 |
+
setStatus("error");
|
| 132 |
+
} finally {
|
| 133 |
+
abortRef.current = null;
|
| 134 |
+
}
|
| 135 |
+
},
|
| 136 |
+
[accessToken, reset],
|
| 137 |
+
);
|
| 138 |
+
|
| 139 |
+
const cancel = useCallback(() => {
|
| 140 |
+
abortRef.current?.abort();
|
| 141 |
+
abortRef.current = null;
|
| 142 |
+
setStatus("idle");
|
| 143 |
+
}, []);
|
| 144 |
+
|
| 145 |
+
return {
|
| 146 |
+
status,
|
| 147 |
+
steps,
|
| 148 |
+
result,
|
| 149 |
+
error,
|
| 150 |
+
errorCode,
|
| 151 |
+
publish,
|
| 152 |
+
cancel,
|
| 153 |
+
reset,
|
| 154 |
+
};
|
| 155 |
+
}
|
frontend/src/hooks/useVirtualFS.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useCallback, useRef, useState } from "react";
|
| 2 |
+
|
| 3 |
+
export interface VirtualFile {
|
| 4 |
+
path: string;
|
| 5 |
+
content: string;
|
| 6 |
+
updatedAt: number;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
/**
|
| 10 |
+
* In-memory virtual file system for the app the agent is building.
|
| 11 |
+
*
|
| 12 |
+
* The agent mutates it through client-side tool execution (`onToolCall` in
|
| 13 |
+
* `useAgentChat`), the user can also edit files by hand in the Monaco pane.
|
| 14 |
+
* `version` is bumped on every mutation so the preview iframe and the
|
| 15 |
+
* publish flow can react to changes.
|
| 16 |
+
*/
|
| 17 |
+
export function useVirtualFS(initial?: VirtualFile[]) {
|
| 18 |
+
const [files, setFiles] = useState<Map<string, VirtualFile>>(() => {
|
| 19 |
+
const map = new Map<string, VirtualFile>();
|
| 20 |
+
(initial ?? []).forEach((f) => map.set(f.path, f));
|
| 21 |
+
return map;
|
| 22 |
+
});
|
| 23 |
+
const [version, setVersion] = useState(0);
|
| 24 |
+
const filesRef = useRef(files);
|
| 25 |
+
filesRef.current = files;
|
| 26 |
+
|
| 27 |
+
const bump = () => setVersion((v) => v + 1);
|
| 28 |
+
|
| 29 |
+
const writeFile = useCallback((path: string, content: string) => {
|
| 30 |
+
setFiles((prev) => {
|
| 31 |
+
const next = new Map(prev);
|
| 32 |
+
next.set(path, { path, content, updatedAt: Date.now() });
|
| 33 |
+
return next;
|
| 34 |
+
});
|
| 35 |
+
bump();
|
| 36 |
+
}, []);
|
| 37 |
+
|
| 38 |
+
const editFile = useCallback(
|
| 39 |
+
(path: string, oldString: string, newString: string): { ok: boolean; error?: string } => {
|
| 40 |
+
const current = filesRef.current.get(path);
|
| 41 |
+
if (!current) {
|
| 42 |
+
return { ok: false, error: `File "${path}" does not exist (call write_file first).` };
|
| 43 |
+
}
|
| 44 |
+
const count = current.content.split(oldString).length - 1;
|
| 45 |
+
if (count === 0) {
|
| 46 |
+
return { ok: false, error: `old_string not found in "${path}".` };
|
| 47 |
+
}
|
| 48 |
+
if (count > 1) {
|
| 49 |
+
return {
|
| 50 |
+
ok: false,
|
| 51 |
+
error: `old_string is not unique in "${path}" (${count} occurrences). Add more surrounding context.`,
|
| 52 |
+
};
|
| 53 |
+
}
|
| 54 |
+
const nextContent = current.content.replace(oldString, newString);
|
| 55 |
+
setFiles((prev) => {
|
| 56 |
+
const next = new Map(prev);
|
| 57 |
+
next.set(path, { path, content: nextContent, updatedAt: Date.now() });
|
| 58 |
+
return next;
|
| 59 |
+
});
|
| 60 |
+
bump();
|
| 61 |
+
return { ok: true };
|
| 62 |
+
},
|
| 63 |
+
[],
|
| 64 |
+
);
|
| 65 |
+
|
| 66 |
+
const deleteFile = useCallback((path: string): { ok: boolean; error?: string } => {
|
| 67 |
+
if (!filesRef.current.has(path)) {
|
| 68 |
+
return { ok: false, error: `File "${path}" does not exist.` };
|
| 69 |
+
}
|
| 70 |
+
setFiles((prev) => {
|
| 71 |
+
const next = new Map(prev);
|
| 72 |
+
next.delete(path);
|
| 73 |
+
return next;
|
| 74 |
+
});
|
| 75 |
+
bump();
|
| 76 |
+
return { ok: true };
|
| 77 |
+
}, []);
|
| 78 |
+
|
| 79 |
+
const readFile = useCallback((path: string): VirtualFile | null => {
|
| 80 |
+
return filesRef.current.get(path) ?? null;
|
| 81 |
+
}, []);
|
| 82 |
+
|
| 83 |
+
const listFiles = useCallback((): VirtualFile[] => {
|
| 84 |
+
return Array.from(filesRef.current.values()).sort((a, b) =>
|
| 85 |
+
a.path.localeCompare(b.path),
|
| 86 |
+
);
|
| 87 |
+
}, []);
|
| 88 |
+
|
| 89 |
+
const clear = useCallback(() => {
|
| 90 |
+
setFiles(new Map());
|
| 91 |
+
bump();
|
| 92 |
+
}, []);
|
| 93 |
+
|
| 94 |
+
return {
|
| 95 |
+
files: Array.from(files.values()).sort((a, b) => a.path.localeCompare(b.path)),
|
| 96 |
+
filesMap: files,
|
| 97 |
+
version,
|
| 98 |
+
writeFile,
|
| 99 |
+
editFile,
|
| 100 |
+
deleteFile,
|
| 101 |
+
readFile,
|
| 102 |
+
listFiles,
|
| 103 |
+
clear,
|
| 104 |
+
getAll: () => Array.from(filesRef.current.values()),
|
| 105 |
+
};
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
export type VirtualFSHandle = ReturnType<typeof useVirtualFS>;
|
frontend/src/main.tsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from "react";
|
| 2 |
+
import ReactDOM from "react-dom/client";
|
| 3 |
+
import { App } from "./App";
|
| 4 |
+
import "./styles.css";
|
| 5 |
+
|
| 6 |
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
| 7 |
+
<React.StrictMode>
|
| 8 |
+
<App />
|
| 9 |
+
</React.StrictMode>,
|
| 10 |
+
);
|
frontend/src/styles.css
ADDED
|
@@ -0,0 +1,798 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
| 3 |
+
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
| 4 |
+
|
| 5 |
+
--bg: #0b0c10;
|
| 6 |
+
--bg-elev-1: #111218;
|
| 7 |
+
--bg-elev-2: #171922;
|
| 8 |
+
--bg-elev-3: #1e2130;
|
| 9 |
+
--border: #242838;
|
| 10 |
+
--border-strong: #30354a;
|
| 11 |
+
|
| 12 |
+
--text: #e7e9f1;
|
| 13 |
+
--text-dim: #a1a5b7;
|
| 14 |
+
--text-faint: #6a6f82;
|
| 15 |
+
|
| 16 |
+
--accent: #ffb020;
|
| 17 |
+
--accent-dim: #bf8418;
|
| 18 |
+
--accent-strong: #ffcd66;
|
| 19 |
+
--danger: #ff5c5c;
|
| 20 |
+
--success: #4ade80;
|
| 21 |
+
|
| 22 |
+
--user-bubble: #1e2130;
|
| 23 |
+
--agent-bubble: transparent;
|
| 24 |
+
|
| 25 |
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
| 26 |
+
--shadow-md: 0 4px 20px rgba(0, 0, 0, 0.4);
|
| 27 |
+
|
| 28 |
+
--radius-sm: 6px;
|
| 29 |
+
--radius-md: 10px;
|
| 30 |
+
--radius-lg: 14px;
|
| 31 |
+
|
| 32 |
+
color-scheme: dark;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
* {
|
| 36 |
+
box-sizing: border-box;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
html,
|
| 40 |
+
body,
|
| 41 |
+
#root {
|
| 42 |
+
height: 100%;
|
| 43 |
+
margin: 0;
|
| 44 |
+
padding: 0;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
body {
|
| 48 |
+
background: var(--bg);
|
| 49 |
+
color: var(--text);
|
| 50 |
+
font-family: var(--font-sans);
|
| 51 |
+
font-size: 14px;
|
| 52 |
+
line-height: 1.5;
|
| 53 |
+
-webkit-font-smoothing: antialiased;
|
| 54 |
+
-moz-osx-font-smoothing: grayscale;
|
| 55 |
+
overflow: hidden;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
button {
|
| 59 |
+
font-family: inherit;
|
| 60 |
+
font-size: inherit;
|
| 61 |
+
color: inherit;
|
| 62 |
+
background: none;
|
| 63 |
+
border: none;
|
| 64 |
+
cursor: pointer;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
input,
|
| 68 |
+
textarea,
|
| 69 |
+
select {
|
| 70 |
+
font-family: inherit;
|
| 71 |
+
font-size: inherit;
|
| 72 |
+
color: inherit;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
code,
|
| 76 |
+
pre,
|
| 77 |
+
.mono {
|
| 78 |
+
font-family: var(--font-mono);
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
/* ── App shell ─────────────────────────────────────────────────────────── */
|
| 82 |
+
.app-shell {
|
| 83 |
+
display: flex;
|
| 84 |
+
flex-direction: column;
|
| 85 |
+
height: 100vh;
|
| 86 |
+
min-height: 0;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.app-main {
|
| 90 |
+
display: grid;
|
| 91 |
+
grid-template-columns: minmax(360px, 420px) 1fr;
|
| 92 |
+
flex: 1;
|
| 93 |
+
min-height: 0;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
/* ── Top bar ───────────────────────────────────────────────────────────── */
|
| 97 |
+
.topbar {
|
| 98 |
+
display: flex;
|
| 99 |
+
align-items: center;
|
| 100 |
+
gap: 12px;
|
| 101 |
+
padding: 0 16px;
|
| 102 |
+
height: 52px;
|
| 103 |
+
border-bottom: 1px solid var(--border);
|
| 104 |
+
background: var(--bg-elev-1);
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.topbar-brand {
|
| 108 |
+
display: flex;
|
| 109 |
+
align-items: center;
|
| 110 |
+
gap: 10px;
|
| 111 |
+
font-weight: 600;
|
| 112 |
+
letter-spacing: 0.01em;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.topbar-logo {
|
| 116 |
+
display: inline-flex;
|
| 117 |
+
align-items: center;
|
| 118 |
+
justify-content: center;
|
| 119 |
+
width: 28px;
|
| 120 |
+
height: 28px;
|
| 121 |
+
border-radius: 6px;
|
| 122 |
+
background: var(--accent);
|
| 123 |
+
color: #111;
|
| 124 |
+
font-size: 18px;
|
| 125 |
+
font-weight: 700;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
.topbar-title {
|
| 129 |
+
display: flex;
|
| 130 |
+
flex-direction: column;
|
| 131 |
+
line-height: 1.15;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.topbar-title-main {
|
| 135 |
+
font-weight: 600;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.topbar-title-sub {
|
| 139 |
+
font-size: 11px;
|
| 140 |
+
color: var(--text-faint);
|
| 141 |
+
letter-spacing: 0.02em;
|
| 142 |
+
text-transform: uppercase;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.topbar-spacer {
|
| 146 |
+
flex: 1;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
.topbar-model {
|
| 150 |
+
display: flex;
|
| 151 |
+
align-items: center;
|
| 152 |
+
gap: 8px;
|
| 153 |
+
font-size: 12px;
|
| 154 |
+
color: var(--text-dim);
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
.topbar-model select {
|
| 158 |
+
background: var(--bg-elev-2);
|
| 159 |
+
border: 1px solid var(--border);
|
| 160 |
+
border-radius: var(--radius-sm);
|
| 161 |
+
padding: 4px 8px;
|
| 162 |
+
font-size: 12px;
|
| 163 |
+
cursor: pointer;
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
.topbar-user {
|
| 167 |
+
display: flex;
|
| 168 |
+
align-items: center;
|
| 169 |
+
gap: 8px;
|
| 170 |
+
padding: 4px 10px 4px 4px;
|
| 171 |
+
border: 1px solid var(--border);
|
| 172 |
+
border-radius: 999px;
|
| 173 |
+
background: var(--bg-elev-2);
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.topbar-user img,
|
| 177 |
+
.topbar-user .avatar-fallback {
|
| 178 |
+
width: 24px;
|
| 179 |
+
height: 24px;
|
| 180 |
+
border-radius: 50%;
|
| 181 |
+
object-fit: cover;
|
| 182 |
+
background: var(--bg-elev-3);
|
| 183 |
+
display: inline-flex;
|
| 184 |
+
align-items: center;
|
| 185 |
+
justify-content: center;
|
| 186 |
+
font-size: 11px;
|
| 187 |
+
font-weight: 600;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.topbar-user-name {
|
| 191 |
+
font-size: 12px;
|
| 192 |
+
color: var(--text);
|
| 193 |
+
max-width: 140px;
|
| 194 |
+
overflow: hidden;
|
| 195 |
+
text-overflow: ellipsis;
|
| 196 |
+
white-space: nowrap;
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
.topbar-user-menu {
|
| 200 |
+
margin-left: 4px;
|
| 201 |
+
color: var(--text-faint);
|
| 202 |
+
font-size: 11px;
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
.btn {
|
| 206 |
+
display: inline-flex;
|
| 207 |
+
align-items: center;
|
| 208 |
+
gap: 6px;
|
| 209 |
+
padding: 6px 12px;
|
| 210 |
+
border-radius: var(--radius-sm);
|
| 211 |
+
background: var(--bg-elev-2);
|
| 212 |
+
border: 1px solid var(--border);
|
| 213 |
+
color: var(--text);
|
| 214 |
+
font-size: 12px;
|
| 215 |
+
font-weight: 500;
|
| 216 |
+
transition: all 0.15s ease;
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
.btn:hover {
|
| 220 |
+
background: var(--bg-elev-3);
|
| 221 |
+
border-color: var(--border-strong);
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
.btn:disabled {
|
| 225 |
+
opacity: 0.5;
|
| 226 |
+
cursor: not-allowed;
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
.btn-primary {
|
| 230 |
+
background: var(--accent);
|
| 231 |
+
border-color: var(--accent);
|
| 232 |
+
color: #111;
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
.btn-primary:hover {
|
| 236 |
+
background: var(--accent-strong);
|
| 237 |
+
border-color: var(--accent-strong);
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.btn-ghost {
|
| 241 |
+
background: transparent;
|
| 242 |
+
border: 1px solid transparent;
|
| 243 |
+
color: var(--text-dim);
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
.btn-ghost:hover {
|
| 247 |
+
background: var(--bg-elev-2);
|
| 248 |
+
color: var(--text);
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
.btn-danger {
|
| 252 |
+
background: transparent;
|
| 253 |
+
border: 1px solid transparent;
|
| 254 |
+
color: var(--danger);
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
/* ── Sign-in screen ────────────────────────────────────────────────────── */
|
| 258 |
+
.signin-screen {
|
| 259 |
+
display: flex;
|
| 260 |
+
flex-direction: column;
|
| 261 |
+
align-items: center;
|
| 262 |
+
justify-content: center;
|
| 263 |
+
height: 100%;
|
| 264 |
+
text-align: center;
|
| 265 |
+
padding: 40px 24px;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
.signin-emoji {
|
| 269 |
+
font-size: 56px;
|
| 270 |
+
margin-bottom: 16px;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
.signin-title {
|
| 274 |
+
font-size: 28px;
|
| 275 |
+
font-weight: 700;
|
| 276 |
+
margin: 0 0 8px;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
.signin-subtitle {
|
| 280 |
+
font-size: 14px;
|
| 281 |
+
color: var(--text-dim);
|
| 282 |
+
max-width: 480px;
|
| 283 |
+
margin: 0 0 28px;
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
.signin-button {
|
| 287 |
+
display: inline-flex;
|
| 288 |
+
align-items: center;
|
| 289 |
+
gap: 10px;
|
| 290 |
+
padding: 12px 22px;
|
| 291 |
+
border-radius: 999px;
|
| 292 |
+
background: var(--accent);
|
| 293 |
+
color: #111;
|
| 294 |
+
font-weight: 600;
|
| 295 |
+
border: none;
|
| 296 |
+
font-size: 14px;
|
| 297 |
+
transition: all 0.15s ease;
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
.signin-button:hover {
|
| 301 |
+
background: var(--accent-strong);
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
.signin-hint {
|
| 305 |
+
margin-top: 16px;
|
| 306 |
+
font-size: 12px;
|
| 307 |
+
color: var(--text-faint);
|
| 308 |
+
max-width: 480px;
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
.signin-error {
|
| 312 |
+
margin-top: 20px;
|
| 313 |
+
padding: 10px 14px;
|
| 314 |
+
background: rgba(255, 92, 92, 0.1);
|
| 315 |
+
border: 1px solid rgba(255, 92, 92, 0.4);
|
| 316 |
+
border-radius: var(--radius-sm);
|
| 317 |
+
color: var(--danger);
|
| 318 |
+
font-size: 12px;
|
| 319 |
+
max-width: 520px;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
/* ── Chat panel ────────────────────────────────────────────────────────── */
|
| 323 |
+
.chat-panel {
|
| 324 |
+
display: flex;
|
| 325 |
+
flex-direction: column;
|
| 326 |
+
min-height: 0;
|
| 327 |
+
border-right: 1px solid var(--border);
|
| 328 |
+
background: var(--bg-elev-1);
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
.chat-messages {
|
| 332 |
+
flex: 1;
|
| 333 |
+
overflow-y: auto;
|
| 334 |
+
padding: 16px;
|
| 335 |
+
display: flex;
|
| 336 |
+
flex-direction: column;
|
| 337 |
+
gap: 14px;
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
.chat-empty {
|
| 341 |
+
padding: 20px;
|
| 342 |
+
color: var(--text-faint);
|
| 343 |
+
font-size: 13px;
|
| 344 |
+
line-height: 1.6;
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
.chat-empty-examples {
|
| 348 |
+
margin-top: 12px;
|
| 349 |
+
display: flex;
|
| 350 |
+
flex-direction: column;
|
| 351 |
+
gap: 6px;
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
.chat-empty-example {
|
| 355 |
+
text-align: left;
|
| 356 |
+
padding: 8px 12px;
|
| 357 |
+
background: var(--bg-elev-2);
|
| 358 |
+
border: 1px solid var(--border);
|
| 359 |
+
border-radius: var(--radius-sm);
|
| 360 |
+
color: var(--text-dim);
|
| 361 |
+
cursor: pointer;
|
| 362 |
+
font-size: 12px;
|
| 363 |
+
transition: all 0.15s ease;
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
.chat-empty-example:hover {
|
| 367 |
+
background: var(--bg-elev-3);
|
| 368 |
+
color: var(--text);
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
.bubble-user {
|
| 372 |
+
align-self: flex-end;
|
| 373 |
+
max-width: 85%;
|
| 374 |
+
padding: 10px 14px;
|
| 375 |
+
background: var(--user-bubble);
|
| 376 |
+
border: 1px solid var(--border);
|
| 377 |
+
border-radius: var(--radius-md);
|
| 378 |
+
white-space: pre-wrap;
|
| 379 |
+
word-wrap: break-word;
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
.bubble-agent {
|
| 383 |
+
align-self: stretch;
|
| 384 |
+
display: flex;
|
| 385 |
+
flex-direction: column;
|
| 386 |
+
gap: 8px;
|
| 387 |
+
color: var(--text);
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
.bubble-agent-text {
|
| 391 |
+
white-space: pre-wrap;
|
| 392 |
+
word-wrap: break-word;
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
.bubble-tool {
|
| 396 |
+
align-self: stretch;
|
| 397 |
+
padding: 6px 10px;
|
| 398 |
+
background: var(--bg-elev-2);
|
| 399 |
+
border: 1px solid var(--border);
|
| 400 |
+
border-radius: var(--radius-sm);
|
| 401 |
+
font-family: var(--font-mono);
|
| 402 |
+
font-size: 11px;
|
| 403 |
+
color: var(--text-dim);
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
.bubble-tool.status-done {
|
| 407 |
+
border-color: rgba(74, 222, 128, 0.3);
|
| 408 |
+
background: rgba(74, 222, 128, 0.05);
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
.bubble-tool.status-error {
|
| 412 |
+
border-color: rgba(255, 92, 92, 0.4);
|
| 413 |
+
background: rgba(255, 92, 92, 0.08);
|
| 414 |
+
color: var(--danger);
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
.bubble-tool-header {
|
| 418 |
+
display: flex;
|
| 419 |
+
align-items: center;
|
| 420 |
+
gap: 6px;
|
| 421 |
+
font-weight: 500;
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
.bubble-tool-detail {
|
| 425 |
+
margin-top: 4px;
|
| 426 |
+
color: var(--text-faint);
|
| 427 |
+
font-size: 10px;
|
| 428 |
+
white-space: pre-wrap;
|
| 429 |
+
word-break: break-all;
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
.chat-composer {
|
| 433 |
+
border-top: 1px solid var(--border);
|
| 434 |
+
padding: 12px 16px;
|
| 435 |
+
background: var(--bg-elev-1);
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
.chat-composer-field {
|
| 439 |
+
display: flex;
|
| 440 |
+
align-items: flex-end;
|
| 441 |
+
gap: 8px;
|
| 442 |
+
padding: 8px;
|
| 443 |
+
background: var(--bg-elev-2);
|
| 444 |
+
border: 1px solid var(--border);
|
| 445 |
+
border-radius: var(--radius-md);
|
| 446 |
+
transition: border-color 0.15s ease;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
.chat-composer-field:focus-within {
|
| 450 |
+
border-color: var(--accent);
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
.chat-composer textarea {
|
| 454 |
+
flex: 1;
|
| 455 |
+
min-height: 20px;
|
| 456 |
+
max-height: 160px;
|
| 457 |
+
resize: none;
|
| 458 |
+
background: transparent;
|
| 459 |
+
border: none;
|
| 460 |
+
outline: none;
|
| 461 |
+
padding: 4px 6px;
|
| 462 |
+
line-height: 1.4;
|
| 463 |
+
color: var(--text);
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
.chat-composer textarea::placeholder {
|
| 467 |
+
color: var(--text-faint);
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
.chat-send {
|
| 471 |
+
display: inline-flex;
|
| 472 |
+
align-items: center;
|
| 473 |
+
justify-content: center;
|
| 474 |
+
width: 32px;
|
| 475 |
+
height: 32px;
|
| 476 |
+
border-radius: var(--radius-sm);
|
| 477 |
+
background: var(--accent);
|
| 478 |
+
color: #111;
|
| 479 |
+
border: none;
|
| 480 |
+
font-size: 16px;
|
| 481 |
+
transition: all 0.15s ease;
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
.chat-send:hover:not(:disabled) {
|
| 485 |
+
background: var(--accent-strong);
|
| 486 |
+
}
|
| 487 |
+
|
| 488 |
+
.chat-send:disabled {
|
| 489 |
+
opacity: 0.4;
|
| 490 |
+
cursor: not-allowed;
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
.chat-status {
|
| 494 |
+
margin-top: 6px;
|
| 495 |
+
font-size: 11px;
|
| 496 |
+
color: var(--text-faint);
|
| 497 |
+
display: flex;
|
| 498 |
+
align-items: center;
|
| 499 |
+
gap: 8px;
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
/* ── Right pane ────────────────────────────────────────────────────────── */
|
| 503 |
+
.right-pane {
|
| 504 |
+
display: flex;
|
| 505 |
+
flex-direction: column;
|
| 506 |
+
min-height: 0;
|
| 507 |
+
background: var(--bg);
|
| 508 |
+
}
|
| 509 |
+
|
| 510 |
+
.tab-bar {
|
| 511 |
+
display: flex;
|
| 512 |
+
align-items: center;
|
| 513 |
+
gap: 4px;
|
| 514 |
+
padding: 0 8px;
|
| 515 |
+
height: 40px;
|
| 516 |
+
border-bottom: 1px solid var(--border);
|
| 517 |
+
background: var(--bg-elev-1);
|
| 518 |
+
}
|
| 519 |
+
|
| 520 |
+
.tab {
|
| 521 |
+
padding: 6px 14px;
|
| 522 |
+
color: var(--text-dim);
|
| 523 |
+
font-size: 12px;
|
| 524 |
+
font-weight: 500;
|
| 525 |
+
border-radius: var(--radius-sm);
|
| 526 |
+
border: 1px solid transparent;
|
| 527 |
+
cursor: pointer;
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
.tab.active {
|
| 531 |
+
color: var(--text);
|
| 532 |
+
background: var(--bg-elev-2);
|
| 533 |
+
border-color: var(--border);
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
.tab-spacer {
|
| 537 |
+
flex: 1;
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
.tab-actions {
|
| 541 |
+
display: flex;
|
| 542 |
+
align-items: center;
|
| 543 |
+
gap: 6px;
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
.pane-content {
|
| 547 |
+
flex: 1;
|
| 548 |
+
min-height: 0;
|
| 549 |
+
display: flex;
|
| 550 |
+
flex-direction: column;
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
.pane-content.hidden {
|
| 554 |
+
display: none;
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
/* ── Code editor ───────────────────────────────────────────────────────── */
|
| 558 |
+
.code-layout {
|
| 559 |
+
display: grid;
|
| 560 |
+
grid-template-columns: 220px 1fr;
|
| 561 |
+
height: 100%;
|
| 562 |
+
min-height: 0;
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
.file-tree {
|
| 566 |
+
border-right: 1px solid var(--border);
|
| 567 |
+
background: var(--bg-elev-1);
|
| 568 |
+
overflow-y: auto;
|
| 569 |
+
padding: 8px 0;
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
.file-tree-empty {
|
| 573 |
+
padding: 16px;
|
| 574 |
+
color: var(--text-faint);
|
| 575 |
+
font-size: 12px;
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
.file-tree-item {
|
| 579 |
+
display: flex;
|
| 580 |
+
align-items: center;
|
| 581 |
+
gap: 6px;
|
| 582 |
+
padding: 5px 12px;
|
| 583 |
+
font-family: var(--font-mono);
|
| 584 |
+
font-size: 12px;
|
| 585 |
+
color: var(--text-dim);
|
| 586 |
+
cursor: pointer;
|
| 587 |
+
border-left: 2px solid transparent;
|
| 588 |
+
}
|
| 589 |
+
|
| 590 |
+
.file-tree-item:hover {
|
| 591 |
+
background: var(--bg-elev-2);
|
| 592 |
+
color: var(--text);
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
.file-tree-item.active {
|
| 596 |
+
background: var(--bg-elev-2);
|
| 597 |
+
color: var(--text);
|
| 598 |
+
border-left-color: var(--accent);
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
.file-tree-size {
|
| 602 |
+
margin-left: auto;
|
| 603 |
+
color: var(--text-faint);
|
| 604 |
+
font-size: 10px;
|
| 605 |
+
}
|
| 606 |
+
|
| 607 |
+
.editor-area {
|
| 608 |
+
min-height: 0;
|
| 609 |
+
display: flex;
|
| 610 |
+
flex-direction: column;
|
| 611 |
+
}
|
| 612 |
+
|
| 613 |
+
.editor-placeholder {
|
| 614 |
+
margin: auto;
|
| 615 |
+
color: var(--text-faint);
|
| 616 |
+
font-size: 13px;
|
| 617 |
+
text-align: center;
|
| 618 |
+
padding: 40px;
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
/* ── Preview ───────────────────────────────────────────────────────────── */
|
| 622 |
+
.preview-frame {
|
| 623 |
+
flex: 1;
|
| 624 |
+
min-height: 0;
|
| 625 |
+
background: white;
|
| 626 |
+
border: none;
|
| 627 |
+
}
|
| 628 |
+
|
| 629 |
+
.preview-empty {
|
| 630 |
+
margin: auto;
|
| 631 |
+
color: var(--text-faint);
|
| 632 |
+
font-size: 13px;
|
| 633 |
+
text-align: center;
|
| 634 |
+
padding: 40px;
|
| 635 |
+
}
|
| 636 |
+
|
| 637 |
+
/* ── Dialog ────────────────────────────────────────────────────────────── */
|
| 638 |
+
.dialog-backdrop {
|
| 639 |
+
position: fixed;
|
| 640 |
+
inset: 0;
|
| 641 |
+
background: rgba(0, 0, 0, 0.6);
|
| 642 |
+
display: flex;
|
| 643 |
+
align-items: center;
|
| 644 |
+
justify-content: center;
|
| 645 |
+
z-index: 100;
|
| 646 |
+
padding: 24px;
|
| 647 |
+
}
|
| 648 |
+
|
| 649 |
+
.dialog {
|
| 650 |
+
width: 100%;
|
| 651 |
+
max-width: 480px;
|
| 652 |
+
background: var(--bg-elev-1);
|
| 653 |
+
border: 1px solid var(--border);
|
| 654 |
+
border-radius: var(--radius-lg);
|
| 655 |
+
box-shadow: var(--shadow-md);
|
| 656 |
+
overflow: hidden;
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
.dialog-header {
|
| 660 |
+
display: flex;
|
| 661 |
+
align-items: center;
|
| 662 |
+
justify-content: space-between;
|
| 663 |
+
padding: 14px 18px;
|
| 664 |
+
border-bottom: 1px solid var(--border);
|
| 665 |
+
font-weight: 600;
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
+
.dialog-body {
|
| 669 |
+
padding: 18px;
|
| 670 |
+
display: flex;
|
| 671 |
+
flex-direction: column;
|
| 672 |
+
gap: 14px;
|
| 673 |
+
}
|
| 674 |
+
|
| 675 |
+
.dialog-footer {
|
| 676 |
+
display: flex;
|
| 677 |
+
justify-content: flex-end;
|
| 678 |
+
gap: 8px;
|
| 679 |
+
padding: 14px 18px;
|
| 680 |
+
border-top: 1px solid var(--border);
|
| 681 |
+
background: var(--bg-elev-2);
|
| 682 |
+
}
|
| 683 |
+
|
| 684 |
+
.field {
|
| 685 |
+
display: flex;
|
| 686 |
+
flex-direction: column;
|
| 687 |
+
gap: 6px;
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
.field label {
|
| 691 |
+
font-size: 12px;
|
| 692 |
+
color: var(--text-dim);
|
| 693 |
+
font-weight: 500;
|
| 694 |
+
}
|
| 695 |
+
|
| 696 |
+
.field input[type="text"],
|
| 697 |
+
.field input[type="password"],
|
| 698 |
+
.field textarea {
|
| 699 |
+
padding: 8px 10px;
|
| 700 |
+
background: var(--bg-elev-2);
|
| 701 |
+
border: 1px solid var(--border);
|
| 702 |
+
border-radius: var(--radius-sm);
|
| 703 |
+
color: var(--text);
|
| 704 |
+
}
|
| 705 |
+
|
| 706 |
+
.field input:focus,
|
| 707 |
+
.field textarea:focus {
|
| 708 |
+
outline: none;
|
| 709 |
+
border-color: var(--accent);
|
| 710 |
+
}
|
| 711 |
+
|
| 712 |
+
.field-hint {
|
| 713 |
+
font-size: 11px;
|
| 714 |
+
color: var(--text-faint);
|
| 715 |
+
}
|
| 716 |
+
|
| 717 |
+
.field-row {
|
| 718 |
+
display: flex;
|
| 719 |
+
align-items: center;
|
| 720 |
+
gap: 8px;
|
| 721 |
+
}
|
| 722 |
+
|
| 723 |
+
.publish-log {
|
| 724 |
+
display: flex;
|
| 725 |
+
flex-direction: column;
|
| 726 |
+
gap: 6px;
|
| 727 |
+
padding: 12px;
|
| 728 |
+
background: var(--bg-elev-2);
|
| 729 |
+
border: 1px solid var(--border);
|
| 730 |
+
border-radius: var(--radius-sm);
|
| 731 |
+
font-family: var(--font-mono);
|
| 732 |
+
font-size: 11px;
|
| 733 |
+
max-height: 180px;
|
| 734 |
+
overflow-y: auto;
|
| 735 |
+
}
|
| 736 |
+
|
| 737 |
+
.publish-log-step {
|
| 738 |
+
color: var(--text-dim);
|
| 739 |
+
}
|
| 740 |
+
|
| 741 |
+
.publish-log-step.done {
|
| 742 |
+
color: var(--success);
|
| 743 |
+
}
|
| 744 |
+
|
| 745 |
+
.publish-log-step.error {
|
| 746 |
+
color: var(--danger);
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
+
.publish-success {
|
| 750 |
+
padding: 10px 12px;
|
| 751 |
+
background: rgba(74, 222, 128, 0.08);
|
| 752 |
+
border: 1px solid rgba(74, 222, 128, 0.3);
|
| 753 |
+
border-radius: var(--radius-sm);
|
| 754 |
+
color: var(--success);
|
| 755 |
+
font-size: 12px;
|
| 756 |
+
display: flex;
|
| 757 |
+
flex-direction: column;
|
| 758 |
+
gap: 4px;
|
| 759 |
+
}
|
| 760 |
+
|
| 761 |
+
.publish-success a {
|
| 762 |
+
color: inherit;
|
| 763 |
+
font-weight: 600;
|
| 764 |
+
}
|
| 765 |
+
|
| 766 |
+
.publish-error {
|
| 767 |
+
padding: 10px 12px;
|
| 768 |
+
background: rgba(255, 92, 92, 0.1);
|
| 769 |
+
border: 1px solid rgba(255, 92, 92, 0.3);
|
| 770 |
+
border-radius: var(--radius-sm);
|
| 771 |
+
color: var(--danger);
|
| 772 |
+
font-size: 12px;
|
| 773 |
+
}
|
| 774 |
+
|
| 775 |
+
/* ── Utility ───────────────────────────────────────────────────────────── */
|
| 776 |
+
.dot {
|
| 777 |
+
display: inline-block;
|
| 778 |
+
width: 6px;
|
| 779 |
+
height: 6px;
|
| 780 |
+
border-radius: 50%;
|
| 781 |
+
background: currentColor;
|
| 782 |
+
}
|
| 783 |
+
|
| 784 |
+
.spinner {
|
| 785 |
+
display: inline-block;
|
| 786 |
+
width: 12px;
|
| 787 |
+
height: 12px;
|
| 788 |
+
border: 2px solid var(--text-faint);
|
| 789 |
+
border-top-color: var(--accent);
|
| 790 |
+
border-radius: 50%;
|
| 791 |
+
animation: spin 0.8s linear infinite;
|
| 792 |
+
}
|
| 793 |
+
|
| 794 |
+
@keyframes spin {
|
| 795 |
+
to {
|
| 796 |
+
transform: rotate(360deg);
|
| 797 |
+
}
|
| 798 |
+
}
|
frontend/src/vite-env.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// <reference types="vite/client" />
|
| 2 |
+
|
| 3 |
+
interface ImportMetaEnv {
|
| 4 |
+
readonly VITE_HF_OAUTH_CLIENT_ID?: string;
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
interface ImportMeta {
|
| 8 |
+
readonly env: ImportMetaEnv;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
interface Window {
|
| 12 |
+
huggingface?: {
|
| 13 |
+
variables?: {
|
| 14 |
+
OAUTH_CLIENT_ID?: string;
|
| 15 |
+
OAUTH_SCOPES?: string;
|
| 16 |
+
SPACE_HOST?: string;
|
| 17 |
+
SPACE_ID?: string;
|
| 18 |
+
};
|
| 19 |
+
};
|
| 20 |
+
}
|
frontend/tsconfig.app.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2022",
|
| 4 |
+
"useDefineForClassFields": true,
|
| 5 |
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
| 6 |
+
"module": "ESNext",
|
| 7 |
+
"skipLibCheck": true,
|
| 8 |
+
"moduleResolution": "bundler",
|
| 9 |
+
"allowImportingTsExtensions": false,
|
| 10 |
+
"isolatedModules": true,
|
| 11 |
+
"moduleDetection": "force",
|
| 12 |
+
"noEmit": true,
|
| 13 |
+
"jsx": "react-jsx",
|
| 14 |
+
"strict": true,
|
| 15 |
+
"noUnusedLocals": false,
|
| 16 |
+
"noUnusedParameters": false,
|
| 17 |
+
"noFallthroughCasesInSwitch": true,
|
| 18 |
+
"resolveJsonModule": true,
|
| 19 |
+
"types": ["vite/client"]
|
| 20 |
+
},
|
| 21 |
+
"include": ["src"]
|
| 22 |
+
}
|
frontend/tsconfig.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"files": [],
|
| 3 |
+
"references": [
|
| 4 |
+
{ "path": "./tsconfig.app.json" },
|
| 5 |
+
{ "path": "./tsconfig.node.json" }
|
| 6 |
+
]
|
| 7 |
+
}
|
frontend/tsconfig.node.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2022",
|
| 4 |
+
"lib": ["ES2023"],
|
| 5 |
+
"module": "ESNext",
|
| 6 |
+
"skipLibCheck": true,
|
| 7 |
+
"moduleResolution": "bundler",
|
| 8 |
+
"allowImportingTsExtensions": true,
|
| 9 |
+
"isolatedModules": true,
|
| 10 |
+
"moduleDetection": "force",
|
| 11 |
+
"noEmit": true,
|
| 12 |
+
"strict": true
|
| 13 |
+
},
|
| 14 |
+
"include": ["vite.config.ts"]
|
| 15 |
+
}
|
frontend/vite.config.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from "vite";
|
| 2 |
+
import react from "@vitejs/plugin-react";
|
| 3 |
+
|
| 4 |
+
/**
|
| 5 |
+
* Dev: Vite serves the React app on :5173 and proxies `/api/*` to the
|
| 6 |
+
* Hono backend on :7860. In production the same Hono server serves the
|
| 7 |
+
* static build from `frontend/dist` (see `backend/src/server.ts`).
|
| 8 |
+
*/
|
| 9 |
+
export default defineConfig({
|
| 10 |
+
plugins: [react()],
|
| 11 |
+
server: {
|
| 12 |
+
port: 5173,
|
| 13 |
+
proxy: {
|
| 14 |
+
"/api": {
|
| 15 |
+
target: "http://localhost:7860",
|
| 16 |
+
changeOrigin: true,
|
| 17 |
+
},
|
| 18 |
+
},
|
| 19 |
+
},
|
| 20 |
+
build: {
|
| 21 |
+
outDir: "dist",
|
| 22 |
+
sourcemap: true,
|
| 23 |
+
},
|
| 24 |
+
});
|