Implement a serverside fix for reverse proxy users (#169)
This commit is contained in:
parent
2fa2956c4c
commit
04b5a9c96a
11 changed files with 409 additions and 20 deletions
18
.env.example
18
.env.example
|
|
@ -1,8 +1,22 @@
|
|||
|
||||
# API CONFIGURATION
|
||||
# URL where the API can be accessed by the browser
|
||||
# Default: http://localhost:5055 (works for most Docker setups)
|
||||
# Change this if you're running on a different host/port (e.g., http://your-server-ip:5055)
|
||||
# This setting allows the frontend to connect to the API at runtime (no rebuild needed!)
|
||||
#
|
||||
# IMPORTANT: Do NOT include /api at the end - it will be added automatically!
|
||||
#
|
||||
# Common scenarios:
|
||||
# - Docker on localhost: http://localhost:5055 (default, works for most cases)
|
||||
# - Docker on LAN/remote server: http://192.168.1.100:5055 or http://your-server-ip:5055
|
||||
# - Behind reverse proxy with custom domain: https://your-domain.com
|
||||
# - Behind reverse proxy with subdomain: https://api.your-domain.com
|
||||
#
|
||||
# Examples for reverse proxy users:
|
||||
# - API_URL=https://notebook.example.com (frontend will call https://notebook.example.com/api/*)
|
||||
# - API_URL=https://api.example.com (frontend will call https://api.example.com/api/*)
|
||||
#
|
||||
# Note: If not set, the system will auto-detect based on the incoming request.
|
||||
# Only set this if you need to override the auto-detection (e.g., reverse proxy scenarios).
|
||||
API_URL=http://localhost:5055
|
||||
|
||||
# SECURITY
|
||||
|
|
|
|||
11
Dockerfile
11
Dockerfile
|
|
@ -84,7 +84,14 @@ COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
|||
# Create log directories
|
||||
RUN mkdir -p /var/log/supervisor
|
||||
|
||||
# No default API_URL - the API will auto-detect from incoming requests
|
||||
# Users can still override by setting API_URL environment variable if needed
|
||||
# Runtime API URL Configuration
|
||||
# The API_URL environment variable can be set at container runtime to configure
|
||||
# where the frontend should connect to the API. This allows the same Docker image
|
||||
# to work in different deployment scenarios without rebuilding.
|
||||
#
|
||||
# If not set, the system will auto-detect based on incoming requests.
|
||||
# Set API_URL when using reverse proxies or custom domains.
|
||||
#
|
||||
# Example: docker run -e API_URL=https://your-domain.com/api ...
|
||||
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
|
|
|
|||
|
|
@ -88,7 +88,14 @@ COPY supervisord.single.conf /etc/supervisor/conf.d/supervisord.conf
|
|||
# Create log directories
|
||||
RUN mkdir -p /var/log/supervisor
|
||||
|
||||
# No default API_URL - the API will auto-detect from incoming requests
|
||||
# Users can still override by setting API_URL environment variable if needed
|
||||
# Runtime API URL Configuration
|
||||
# The API_URL environment variable can be set at container runtime to configure
|
||||
# where the frontend should connect to the API. This allows the same Docker image
|
||||
# to work in different deployment scenarios without rebuilding.
|
||||
#
|
||||
# If not set, the system will auto-detect based on incoming requests.
|
||||
# Set API_URL when using reverse proxies or custom domains.
|
||||
#
|
||||
# Example: docker run -e API_URL=https://your-domain.com/api ...
|
||||
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
|
|
@ -29,7 +29,14 @@ This section provides comprehensive guides for deploying Open Notebook in differ
|
|||
- Development tools and debugging
|
||||
- Contributing to the project
|
||||
|
||||
### 4. [Security Configuration](security.md)
|
||||
### 4. [Reverse Proxy Configuration](reverse-proxy.md)
|
||||
**For production deployments with custom domains**
|
||||
- nginx, Caddy, Traefik configurations
|
||||
- Custom domain setup
|
||||
- SSL/HTTPS configuration
|
||||
- Runtime API URL configuration
|
||||
|
||||
### 5. [Security Configuration](security.md)
|
||||
**Essential for public deployments**
|
||||
- Password protection setup
|
||||
- Security best practices
|
||||
|
|
@ -50,6 +57,12 @@ This section provides comprehensive guides for deploying Open Notebook in differ
|
|||
- You have resource constraints
|
||||
- You don't need to scale services independently
|
||||
|
||||
### Use Reverse Proxy Setup if:
|
||||
- You're deploying with a custom domain
|
||||
- You need HTTPS/SSL encryption
|
||||
- You're using nginx, Caddy, or Traefik
|
||||
- You want to expose only specific ports publicly
|
||||
|
||||
### Use Development Setup if:
|
||||
- You want to contribute to the project
|
||||
- You need to modify the source code
|
||||
|
|
|
|||
304
docs/deployment/reverse-proxy.md
Normal file
304
docs/deployment/reverse-proxy.md
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
# Reverse Proxy Configuration
|
||||
|
||||
This guide helps you deploy Open Notebook behind a reverse proxy (nginx, Caddy, Traefik, etc.) or with a custom domain.
|
||||
|
||||
## The API_URL Environment Variable
|
||||
|
||||
Starting with v1.0+, Open Notebook supports runtime configuration of the API URL through the `API_URL` environment variable. This means you can use the same Docker image in different deployment scenarios without rebuilding.
|
||||
|
||||
### How It Works
|
||||
|
||||
The frontend uses a three-tier priority system to determine the API URL:
|
||||
|
||||
1. **Runtime Configuration** (Highest Priority): `API_URL` environment variable set at container runtime
|
||||
2. **Build-time Configuration**: `NEXT_PUBLIC_API_URL` baked into the Docker image
|
||||
3. **Auto-detection** (Fallback): Infers from the incoming HTTP request
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario 1: Docker on Localhost (Default)
|
||||
|
||||
No configuration needed! The system auto-detects.
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name open-notebook \
|
||||
-p 8502:8502 -p 5055:5055 \
|
||||
-v ./notebook_data:/app/data \
|
||||
-v ./surreal_data:/mydata \
|
||||
lfnovo/open_notebook:v1-latest-single
|
||||
```
|
||||
|
||||
### Scenario 2: Docker on Remote Server (LAN/VPS)
|
||||
|
||||
Access via IP address - auto-detection works, but you can be explicit:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name open-notebook \
|
||||
-p 8502:8502 -p 5055:5055 \
|
||||
-e API_URL=http://192.168.1.100:5055 \
|
||||
-v ./notebook_data:/app/data \
|
||||
-v ./surreal_data:/mydata \
|
||||
lfnovo/open_notebook:v1-latest-single
|
||||
```
|
||||
|
||||
> **Note**: Don't include `/api` at the end - the system adds this automatically!
|
||||
|
||||
### Scenario 3: Behind Reverse Proxy with Custom Domain
|
||||
|
||||
This is where `API_URL` is **essential**. Your reverse proxy handles HTTPS and routing.
|
||||
|
||||
> **Important**: If your reverse proxy forwards `/api` requests to the backend, set `API_URL` to just the domain (without `/api` suffix). The frontend will append `/api` automatically.
|
||||
|
||||
#### Example: nginx + Docker Compose
|
||||
|
||||
**docker-compose.yml:**
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
open-notebook:
|
||||
image: lfnovo/open_notebook:v1-latest-single
|
||||
container_name: open-notebook
|
||||
environment:
|
||||
- API_URL=https://notebook.example.com
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||
volumes:
|
||||
- ./notebook_data:/app/data
|
||||
- ./surreal_data:/mydata
|
||||
ports:
|
||||
- "8502:8502" # Frontend
|
||||
- "5055:5055" # API
|
||||
restart: unless-stopped
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: nginx-proxy
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./ssl:/etc/nginx/ssl:ro
|
||||
depends_on:
|
||||
- open-notebook
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
**nginx.conf:**
|
||||
```nginx
|
||||
http {
|
||||
upstream frontend {
|
||||
server open-notebook:8502;
|
||||
}
|
||||
|
||||
upstream api {
|
||||
server open-notebook:5055;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name notebook.example.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name notebook.example.com;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
|
||||
|
||||
# Frontend
|
||||
location / {
|
||||
proxy_pass http://frontend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
|
||||
# API
|
||||
location /api/ {
|
||||
proxy_pass http://api/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Scenario 4: Behind Reverse Proxy with Subdomain
|
||||
|
||||
If you want API on a separate subdomain:
|
||||
|
||||
**docker-compose.yml:**
|
||||
```yaml
|
||||
services:
|
||||
open-notebook:
|
||||
image: lfnovo/open_notebook:v1-latest-single
|
||||
environment:
|
||||
- API_URL=https://api.notebook.example.com
|
||||
# ... other env vars
|
||||
```
|
||||
|
||||
**nginx.conf:**
|
||||
```nginx
|
||||
# Frontend server
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name notebook.example.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://open-notebook:8502;
|
||||
# ... proxy headers
|
||||
}
|
||||
}
|
||||
|
||||
# API server
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name api.notebook.example.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://open-notebook:5055;
|
||||
# ... proxy headers
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Scenario 5: Traefik
|
||||
|
||||
**docker-compose.yml:**
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
open-notebook:
|
||||
image: lfnovo/open_notebook:v1-latest-single
|
||||
environment:
|
||||
- API_URL=https://notebook.example.com
|
||||
labels:
|
||||
# Frontend
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.notebook-frontend.rule=Host(`notebook.example.com`)"
|
||||
- "traefik.http.routers.notebook-frontend.entrypoints=websecure"
|
||||
- "traefik.http.routers.notebook-frontend.tls.certresolver=myresolver"
|
||||
- "traefik.http.services.notebook-frontend.loadbalancer.server.port=8502"
|
||||
|
||||
# API
|
||||
- "traefik.http.routers.notebook-api.rule=Host(`notebook.example.com`) && PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.notebook-api.entrypoints=websecure"
|
||||
- "traefik.http.routers.notebook-api.tls.certresolver=myresolver"
|
||||
- "traefik.http.services.notebook-api.loadbalancer.server.port=5055"
|
||||
networks:
|
||||
- traefik-network
|
||||
|
||||
networks:
|
||||
traefik-network:
|
||||
external: true
|
||||
```
|
||||
|
||||
### Scenario 6: Caddy
|
||||
|
||||
**Caddyfile:**
|
||||
```caddy
|
||||
notebook.example.com {
|
||||
# Frontend
|
||||
reverse_proxy / open-notebook:8502
|
||||
|
||||
# API
|
||||
reverse_proxy /api/* open-notebook:5055
|
||||
}
|
||||
```
|
||||
|
||||
**docker-compose.yml:**
|
||||
```yaml
|
||||
services:
|
||||
open-notebook:
|
||||
image: lfnovo/open_notebook:v1-latest-single
|
||||
environment:
|
||||
- API_URL=https://notebook.example.com
|
||||
# No need to expose ports if using Caddy in same network
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Error: Unable to connect to server
|
||||
|
||||
**Symptoms**: Frontend displays "Unable to connect to server. Please check if the API is running."
|
||||
|
||||
**Possible Causes**:
|
||||
|
||||
1. **API_URL not set correctly** for your reverse proxy setup
|
||||
- Check browser console (F12) for connection errors
|
||||
- Look for logs showing what URL the frontend is trying
|
||||
|
||||
2. **Reverse proxy not forwarding to correct port**
|
||||
- API should be accessible at the URL specified in `API_URL`
|
||||
- Test: `curl https://your-domain.com/api/config` should return JSON
|
||||
|
||||
3. **CORS issues**
|
||||
- Ensure `X-Forwarded-Proto` and `X-Forwarded-For` headers are set in proxy config
|
||||
- Check API logs for CORS errors
|
||||
|
||||
4. **SSL/TLS certificate issues**
|
||||
- Ensure your reverse proxy has valid SSL certificates
|
||||
- Mixed content errors (HTTPS frontend trying to reach HTTP API)
|
||||
|
||||
### How to Debug
|
||||
|
||||
1. **Check browser console** (F12 → Console tab):
|
||||
- Look for messages starting with `🔧 [Config]`
|
||||
- These show the configuration detection process
|
||||
- You'll see which API URL is being used
|
||||
|
||||
2. **Test API directly**:
|
||||
```bash
|
||||
# Should return JSON config
|
||||
curl https://your-domain.com/api/config
|
||||
```
|
||||
|
||||
3. **Check Docker logs**:
|
||||
```bash
|
||||
docker logs open-notebook
|
||||
```
|
||||
- Look for frontend and API startup messages
|
||||
- Check for connection errors
|
||||
|
||||
4. **Verify environment variable**:
|
||||
```bash
|
||||
docker exec open-notebook env | grep API_URL
|
||||
```
|
||||
|
||||
### Missing Authorization Header
|
||||
|
||||
**Symptoms**: API returns `{"detail": "Missing authorization header"}`
|
||||
|
||||
This happens when:
|
||||
- You have set `OPEN_NOTEBOOK_PASSWORD` for authentication
|
||||
- You're trying to access `/api/config` directly without logging in first
|
||||
|
||||
**Solution**: This is expected behavior! The frontend handles this automatically. Just access the frontend URL and log in through the UI.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use HTTPS** in production with reverse proxies
|
||||
2. **Set `API_URL` explicitly** when using reverse proxies to avoid auto-detection issues
|
||||
3. **Use environment files** (`.env` or `docker.env`) to manage configuration
|
||||
4. **Test your setup** by accessing the frontend and checking browser console logs
|
||||
5. **Keep ports 5055 and 8502 accessible** from your reverse proxy container
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Docker Deployment Guide](./docker.md)
|
||||
- [Security Guide](./security.md)
|
||||
- [Troubleshooting](../troubleshooting/common-issues.md)
|
||||
22
frontend/src/app/api/runtime-config/route.ts
Normal file
22
frontend/src/app/api/runtime-config/route.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
|
||||
/**
|
||||
* Runtime Configuration Endpoint
|
||||
*
|
||||
* This endpoint provides server-side environment variables to the client at runtime.
|
||||
* This solves the NEXT_PUBLIC_* limitation where variables are baked into the build.
|
||||
*
|
||||
* Users can now set API_URL in their docker.env and it will be picked up at runtime,
|
||||
* allowing the same Docker image to work in different deployment scenarios.
|
||||
*/
|
||||
export async function GET() {
|
||||
// Priority:
|
||||
// 1. API_URL from environment (set by user at runtime)
|
||||
// 2. NEXT_PUBLIC_API_URL from build time (fallback)
|
||||
// 3. Default to localhost:5055
|
||||
const apiUrl = process.env.API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5055'
|
||||
|
||||
return NextResponse.json({
|
||||
apiUrl,
|
||||
})
|
||||
}
|
||||
|
|
@ -60,11 +60,30 @@ async function fetchConfig(): Promise<AppConfig> {
|
|||
console.log('🔧 [Config] Starting configuration detection...')
|
||||
console.log('🔧 [Config] Build time:', BUILD_TIME)
|
||||
|
||||
// Try to get from environment variable first (for development)
|
||||
// STEP 1: Try to get runtime config from Next.js server-side API route
|
||||
// This allows API_URL to be set at runtime (not baked into build)
|
||||
let runtimeApiUrl: string | null = null
|
||||
try {
|
||||
console.log('🔧 [Config] Attempting to fetch runtime config from Next.js API route...')
|
||||
const runtimeResponse = await fetch('/api/runtime-config', {
|
||||
cache: 'no-store',
|
||||
})
|
||||
if (runtimeResponse.ok) {
|
||||
const runtimeData = await runtimeResponse.json()
|
||||
runtimeApiUrl = runtimeData.apiUrl
|
||||
console.log('✅ [Config] Runtime API URL from server:', runtimeApiUrl)
|
||||
} else {
|
||||
console.log('⚠️ [Config] Runtime config endpoint returned status:', runtimeResponse.status)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ [Config] Could not fetch runtime config:', error)
|
||||
}
|
||||
|
||||
// STEP 2: Fallback to build-time environment variable
|
||||
const envApiUrl = process.env.NEXT_PUBLIC_API_URL
|
||||
console.log('🔧 [Config] NEXT_PUBLIC_API_URL from build:', envApiUrl || '(not set)')
|
||||
|
||||
// Smart default: infer API URL from current frontend URL
|
||||
// STEP 3: Smart default - infer API URL from current frontend URL
|
||||
// If frontend is at http://10.20.30.20:8502, API should be at http://10.20.30.20:5055
|
||||
let defaultApiUrl = 'http://localhost:5055'
|
||||
|
||||
|
|
@ -82,13 +101,16 @@ async function fetchConfig(): Promise<AppConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
// Use env var if available, otherwise smart default
|
||||
const baseUrl = envApiUrl || defaultApiUrl
|
||||
// Priority: Runtime config > Build-time env var > Smart default
|
||||
const baseUrl = runtimeApiUrl || envApiUrl || defaultApiUrl
|
||||
console.log('🔧 [Config] Final base URL to try:', baseUrl)
|
||||
console.log('🔧 [Config] Selection priority: runtime=' + (runtimeApiUrl ? '✅' : '❌') +
|
||||
', build-time=' + (envApiUrl ? '✅' : '❌') +
|
||||
', smart-default=' + (!runtimeApiUrl && !envApiUrl ? '✅' : '❌'))
|
||||
|
||||
try {
|
||||
console.log('🔧 [Config] Fetching runtime config from:', `${baseUrl}/api/config`)
|
||||
// Try to fetch runtime config from API
|
||||
console.log('🔧 [Config] Fetching backend config from:', `${baseUrl}/api/config`)
|
||||
// Try to fetch runtime config from backend API
|
||||
const response = await fetch(`${baseUrl}/api/config`, {
|
||||
cache: 'no-store',
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "open-notebook"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
description = "An open source implementation of a research assistant, inspired by Google Notebook LM"
|
||||
authors = [
|
||||
{name = "Luis Novo", email = "lfnovo@gmail.com"}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ startsecs=3
|
|||
[program:frontend]
|
||||
command=npm run start
|
||||
directory=/app/frontend
|
||||
environment=NODE_ENV="production",PORT="8502"
|
||||
environment=NODE_ENV="production",PORT="8502",API_URL="%(ENV_API_URL)s"
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ startsecs=3
|
|||
[program:frontend]
|
||||
command=npm run start
|
||||
directory=/app/frontend
|
||||
environment=NODE_ENV="production",PORT="8502"
|
||||
environment=NODE_ENV="production",PORT="8502",API_URL="%(ENV_API_URL)s"
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
|
|
|
|||
8
uv.lock
8
uv.lock
|
|
@ -620,15 +620,15 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "esperanto"
|
||||
version = "2.7.0"
|
||||
version = "2.7.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6d/cf/0da02a603a63b3850abd14d23629f101942db5c18840b0cc6f34d7db9a04/esperanto-2.7.0.tar.gz", hash = "sha256:3861e4e20697813b19f0070a1142934bd6792077c3c174a2c3dd4b6ca0676b06", size = 553433, upload-time = "2025-10-19T02:04:30.21Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/8c/a2a9c1e7428d700963e7450aba591b2f4fbcfa61bad88654af8d92df3a00/esperanto-2.7.1.tar.gz", hash = "sha256:a18abc38d30d38c496eac62f252a9c12d243fc49fef7cc92d9d23c5b5faee741", size = 554840, upload-time = "2025-10-19T02:30:45.251Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/14/9c/79827f246965ed66ae8d2f3e3937e552730eaf48b270dac852a4756c7bf4/esperanto-2.7.0-py3-none-any.whl", hash = "sha256:2ea3fa98d8622d08a18dc6701ad362461de02492a3252326c70c969b3aba3db6", size = 129524, upload-time = "2025-10-19T02:04:28.57Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/91/a2c43a4eb3c56f349d59c656783cdd8cd13149a8e521e75f77e84dcd5ac0/esperanto-2.7.1-py3-none-any.whl", hash = "sha256:ea81e531085e32828b2ecd02680088801222dd21e7a73012efdb9b68fa627ed4", size = 129593, upload-time = "2025-10-19T02:30:44.149Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2199,7 +2199,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "open-notebook"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "ai-prompter" },
|
||||
|
|
|
|||
Loading…
Reference in a new issue