Compare commits

...

No commits in common. "master" and "main" have entirely different histories.
master ... main

32 changed files with 59 additions and 3265 deletions

15
.gitignore vendored
View file

@ -1,15 +0,0 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
.python-version
.env
# Databases
*.sqlite3

View file

@ -1,42 +0,0 @@
# Multi-stage build for a Python backend with Deno frontend
# Stage 1: Build the frontend
FROM denoland/deno:2.4.3 AS frontend-builder
# Set working directory for frontend
WORKDIR /app/frontend
# Copy frontend files
COPY frontend/ .
# Install dependencies and build the frontend
RUN deno install --allow-scripts
RUN deno run build
# Stage 2: Setup Python backend with uv
FROM python:3.11-slim AS backend
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
# Set working directory
WORKDIR /app
# Copy Python project files
COPY pyproject.toml uv.lock ./
# Install Python dependencies
RUN uv sync --frozen
# Copy backend source code and .env file
COPY *.py ./
COPY .env ./
# Copy built frontend from previous stage
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist
# Expose the port (adjust if your app uses a different port)
EXPOSE 8000
# Run the application
CMD ["uv", "run", "app.py"]

View file

@ -1,131 +0,0 @@
# Backend API Testing with Hurl
This document provides instructions for testing the chat backend API using Hurl.
## Prerequisites
1. **Install Hurl**:
- macOS: `brew install hurl`
- Ubuntu/Debian: `sudo apt update && sudo apt install hurl`
- Windows: Download from [hurl.dev](https://hurl.dev)
- Or use Docker: `docker pull ghcr.io/orange-opensource/hurl:latest`
2. **Start the backend server**:
```bash
# Make sure you're in the project root directory
python -m uvicorn app:application --host 0.0.0.0 --port 8000 --reload
```
## Running the Tests
### Basic Test Run
```bash
# Run all tests
hurl test-backend.hurl
# Run with verbose output
hurl --verbose test-backend.hurl
# Run with color output
hurl --color test-backend.hurl
```
### Advanced Options
```bash
# Run with detailed report
hurl --report-html report.html test-backend.hurl
# Run specific tests (first 5 tests)
hurl --to-entry 5 test-backend.hurl
# Run tests with custom variables
hurl --variable host=localhost --variable port=8000 test-backend.hurl
# Run with retry on failure
hurl --retry 3 test-backend.hurl
```
## Test Coverage
The test script covers:
### ✅ Happy Path Tests
- **GET /api/models** - Retrieves available AI models
- **POST /api/chats** - Creates new chat sessions
- **GET /api/chats/{id}** - Retrieves chat history
- **POST /api/chats/{id}/messages** - Sends messages to chat
- **GET /api/chats/{id}/stream** - Streams AI responses via SSE
### ✅ Error Handling Tests
- Invalid model names
- Non-existent chat IDs
- Missing required parameters
### ✅ Multi-turn Conversation Tests
- Multiple message exchanges
- Conversation history persistence
- Different model selection
## Test Structure
The tests are organized in logical flow:
1. **Model Discovery** - Get available models
2. **Chat Creation** - Create new chat sessions
3. **Message Exchange** - Send messages and receive responses
4. **History Verification** - Ensure messages are persisted
5. **Error Scenarios** - Test edge cases and error handling
## Environment Variables
You can customize the test environment:
```bash
# Set custom host/port
export HURL_host=localhost
export HURL_port=8000
# Or pass as arguments
hurl --variable host=127.0.0.1 --variable port=8080 test-backend.hurl
```
## Troubleshooting
### Common Issues
1. **Connection refused**
- Ensure the backend is running on port 8000
- Check firewall settings
2. **Tests fail with 404 errors**
- Verify the backend routes are correctly configured
- Check if database migrations have been run
3. **SSE streaming tests timeout**
- Increase timeout: `hurl --max-time 30 test-backend.hurl`
- Check if AI provider is responding
### Debug Mode
Run tests with maximum verbosity:
```bash
hurl --very-verbose test-backend.hurl
```
## Continuous Integration
Add to your CI/CD pipeline:
```yaml
# GitHub Actions example
- name: Run API Tests
run: |
hurl --test --report-junit results.xml test-backend.hurl
```
## Test Results
After running tests, you'll see:
- ✅ **Green** - Tests passed
- ❌ **Red** - Tests failed with details
- 📊 **Summary** - Total tests, duration, and success rate

46
app.py
View file

@ -1,46 +0,0 @@
from starlette.applications import Starlette
from starlette.routing import Route, Mount
from starlette.staticfiles import StaticFiles
from starlette.responses import FileResponse
from controllers import create_chat, post_message, chat_stream, history, get_models
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
import os
middleware = [
Middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
]
async def serve_frontend(request):
"""Serve the frontend index.html file"""
return FileResponse(os.path.join("frontend", "dist", "index.html"))
async def serve_chat(request):
"""Serve the chat.html file for specific chat routes"""
return FileResponse(os.path.join("frontend", "dist", "chat.html"))
routes = [
Route("/", serve_frontend, methods=["GET"]),
Route("/chats/{chat_id:str}", serve_chat, methods=["GET"]),
Route("/api/models", get_models, methods=["GET"]),
Route("/api/chats", create_chat, methods=["POST"]),
Route("/api/chats/{chat_id:str}", history, methods=["GET"]),
Route("/api/chats/{chat_id:str}/messages", post_message, methods=["POST"]),
Route("/api/chats/{chat_id:str}/stream", chat_stream, methods=["GET"]),
Mount("/assets", StaticFiles(directory=os.path.join("frontend", "dist", "assets")), name="assets"),
Mount("/icon", StaticFiles(directory=os.path.join("frontend", "dist", "icon")), name="icon"),
]
application = Starlette(debug=True, routes=routes, middleware=middleware)
# ----------------- Run -----------------
if __name__ == "__main__":
import uvicorn
uvicorn.run("app:application", host="0.0.0.0", port=8000, reload=True)

View file

@ -1,19 +0,0 @@
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
from os import getenv
from dotenv import load_dotenv
from pydantic import SecretStr
load_dotenv()
def get_llm(provider: str):
"""Return a LangChain chat model for the requested provider."""
return ChatOpenAI(
api_key=SecretStr(getenv("OPENROUTER_API_KEY","")),
base_url=getenv("OPENROUTER_BASE_URL"),
model=provider,
)
def get_messages(chats, chat_id):
print(chats)
return [HumanMessage(**m) if m["role"] == "human" else AIMessage(**m) for m in chats]

View file

@ -1 +0,0 @@
# Masonite-orm module

View file

@ -1,11 +0,0 @@
from masoniteorm.connections import ConnectionResolver
DATABASES = {
"default": "sqlite",
"sqlite": {
"driver": "sqlite",
"database": "database.sqlite3",
}
}
DB = ConnectionResolver().set_connection_details(DATABASES)

View file

@ -1,115 +0,0 @@
import uuid
import json
from typing import Dict, List, Tuple
from starlette.responses import JSONResponse
from starlette.requests import Request
from sse_starlette.sse import EventSourceResponse
from chatgraph import get_messages, get_llm
from models.Chat import Chat
PENDING: Dict[str, Tuple[str, str]] = {} # message_id -> (chat_id, provider)
MODELS = {
"qwen/qwen3-235b-a22b-2507",
"deepseek/deepseek-r1-0528",
"moonshotai/kimi-k2",
"x-ai/grok-4",
"openai/gpt-4.1",
"anthropic/claude-sonnet-4",
"meta-llama/llama-4-maverick",
"mistralai/devstral-medium",
"qwen/qwen3-coder",
"google/gemini-2.5-pro",
}
async def get_models(request: Request):
"""GET /models -> {models: [...]}"""
return JSONResponse({"models": list(MODELS)})
async def create_chat(request: Request):
"""POST /chats -> {chat_id, model}"""
body = await request.json()
provider = body.get("model","")
if provider not in MODELS:
return JSONResponse({"error": "Unknown model"}, status_code=400)
chat = Chat()
chat_id = str(uuid.uuid4())
chat.id = chat_id
chat.title = "New Chat"
chat.messages = json.dumps([])
chat.save()
return JSONResponse({"id": chat_id, "model": provider})
async def history(request : Request):
"""GET /chats/{chat_id} -> previous messages"""
chat_id = request.path_params["chat_id"]
chat = Chat.find(chat_id)
if not chat:
return JSONResponse({"error": "Not found"}, status_code=404)
messages = json.loads(chat.messages) if chat.messages else []
return JSONResponse({"messages": messages})
async def post_message(request: Request):
"""POST /chats/{chat_id}/messages
Body: {"message": "...", "model": "model_name"}
Returns: {"message_id": "<chat_id>"}
"""
chat_id = request.path_params["chat_id"]
chat = Chat.find(chat_id)
if not chat:
return JSONResponse({"error": "Chat not found"}, status_code=404)
body = await request.json()
user_text = body.get("message", "")
provider = body.get("model", "")
if provider not in MODELS:
return JSONResponse({"error": "Unknown model"}, status_code=400)
# Load existing messages and add the new user message
messages = json.loads(chat.messages) if chat.messages else []
messages.append({"role": "human", "content": user_text})
chat.messages = json.dumps(messages)
chat.save()
message_id = str(uuid.uuid4())
PENDING[message_id] = (chat_id, provider)
return JSONResponse({
"status": "queued",
"message_id": message_id
})
async def chat_stream(request):
"""GET /chats/{chat_id}/stream?message_id=<chat_id>"""
chat_id = request.path_params["chat_id"]
message_id = request.query_params.get("message_id")
if message_id not in PENDING:
return JSONResponse({"error": "Not found"}, status_code=404)
chat_id_from_map, provider = PENDING.pop(message_id)
assert chat_id == chat_id_from_map
chat = Chat.find(chat_id)
if not chat:
return JSONResponse({"error": "Chat not found"}, status_code=404)
messages = json.loads(chat.messages) if chat.messages else []
msgs = get_messages( messages , chat_id)
llm = get_llm(provider)
async def event_generator():
buffer = ""
async for chunk in llm.astream(msgs):
token = chunk.content
buffer += token
yield {"data": token}
# Finished: store assistant reply
messages.append({"role": "assistant", "content": buffer})
chat.messages = json.dumps(messages)
chat.save()
yield {"event": "done", "data": ""}
return EventSourceResponse(event_generator())

View file

@ -1,12 +0,0 @@
from masoniteorm.migrations import Migration
class CreateChatsTable(Migration):
def up(self):
with self.schema.create("chats") as table:
table.uuid("id").primary()
table.string("title")
table.json("messages")
def down(self):
self.schema.drop("chats")

24
frontend/.gitignore vendored
View file

@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

972
frontend/deno.lock generated
View file

@ -1,972 +0,0 @@
{
"version": "5",
"specifiers": {
"npm:@sveltejs/vite-plugin-svelte@6": "6.1.0_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__picomatch@4.0.3",
"npm:@tailwindcss/typography@~0.5.16": "0.5.16_tailwindcss@4.1.11",
"npm:@tailwindcss/vite@^4.1.11": "4.1.11_vite@7.0.5__picomatch@4.0.3",
"npm:daisyui@^5.0.46": "5.0.46",
"npm:marked@^16.1.1": "16.1.1",
"npm:rolldown-vite@latest": "7.0.12_picomatch@4.0.3",
"npm:svelte@^5.35.5": "5.36.8_acorn@8.15.0",
"npm:tailwindcss@^4.1.11": "4.1.11"
},
"npm": {
"@ampproject/remapping@2.3.0": {
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dependencies": [
"@jridgewell/gen-mapping",
"@jridgewell/trace-mapping"
]
},
"@emnapi/core@1.4.5": {
"integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==",
"dependencies": [
"@emnapi/wasi-threads",
"tslib"
]
},
"@emnapi/runtime@1.4.5": {
"integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
"dependencies": [
"tslib"
]
},
"@emnapi/wasi-threads@1.0.4": {
"integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==",
"dependencies": [
"tslib"
]
},
"@esbuild/aix-ppc64@0.25.6": {
"integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==",
"os": ["aix"],
"cpu": ["ppc64"]
},
"@esbuild/android-arm64@0.25.6": {
"integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==",
"os": ["android"],
"cpu": ["arm64"]
},
"@esbuild/android-arm@0.25.6": {
"integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==",
"os": ["android"],
"cpu": ["arm"]
},
"@esbuild/android-x64@0.25.6": {
"integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==",
"os": ["android"],
"cpu": ["x64"]
},
"@esbuild/darwin-arm64@0.25.6": {
"integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==",
"os": ["darwin"],
"cpu": ["arm64"]
},
"@esbuild/darwin-x64@0.25.6": {
"integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==",
"os": ["darwin"],
"cpu": ["x64"]
},
"@esbuild/freebsd-arm64@0.25.6": {
"integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==",
"os": ["freebsd"],
"cpu": ["arm64"]
},
"@esbuild/freebsd-x64@0.25.6": {
"integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==",
"os": ["freebsd"],
"cpu": ["x64"]
},
"@esbuild/linux-arm64@0.25.6": {
"integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@esbuild/linux-arm@0.25.6": {
"integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==",
"os": ["linux"],
"cpu": ["arm"]
},
"@esbuild/linux-ia32@0.25.6": {
"integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==",
"os": ["linux"],
"cpu": ["ia32"]
},
"@esbuild/linux-loong64@0.25.6": {
"integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==",
"os": ["linux"],
"cpu": ["loong64"]
},
"@esbuild/linux-mips64el@0.25.6": {
"integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==",
"os": ["linux"],
"cpu": ["mips64el"]
},
"@esbuild/linux-ppc64@0.25.6": {
"integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==",
"os": ["linux"],
"cpu": ["ppc64"]
},
"@esbuild/linux-riscv64@0.25.6": {
"integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==",
"os": ["linux"],
"cpu": ["riscv64"]
},
"@esbuild/linux-s390x@0.25.6": {
"integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==",
"os": ["linux"],
"cpu": ["s390x"]
},
"@esbuild/linux-x64@0.25.6": {
"integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==",
"os": ["linux"],
"cpu": ["x64"]
},
"@esbuild/netbsd-arm64@0.25.6": {
"integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==",
"os": ["netbsd"],
"cpu": ["arm64"]
},
"@esbuild/netbsd-x64@0.25.6": {
"integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==",
"os": ["netbsd"],
"cpu": ["x64"]
},
"@esbuild/openbsd-arm64@0.25.6": {
"integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==",
"os": ["openbsd"],
"cpu": ["arm64"]
},
"@esbuild/openbsd-x64@0.25.6": {
"integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==",
"os": ["openbsd"],
"cpu": ["x64"]
},
"@esbuild/openharmony-arm64@0.25.6": {
"integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==",
"os": ["openharmony"],
"cpu": ["arm64"]
},
"@esbuild/sunos-x64@0.25.6": {
"integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==",
"os": ["sunos"],
"cpu": ["x64"]
},
"@esbuild/win32-arm64@0.25.6": {
"integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==",
"os": ["win32"],
"cpu": ["arm64"]
},
"@esbuild/win32-ia32@0.25.6": {
"integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==",
"os": ["win32"],
"cpu": ["ia32"]
},
"@esbuild/win32-x64@0.25.6": {
"integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==",
"os": ["win32"],
"cpu": ["x64"]
},
"@isaacs/fs-minipass@4.0.1": {
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"dependencies": [
"minipass"
]
},
"@jridgewell/gen-mapping@0.3.12": {
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"dependencies": [
"@jridgewell/sourcemap-codec",
"@jridgewell/trace-mapping"
]
},
"@jridgewell/resolve-uri@3.1.2": {
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
},
"@jridgewell/sourcemap-codec@1.5.4": {
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="
},
"@jridgewell/trace-mapping@0.3.29": {
"integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"dependencies": [
"@jridgewell/resolve-uri",
"@jridgewell/sourcemap-codec"
]
},
"@napi-rs/wasm-runtime@0.2.12": {
"integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
"dependencies": [
"@emnapi/core",
"@emnapi/runtime",
"@tybys/wasm-util@0.10.0"
]
},
"@napi-rs/wasm-runtime@1.0.1": {
"integrity": "sha512-KVlQ/jgywZpixGCKMNwxStmmbYEMyokZpCf2YuIChhfJA2uqfAKNEM8INz7zzTo55iEXfBhIIs3VqYyqzDLj8g==",
"dependencies": [
"@emnapi/core",
"@emnapi/runtime",
"@tybys/wasm-util@0.10.0"
]
},
"@oxc-project/runtime@0.78.0": {
"integrity": "sha512-jOU7sDFMyq5ShGJC21UobalVzqcdtWGfySVp8ELvKoVLzMpLHb4kv1bs9VKxaP8XC7Z9hlAXwEKVhCTN+j21aQ=="
},
"@oxc-project/types@0.78.0": {
"integrity": "sha512-8FvExh0WRWN1FoSTjah1xa9RlavZcJQ8/yxRbZ7ElmSa2Ij5f5Em7MvRbSthE6FbwC6Wh8iAw0Gpna7QdoqLGg=="
},
"@rolldown/binding-android-arm64@1.0.0-beta.30": {
"integrity": "sha512-4j7QBitb/WMT1fzdJo7BsFvVNaFR5WCQPdf/RPDHEsgQIYwBaHaL47KTZxncGFQDD1UAKN3XScJ0k7LAsZfsvg==",
"os": ["android"],
"cpu": ["arm64"]
},
"@rolldown/binding-darwin-arm64@1.0.0-beta.30": {
"integrity": "sha512-4vWFTe1o5LXeitI2lW8qMGRxxwrH/LhKd2HDLa/QPhdxohvdnfKyDZWN96XUhDyje2bHFCFyhMs3ak2lg2mJFA==",
"os": ["darwin"],
"cpu": ["arm64"]
},
"@rolldown/binding-darwin-x64@1.0.0-beta.30": {
"integrity": "sha512-MxrfodqImbsDFFFU/8LxyFPZjt7s4ht8g2Zb76EmIQ+xlmit46L9IzvWiuMpEaSJ5WbnjO7fCDWwakMGyJJ+Dw==",
"os": ["darwin"],
"cpu": ["x64"]
},
"@rolldown/binding-freebsd-x64@1.0.0-beta.30": {
"integrity": "sha512-c/TQXcATKoO8qE1bCjCOkymZTu7yVUAxBSNLp42Q97XHCb0Cu9v6MjZpB6c7Hq9NQ9NzW44uglak9D/r77JeDw==",
"os": ["freebsd"],
"cpu": ["x64"]
},
"@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.30": {
"integrity": "sha512-Vxci4xylM11zVqvrmezAaRjGBDyOlMRtlt7TDgxaBmSYLuiokXbZpD8aoSuOyjUAeN0/tmWItkxNGQza8UWGNQ==",
"os": ["linux"],
"cpu": ["arm"]
},
"@rolldown/binding-linux-arm64-gnu@1.0.0-beta.30": {
"integrity": "sha512-iEBEdSs25Ol0lXyVNs763f7YPAIP0t1EAjoXME81oJ94DesJslaLTj71Rn1shoMDVA+dfkYA286w5uYnOs9ZNA==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@rolldown/binding-linux-arm64-musl@1.0.0-beta.30": {
"integrity": "sha512-Ny684Sn1X8c+gGLuDlxkOuwiEE3C7eEOqp1/YVBzQB4HO7U/b4n7alvHvShboOEY5DP1fFUjq6Z+sBLYlCIZbQ==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@rolldown/binding-linux-arm64-ohos@1.0.0-beta.30": {
"integrity": "sha512-6moyULHDPKwt5RDEV72EqYw5n+s46AerTwtEBau5wCsZd1wuHS1L9z6wqhKISXAFTK9sneN0TEjvYKo+sgbbiA==",
"os": ["openharmony"],
"cpu": ["arm64"]
},
"@rolldown/binding-linux-x64-gnu@1.0.0-beta.30": {
"integrity": "sha512-p0yoPdoGg5Ow2YZKKB5Ypbn58i7u4XFk3PvMkriFnEcgtVk40c5u7miaX7jH0JdzahyXVBJ/KT5yEpJrzQn8yg==",
"os": ["linux"],
"cpu": ["x64"]
},
"@rolldown/binding-linux-x64-musl@1.0.0-beta.30": {
"integrity": "sha512-sM/KhCrsT0YdHX10mFSr0cvbfk1+btG6ftepAfqhbcDfhi0s65J4dTOxGmklJnJL9i1LXZ8WA3N4wmnqsfoK8Q==",
"os": ["linux"],
"cpu": ["x64"]
},
"@rolldown/binding-wasm32-wasi@1.0.0-beta.30": {
"integrity": "sha512-i3kD5OWs8PQP0V+JW3TFyCLuyjuNzrB45em0g84Jc+gvnDsGVlzVjMNPo7txE/yT8CfE90HC/lDs3ry9FvaUyw==",
"dependencies": [
"@napi-rs/wasm-runtime@1.0.1"
],
"cpu": ["wasm32"]
},
"@rolldown/binding-win32-arm64-msvc@1.0.0-beta.30": {
"integrity": "sha512-q7mrYln30V35VrCqnBVQQvNPQm8Om9HC59I3kMYiOWogvJobzSPyO+HA1MP363+Qgwe39I2I1nqBKPOtWZ33AQ==",
"os": ["win32"],
"cpu": ["arm64"]
},
"@rolldown/binding-win32-ia32-msvc@1.0.0-beta.30": {
"integrity": "sha512-nUqGBt39XTpbBEREEnyKofdP3uz+SN/x2884BH+N3B2NjSUrP6NXwzltM35C0wKK42hX/nthRrwSgj715m99Jw==",
"os": ["win32"],
"cpu": ["ia32"]
},
"@rolldown/binding-win32-x64-msvc@1.0.0-beta.30": {
"integrity": "sha512-lbnvUwAXIVWSXAeZrCa4b1KvV/DW0rBnMHuX0T7I6ey1IsXZ90J37dEgt3j48Ex1Cw1E+5H7VDNP2gyOX8iu3w==",
"os": ["win32"],
"cpu": ["x64"]
},
"@rolldown/pluginutils@1.0.0-beta.30": {
"integrity": "sha512-whXaSoNUFiyDAjkUF8OBpOm77Szdbk5lGNqFe6CbVbJFrhCCPinCbRA3NjawwlNHla1No7xvXXh+CpSxnPfUEw=="
},
"@rollup/rollup-android-arm-eabi@4.45.1": {
"integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==",
"os": ["android"],
"cpu": ["arm"]
},
"@rollup/rollup-android-arm64@4.45.1": {
"integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==",
"os": ["android"],
"cpu": ["arm64"]
},
"@rollup/rollup-darwin-arm64@4.45.1": {
"integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==",
"os": ["darwin"],
"cpu": ["arm64"]
},
"@rollup/rollup-darwin-x64@4.45.1": {
"integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==",
"os": ["darwin"],
"cpu": ["x64"]
},
"@rollup/rollup-freebsd-arm64@4.45.1": {
"integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==",
"os": ["freebsd"],
"cpu": ["arm64"]
},
"@rollup/rollup-freebsd-x64@4.45.1": {
"integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==",
"os": ["freebsd"],
"cpu": ["x64"]
},
"@rollup/rollup-linux-arm-gnueabihf@4.45.1": {
"integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==",
"os": ["linux"],
"cpu": ["arm"]
},
"@rollup/rollup-linux-arm-musleabihf@4.45.1": {
"integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==",
"os": ["linux"],
"cpu": ["arm"]
},
"@rollup/rollup-linux-arm64-gnu@4.45.1": {
"integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@rollup/rollup-linux-arm64-musl@4.45.1": {
"integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@rollup/rollup-linux-loongarch64-gnu@4.45.1": {
"integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==",
"os": ["linux"],
"cpu": ["loong64"]
},
"@rollup/rollup-linux-powerpc64le-gnu@4.45.1": {
"integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==",
"os": ["linux"],
"cpu": ["ppc64"]
},
"@rollup/rollup-linux-riscv64-gnu@4.45.1": {
"integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==",
"os": ["linux"],
"cpu": ["riscv64"]
},
"@rollup/rollup-linux-riscv64-musl@4.45.1": {
"integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==",
"os": ["linux"],
"cpu": ["riscv64"]
},
"@rollup/rollup-linux-s390x-gnu@4.45.1": {
"integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==",
"os": ["linux"],
"cpu": ["s390x"]
},
"@rollup/rollup-linux-x64-gnu@4.45.1": {
"integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==",
"os": ["linux"],
"cpu": ["x64"]
},
"@rollup/rollup-linux-x64-musl@4.45.1": {
"integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==",
"os": ["linux"],
"cpu": ["x64"]
},
"@rollup/rollup-win32-arm64-msvc@4.45.1": {
"integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==",
"os": ["win32"],
"cpu": ["arm64"]
},
"@rollup/rollup-win32-ia32-msvc@4.45.1": {
"integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==",
"os": ["win32"],
"cpu": ["ia32"]
},
"@rollup/rollup-win32-x64-msvc@4.45.1": {
"integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==",
"os": ["win32"],
"cpu": ["x64"]
},
"@sveltejs/acorn-typescript@1.0.5_acorn@8.15.0": {
"integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==",
"dependencies": [
"acorn"
]
},
"@sveltejs/vite-plugin-svelte-inspector@5.0.0_@sveltejs+vite-plugin-svelte@6.1.0__svelte@5.36.8___acorn@8.15.0__vite@7.0.5___picomatch@4.0.3_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__picomatch@4.0.3": {
"integrity": "sha512-iwQ8Z4ET6ZFSt/gC+tVfcsSBHwsqc6RumSaiLUkAurW3BCpJam65cmHw0oOlDMTO0u+PZi9hilBRYN+LZNHTUQ==",
"dependencies": [
"@sveltejs/vite-plugin-svelte",
"debug",
"svelte",
"vite"
]
},
"@sveltejs/vite-plugin-svelte@6.1.0_svelte@5.36.8__acorn@8.15.0_vite@7.0.5__picomatch@4.0.3": {
"integrity": "sha512-+U6lz1wvGEG/BvQyL4z/flyNdQ9xDNv5vrh+vWBWTHaebqT0c9RNggpZTo/XSPoHsSCWBlYaTlRX8pZ9GATXCw==",
"dependencies": [
"@sveltejs/vite-plugin-svelte-inspector",
"debug",
"deepmerge",
"kleur",
"magic-string",
"svelte",
"vite",
"vitefu"
]
},
"@tailwindcss/node@4.1.11": {
"integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==",
"dependencies": [
"@ampproject/remapping",
"enhanced-resolve",
"jiti",
"lightningcss",
"magic-string",
"source-map-js",
"tailwindcss"
]
},
"@tailwindcss/oxide-android-arm64@4.1.11": {
"integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==",
"os": ["android"],
"cpu": ["arm64"]
},
"@tailwindcss/oxide-darwin-arm64@4.1.11": {
"integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==",
"os": ["darwin"],
"cpu": ["arm64"]
},
"@tailwindcss/oxide-darwin-x64@4.1.11": {
"integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==",
"os": ["darwin"],
"cpu": ["x64"]
},
"@tailwindcss/oxide-freebsd-x64@4.1.11": {
"integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==",
"os": ["freebsd"],
"cpu": ["x64"]
},
"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11": {
"integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==",
"os": ["linux"],
"cpu": ["arm"]
},
"@tailwindcss/oxide-linux-arm64-gnu@4.1.11": {
"integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@tailwindcss/oxide-linux-arm64-musl@4.1.11": {
"integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@tailwindcss/oxide-linux-x64-gnu@4.1.11": {
"integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==",
"os": ["linux"],
"cpu": ["x64"]
},
"@tailwindcss/oxide-linux-x64-musl@4.1.11": {
"integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==",
"os": ["linux"],
"cpu": ["x64"]
},
"@tailwindcss/oxide-wasm32-wasi@4.1.11": {
"integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==",
"dependencies": [
"@emnapi/core",
"@emnapi/runtime",
"@emnapi/wasi-threads",
"@napi-rs/wasm-runtime@0.2.12",
"@tybys/wasm-util@0.9.0",
"tslib"
],
"cpu": ["wasm32"]
},
"@tailwindcss/oxide-win32-arm64-msvc@4.1.11": {
"integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==",
"os": ["win32"],
"cpu": ["arm64"]
},
"@tailwindcss/oxide-win32-x64-msvc@4.1.11": {
"integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==",
"os": ["win32"],
"cpu": ["x64"]
},
"@tailwindcss/oxide@4.1.11": {
"integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==",
"dependencies": [
"detect-libc",
"tar"
],
"optionalDependencies": [
"@tailwindcss/oxide-android-arm64",
"@tailwindcss/oxide-darwin-arm64",
"@tailwindcss/oxide-darwin-x64",
"@tailwindcss/oxide-freebsd-x64",
"@tailwindcss/oxide-linux-arm-gnueabihf",
"@tailwindcss/oxide-linux-arm64-gnu",
"@tailwindcss/oxide-linux-arm64-musl",
"@tailwindcss/oxide-linux-x64-gnu",
"@tailwindcss/oxide-linux-x64-musl",
"@tailwindcss/oxide-wasm32-wasi",
"@tailwindcss/oxide-win32-arm64-msvc",
"@tailwindcss/oxide-win32-x64-msvc"
],
"scripts": true
},
"@tailwindcss/typography@0.5.16_tailwindcss@4.1.11": {
"integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==",
"dependencies": [
"lodash.castarray",
"lodash.isplainobject",
"lodash.merge",
"postcss-selector-parser",
"tailwindcss"
]
},
"@tailwindcss/vite@4.1.11_vite@7.0.5__picomatch@4.0.3": {
"integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==",
"dependencies": [
"@tailwindcss/node",
"@tailwindcss/oxide",
"tailwindcss",
"vite"
]
},
"@tybys/wasm-util@0.10.0": {
"integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==",
"dependencies": [
"tslib"
]
},
"@tybys/wasm-util@0.9.0": {
"integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==",
"dependencies": [
"tslib"
]
},
"@types/estree@1.0.8": {
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
},
"acorn@8.15.0": {
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"bin": true
},
"ansis@4.1.0": {
"integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w=="
},
"aria-query@5.3.2": {
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="
},
"axobject-query@4.1.0": {
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="
},
"chownr@3.0.0": {
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="
},
"clsx@2.1.1": {
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="
},
"cssesc@3.0.0": {
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"bin": true
},
"daisyui@5.0.46": {
"integrity": "sha512-vMDZK1tI/bOb2Mc3Mk5WpquBG3ZqBz1YKZ0xDlvpOvey60dOS4/5Qhdowq1HndbQl7PgDLDYysxAjjUjwR7/eQ=="
},
"debug@4.4.1": {
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dependencies": [
"ms"
]
},
"deepmerge@4.3.1": {
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
},
"detect-libc@2.0.4": {
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="
},
"enhanced-resolve@5.18.2": {
"integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
"dependencies": [
"graceful-fs",
"tapable"
]
},
"esbuild@0.25.6": {
"integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==",
"optionalDependencies": [
"@esbuild/aix-ppc64",
"@esbuild/android-arm",
"@esbuild/android-arm64",
"@esbuild/android-x64",
"@esbuild/darwin-arm64",
"@esbuild/darwin-x64",
"@esbuild/freebsd-arm64",
"@esbuild/freebsd-x64",
"@esbuild/linux-arm",
"@esbuild/linux-arm64",
"@esbuild/linux-ia32",
"@esbuild/linux-loong64",
"@esbuild/linux-mips64el",
"@esbuild/linux-ppc64",
"@esbuild/linux-riscv64",
"@esbuild/linux-s390x",
"@esbuild/linux-x64",
"@esbuild/netbsd-arm64",
"@esbuild/netbsd-x64",
"@esbuild/openbsd-arm64",
"@esbuild/openbsd-x64",
"@esbuild/openharmony-arm64",
"@esbuild/sunos-x64",
"@esbuild/win32-arm64",
"@esbuild/win32-ia32",
"@esbuild/win32-x64"
],
"scripts": true,
"bin": true
},
"esm-env@1.2.2": {
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="
},
"esrap@2.1.0": {
"integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==",
"dependencies": [
"@jridgewell/sourcemap-codec"
]
},
"fdir@6.4.6_picomatch@4.0.3": {
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
"dependencies": [
"picomatch"
],
"optionalPeers": [
"picomatch"
]
},
"fsevents@2.3.3": {
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"os": ["darwin"],
"scripts": true
},
"graceful-fs@4.2.11": {
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"is-reference@3.0.3": {
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
"dependencies": [
"@types/estree"
]
},
"jiti@2.4.2": {
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
"bin": true
},
"kleur@4.1.5": {
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="
},
"lightningcss-darwin-arm64@1.30.1": {
"integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
"os": ["darwin"],
"cpu": ["arm64"]
},
"lightningcss-darwin-x64@1.30.1": {
"integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
"os": ["darwin"],
"cpu": ["x64"]
},
"lightningcss-freebsd-x64@1.30.1": {
"integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
"os": ["freebsd"],
"cpu": ["x64"]
},
"lightningcss-linux-arm-gnueabihf@1.30.1": {
"integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
"os": ["linux"],
"cpu": ["arm"]
},
"lightningcss-linux-arm64-gnu@1.30.1": {
"integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
"os": ["linux"],
"cpu": ["arm64"]
},
"lightningcss-linux-arm64-musl@1.30.1": {
"integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
"os": ["linux"],
"cpu": ["arm64"]
},
"lightningcss-linux-x64-gnu@1.30.1": {
"integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
"os": ["linux"],
"cpu": ["x64"]
},
"lightningcss-linux-x64-musl@1.30.1": {
"integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
"os": ["linux"],
"cpu": ["x64"]
},
"lightningcss-win32-arm64-msvc@1.30.1": {
"integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
"os": ["win32"],
"cpu": ["arm64"]
},
"lightningcss-win32-x64-msvc@1.30.1": {
"integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
"os": ["win32"],
"cpu": ["x64"]
},
"lightningcss@1.30.1": {
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
"dependencies": [
"detect-libc"
],
"optionalDependencies": [
"lightningcss-darwin-arm64",
"lightningcss-darwin-x64",
"lightningcss-freebsd-x64",
"lightningcss-linux-arm-gnueabihf",
"lightningcss-linux-arm64-gnu",
"lightningcss-linux-arm64-musl",
"lightningcss-linux-x64-gnu",
"lightningcss-linux-x64-musl",
"lightningcss-win32-arm64-msvc",
"lightningcss-win32-x64-msvc"
]
},
"locate-character@3.0.0": {
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
},
"lodash.castarray@4.4.0": {
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q=="
},
"lodash.isplainobject@4.0.6": {
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
},
"lodash.merge@4.6.2": {
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"magic-string@0.30.17": {
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"dependencies": [
"@jridgewell/sourcemap-codec"
]
},
"marked@16.1.1": {
"integrity": "sha512-ij/2lXfCRT71L6u0M29tJPhP0bM5shLL3u5BePhFwPELj2blMJ6GDtD7PfJhRLhJ/c2UwrK17ySVcDzy2YHjHQ==",
"bin": true
},
"minipass@7.1.2": {
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="
},
"minizlib@3.0.2": {
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
"dependencies": [
"minipass"
]
},
"mkdirp@3.0.1": {
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"bin": true
},
"ms@2.1.3": {
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"nanoid@3.3.11": {
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"bin": true
},
"picocolors@1.1.1": {
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"picomatch@4.0.3": {
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
},
"postcss-selector-parser@6.0.10": {
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dependencies": [
"cssesc",
"util-deprecate"
]
},
"postcss@8.5.6": {
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dependencies": [
"nanoid",
"picocolors",
"source-map-js"
]
},
"rolldown-vite@7.0.12_picomatch@4.0.3": {
"integrity": "sha512-Gr40FRnE98FwPJcMwcJgBwP6U7Qxw/VEtDsFdFjvGUTdgI/tTmF7z7dbVo/ajItM54G+Zo9w5BIrUmat6MbuWQ==",
"dependencies": [
"fdir",
"lightningcss",
"picomatch",
"postcss",
"rolldown",
"tinyglobby"
],
"optionalDependencies": [
"fsevents"
],
"bin": true
},
"rolldown@1.0.0-beta.30": {
"integrity": "sha512-H/LmDTUPlm65hWOTjXvd1k0qrGinNi8LrG3JsHVm6Oit7STg0upBmgoG5PZUHbAnGTHr0MLoLyzjmH261lIqSg==",
"dependencies": [
"@oxc-project/runtime",
"@oxc-project/types",
"@rolldown/pluginutils",
"ansis"
],
"optionalDependencies": [
"@rolldown/binding-android-arm64",
"@rolldown/binding-darwin-arm64",
"@rolldown/binding-darwin-x64",
"@rolldown/binding-freebsd-x64",
"@rolldown/binding-linux-arm-gnueabihf",
"@rolldown/binding-linux-arm64-gnu",
"@rolldown/binding-linux-arm64-musl",
"@rolldown/binding-linux-arm64-ohos",
"@rolldown/binding-linux-x64-gnu",
"@rolldown/binding-linux-x64-musl",
"@rolldown/binding-wasm32-wasi",
"@rolldown/binding-win32-arm64-msvc",
"@rolldown/binding-win32-ia32-msvc",
"@rolldown/binding-win32-x64-msvc"
],
"bin": true
},
"rollup@4.45.1": {
"integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==",
"dependencies": [
"@types/estree"
],
"optionalDependencies": [
"@rollup/rollup-android-arm-eabi",
"@rollup/rollup-android-arm64",
"@rollup/rollup-darwin-arm64",
"@rollup/rollup-darwin-x64",
"@rollup/rollup-freebsd-arm64",
"@rollup/rollup-freebsd-x64",
"@rollup/rollup-linux-arm-gnueabihf",
"@rollup/rollup-linux-arm-musleabihf",
"@rollup/rollup-linux-arm64-gnu",
"@rollup/rollup-linux-arm64-musl",
"@rollup/rollup-linux-loongarch64-gnu",
"@rollup/rollup-linux-powerpc64le-gnu",
"@rollup/rollup-linux-riscv64-gnu",
"@rollup/rollup-linux-riscv64-musl",
"@rollup/rollup-linux-s390x-gnu",
"@rollup/rollup-linux-x64-gnu",
"@rollup/rollup-linux-x64-musl",
"@rollup/rollup-win32-arm64-msvc",
"@rollup/rollup-win32-ia32-msvc",
"@rollup/rollup-win32-x64-msvc",
"fsevents"
],
"bin": true
},
"source-map-js@1.2.1": {
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
},
"svelte@5.36.8_acorn@8.15.0": {
"integrity": "sha512-8JbZWQu96hMjH/oYQPxXW6taeC6Awl6muGHeZzJTxQx7NGRQ/J9wN1hkzRKLOlSDlbS2igiFg7p5xyTp5uXG3A==",
"dependencies": [
"@ampproject/remapping",
"@jridgewell/sourcemap-codec",
"@sveltejs/acorn-typescript",
"@types/estree",
"acorn",
"aria-query",
"axobject-query",
"clsx",
"esm-env",
"esrap",
"is-reference",
"locate-character",
"magic-string",
"zimmerframe"
]
},
"tailwindcss@4.1.11": {
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="
},
"tapable@2.2.2": {
"integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="
},
"tar@7.4.3": {
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
"dependencies": [
"@isaacs/fs-minipass",
"chownr",
"minipass",
"minizlib",
"mkdirp",
"yallist"
]
},
"tinyglobby@0.2.14_picomatch@4.0.3": {
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
"dependencies": [
"fdir",
"picomatch"
]
},
"tslib@2.8.1": {
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"util-deprecate@1.0.2": {
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"vite@7.0.5_picomatch@4.0.3": {
"integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==",
"dependencies": [
"esbuild",
"fdir",
"picomatch",
"postcss",
"rollup",
"tinyglobby"
],
"optionalDependencies": [
"fsevents"
],
"bin": true
},
"vitefu@1.1.1_vite@7.0.5__picomatch@4.0.3": {
"integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==",
"dependencies": [
"vite"
],
"optionalPeers": [
"vite"
]
},
"yallist@5.0.0": {
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="
},
"zimmerframe@1.1.2": {
"integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="
}
},
"workspace": {
"packageJson": {
"dependencies": [
"npm:@sveltejs/vite-plugin-svelte@6",
"npm:@tailwindcss/typography@~0.5.16",
"npm:@tailwindcss/vite@^4.1.11",
"npm:daisyui@^5.0.46",
"npm:marked@^16.1.1",
"npm:rolldown-vite@latest",
"npm:svelte@^5.35.5",
"npm:tailwindcss@^4.1.11"
]
}
}
}

View file

@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/icon/multibot_32.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Multi chat LLM</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View file

@ -1,32 +0,0 @@
{
"compilerOptions": {
"moduleResolution": "bundler",
"target": "ESNext",
"module": "ESNext",
/**
* svelte-preprocess cannot figure out whether you have
* a value or a type, so tell TypeScript to enforce using
* `import type` instead of `import` for Types.
*/
"verbatimModuleSyntax": true,
"isolatedModules": true,
"resolveJsonModule": true,
/**
* To have warnings / errors of the Svelte compiler at the
* correct position, enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable this if you'd like to use dynamic types.
*/
"checkJs": true
},
/**
* Use global.d.ts instead of compilerOptions.types
* to avoid limiting type declarations.
*/
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
}

View file

@ -1,21 +0,0 @@
{
"name": "chatsbt",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.1.11",
"daisyui": "^5.0.46",
"marked": "^16.1.1",
"svelte": "^5.35.5",
"tailwindcss": "^4.1.11",
"vite": "npm:rolldown-vite@latest"
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

View file

@ -1,16 +0,0 @@
<script>
import Chat from "./lib/Chat.svelte";
import ChatList from "./lib/ChatList.svelte";
</script>
<div class="drawer lg:drawer-open">
<input id="drawer-toggle" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col h-screen">
<Chat />
</div>
<div class="drawer-side">
<label for="drawer-toggle" class="drawer-overlay"></label>
<ChatList />
</div>
</div>

View file

@ -1,3 +0,0 @@
@import "tailwindcss";
@plugin "daisyui";
@plugin "@tailwindcss/typography";

View file

@ -1,66 +0,0 @@
<script>
import ChatMessage from "./ChatMessage.svelte";
import { chatStore } from "./chatStore.svelte.js";
</script>
<!-- header -->
<header class="p-4">
<h1 class="text-5xl font-bold">Multi AI Chat</h1>
</header>
<!-- messages -->
<main class="flex-1 p-4 space-y-3 overflow-y-auto">
{#each chatStore.messages as m (m.id)}
<ChatMessage message={m} />
{/each}
{#if chatStore.loading}
<div class="chat chat-start">
<div class="chat-bubble chat-bubble-secondary loading"></div>
</div>
{/if}
</main>
<!-- input -->
<footer class="bg-neutral-content rounded-xl">
<div class="flex items-center">
<div class="form-control flex-1 m-4">
<textarea
class="textarea w-full"
rows="1"
placeholder="Type something…"
bind:value={chatStore.input}
onkeydown={chatStore.handleKey}
disabled={chatStore.loading}
></textarea>
</div>
</div>
<div class="flex items-center m-4">
<select
class="select select-bordered join-item"
bind:value={chatStore.model}
disabled={chatStore.loading || chatStore.loadingModels}
>
{#if chatStore.loadingModels}
<option value="" disabled>Loading models...</option>
{:else if chatStore.models.length === 0}
<option value="" disabled>No model available</option>
{:else}
{#each chatStore.models as modelOption}
<option value={modelOption.id || modelOption}>
{modelOption.name || modelOption.id || modelOption}
</option>
{/each}
{/if}
</select>
<button
class="btn btn-primary ml-auto"
onclick={chatStore.send}
disabled={!chatStore.input.trim() || chatStore.models.length === 0}
>
{#if chatStore.loading}
<span class="loading loading-spinner loading-xs"></span>
{:else}Send{/if}
</button>
</div>
</footer>

View file

@ -1,33 +0,0 @@
<script>
import { chatStore } from "./chatStore.svelte.js";
</script>
<aside class="menu p-4 w-64 bg-base-200 min-h-full">
<div class="flex justify-between items-center mb-4">
<span class="text-lg font-bold">Chats</span>
<button
class="btn btn-xs btn-primary"
onclick={() =>
chatStore.selectChat(null) && chatStore.createAndSelect()}
>
New
</button>
</div>
<ul class="menu menu-compact">
{#each chatStore.history as c}
<li>
<a
href="?chat={c.id}"
class={chatStore.chatId === c.id ? "active" : ""}
onclick={(e) => {
e.preventDefault();
chatStore.selectChat(c.id);
}}
>
{c.title}
</a>
</li>
{/each}
</ul>
</aside>

View file

@ -1,29 +0,0 @@
<script>
import { marked } from "marked";
let { message } = $props(); // { id, role, text }
const text = $derived(message.text);
const me = $derived(message.role == "user");
/* optional: allow HTML inside the markdown (default is escaped) */
marked.setOptions({ breaks: true, gfm: true });
</script>
{#if me}
<div class="chat chat-end">
<div class="chat-bele chat-bubble chat-bubble-primary">
{text}
</div>
</div>
{:else}
<div class="chat chat-start">
<div
class="chat-bele chat-bubble {message.role === 'error'
? 'text-error'
: ''} prose max-w-none"
>
<!-- eslint-disable svelte/no-at-html-tags -->
{@html marked(text)}
</div>
</div>
{/if}

View file

@ -1,35 +0,0 @@
const API = import.meta.env.CHATSBT_API_URL || "";
export async function createChat(model = "qwen/qwen3-235b-a22b-2507") {
const r = await fetch(`${API}/api/chats`, {
method: "POST",
body: JSON.stringify({ model }),
});
return r.json(); // { chat_id }
}
export async function sendUserMessage(chatId, text, model = "") {
const r = await fetch(`${API}/api/chats/${chatId}/messages`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: text, model }),
});
return r.json(); // { message_id }
}
export function openStream(chatId, messageId) {
return new EventSource(
`${API}/api/chats/${chatId}/stream?message_id=${messageId}`,
);
}
export async function fetchModels() {
try {
const response = await fetch(`${API}/api/models`);
const data = await response.json();
return data.models || [];
} catch (error) {
console.error("Failed to fetch models:", error);
return [];
}
}

View file

@ -1,161 +0,0 @@
import { createChat, sendUserMessage, openStream, fetchModels } from "./chatApi.svelte.js";
const STORAGE_KEY = "chatHistory";
function loadHistory() {
try {
return JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]");
} catch {
return [];
}
}
function saveHistory(list) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
}
export const chatStore = (() => {
let chatId = $state(null);
let messages = $state([]);
let loading = $state(false);
let input = $state("");
let model = $state("qwen/qwen3-235b-a22b-2507"); // default
let models = $state([]);
let loadingModels = $state(true);
// public helpers
const history = $derived(loadHistory());
function pushHistory(id, title, msgs) {
console.log(`push history: ${id} - ${title}`);
const h = history.filter((c) => c.id !== id);
h.unshift({ id, title, messages: msgs });
saveHistory(h.slice(0, 50)); // keep last 50
}
async function selectChat(id) {
if (id === chatId) return;
chatId = id;
const stored = loadHistory().find((c) => c.id === id);
messages = stored?.messages || [];
loading = true;
loading = false;
// Update URL with GET parameter
const url = new URL(window.location.href);
if (id) {
url.searchParams.set('chat', id);
} else {
url.searchParams.delete('chat');
}
window.history.replaceState({}, "", url);
}
async function createAndSelect() {
const { id } = await createChat(model);
console.log(id);
selectChat(id);
return id;
}
async function send() {
if (!input.trim()) return;
if (!chatId) await createAndSelect();
const userMsg = { id: crypto.randomUUID(), role: "user", text: input };
messages = [...messages, userMsg];
pushHistory(chatId, userMsg.text.slice(0, 30), messages);
loading = true;
const { message_id } = await sendUserMessage(chatId, input, model);
input = "";
let assistantMsg = { id: message_id, role: "assistant", text: "" };
messages = [...messages, assistantMsg];
const es = openStream(chatId, message_id);
es.onmessage = (e) => {
assistantMsg = { ...assistantMsg, text: assistantMsg.text + e.data };
messages = [...messages.slice(0, -1), assistantMsg];
};
es.onerror = () => {
es.close();
loading = false;
};
es.addEventListener("done", (e) => {
console.log(e);
es.close();
loading = false;
pushHistory(chatId, userMsg.text.slice(0, 30), messages);
});
}
async function loadModels() {
loadingModels = true;
models = await fetchModels();
loadingModels = false;
// Set default model if available and not already set
if (models.length > 0 && !model) {
model = models[0].id || models[0];
}
}
function handleKey(e) {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
send();
}
}
// initial route handling - use GET parameter instead of path
const params = new URLSearchParams(window.location.search);
const chatIdFromUrl = params.get('chat');
const storedHistory = loadHistory();
if (chatIdFromUrl && !storedHistory.find((c) => c.id === chatIdFromUrl)) {
createAndSelect();
} else if (chatIdFromUrl) {
selectChat(chatIdFromUrl);
}
// Load models on initialization
loadModels();
return {
get chatId() {
return chatId;
},
get messages() {
return messages;
},
get loading() {
return loading;
},
get input() {
return input;
},
set input(v) {
input = v;
},
get model() {
return model;
},
set model(v) {
model = v;
},
get models() {
return models;
},
get loadingModels() {
return loadingModels;
},
get history() {
return loadHistory();
},
selectChat,
send,
handleKey,
createAndSelect,
loadModels,
};
})();

View file

@ -1,44 +0,0 @@
// Parse chat ID from GET parameter
export function getChatIdFromUrl() {
const params = new URLSearchParams(window.location.search);
return params.get('chat');
}
// Update URL with GET parameter
export function updateUrlWithChatId(chatId) {
const url = new URL(window.location.href);
if (chatId) {
url.searchParams.set('chat', chatId);
} else {
url.searchParams.delete('chat');
}
window.history.replaceState({}, "", url);
}
// which chat is on screen right now
export let activeChatId = $state(null);
export function switchChat(chatId) {
activeChatId = chatId;
updateUrlWithChatId(chatId);
}
export function newChat() {
const id = "chat_" + crypto.randomUUID();
switchChat(id);
return id;
}
// restore last opened chat (or create first one)
(() => {
const ids = JSON.parse(localStorage.getItem("chat_ids") || "[]");
const urlChatId = getChatIdFromUrl();
if (urlChatId) {
switchChat(urlChatId);
} else if (ids.length) {
switchChat(ids[0]);
} else {
newChat();
}
})();

View file

@ -1,9 +0,0 @@
import { mount } from 'svelte'
import './app.css'
import App from './App.svelte'
const app = mount(App, {
target: document.getElementById('app'),
})
export default app

View file

@ -1,2 +0,0 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

View file

@ -1,8 +0,0 @@
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
dev: true,
};

View file

@ -1,11 +0,0 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [
svelte(),
tailwindcss(),
],
})

View file

@ -1,10 +0,0 @@
from masoniteorm.models import Model
from masoniteorm.scopes import UUIDPrimaryKeyMixin
class Chat(Model, UUIDPrimaryKeyMixin):
__table__ = "chats"
__timestamps__ = False
__primary_key__ = "id"
__incrementing__ = False
__fillable__ = ["id", "title", "messages"]

59
myservers.py Normal file
View file

@ -0,0 +1,59 @@
# /// script
# dependencies = [
# "paramiko",
# ]
# ///
import paramiko
# Server configurations
servers = [
{
"host": "server3.example.com",
"user": "root",
"password": "your_password"
},
{
"host": "192.168.1.100",
"user": "deploy",
"key": "~/.ssh/deploy_key",
"port": 2222 # Optional: custom SSH port
}
]
for server in servers:
print(f"\n--- {server['host']} ---")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
# Get connection parameters
host = server["host"]
user = server["user"]
port = server.get("port", 22)
# Connect based on available auth method
if "password" in server:
ssh.connect(host, port=port, username=user, password=server["password"])
elif "key" in server:
ssh.connect(host, port=port, username=user, key_filename=server["key"])
else:
# Try default key locations
ssh.connect(host, port=port, username=user)
# Execute command
stdin, stdout, stderr = ssh.exec_command("docker ps")
output = stdout.read().decode()
error = stderr.read().decode()
if output:
print(output)
if error:
print(f"Error: {error}")
except Exception as e:
print(f"Failed: {e}")
finally:
ssh.close()

View file

@ -1,18 +0,0 @@
[project]
name = "chatsbt"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"jinja2>=3.1.6",
"langchain>=0.3.26",
"langchain-core>=0.3.68",
"langchain-openai>=0.3.28",
"starlette>=0.47.1",
"uvicorn>=0.35.0",
"python-dotenv>=1.1.1",
"sse-starlette>=2.4.1",
"aiosqlite>=0.21.0",
"masonite-orm>=3.0.0",
]

View file

@ -1,165 +0,0 @@
# Hurl Test Script for Chat Backend API
# This script tests the complete flow of the backend API
# Test 1: Get available models
GET http://localhost:8000/api/models
HTTP 200
[Captures]
models: jsonpath "$.models"
# Validate models response
[Asserts]
jsonpath "$.models" count > 0
# Test 2: Create a new chat
POST http://localhost:8000/api/chats
Content-Type: application/json
{
"model": "qwen/qwen3-235b-a22b-2507"
}
HTTP 200
[Captures]
chat_id: jsonpath "$.id"
model: jsonpath "$.model"
# Validate chat creation response
[Asserts]
jsonpath "$.id" != null
jsonpath "$.model" == "qwen/qwen3-235b-a22b-2507"
# Test 3: Get chat history (should be empty initially)
GET http://localhost:8000/api/chats/{{chat_id}}
HTTP 200
[Captures]
messages: jsonpath "$.messages"
# Validate empty history
[Asserts]
jsonpath "$.messages" count == 0
# Test 4: Post a message to the chat
POST http://localhost:8000/api/chats/{{chat_id}}/messages
Content-Type: application/json
{
"message": "Hello, this is a test message",
"model": "{{model}}"
}
HTTP 200
[Captures]
message_id: jsonpath "$.message_id"
status: jsonpath "$.status"
# Validate message posting
[Asserts]
jsonpath "$.status" == "queued"
jsonpath "$.message_id" != null
# Test 5: Stream the response (SSE)
GET http://localhost:8000/api/chats/{{chat_id}}/stream?message_id={{message_id}}
HTTP 200
[Asserts]
header "Content-Type" == "text/event-stream; charset=utf-8"
# Test 6: Verify chat history now contains messages
GET http://localhost:8000/api/chats/{{chat_id}}
HTTP 200
[Captures]
updated_messages: jsonpath "$.messages"
# Validate messages are stored
[Asserts]
jsonpath "$.messages" count == 2
jsonpath "$.messages[0].role" == "human"
jsonpath "$.messages[0].content" == "Hello, this is a test message"
jsonpath "$.messages[1].role" == "assistant"
# Test 7: Post another message to test multi-turn conversation
POST http://localhost:8000/api/chats/{{chat_id}}/messages
Content-Type: application/json
{
"message": "Can you tell me a joke?",
"model": "{{model}}"
}
HTTP 200
[Captures]
message_id2: jsonpath "$.message_id"
# Test 8: Stream second response
GET http://localhost:8000/api/chats/{{chat_id}}/stream?message_id={{message_id2}}
HTTP 200
# Test 9: Verify multi-turn conversation history
GET http://localhost:8000/api/chats/{{chat_id}}
HTTP 200
[Captures]
final_messages: jsonpath "$.messages"
# Validate 4 messages (2 human + 2 assistant)
[Asserts]
jsonpath "$.messages" count == 4
# Test 10: Error handling - Invalid model
POST http://localhost:8000/api/chats
Content-Type: application/json
{
"model": "invalid-model-name"
}
HTTP 400
[Asserts]
jsonpath "$.error" == "Unknown model"
# Test 11: Error handling - Chat not found
GET http://localhost:8000/api/chats/non-existent-chat-id
HTTP 404
[Asserts]
jsonpath "$.error" == "Not found"
# Test 12: Error handling - Invalid chat ID for messages
POST http://localhost:8000/api/chats/non-existent-chat-id/messages
Content-Type: application/json
{
"message": "This should fail",
"model": "qwen/qwen3-235b-a22b-2507"
}
HTTP 404
[Asserts]
jsonpath "$.error" == "Chat not found"
# Test 13: Error handling - Missing message in post
POST http://localhost:8000/api/chats/{{chat_id}}/messages
Content-Type: application/json
{
"model": "{{model}}"
}
HTTP 200
# Note: The backend seems to accept empty messages, so this might not fail
# Test 14: Create another chat with different model
POST http://localhost:8000/api/chats
Content-Type: application/json
{
"model": "openai/gpt-4.1"
}
HTTP 200
[Captures]
chat_id2: jsonpath "$.id"
model2: jsonpath "$.model"
# Test 15: Verify second chat has different ID
[Asserts]
variable "chat_id" != "chat_id2"
variable "model2" == "openai/gpt-4.1"

1190
uv.lock generated

File diff suppressed because it is too large Load diff