mirror of
https://github.com/bytebot-ai/bytebot.git
synced 2026-09-01 15:30:04 +08:00
Merge branch 'development' of https://github.com/bytebot-ai/bytebot into development
This commit is contained in:
@@ -1 +0,0 @@
|
||||
20.18.2
|
||||
@@ -27,6 +27,19 @@ https://github.com/user-attachments/assets/32a76e83-ea3a-4d5e-b34b-3b57f3604948
|
||||
|
||||
https://github.com/user-attachments/assets/5f946df9-9161-4e7e-8262-9eda83ee7d22
|
||||
|
||||
## ☁️ Deploy on Railway (1-Click)
|
||||
|
||||
[](https://railway.com/deploy/bytebot?referralCode=L9lKXQ)
|
||||
|
||||
1. Click the Deploy Now button in the Bytebot Railway template.
|
||||
2. Paste your `ANTHROPIC_API_KEY` in the single required environment variable.
|
||||
3. Press **Deploy**. Railway will spin up the Desktop, Agent, UI and Postgres services using pre-built container images, connect them via [private networking](https://docs.railway.com/guides/private-networking) and expose only the UI publicly.
|
||||
4. In about two minutes your agent will be live at your project's public URL.
|
||||
|
||||
_For an in-depth guide see [here](https://docs.bytebot.ai/deployment/railway)._
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
title: "Deploying Bytebot on Railway"
|
||||
description: "Comprehensive guide to deploying the full Bytebot stack on Railway using the official 1-click template"
|
||||
---
|
||||
|
||||
> **TL;DR –** Click the button below, add your Anthropic key, and your personal Bytebot instance will be live in ~2 minutes.
|
||||
|
||||
[](https://railway.com/deploy/bytebot?referralCode=L9lKXQ)
|
||||
|
||||
---
|
||||
|
||||
## Why Railway?
|
||||
|
||||
Railway provides a zero-ops PaaS experience with private networking and per-service logs that perfectly fits Bytebot’s multi-container architecture. The official template wires every service together using the latest container images pushed to the `edge` branch.
|
||||
|
||||
---
|
||||
|
||||
## What Gets Deployed
|
||||
|
||||
| Service | Container Image (edge) | Port | Exposed? | Purpose |
|
||||
| ---------------- | -------------------------------------------------------------------- | ---- | -------- | ------------------------------------ |
|
||||
| **bytebot-ui** | `ghcr.io/bytebot-ai/bytebot-ui:edge` | 9992 | **Yes** | Next.js web UI rendered to the world |
|
||||
| **bytebot-agent**| `ghcr.io/bytebot-ai/bytebot-agent:edge` | 9991 | No | Task orchestration & LLM calls |
|
||||
| **bytebot-desktop**| `ghcr.io/bytebot-ai/bytebot-desktop:edge` | 9990 | No | Containerised Ubuntu + XFCE desktop |
|
||||
| **postgres** | `postgres:14-alpine` | 5432 | No | Persistence layer |
|
||||
|
||||
All internal traffic flows through Railway’s [private networking](https://docs.railway.com/guides/private-networking). Only `bytebot-ui` is assigned a public domain.
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step Walk-through
|
||||
|
||||
<Steps>
|
||||
<Step title="1. Open the Template">
|
||||
Click the **Deploy on Railway** button above or visit [https://railway.com/deploy/bytebot?referralCode=L9lKXQ](https://railway.com/deploy/bytebot?referralCode=L9lKXQ).
|
||||
</Step>
|
||||
<Step title="2. Configure Environment">
|
||||
Railway automatically detects required variables. Paste your **Anthropic API key** into `ANTHROPIC_API_KEY` and keep other defaults.
|
||||
</Step>
|
||||
<Step title="3. Kick off the Deployment">
|
||||
Press **Deploy**. Railway will pull the pre-built images, create the Postgres database and link all services on a private network.
|
||||
</Step>
|
||||
<Step title="4. Launch Bytebot">
|
||||
When the build logs show *"bytebot-ui: ready"*, click the generated URL (e.g. `https://bytebot-ui-prod.up.railway.app`). You should see the task interface. Create a task and watch the desktop stream!
|
||||
_Tip: You can tail logs for each service from the Railway dashboard._
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Note>
|
||||
The first deploy downloads several container layers – expect ~2 minutes. Subsequent redeploys are much faster.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Private Networking & Security
|
||||
|
||||
• **Private networking** ensures that the agent, desktop and database can communicate securely without exposing their ports to the internet.
|
||||
• **Public exposure** is limited to the UI which serves static assets and proxies WebSocket traffic.
|
||||
• **Add authentication** by placing the UI behind Railway’s built-in password protection or an external provider (e.g. Cloudflare Access, Auth0, OAuth proxy).
|
||||
• You can also point a custom domain to the UI from the Railway dashboard and enable Cloudflare for WAF/CDN protection.
|
||||
|
||||
---
|
||||
|
||||
## Customisation & Scaling
|
||||
|
||||
1. **Change images** – Fork the repo, push your own images and edit the template’s `Dockerfile` references.
|
||||
2. **Increase resources** – Each service has an independent CPU/RAM slider in Railway. Bump up the desktop or agent if you plan heavy automations.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Likely Cause | Fix |
|
||||
| ------- | ------------ | ---- |
|
||||
| Web UI shows “connecting…” | Desktop not ready or private networking mis-config | Wait for `bytebot-desktop` container to finish starting, or restart service |
|
||||
| Agent errors `401 Anthropic` | Missing/invalid API key | Re-enter `ANTHROPIC_API_KEY` in Railway variables |
|
||||
| Slow desktop video | Free Railway plan throttling | Upgrade plan or reduce screen resolution in desktop settings |
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
• Explore the [REST APIs](/api-reference/introduction) to script tasks programmatically.
|
||||
• Join our [Discord](https://discord.com/invite/zcb5wA2t4u) community for support and showcase your automations!
|
||||
@@ -17,6 +17,10 @@
|
||||
"group": "Getting Started",
|
||||
"pages": ["introduction", "quickstart"]
|
||||
},
|
||||
{
|
||||
"group": "Deployment",
|
||||
"pages": ["deployment/railway"]
|
||||
},
|
||||
{
|
||||
"group": "Core Concepts",
|
||||
"pages": [
|
||||
|
||||
@@ -95,11 +95,11 @@ Bytebot consists of four integrated components working together:
|
||||
Ubuntu 22.04 with XFCE4, pre-installed apps, and the automation daemon
|
||||
</Card>
|
||||
<Card title="AI Agent" icon="brain" href="/core-concepts/agent-system">
|
||||
NestJS service that uses Claude AI to plan and execute tasks
|
||||
NestJS service that uses an LLM to plan and execute tasks
|
||||
</Card>
|
||||
<Card
|
||||
title="Task Interface"
|
||||
icon="comments"
|
||||
icon="window"
|
||||
href="/core-concepts/agent-system#task-ui"
|
||||
>
|
||||
Next.js web app for creating and managing tasks
|
||||
|
||||
@@ -3,6 +3,32 @@ title: "Quick Start"
|
||||
description: "Get your AI desktop agent running in 2 minutes"
|
||||
---
|
||||
|
||||
# Host Bytebot on Railway
|
||||
|
||||
## ☁️ One-click Deploy on Railway
|
||||
|
||||
[](https://railway.com/deploy/bytebot?referralCode=L9lKXQ)
|
||||
|
||||
<Steps>
|
||||
<Step title="Visit the Template">
|
||||
Click the Deploy Now button in the Bytebot template on Railway.
|
||||
</Step>
|
||||
<Step title="Add Anthropic Key">
|
||||
Enter your `ANTHROPIC_API_KEY` as the single required environment variable.
|
||||
</Step>
|
||||
<Step title="Deploy & Launch">
|
||||
Hit **Deploy**. Railway will build the stack, wire the services together via private networking and output a public URL for the UI. Your agent should be ready within a couple of minutes!
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Note>
|
||||
Need more details? See the full <a href="/deployment/railway">Railway deployment guide</a>.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
# Self-host Bytebot
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker ≥ 20.10
|
||||
|
||||
@@ -16,7 +16,7 @@ ENV DISPLAY=:0
|
||||
# -----------------------------------------------------------------------------
|
||||
# 2. System dependencies installation
|
||||
# -----------------------------------------------------------------------------
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
RUN apt-get update && apt-get install -y \
|
||||
# X11 / VNC
|
||||
xvfb x11vnc xauth x11-xserver-utils \
|
||||
x11-apps sudo software-properties-common \
|
||||
@@ -45,7 +45,7 @@ RUN mkdir -p /run/dbus && \
|
||||
# 3. Additional software installation
|
||||
# -----------------------------------------------------------------------------
|
||||
# Install Firefox
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
RUN apt-get update && apt-get install -y \
|
||||
# Install necessary for adding PPA
|
||||
software-properties-common apt-transport-https wget gnupg \
|
||||
# Install Additional Graphics Libraries
|
||||
@@ -61,7 +61,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
fonts-freefont-ttf \
|
||||
&& add-apt-repository -y ppa:mozillateam/ppa \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends firefox-esr thunderbird \
|
||||
&& apt-get install -y firefox-esr thunderbird \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
# Set Firefox as default browser system-wide
|
||||
&& update-alternatives --install /usr/bin/x-www-browser x-www-browser /usr/bin/firefox-esr 200 \
|
||||
@@ -136,11 +136,11 @@ RUN ARCH=$(dpkg --print-architecture) && \
|
||||
mkdir -p /usr/share/debsig/keyrings/AC2D62742012EA22 && \
|
||||
curl -sS https://downloads.1password.com/linux/keys/1password.asc | \
|
||||
gpg --dearmor --output /usr/share/debsig/keyrings/AC2D62742012EA22/debsig.gpg && \
|
||||
apt-get update && apt-get install -y --no-install-recommends 1password && \
|
||||
apt-get update && apt-get install -y 1password && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*; \
|
||||
elif [ "$ARCH" = "arm64" ]; then \
|
||||
# Install from tar.gz for ARM64
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
apt-get update && apt-get install -y \
|
||||
libgtk-3-0 libnotify4 libnss3 libxss1 libxtst6 xdg-utils \
|
||||
libatspi2.0-0 libdrm2 libgbm1 libxcb-dri3-0 libxkbcommon0 \
|
||||
libsecret-1-0 && \
|
||||
@@ -183,7 +183,7 @@ RUN ARCH=$(dpkg --print-architecture) && \
|
||||
# Install Node.js
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& apt-get install -y nodejs \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Upgrade pip
|
||||
@@ -205,7 +205,7 @@ RUN git clone https://github.com/novnc/noVNC.git /opt/noVNC \
|
||||
|
||||
# Install dependencies required to build libnut-core and uiohook-napi
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
apt-get install -y \
|
||||
cmake \
|
||||
libx11-dev \
|
||||
libxtst-dev \
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
DATABASE_URL=postgresql://postgres:postgres@postgres:5432/bytebotdb
|
||||
ANTHROPIC_API_KEY=
|
||||
BYTEBOT_DESKTOP_BASE_URL=http://localhost:9990
|
||||
BETTER_AUTH_SECRET=
|
||||
BETTER_AUTH_URL=http://localhost:9991
|
||||
AUTH_ENABLED=true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Base image
|
||||
FROM node:20.18.2-alpine3.21
|
||||
FROM node:20-alpine
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /app
|
||||
|
||||
Generated
+249
-1
@@ -20,6 +20,8 @@
|
||||
"@nestjs/schedule": "^6.0.0",
|
||||
"@nestjs/websockets": "^11.1.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"@thallesp/nestjs-better-auth": "^1.0.0",
|
||||
"better-auth": "^1.2.9",
|
||||
"class-validator": "^0.14.2",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
@@ -783,6 +785,20 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@better-auth/utils": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.2.5.tgz",
|
||||
"integrity": "sha512-uI2+/8h/zVsH8RrYdG8eUErbuGBk16rZKQfz8CjxQOyCE6v7BqFYEbFwvOkvl1KbUdxhqOnXp78+uE5h8qVEgQ==",
|
||||
"dependencies": {
|
||||
"typescript": "^5.8.2",
|
||||
"uncrypto": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@better-fetch/fetch": {
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.18.tgz",
|
||||
"integrity": "sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA=="
|
||||
},
|
||||
"node_modules/@bytebot/shared": {
|
||||
"resolved": "../shared",
|
||||
"link": true
|
||||
@@ -986,6 +1002,11 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@hexagon/base64": {
|
||||
"version": "1.1.28",
|
||||
"resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz",
|
||||
"integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw=="
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@@ -1985,6 +2006,11 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@levischuck/tiny-cbor": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz",
|
||||
"integrity": "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow=="
|
||||
},
|
||||
"node_modules/@lukeed/csprng": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz",
|
||||
@@ -2746,6 +2772,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/ciphers": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.6.0.tgz",
|
||||
"integrity": "sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -2800,6 +2845,59 @@
|
||||
"npm": ">=5.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@peculiar/asn1-android": {
|
||||
"version": "2.3.16",
|
||||
"resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.3.16.tgz",
|
||||
"integrity": "sha512-a1viIv3bIahXNssrOIkXZIlI2ePpZaNmR30d4aBL99mu2rO+mT9D6zBsp7H6eROWGtmwv0Ionp5olJurIo09dw==",
|
||||
"dependencies": {
|
||||
"@peculiar/asn1-schema": "^2.3.15",
|
||||
"asn1js": "^3.0.5",
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@peculiar/asn1-ecc": {
|
||||
"version": "2.3.15",
|
||||
"resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.3.15.tgz",
|
||||
"integrity": "sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==",
|
||||
"dependencies": {
|
||||
"@peculiar/asn1-schema": "^2.3.15",
|
||||
"@peculiar/asn1-x509": "^2.3.15",
|
||||
"asn1js": "^3.0.5",
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@peculiar/asn1-rsa": {
|
||||
"version": "2.3.15",
|
||||
"resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.3.15.tgz",
|
||||
"integrity": "sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==",
|
||||
"dependencies": {
|
||||
"@peculiar/asn1-schema": "^2.3.15",
|
||||
"@peculiar/asn1-x509": "^2.3.15",
|
||||
"asn1js": "^3.0.5",
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@peculiar/asn1-schema": {
|
||||
"version": "2.3.15",
|
||||
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.15.tgz",
|
||||
"integrity": "sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==",
|
||||
"dependencies": {
|
||||
"asn1js": "^3.0.5",
|
||||
"pvtsutils": "^1.3.6",
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@peculiar/asn1-x509": {
|
||||
"version": "2.3.15",
|
||||
"resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.3.15.tgz",
|
||||
"integrity": "sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==",
|
||||
"dependencies": {
|
||||
"@peculiar/asn1-schema": "^2.3.15",
|
||||
"asn1js": "^3.0.5",
|
||||
"pvtsutils": "^1.3.6",
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgr/core": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.2.tgz",
|
||||
@@ -2842,6 +2940,28 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@simplewebauthn/browser": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.1.0.tgz",
|
||||
"integrity": "sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg=="
|
||||
},
|
||||
"node_modules/@simplewebauthn/server": {
|
||||
"version": "13.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-13.1.1.tgz",
|
||||
"integrity": "sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==",
|
||||
"dependencies": {
|
||||
"@hexagon/base64": "^1.1.27",
|
||||
"@levischuck/tiny-cbor": "^0.2.2",
|
||||
"@peculiar/asn1-android": "^2.3.10",
|
||||
"@peculiar/asn1-ecc": "^2.3.8",
|
||||
"@peculiar/asn1-rsa": "^2.3.8",
|
||||
"@peculiar/asn1-schema": "^2.3.8",
|
||||
"@peculiar/asn1-x509": "^2.3.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinclair/typebox": {
|
||||
"version": "0.27.8",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||
@@ -3197,6 +3317,18 @@
|
||||
"node": ">=14.16"
|
||||
}
|
||||
},
|
||||
"node_modules/@thallesp/nestjs-better-auth": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@thallesp/nestjs-better-auth/-/nestjs-better-auth-1.0.0.tgz",
|
||||
"integrity": "sha512-fOu1z8LLyPyWOATm4g0ylz8miiCKxyAoWIssx/bdCvJKmUQXPltSHA/ne1MveD5DBbUI0aO24gdF0FnEsCR2Zw==",
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^11.0.10",
|
||||
"@nestjs/core": "^11.0.10",
|
||||
"better-auth": "^1.2.8",
|
||||
"express": "^5.0.0",
|
||||
"typescript": "^5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/inflate": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
|
||||
@@ -4898,6 +5030,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asn1js": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz",
|
||||
"integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==",
|
||||
"dependencies": {
|
||||
"pvtsutils": "^1.3.6",
|
||||
"pvutils": "^1.1.3",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
@@ -5088,6 +5233,36 @@
|
||||
"node": "^4.5.0 || >= 5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/better-auth": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.2.9.tgz",
|
||||
"integrity": "sha512-WLqBXDzuaCQetQctLGC5oTfGmL32zUvxnM4Y+LZkhwseMaZWq5EKI+c/ZATgz2YkFt7726q659PF8CfB9P1VuA==",
|
||||
"dependencies": {
|
||||
"@better-auth/utils": "0.2.5",
|
||||
"@better-fetch/fetch": "^1.1.18",
|
||||
"@noble/ciphers": "^0.6.0",
|
||||
"@noble/hashes": "^1.6.1",
|
||||
"@simplewebauthn/browser": "^13.0.0",
|
||||
"@simplewebauthn/server": "^13.0.0",
|
||||
"better-call": "^1.0.8",
|
||||
"defu": "^6.1.4",
|
||||
"jose": "^5.9.6",
|
||||
"kysely": "^0.28.2",
|
||||
"nanostores": "^0.11.3",
|
||||
"zod": "^3.24.1"
|
||||
}
|
||||
},
|
||||
"node_modules/better-call": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/better-call/-/better-call-1.0.9.tgz",
|
||||
"integrity": "sha512-Qfm0gjk0XQz0oI7qvTK1hbqTsBY4xV2hsHAxF8LZfUYl3RaECCIifXuVqtPpZJWvlCCMlQSvkvhhyuApGUba6g==",
|
||||
"dependencies": {
|
||||
"@better-fetch/fetch": "^1.1.4",
|
||||
"rou3": "^0.5.1",
|
||||
"set-cookie-parser": "^2.7.1",
|
||||
"uncrypto": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/bin-version": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz",
|
||||
@@ -5984,6 +6159,11 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/defu": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@@ -8656,6 +8836,14 @@
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
|
||||
"integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -8780,6 +8968,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/kysely": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.2.tgz",
|
||||
"integrity": "sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A==",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/leven": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
||||
@@ -9230,6 +9426,20 @@
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nanostores": {
|
||||
"version": "0.11.4",
|
||||
"resolved": "https://registry.npmjs.org/nanostores/-/nanostores-0.11.4.tgz",
|
||||
"integrity": "sha512-k1oiVNN4hDK8NcNERSZLQiMfRzEGtfnvZvdBvey3SQbgn8Dcrk0h1I6vpxApjb10PFUflZrgJ2WEZyJQ+5v7YQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/natural-compare": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
@@ -9944,6 +10154,22 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pvtsutils": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz",
|
||||
"integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/pvutils": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz",
|
||||
"integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
@@ -10217,6 +10443,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rou3": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/rou3/-/rou3-0.5.1.tgz",
|
||||
"integrity": "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="
|
||||
},
|
||||
"node_modules/router": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||
@@ -10424,6 +10655,11 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
@@ -11715,7 +11951,6 @@
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -11783,6 +12018,11 @@
|
||||
"through": "^2.3.8"
|
||||
}
|
||||
},
|
||||
"node_modules/uncrypto": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
|
||||
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
@@ -12431,6 +12671,14 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.67",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz",
|
||||
"integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,49 +22,51 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "0.39.0",
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@bytebot/shared": "../shared",
|
||||
"@nestjs/common": "11.0.1",
|
||||
"@nestjs/config": "4.0.2",
|
||||
"@nestjs/core": "11.0.1",
|
||||
"@nestjs/platform-express": "11.1.3",
|
||||
"@nestjs/event-emitter": "3.0.1",
|
||||
"@nestjs/platform-socket.io": "11.1.1",
|
||||
"@nestjs/schedule": "6.0.0",
|
||||
"@nestjs/websockets": "11.1.1",
|
||||
"@prisma/client": "6.6.0",
|
||||
"class-validator": "0.14.2",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rxjs": "7.8.1",
|
||||
"socket.io": "4.8.1",
|
||||
"socket.io-client": "4.8.1"
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/event-emitter": "^3.0.1",
|
||||
"@nestjs/platform-express": "^11.1.3",
|
||||
"@nestjs/platform-socket.io": "^11.1.1",
|
||||
"@nestjs/schedule": "^6.0.0",
|
||||
"@nestjs/websockets": "^11.1.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"@thallesp/nestjs-better-auth": "^1.0.0",
|
||||
"better-auth": "^1.2.9",
|
||||
"class-validator": "^0.14.2",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-client": "^4.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "3.2.0",
|
||||
"@eslint/js": "9.18.0",
|
||||
"@nestjs/cli": "11.0.0",
|
||||
"@nestjs/schematics": "11.0.0",
|
||||
"@nestjs/testing": "11.0.1",
|
||||
"@swc/cli": "0.6.0",
|
||||
"@swc/core": "1.10.7",
|
||||
"@types/express": "5.0.0",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/node": "22.10.7",
|
||||
"@types/supertest": "6.0.2",
|
||||
"eslint": "9.18.0",
|
||||
"eslint-config-prettier": "10.0.1",
|
||||
"eslint-plugin-prettier": "5.2.2",
|
||||
"globals": "15.14.0",
|
||||
"jest": "29.7.0",
|
||||
"prettier": "3.4.2",
|
||||
"source-map-support": "0.5.21",
|
||||
"supertest": "7.0.0",
|
||||
"ts-jest": "29.2.5",
|
||||
"ts-loader": "9.5.2",
|
||||
"ts-node": "10.9.2",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.7.3",
|
||||
"typescript-eslint": "8.20.0"
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@nestjs/cli": "^11.0.0",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@nestjs/testing": "^11.0.1",
|
||||
"@swc/cli": "^0.6.0",
|
||||
"@swc/core": "^1.10.7",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.2",
|
||||
"globals": "^15.14.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.4.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
"ts-loader": "^9.5.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.20.0"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Message" ADD COLUMN "userId" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"email" TEXT NOT NULL,
|
||||
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"image" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"ipAddress" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Account" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"accountId" TEXT NOT NULL,
|
||||
"providerId" TEXT NOT NULL,
|
||||
"accessToken" TEXT,
|
||||
"refreshToken" TEXT,
|
||||
"accessTokenExpiresAt" TIMESTAMP(3),
|
||||
"refreshTokenExpiresAt" TIMESTAMP(3),
|
||||
"scope" TEXT,
|
||||
"idToken" TEXT,
|
||||
"password" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Verification" (
|
||||
"id" TEXT NOT NULL,
|
||||
"identifier" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Verification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Session_token_key" ON "Session"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Account_providerId_accountId_key" ON "Account"("providerId", "accountId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Verification_identifier_value_key" ON "Verification"("identifier", "value");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Message" ADD CONSTRAINT "Message_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Task" ADD COLUMN "userId" TEXT;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Task" ADD CONSTRAINT "Task_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -58,6 +58,8 @@ model Task {
|
||||
result Json?
|
||||
messages Message[]
|
||||
summaries Summary[]
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId String? // Optional foreign key to User for task ownership
|
||||
}
|
||||
|
||||
model Summary {
|
||||
@@ -92,4 +94,69 @@ model Message {
|
||||
taskId String
|
||||
summary Summary? @relation(fields: [summaryId], references: [id])
|
||||
summaryId String? // Optional foreign key to Summary
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId String? // Optional foreign key to User for message ownership
|
||||
}
|
||||
|
||||
// Better Auth Models
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
name String?
|
||||
email String @unique
|
||||
emailVerified Boolean @default(false)
|
||||
image String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
sessions Session[]
|
||||
accounts Account[]
|
||||
messages Message[]
|
||||
tasks Task[]
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
token String @unique
|
||||
expiresAt DateTime
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model Account {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
accountId String
|
||||
providerId String
|
||||
accessToken String?
|
||||
refreshToken String?
|
||||
accessTokenExpiresAt DateTime?
|
||||
refreshTokenExpiresAt DateTime?
|
||||
scope String?
|
||||
idToken String?
|
||||
password String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([providerId, accountId])
|
||||
}
|
||||
|
||||
model Verification {
|
||||
id String @id @default(uuid())
|
||||
identifier String
|
||||
value String
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([identifier, value])
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export class InputCaptureService {
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket = io(baseUrl, { transports: ['websocket'], secure: true });
|
||||
this.socket = io(baseUrl, { transports: ['websocket'] });
|
||||
|
||||
this.socket.on('connect', () => {
|
||||
this.logger.log('Input socket connected');
|
||||
|
||||
@@ -9,9 +9,16 @@ import { PrismaModule } from './prisma/prisma.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||
import { AuthModule } from '@thallesp/nestjs-better-auth';
|
||||
import { auth } from './auth';
|
||||
|
||||
const authEnabled = process.env.AUTH_ENABLED === 'true';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
...(authEnabled ? [AuthModule.forRoot(auth, {
|
||||
disableTrustedOriginsCors: false,
|
||||
})] : []),
|
||||
ScheduleModule.forRoot(),
|
||||
EventEmitterModule.forRoot(),
|
||||
ConfigModule.forRoot({
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { betterAuth } from 'better-auth';
|
||||
import { prismaAdapter } from 'better-auth/adapters/prisma';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export const auth = process.env.AUTH_ENABLED === 'true' ? betterAuth({
|
||||
database: prismaAdapter(prisma, {
|
||||
provider: 'postgresql',
|
||||
}),
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
trustedOrigins: [
|
||||
'http://localhost:9992',
|
||||
'http://localhost:9991',
|
||||
],
|
||||
}) : null;
|
||||
|
||||
// @ts-ignore
|
||||
export type Session = typeof auth extends null ? null : typeof auth.$Infer.Session;
|
||||
// @ts-ignore
|
||||
export type User = typeof auth extends null ? null : typeof auth.$Infer.Session.user;
|
||||
@@ -11,13 +11,17 @@ async function bootstrap() {
|
||||
console.log('Starting bytebot-agent application...');
|
||||
|
||||
try {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
bodyParser: false,
|
||||
});
|
||||
|
||||
// Set global prefix for all routes
|
||||
app.setGlobalPrefix('api');
|
||||
|
||||
// Enable CORS
|
||||
app.enableCors({
|
||||
origin: '*',
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
|
||||
});
|
||||
|
||||
await app.listen(process.env.PORT ?? 9991);
|
||||
|
||||
@@ -21,4 +21,8 @@ export class CreateTaskDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
createdBy?: Role;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import {
|
||||
Delete,
|
||||
HttpStatus,
|
||||
HttpCode,
|
||||
UseGuards,
|
||||
Query,
|
||||
Req,
|
||||
} from '@nestjs/common';
|
||||
import { TasksService } from './tasks.service';
|
||||
import { CreateTaskDto } from './dto/create-task.dto';
|
||||
@@ -16,8 +18,13 @@ import { UpdateTaskDto } from './dto/update-task.dto';
|
||||
import { Message, Task } from '@prisma/client';
|
||||
import { GuideTaskDto } from './dto/guide-task.dto';
|
||||
import { MessagesService } from 'src/messages/messages.service';
|
||||
import { AuthGuard } from '@thallesp/nestjs-better-auth';
|
||||
import { Request } from 'express';
|
||||
|
||||
const authEnabled = process.env.AUTH_ENABLED === 'true';
|
||||
|
||||
@Controller('tasks')
|
||||
@UseGuards(...(authEnabled ? [AuthGuard] : []))
|
||||
export class TasksController {
|
||||
constructor(
|
||||
private readonly tasksService: TasksService,
|
||||
@@ -26,7 +33,14 @@ export class TasksController {
|
||||
|
||||
@Post()
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
async create(@Body() createTaskDto: CreateTaskDto): Promise<Task> {
|
||||
async create(
|
||||
@Body() createTaskDto: CreateTaskDto,
|
||||
@Req() req: Request,
|
||||
): Promise<Task> {
|
||||
// Add user ID if auth is enabled and user is authenticated
|
||||
if (authEnabled && (req as any).user?.id) {
|
||||
createTaskDto.userId = (req as any).user.id;
|
||||
}
|
||||
return this.tasksService.create(createTaskDto);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ export class TasksService {
|
||||
priority: createTaskDto.priority || TaskPriority.MEDIUM,
|
||||
status: TaskStatus.PENDING,
|
||||
createdBy: createTaskDto.createdBy || Role.USER,
|
||||
...(createTaskDto.userId ? { userId: createTaskDto.userId } : {}),
|
||||
...(createTaskDto.scheduledFor
|
||||
? { scheduledFor: createTaskDto.scheduledFor }
|
||||
: {}),
|
||||
@@ -123,6 +124,15 @@ export class TasksService {
|
||||
this.logger.log('Retrieving all tasks');
|
||||
|
||||
const tasks = await this.prisma.task.findMany({
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
@@ -140,6 +150,15 @@ export class TasksService {
|
||||
try {
|
||||
const task = await this.prisma.task.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!task) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
BYTEBOT_AGENT_BASE_URL=http://localhost:9991
|
||||
BYTEBOT_DESKTOP_VNC_URL=ws://localhost:9990/websockify
|
||||
NEXT_PUBLIC_API_URL=http://localhost:9991
|
||||
NEXT_PUBLIC_AUTH_ENABLED=true
|
||||
@@ -1,5 +1,5 @@
|
||||
# Base image
|
||||
FROM node:20.18.2-alpine3.21
|
||||
FROM node:20-alpine
|
||||
|
||||
# Declare build arguments
|
||||
ARG BYTEBOT_AGENT_BASE_URL
|
||||
|
||||
Generated
+769
-70
File diff suppressed because it is too large
Load Diff
@@ -9,53 +9,56 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "0.39.0",
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@bytebot/shared": "../shared",
|
||||
"@hugeicons/core-free-icons": "1.0.14",
|
||||
"@hugeicons/react": "1.0.5",
|
||||
"@prisma/client": "6.5.0",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.6",
|
||||
"@radix-ui/react-popover": "1.1.11",
|
||||
"@radix-ui/react-scroll-area": "1.2.3",
|
||||
"@radix-ui/react-select": "2.2.2",
|
||||
"@radix-ui/react-separator": "1.1.2",
|
||||
"@radix-ui/react-slot": "1.1.2",
|
||||
"@radix-ui/react-switch": "1.1.3",
|
||||
"@types/express": "5.0.1",
|
||||
"@types/http-proxy": "1.17.16",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clsx": "2.1.1",
|
||||
"date-fns": "4.1.0",
|
||||
"dotenv": "16.4.7",
|
||||
"express": "4.21.2",
|
||||
"http-proxy": "1.18.1",
|
||||
"http-proxy-middleware": "3.0.5",
|
||||
"motion": "12.12.1",
|
||||
"next": "15.2.4",
|
||||
"next-themes": "0.4.6",
|
||||
"next-transpile-modules": "10.0.1",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-vnc": "3.1.0",
|
||||
"socket.io-client": "4.8.1",
|
||||
"tailwind-merge": "3.0.2",
|
||||
"tsx": "4.19.3",
|
||||
"tw-animate-css": "1.2.4",
|
||||
"zod": "3.24.2"
|
||||
"@hugeicons/core-free-icons": "^1.0.14",
|
||||
"@hugeicons/react": "^1.0.5",
|
||||
"@prisma/client": "^6.5.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-popover": "^1.1.11",
|
||||
"@radix-ui/react-scroll-area": "^1.2.3",
|
||||
"@radix-ui/react-select": "^2.2.2",
|
||||
"@radix-ui/react-separator": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.1.3",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/http-proxy": "^1.17.16",
|
||||
"better-auth": "^1.2.9",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"http-proxy": "^1.18.1",
|
||||
"http-proxy-middleware": "^3.0.5",
|
||||
"lucide-react": "^0.517.0",
|
||||
"motion": "^12.12.1",
|
||||
"next": ">=15.2.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"next-transpile-modules": "^10.0.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-vnc": "^3.1.0",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tsx": "^4.19.3",
|
||||
"tw-animate-css": "^1.2.4",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "3.2.0",
|
||||
"@tailwindcss/postcss": "4.1.3",
|
||||
"@types/node": "20.17.27",
|
||||
"@types/react": "19.0.7",
|
||||
"@types/react-dom": "19.0.3",
|
||||
"eslint": "9.18.0",
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4.1.3",
|
||||
"@types/node": "^20.17.27",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.2.3",
|
||||
"prettier": "3.5.3",
|
||||
"prettier-plugin-tailwindcss": "0.6.11",
|
||||
"prisma": "6.5.0",
|
||||
"tailwindcss": "4.1.4",
|
||||
"typescript": "5.7.3"
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"prisma": "^6.5.0",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,17 @@ import { NextRequest } from "next/server";
|
||||
async function proxy(req: NextRequest, path: string[]): Promise<Response> {
|
||||
const BASE_URL = process.env.BYTEBOT_AGENT_BASE_URL!;
|
||||
const subPath = path.length ? `/${path.join("/")}` : "";
|
||||
const url = `${BASE_URL}${subPath}${req.nextUrl.search}`;
|
||||
const url = `${BASE_URL}/api${subPath}${req.nextUrl.search}`;
|
||||
|
||||
// Extract cookies from the incoming request
|
||||
const cookies = req.headers.get('cookie');
|
||||
|
||||
const init: RequestInit = {
|
||||
method: req.method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(cookies && { "Cookie": cookies })
|
||||
},
|
||||
body:
|
||||
req.method === "GET" || req.method === "HEAD"
|
||||
? undefined
|
||||
@@ -20,9 +26,22 @@ async function proxy(req: NextRequest, path: string[]): Promise<Response> {
|
||||
const res = await fetch(url, init);
|
||||
const body = await res.text();
|
||||
|
||||
// Extract Set-Cookie headers from the backend response
|
||||
const setCookieHeaders = res.headers.getSetCookie?.() || [];
|
||||
|
||||
// Create response headers
|
||||
const responseHeaders = new Headers({
|
||||
"Content-Type": "application/json"
|
||||
});
|
||||
|
||||
// Add Set-Cookie headers if they exist
|
||||
setCookieHeaders.forEach(cookie => {
|
||||
responseHeaders.append("Set-Cookie", cookie);
|
||||
});
|
||||
|
||||
return new Response(body, {
|
||||
status: res.status,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
headers: responseHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type React from "react"
|
||||
import type { Metadata } from "next"
|
||||
import { Inter } from "next/font/google"
|
||||
import "./globals.css"
|
||||
import { AuthProvider } from "@/components/providers/auth-provider"
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] })
|
||||
|
||||
@@ -18,7 +19,9 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className={inter.className}>
|
||||
{children}
|
||||
<AuthProvider>
|
||||
{children}
|
||||
</AuthProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { signIn, signUp } from "@/lib/auth";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function LoginPage() {
|
||||
const [isSignUp, setIsSignUp] = useState(false);
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [name, setName] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
setError("");
|
||||
|
||||
try {
|
||||
if (isSignUp) {
|
||||
const result = await signUp.email({
|
||||
email,
|
||||
password,
|
||||
name,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
setError(result.error.message || "Sign up failed");
|
||||
} else {
|
||||
router.push("/");
|
||||
}
|
||||
} else {
|
||||
const result = await signIn.email({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
setError(result.error.message || "Sign in failed");
|
||||
} else {
|
||||
router.push("/");
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError("An unexpected error occurred");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="max-w-md w-full space-y-8 p-8">
|
||||
<div className="text-center">
|
||||
<div className="flex justify-center mb-4">
|
||||
<Image
|
||||
src="/bytebot_square_light.svg"
|
||||
alt="Bytebot"
|
||||
width={64}
|
||||
height={64}
|
||||
className="h-12 w-12"
|
||||
/>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-900">
|
||||
{isSignUp ? "Create your account" : "Sign in to Bytebot"}
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-gray-600">
|
||||
{isSignUp
|
||||
? "Join Bytebot to start automating your tasks"
|
||||
: "Welcome back! Please sign in to continue"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
{isSignUp && (
|
||||
<div>
|
||||
<Label htmlFor="name">Full Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
required={isSignUp}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="mt-1"
|
||||
placeholder="Enter your full name"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Label htmlFor="email">Email Address</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="mt-1"
|
||||
placeholder="Enter your email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="mt-1"
|
||||
placeholder="Enter your password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full cursor-pointer"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading
|
||||
? "Loading..."
|
||||
: isSignUp
|
||||
? "Create Account"
|
||||
: "Sign In"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm text-gray-600 hover:text-gray-500 cursor-pointer"
|
||||
onClick={() => setIsSignUp(!isSignUp)}
|
||||
>
|
||||
{isSignUp
|
||||
? "Already have an account? Sign in"
|
||||
: "Need an account? Sign up"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export default function Home() {
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
popoverRef.current &&
|
||||
popoverRef.current &&
|
||||
!popoverRef.current.contains(event.target as Node) &&
|
||||
buttonsRef.current &&
|
||||
!buttonsRef.current.contains(event.target as Node)
|
||||
@@ -144,7 +144,10 @@ export default function Home() {
|
||||
>;
|
||||
|
||||
return (
|
||||
<div className="relative mt-6 flex w-full flex-wrap justify-start gap-1" ref={popoverRef}>
|
||||
<div
|
||||
className="relative mt-6 flex w-full flex-wrap justify-start gap-1"
|
||||
ref={popoverRef}
|
||||
>
|
||||
{/* Container for buttons */}
|
||||
<div className="flex w-full flex-wrap gap-1" ref={buttonsRef}>
|
||||
{topicNames.map((topic, index) => (
|
||||
@@ -168,9 +171,9 @@ export default function Home() {
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{
|
||||
duration: 0.3,
|
||||
ease: [0.4, 0.0, 0.2, 1]
|
||||
transition={{
|
||||
duration: 0.3,
|
||||
ease: [0.4, 0.0, 0.2, 1],
|
||||
}}
|
||||
className="bg-bytebot-bronze-light-3 shadow-bytebot border-bytebot-bronze-light-7 absolute top-full left-0 z-40 mt-1 w-[500px] overflow-hidden rounded-xl border p-1.5"
|
||||
>
|
||||
@@ -181,10 +184,10 @@ export default function Home() {
|
||||
key={idx}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{
|
||||
duration: 0.2,
|
||||
transition={{
|
||||
duration: 0.2,
|
||||
delay: idx * 0.03,
|
||||
ease: [0.4, 0.0, 0.2, 1]
|
||||
ease: [0.4, 0.0, 0.2, 1],
|
||||
}}
|
||||
className="text-bytebot-bronze-light-12 hover:bg-bytebot-bronze-light-2 cursor-pointer rounded-md px-3 py-1.5 text-sm transition-colors"
|
||||
onClick={() => {
|
||||
@@ -232,12 +235,12 @@ export default function Home() {
|
||||
minLines={3}
|
||||
/>
|
||||
<div className="mt-2">
|
||||
<Select defaultValue="sonnet-4">
|
||||
<Select defaultValue="opus-4">
|
||||
<SelectTrigger className="w-auto">
|
||||
<SelectValue placeholder="Select a model" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="sonnet-4">Model: Sonnet 4</SelectItem>
|
||||
<SelectItem value="opus-4">Model: Opus 4</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -282,12 +285,12 @@ export default function Home() {
|
||||
minLines={3}
|
||||
/>
|
||||
<div className="mt-2">
|
||||
<Select defaultValue="sonnet-4">
|
||||
<Select defaultValue="opus-4">
|
||||
<SelectTrigger className="w-auto">
|
||||
<SelectValue placeholder="Select a model" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="sonnet-4">Model: Sonnet 4</SelectItem>
|
||||
<SelectItem value="opus-4">Model: Opus 4</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@@ -209,7 +209,10 @@ export default function TaskPage() {
|
||||
</div>
|
||||
|
||||
{/* Chat Area */}
|
||||
<div ref={chatContainerRef} className="col-span-3 flex h-full flex-col overflow-scroll">
|
||||
<div
|
||||
ref={chatContainerRef}
|
||||
className="col-span-3 flex h-full flex-col overflow-scroll"
|
||||
>
|
||||
{/* Messages scrollable area */}
|
||||
<div className="flex-1 px-4 pt-4 pb-2">
|
||||
<ChatContainer
|
||||
@@ -235,12 +238,12 @@ export default function TaskPage() {
|
||||
minLines={1}
|
||||
/>
|
||||
<div className="mt-2">
|
||||
<Select value="sonnet-4">
|
||||
<Select value="opus-4">
|
||||
<SelectTrigger className="w-auto">
|
||||
<SelectValue placeholder="Select an model" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="sonnet-4">Sonnet 4</SelectItem>
|
||||
<SelectItem value="opus-4">Opus 4</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,21 @@ import React, { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { signOut, useSession } from "@/lib/auth";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import {
|
||||
DocumentCodeIcon,
|
||||
TaskDaily01Icon,
|
||||
Home01Icon,
|
||||
User03Icon,
|
||||
Logout01Icon,
|
||||
ComputerIcon,
|
||||
} from "@hugeicons/core-free-icons";
|
||||
import { usePathname } from "next/navigation";
|
||||
@@ -21,6 +31,7 @@ export function Header() {
|
||||
const { resolvedTheme } = useTheme();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const pathname = usePathname();
|
||||
const { data: session } = useSession();
|
||||
|
||||
// After mounting, we can safely show the theme-dependent content
|
||||
useEffect(() => {
|
||||
@@ -93,7 +104,27 @@ export function Header() {
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3"></div>
|
||||
<div className="flex items-center gap-3">
|
||||
{session && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm" className="flex items-center gap-2">
|
||||
<HugeiconsIcon icon={User03Icon} className="w-4 h-4" />
|
||||
<span className="text-sm">{session.user.name || session.user.email}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={() => signOut()}
|
||||
className="flex items-center gap-2 text-red-600"
|
||||
>
|
||||
<HugeiconsIcon icon={Logout01Icon} className="w-4 h-4" />
|
||||
Sign Out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,19 +89,18 @@ export function ChatContainer({
|
||||
scrollRef,
|
||||
}: ChatContainerProps) {
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const loadMoreRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
||||
// Group back-to-back messages from the same role
|
||||
const groupedConversation = groupBackToBackMessages(filterMessages(messages));
|
||||
|
||||
// Infinite scroll handler
|
||||
const handleScroll = useCallback(() => {
|
||||
if (!scrollRef?.current || !loadMoreMessages) {
|
||||
console.log('Scroll handler early return:', {
|
||||
console.log("Scroll handler early return:", {
|
||||
hasScrollRef: !!scrollRef?.current,
|
||||
hasLoadMoreMessages: !!loadMoreMessages,
|
||||
hasMoreMessages,
|
||||
isLoadingMoreMessages
|
||||
isLoadingMoreMessages,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -110,18 +109,18 @@ export function ChatContainer({
|
||||
// Check if user scrolled to the bottom (within 20px - much more sensitive)
|
||||
const { scrollTop, scrollHeight, clientHeight } = container;
|
||||
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
|
||||
|
||||
console.log('Scroll metrics:', {
|
||||
|
||||
console.log("Scroll metrics:", {
|
||||
scrollTop,
|
||||
scrollHeight,
|
||||
clientHeight,
|
||||
distanceFromBottom,
|
||||
hasMoreMessages,
|
||||
isLoadingMoreMessages
|
||||
isLoadingMoreMessages,
|
||||
});
|
||||
|
||||
if (distanceFromBottom <= 20 && hasMoreMessages && !isLoadingMoreMessages) {
|
||||
console.log('Triggering loadMoreMessages');
|
||||
console.log("Triggering loadMoreMessages");
|
||||
loadMoreMessages();
|
||||
}
|
||||
}, [scrollRef, loadMoreMessages, hasMoreMessages, isLoadingMoreMessages]);
|
||||
@@ -130,8 +129,8 @@ export function ChatContainer({
|
||||
useEffect(() => {
|
||||
const container = scrollRef?.current;
|
||||
if (container) {
|
||||
container.addEventListener('scroll', handleScroll);
|
||||
return () => container.removeEventListener('scroll', handleScroll);
|
||||
container.addEventListener("scroll", handleScroll);
|
||||
return () => container.removeEventListener("scroll", handleScroll);
|
||||
}
|
||||
}, [handleScroll, scrollRef]);
|
||||
|
||||
@@ -163,13 +162,13 @@ export function ChatContainer({
|
||||
<MessageGroup group={group} messages={messages} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
||||
{taskStatus === TaskStatus.RUNNING && control === Role.ASSISTANT && (
|
||||
<TextShimmer className="text-sm" duration={2}>
|
||||
Bytebot is working...
|
||||
</TextShimmer>
|
||||
)}
|
||||
|
||||
|
||||
{/* Loading indicator for infinite scroll at bottom */}
|
||||
{isLoadingMoreMessages && (
|
||||
<div className="flex justify-center py-4">
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import { useSession } from "@/lib/auth";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
|
||||
interface AuthProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function AuthProvider({ children }: AuthProviderProps) {
|
||||
const { data: session, isPending } = useSession();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const isPublicRoute = pathname === "/login";
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPending) {
|
||||
if (!session && !isPublicRoute) {
|
||||
// No session and not on a public route, redirect to login
|
||||
router.push("/login");
|
||||
} else if (session && isPublicRoute) {
|
||||
// Has session but on login page, redirect to home
|
||||
router.push("/");
|
||||
}
|
||||
}
|
||||
}, [session, isPending, isPublicRoute, router]);
|
||||
|
||||
// Show loading spinner while checking auth
|
||||
if (isPending) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="border-t-primary h-8 w-8 animate-spin rounded-full border-4 border-gray-300"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show login page for unauthenticated users
|
||||
if (!session && !isPublicRoute) {
|
||||
return null; // Router will handle redirect
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -39,12 +39,24 @@ export const TaskItem: React.FC<TaskItemProps> = ({ task }) => {
|
||||
return (
|
||||
<Link href={`/tasks/${task.id}`} className="block">
|
||||
<div className="shadow-bytebot bg-bytebot-bronze-light-2 border-bytebot-bronze-light-5 hover:bg-bytebot-bronze-light-3 flex min-h-24 items-start rounded-lg border-[0.5px] px-5 py-4 transition-colors">
|
||||
<div className="flex-1 space-y-1">
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="text-byhtebot-bronze-dark-7 text-sm font-medium">
|
||||
{capitalizeFirstChar(task.description)}
|
||||
</div>
|
||||
<div className="text-byhtebot-bronze-dark-11 text-xs">
|
||||
{formatDate(task.createdAt)}
|
||||
<div className="text-byhtebot-bronze-dark-11 text-xs flex items-center justify-start space-x-1.5">
|
||||
<span className="text-byhtebot-bronze-dark-9">
|
||||
{formatDate(task.createdAt)}
|
||||
</span>
|
||||
{task.user && (
|
||||
<>
|
||||
<span className="text-byhtebot-bronze-dark-9">
|
||||
•
|
||||
</span>
|
||||
<span className="text-byhtebot-bronze-dark-9">
|
||||
{task.user.name || task.user.email}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{task.status === TaskStatus.COMPLETED && (
|
||||
<div className="bg-bytebot-green-3 border-bytebot-green-a5 inline-flex w-fit items-center space-x-1 rounded-full border px-1.5 py-0.5">
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
import { HugeiconsIcon } from "@hugeicons/react"
|
||||
import { Tick01Icon, ArrowRight01Icon } from "@hugeicons/core-free-icons"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
||||
|
||||
const DropdownMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<HugeiconsIcon icon={ArrowRight01Icon} className="ml-auto h-4 w-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
))
|
||||
DropdownMenuSubTrigger.displayName =
|
||||
DropdownMenuPrimitive.SubTrigger.displayName
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSubContent.displayName =
|
||||
DropdownMenuPrimitive.SubContent.displayName
|
||||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
))
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
|
||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<HugeiconsIcon icon={Tick01Icon} className="h-4 w-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
))
|
||||
DropdownMenuCheckboxItem.displayName =
|
||||
DropdownMenuPrimitive.CheckboxItem.displayName
|
||||
|
||||
const DropdownMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<div className="h-2 w-2 rounded-full bg-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
))
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||
|
||||
const DropdownMenuShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuRadioGroup,
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
)
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
|
||||
export { Label }
|
||||
@@ -0,0 +1,19 @@
|
||||
import { createAuthClient } from 'better-auth/react';
|
||||
|
||||
const authEnabled = process.env.NEXT_PUBLIC_AUTH_ENABLED === 'true';
|
||||
|
||||
export const authClient = authEnabled ? createAuthClient() : null;
|
||||
|
||||
export const {
|
||||
signIn,
|
||||
signUp,
|
||||
signOut,
|
||||
useSession,
|
||||
getSession,
|
||||
} = authClient || {
|
||||
signIn: () => Promise.resolve(null),
|
||||
signUp: () => Promise.resolve(null),
|
||||
signOut: () => Promise.resolve(null),
|
||||
useSession: () => ({ data: null, isPending: false, error: null }),
|
||||
getSession: () => Promise.resolve(null),
|
||||
};
|
||||
@@ -32,12 +32,33 @@ export enum TaskPriority {
|
||||
URGENT = "URGENT",
|
||||
}
|
||||
|
||||
export enum TaskType {
|
||||
IMMEDIATE = "IMMEDIATE",
|
||||
SCHEDULED = "SCHEDULED",
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
name?: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
id: string;
|
||||
description: string;
|
||||
type: TaskType;
|
||||
status: TaskStatus;
|
||||
priority: TaskPriority;
|
||||
control: Role;
|
||||
createdBy: Role;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
scheduledFor?: string;
|
||||
executedAt?: string;
|
||||
completedAt?: string;
|
||||
queuedAt?: string;
|
||||
error?: string;
|
||||
result?: any;
|
||||
userId?: string;
|
||||
user?: User;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ export async function fetchTaskMessages(
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: 'include', // Include cookies for auth
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -55,6 +56,7 @@ export async function fetchTaskById(taskId: string): Promise<Task | null> {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: 'include',
|
||||
},
|
||||
);
|
||||
|
||||
@@ -85,6 +87,7 @@ export async function startTask(message: string): Promise<Task | null> {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ description: message }),
|
||||
credentials: 'include',
|
||||
},
|
||||
);
|
||||
|
||||
@@ -112,6 +115,7 @@ export async function guideTask(
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ message }),
|
||||
credentials: 'include',
|
||||
},
|
||||
);
|
||||
|
||||
@@ -135,6 +139,7 @@ export async function fetchTasks(): Promise<Task[]> {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: 'include',
|
||||
},
|
||||
);
|
||||
|
||||
@@ -159,6 +164,7 @@ export async function takeOverTask(taskId: string): Promise<Task | null> {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: 'include',
|
||||
},
|
||||
);
|
||||
|
||||
@@ -182,6 +188,7 @@ export async function resumeTask(taskId: string): Promise<Task | null> {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: 'include',
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -26,48 +26,48 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "11.1.2",
|
||||
"@nestjs/config": "4.0.1",
|
||||
"@nestjs/core": "11.0.1",
|
||||
"@nestjs/platform-express": "11.1.3",
|
||||
"@nestjs/platform-socket.io": "11.1.2",
|
||||
"@nestjs/serve-static": "5.0.3",
|
||||
"@nestjs/websockets": "11.1.2",
|
||||
"@nut-tree-fork/nut-js": "4.2.6",
|
||||
"class-transformer": "0.5.1",
|
||||
"class-validator": "0.14.2",
|
||||
"http-proxy-middleware": "3.0.5",
|
||||
"reflect-metadata": "0.2.2",
|
||||
"rxjs": "7.8.1",
|
||||
"socket.io": "4.8.1",
|
||||
"uiohook-napi": "1.5.4"
|
||||
"@nestjs/common": "^11.1.2",
|
||||
"@nestjs/config": "^4.0.1",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/platform-express": "^11.1.3",
|
||||
"@nestjs/platform-socket.io": "^11.1.2",
|
||||
"@nestjs/serve-static": "^5.0.3",
|
||||
"@nestjs/websockets": "^11.1.2",
|
||||
"@nut-tree-fork/nut-js": "^4.2.6",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
"http-proxy-middleware": "^3.0.5",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"socket.io": "^4.8.1",
|
||||
"uiohook-napi": "^1.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "3.2.0",
|
||||
"@eslint/js": "9.18.0",
|
||||
"@nestjs/cli": "11.0.0",
|
||||
"@nestjs/schematics": "11.0.0",
|
||||
"@nestjs/testing": "11.0.1",
|
||||
"@swc/cli": "0.6.0",
|
||||
"@swc/core": "1.10.7",
|
||||
"@types/express": "5.0.0",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/node": "22.13.14",
|
||||
"@types/supertest": "6.0.2",
|
||||
"eslint": "9.18.0",
|
||||
"eslint-config-prettier": "10.0.1",
|
||||
"eslint-plugin-prettier": "5.2.2",
|
||||
"globals": "15.14.0",
|
||||
"jest": "29.7.0",
|
||||
"prettier": "3.4.2",
|
||||
"source-map-support": "0.5.21",
|
||||
"supertest": "7.0.0",
|
||||
"ts-jest": "29.2.5",
|
||||
"ts-loader": "9.5.2",
|
||||
"ts-node": "10.9.2",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.7.3",
|
||||
"typescript-eslint": "8.20.0"
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@nestjs/cli": "^11.0.0",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@nestjs/testing": "^11.0.1",
|
||||
"@swc/cli": "^0.6.0",
|
||||
"@swc/core": "^1.10.7",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.13.14",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.2",
|
||||
"globals": "^15.14.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.4.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
"ts-loader": "^9.5.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.20.0"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
|
||||
@@ -19,15 +19,15 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "3.2.0",
|
||||
"@eslint/js": "9.18.0",
|
||||
"eslint": "9.18.0",
|
||||
"eslint-config-prettier": "10.0.1",
|
||||
"eslint-plugin-prettier": "5.2.2",
|
||||
"globals": "15.14.0",
|
||||
"prettier": "3.4.2",
|
||||
"typescript": "5.7.3",
|
||||
"typescript-eslint": "8.20.0"
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.2",
|
||||
"globals": "^15.14.0",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.20.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
Reference in New Issue
Block a user