diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index e5aa3c3..d609f17 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -69,7 +69,7 @@ jobs: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USERNAME }} key: ${{ secrets.SSH_PRIVATE_KEY }} - source: "docker-compose.yml,docker-compose.prod.yml" + source: "docker-compose.yml,docker-compose.prod.yml,rust-engine/demo-data" target: "/home/github-actions/codered-astra/" - name: Deploy to server via SSH ☁️ @@ -86,6 +86,7 @@ jobs: debug: true script: | cd /home/github-actions/codered-astra + chmod -R o+rX rust-engine/demo-data # wrapper to support both Docker Compose v2 and legacy v1 compose() { docker compose "$@" || docker-compose "$@"; } # Log in to GHCR using the run's GITHUB_TOKEN so compose can pull images. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index b61318a..0000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,268 +0,0 @@ -# CodeRED-Astra Architecture - -## Overview - -CodeRED-Astra is a Retrieval-Augmented Generation (RAG) system for querying ISS technical documentation using vector search, MySQL metadata storage, and Gemini AI for analysis and response generation. - -## System Components - -### 1. **Rust Backend** (`rust-engine/`) -High-performance Rust backend using Warp for HTTP, SQLx for MySQL, and Reqwest for external API calls. - -#### Modules - -**`main.rs`** - Entry point -- Initializes tracing, database, storage -- Spawns FileWorker and QueryWorker background tasks -- Serves API routes on port 8000 - -**`db.rs`** - Database initialization -- Connects to MySQL -- Creates `files` table (id, filename, path, description, pending_analysis, analysis_status) -- Creates `queries` table (id, status, payload, result, timestamps) - -**`api.rs`** - HTTP endpoints -- `POST /api/files` - Upload file (multipart/form-data) -- `POST /api/files/import-demo` - Bulk import from demo-data directory -- `GET /api/files/list` - List all files with status -- `GET /api/files/delete?id=` - Delete file and remove from Qdrant -- `POST /api/query/create` - Create new query (returns query ID) -- `GET /api/query/status?id=` - Check query status -- `GET /api/query/result?id=` - Get query result -- `GET /api/query/cancel?id=` - Cancel in-progress query - -**`file_worker.rs`** - File analysis pipeline -- **Background worker** that processes files with `pending_analysis = TRUE` -- Claims stale/queued files (requeues if stuck >10 min) -- **Stage 1**: Call Gemini 1.5 Flash for initial description -- **Stage 2**: Call Gemini 1.5 Pro for deep vector graph data (keywords, relationships) -- **Stage 3**: Generate embedding and upsert to Qdrant -- **Stage 4**: Mark file as ready (`pending_analysis = FALSE`, `analysis_status = 'Completed'`) -- Resumable: Can recover from crashes/restarts - -**`worker.rs`** - Query processing pipeline -- **Background worker** that processes queries with `status = 'Queued'` -- Requeues stale InProgress jobs (>10 min) -- **Stage 1**: Embed query text -- **Stage 2**: Search top-K similar vectors in Qdrant -- **Stage 3**: Fetch file metadata from MySQL (only completed files) -- **Stage 4**: Call Gemini to analyze relationships between files -- **Stage 5**: Call Gemini for final answer synthesis (strict: no speculation) -- **Stage 6**: Save results to database -- Supports cancellation checks between stages - -**`gemini_client.rs`** - Gemini API integration -- `generate_text(prompt)` - Text generation with model switching via GEMINI_MODEL env var -- `demo_text_embedding(text)` - Demo 64-dim embeddings (replace with real Gemini embeddings) -- Falls back to demo responses if GEMINI_API_KEY not set - -**`vector_db.rs`** - Qdrant client -- `ensure_files_collection(dim)` - Create 'files' collection with Cosine distance -- `upsert_point(id, vector)` - Store file embedding -- `search_top_k(vector, k)` - Find k nearest neighbors -- `delete_point(id)` - Remove file from index - -**`storage.rs`** - File storage utilities -- `storage_dir()` - Get storage path from ASTRA_STORAGE env or default `/app/storage` -- `ensure_storage_dir()` - Create storage directory if missing -- `save_file(filename, contents)` - Save file to storage -- `delete_file(path)` - Remove file from storage - -**`models.rs`** - Data structures -- `FileRecord` - File metadata (mirrors files table) -- `QueryRecord` - Query metadata (mirrors queries table) -- `QueryStatus` enum - Queued, InProgress, Completed, Cancelled, Failed - -### 2. **Web App** (`web-app/`) -React + Vite frontend with Express backend for API proxying. - -#### Backend (`server.mjs`) -- Express server that proxies API calls to rust-engine:8000 -- Serves React static build from `/dist` -- **Why needed**: Docker networking - React can't call rust-engine directly from browser - -#### Frontend (`src/`) -- `App.jsx` - Main chat interface component -- `components/ui/chat/chat-header.jsx` - Header with debug-only "Seed Demo Data" button (visible with `?debug=1`) -- Calls `/api/files/import-demo` endpoint to bulk-load ISS PDFs - -### 3. **MySQL Database** -Two tables for metadata storage: - -**`files` table** -```sql -id VARCHAR(36) PRIMARY KEY -filename TEXT NOT NULL -path TEXT NOT NULL -description TEXT -created_at DATETIME DEFAULT CURRENT_TIMESTAMP -pending_analysis BOOLEAN DEFAULT TRUE -analysis_status VARCHAR(32) DEFAULT 'Queued' -``` - -**`queries` table** -```sql -id VARCHAR(36) PRIMARY KEY -status VARCHAR(32) NOT NULL -payload JSON -result JSON -created_at DATETIME DEFAULT CURRENT_TIMESTAMP -updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -``` - -### 4. **Qdrant Vector Database** -- Collection: `files` -- Dimension: 64 (demo) - replace with real Gemini embedding dimension -- Distance: Cosine similarity -- Stores file embeddings for semantic search - -### 5. **Demo Data** (`rust-engine/demo-data/`) -~20 ISS technical PDFs organized by subsystem: -- Electrical Power System (EPS) -- Environmental Control & Life Support (ECLSS) -- Command & Data Handling (C&DH) -- Structures & Mechanisms - -## Data Flow - -### File Upload & Analysis -``` -1. User uploads PDF → POST /api/files -2. API saves file to storage, inserts DB record (pending_analysis=true) -3. FileWorker claims pending file -4. Gemini 1.5 Flash generates description -5. Gemini 1.5 Pro generates vector graph data -6. Embed text → upsert to Qdrant -7. Mark file as ready (pending_analysis=false) -``` - -### Query Processing -``` -1. User submits query → POST /api/query/create -2. API inserts query record (status='Queued') -3. QueryWorker claims queued query -4. Embed query text -5. Search Qdrant for top-K similar files -6. Fetch file metadata from MySQL -7. Gemini analyzes relationships between files -8. Gemini synthesizes final answer (no speculation) -9. Save results to database -``` - -## Deployment - -### Development (`docker-compose.yml`) -- Local testing with hot-reload -- Bind mounts for code - -### Production (`docker-compose.prod.yml`) -- Used by GitHub Actions for deployment -- Runs rust-engine as user "1004" (github-actions) -- Docker volume: `rust-storage` → `/app/storage` -- Bind mount: `/var/www/codered-astra/rust-engine/demo-data` → `/app/demo-data:ro` -- Environment variables: - - `ASTRA_STORAGE=/app/storage` - - `DEMO_DATA_DIR=/app/demo-data` - - `QDRANT_URL=http://qdrant:6333` - - `GEMINI_API_KEY=` - - `DATABASE_URL=mysql://astraadmin:password@mysql:3306/astra` - -## Key Design Decisions - -### 1. **Two-Stage Analysis (Flash → Pro)** -- Flash is faster/cheaper for initial description -- Pro is better for deep analysis and relationship extraction -- Enables cost-effective scaling - -### 2. **Resumable Workers** -- Workers requeue stale jobs (>10 min in InProgress) -- Survives container restarts without data loss -- Atomic state transitions via SQL - -### 3. **Separation of Concerns** -- FileWorker: Makes files searchable -- QueryWorker: Answers user queries -- Independent scaling and failure isolation - -### 4. **Strict Answer Generation** -- Gemini prompted to not speculate -- Must state uncertainty when info is insufficient -- Prevents hallucination in critical ISS documentation - -### 5. **Demo Embeddings** -- Current: 64-dim deterministic embeddings from text hash -- Production: Replace with real Gemini text embeddings API -- Allows development/testing without embedding API credits - -## API Usage Examples - -### Upload File -```bash -curl -F "file=@document.pdf" http://localhost:3001/api/files -``` - -### Import Demo Data -```bash -curl -X POST http://localhost:3001/api/files/import-demo -``` - -### Create Query -```bash -curl -X POST http://localhost:3001/api/query/create \ - -H "Content-Type: application/json" \ - -d '{"q": "What is the voltage of the ISS main bus?", "top_k": 5}' -``` - -### Check Status -```bash -curl http://localhost:3001/api/query/status?id= -``` - -### Get Result -```bash -curl http://localhost:3001/api/query/result?id= -``` - -## Future Enhancements - -### High Priority -1. Real Gemini text embeddings (replace demo embeddings) -2. File status UI panel (show processing progress) -3. Health check endpoint (`/health`) -4. Data purge endpoint (clear all files/queries) - -### Medium Priority -1. Streaming query responses (SSE/WebSocket) -2. Query result caching -3. File chunking for large PDFs -4. User authentication - -### Low Priority -1. Multi-collection support (different document types) -2. Query history UI -3. File preview in chat -4. Export results to PDF - -## Troubleshooting - -### Storage Permission Errors -- Ensure `/app/storage` is owned by container user -- Docker volume must be writable by user 1004 in production - -### SQL Syntax Errors -- MySQL requires separate `CREATE TABLE` statements -- Cannot combine multiple DDL statements in one `sqlx::query()` - -### Qdrant Connection Issues -- Check QDRANT_URL environment variable -- Ensure qdrant service is running and healthy -- Verify network connectivity between containers - -### Worker Not Processing -- Check logs: `docker logs rust-engine` -- Verify database connectivity -- Look for stale InProgress jobs in queries/files tables - -## Demo Presentation (3 minutes) - -See `rust-engine/DEMODETAILS.md` for curated demo script with example queries. diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md deleted file mode 100644 index c694cfa..0000000 --- a/QUICK_REFERENCE.md +++ /dev/null @@ -1,219 +0,0 @@ -# CodeRED-Astra Quick Reference - -## System Overview - -**Two-worker architecture for ISS document RAG:** - -1. **FileWorker**: Analyzes uploaded files (Flash → Pro → Embed → Qdrant) -2. **QueryWorker**: Answers queries (Embed → Search → Relationships → Answer) - -Both workers are **resumable** and automatically recover from crashes. - -## Core Data Flow - -``` -Upload PDF → Storage → MySQL (pending) → FileWorker → Qdrant → MySQL (ready) - ↓ -User Query → MySQL (queued) → QueryWorker → Search Qdrant → Gemini → Result -``` - -## Module Map - -| Module | Purpose | Key Functions | -|--------|---------|---------------| -| `main.rs` | Entry point | Spawns workers, serves API | -| `db.rs` | Database init | Creates files/queries tables | -| `api.rs` | HTTP endpoints | Upload, list, delete, query CRUD | -| `file_worker.rs` | File analysis | Flash→Pro→embed→upsert | -| `worker.rs` | Query processing | Search→relationships→answer | -| `gemini_client.rs` | AI integration | Text generation, embeddings | -| `vector_db.rs` | Qdrant client | Upsert, search, delete | -| `storage.rs` | File management | Save/delete files | -| `models.rs` | Data structures | FileRecord, QueryRecord | - -## API Endpoints - -### Files -- `POST /api/files` - Upload file -- `POST /api/files/import-demo?force=1` - Bulk import demo PDFs -- `GET /api/files/list` - List all files with status -- `GET /api/files/delete?id=` - Delete file - -### Queries -- `POST /api/query/create` - Create query -- `GET /api/query/status?id=` - Check status -- `GET /api/query/result?id=` - Get result -- `GET /api/query/cancel?id=` - Cancel query - -## Database Schema - -### files -- `id` - UUID primary key -- `filename` - Original filename -- `path` - Storage path -- `description` - Gemini Flash description -- `pending_analysis` - FALSE when ready for search -- `analysis_status` - Queued/InProgress/Completed/Failed - -### queries -- `id` - UUID primary key -- `status` - Queued/InProgress/Completed/Cancelled/Failed -- `payload` - JSON query params `{"q": "...", "top_k": 5}` -- `result` - JSON result `{"summary": "...", "related_files": [...], "relationships": "...", "final_answer": "..."}` - -## Environment Variables - -### Required -- `GEMINI_API_KEY` - Gemini API key -- `DATABASE_URL` - MySQL connection string -- `QDRANT_URL` - Qdrant URL (default: http://qdrant:6333) - -### Optional -- `ASTRA_STORAGE` - Storage directory (default: /app/storage) -- `DEMO_DATA_DIR` - Demo data directory (default: /app/demo-data) -- `GEMINI_MODEL` - Override Gemini model (default: gemini-1.5-pro) - -## Worker States - -### FileWorker -1. **Queued** - File uploaded, awaiting processing -2. **InProgress** - Currently being analyzed -3. **Completed** - Ready for search (pending_analysis=FALSE) -4. **Failed** - Error during processing - -### QueryWorker -1. **Queued** - Query created, awaiting processing -2. **InProgress** - Currently searching/analyzing -3. **Completed** - Result available -4. **Cancelled** - User cancelled -5. **Failed** - Error during processing - -## Gemini Prompts - -### FileWorker Stage 1 (Flash) -``` -Describe the file '{filename}' and extract all key components, keywords, -and details for later vectorization. Be comprehensive and factual. -``` - -### FileWorker Stage 2 (Pro) -``` -Given the file '{filename}' and its description: {desc} -Generate a set of vector graph data (keywords, use cases, relationships) -that can be used for broad and precise search. Only include what is -directly supported by the file. -``` - -### QueryWorker Stage 4 (Relationships) -``` -You are an assistant analyzing relationships STRICTLY within the provided files. -Query: {query} -Files: {file_list} -Tasks: -1) Summarize key details from the files relevant to the query. -2) Describe relationships and linkages strictly supported by these files. -3) List important follow-up questions that could be answered only using the provided files. -Rules: Do NOT guess or invent. If information is insufficient in the files, explicitly state that. -``` - -### QueryWorker Stage 5 (Final Answer) -``` -You are to compose a final answer to the user query using only the information from the files. -Query: {query} -Files considered: {file_list} -Relationship analysis: {relationships} -Requirements: -- Use only information present in the files and analysis above. -- If the answer is uncertain or cannot be determined from the files, clearly state that limitation. -- Avoid speculation or assumptions. -Provide a concise, structured answer. -``` - -## Docker Architecture - -### Services -- **rust-engine** - Warp API + workers (port 8000) -- **web-app** - Express + React (port 3001) -- **mysql** - MySQL 9.1 (port 3306) -- **qdrant** - Qdrant vector DB (port 6333) -- **phpmyadmin** - DB admin UI (port 8080) - -### Volumes (Production) -- `rust-storage:/app/storage` - File storage (writable) -- `/var/www/codered-astra/rust-engine/demo-data:/app/demo-data:ro` - Demo PDFs (read-only) -- `~/astra-logs:/var/log` - Log files - -## Common Issues - -### 1. SQL Syntax Error -**Problem**: `error near 'CREATE TABLE'` -**Cause**: Multiple CREATE TABLE in one query -**Fix**: Split into separate `sqlx::query()` calls - -### 2. Permission Denied -**Problem**: `Permission denied (os error 13)` -**Cause**: Container user can't write to storage -**Fix**: Use Docker volume, ensure ownership matches container user - -### 3. Worker Not Processing -**Problem**: Files/queries stuck in Queued -**Cause**: Worker crashed or not started -**Fix**: Check logs, ensure workers spawned in main.rs - -### 4. Qdrant Connection Failed -**Problem**: `qdrant upsert/search failed` -**Cause**: Qdrant not running or wrong URL -**Fix**: Verify QDRANT_URL, check qdrant container health - -## Development Commands - -```bash -# Build and run locally -cd rust-engine -cargo build -cargo run - -# Check code -cargo check - -# Run with logs -RUST_LOG=info cargo run - -# Docker compose (dev) -docker-compose up --build - -# Docker compose (production) -docker-compose -f docker-compose.prod.yml up -d - -# View logs -docker logs rust-engine -f - -# Rebuild single service -docker-compose build rust-engine -docker-compose up -d rust-engine -``` - -## Testing Flow - -1. Start services: `docker-compose up -d` -2. Import demo data: `curl -X POST http://localhost:3001/api/files/import-demo` -3. Wait for FileWorker to complete (~30 seconds for 20 files) -4. Check file status: `curl http://localhost:3001/api/files/list` -5. Create query: `curl -X POST http://localhost:3001/api/query/create -H "Content-Type: application/json" -d '{"q": "ISS main bus voltage", "top_k": 5}'` -6. Check status: `curl http://localhost:3001/api/query/status?id=` -7. Get result: `curl http://localhost:3001/api/query/result?id=` - -## Performance Notes - -- FileWorker: ~1-2 sec per file (demo embeddings) -- QueryWorker: ~3-5 sec per query (search + 2 Gemini calls) -- Qdrant search: <100ms for 1000s of vectors -- MySQL queries: <10ms for simple selects - -## Security Considerations - -- Store GEMINI_API_KEY in GitHub Secrets (production) -- Use environment variables for all credentials -- Don't commit `.env` files -- Restrict phpmyadmin to internal network only -- Use HTTPS in production deployment diff --git a/README.md b/README.md deleted file mode 100644 index b0b6190..0000000 --- a/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# CodeRED-Astra 🚀 - -A hackathon-ready project with React frontend and Rust backend engine. - -## Quick Start - -```bash -# 1. Setup environment -cp .env.example .env -# Edit .env with your credentials - -# 2. Start everything with Docker -docker-compose up --build - -# 3. Access your app -# Frontend: http://localhost -# API: http://localhost:8000 -# Database Admin: http://127.0.0.1:8080 -``` - -## Development - -**Frontend (React + Vite)**: -```bash -cd web-app -npm install -npm run dev # http://localhost:5173 -``` - -**Backend (Rust)**: -```bash -cd rust-engine -cargo run # http://localhost:8000 -``` - -## Architecture - -- **Frontend**: React 18 + Vite + Tailwind CSS -- **Backend**: Rust + Warp + SQLx -- **Database**: MySQL 8.0 + phpMyAdmin -- **API**: RESTful endpoints with CORS enabled -- **Docker**: Full containerization for easy deployment - -## Project Structure - -``` -├── web-app/ # React frontend -│ ├── src/ -│ │ ├── App.jsx # Main component -│ │ └── main.jsx # Entry point -│ └── Dockerfile -├── rust-engine/ # Rust backend -│ ├── src/ -│ │ └── main.rs # API server -│ └── Dockerfile -├── docker-compose.yml # Full stack orchestration -└── .env.example # Environment template -``` - -## Team Workflow - -- **Frontend devs**: Work in `web-app/src/`, use `/api/*` for backend calls -- **Backend devs**: Work in `rust-engine/src/`, add endpoints to main.rs -- **Database**: Access phpMyAdmin at http://127.0.0.1:8080 - -## Features - -✅ Hot reload for both frontend and backend -✅ Automatic API proxying from React to Rust -✅ Database connection with graceful fallback -✅ CORS configured for cross-origin requests -✅ Production-ready Docker containers -✅ Health monitoring and status dashboard - -Ready for your hackathon! See `DEVELOPMENT.md` for detailed setup instructions. diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml deleted file mode 100644 index b055e2d..0000000 --- a/docker-compose.prod.yml +++ /dev/null @@ -1,70 +0,0 @@ -services: - web-app: - image: ghcr.io/${REPO_NAME_LOWER}/web-app:${IMAGE_TAG} - restart: always - ports: - - "127.0.0.1:3033:3000" - environment: - - DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql:3306/${MYSQL_DATABASE} - - RUST_ENGINE_URL=http://rust-engine:8000 - - GEMINI_API_KEY=${GEMINI_API_KEY} - depends_on: - - mysql - - rust-engine - - rust-engine: - image: ghcr.io/${REPO_NAME_LOWER}/rust-engine:${IMAGE_TAG} - restart: always - environment: - - DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql:3306/${MYSQL_DATABASE} - - ASTRA_STORAGE=/app/storage - - DEMO_DATA_DIR=/app/demo-data - - QDRANT_URL=http://qdrant:6333 - - GEMINI_API_KEY=${GEMINI_API_KEY} - depends_on: - - mysql - - qdrant - user: "1004" - volumes: - - ~/astra-logs:/var/log - - rust-storage:/app/storage - - /var/www/codered-astra/rust-engine/demo-data:/app/demo-data:ro - - mysql: - image: mysql:8.0 - restart: always - ports: - - "45.43.2.25:3306:3306" - environment: - - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} - - MYSQL_DATABASE=${MYSQL_DATABASE} - - MYSQL_USER=${MYSQL_USER} - - MYSQL_PASSWORD=${MYSQL_PASSWORD} - volumes: - - mysql-data:/var/lib/mysql - - phpmyadmin: - image: phpmyadmin/phpmyadmin - restart: always - ports: - - "127.0.0.1:8080:80" - environment: - - PMA_HOST=mysql - depends_on: - - mysql - - qdrant: - image: qdrant/qdrant:latest - restart: unless-stopped - ports: - - "127.0.0.1:6333:6333" - volumes: - - qdrant-data:/qdrant/storage - environment: - - QDRANT__SERVICE__GRPC_PORT=6334 - # expose to rust-engine via service name 'qdrant' - -volumes: - mysql-data: - qdrant-data: - rust-storage: diff --git a/docker-compose.yml b/docker-compose.yml index 78e4c61..393e5d9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,8 @@ services: - DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql:3306/${MYSQL_DATABASE} - RUST_ENGINE_URL=http://rust-engine:8000 - GEMINI_API_KEY=${GEMINI_API_KEY} + volumes: + - rust-storage:/app/storage:ro depends_on: - mysql # <-- Updated dependency - rust-engine diff --git a/web-app/Dockerfile b/frontend/Dockerfile similarity index 92% rename from web-app/Dockerfile rename to frontend/Dockerfile index 9552c3f..5a98079 100644 --- a/web-app/Dockerfile +++ b/frontend/Dockerfile @@ -2,7 +2,7 @@ FROM node:23-alpine WORKDIR /app COPY package*.json ./ -RUN npm ci +RUN npm i COPY . . RUN npm run format && npm run build EXPOSE 3000 diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1 @@ + diff --git a/web-app/eslint.config.js b/frontend/eslint.config.js similarity index 51% rename from web-app/eslint.config.js rename to frontend/eslint.config.js index 53a87c7..050e89d 100644 --- a/web-app/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,18 +1,15 @@ import js from "@eslint/js"; import globals from "globals"; -import reactHooks from "eslint-plugin-react-hooks"; // Or import { configs as reactHooks } from "eslint-plugin-react-hooks"; +import reactHooks from "eslint-plugin-react-hooks"; import reactRefresh from "eslint-plugin-react-refresh"; -import { defineConfig, globalIgnores } from "eslint/config"; -export default defineConfig([ - globalIgnores(["dist"]), +export default [ { - files: ["**/*{js,jsx}"], - extends: [ - js.configs.recommended, - reactHooks.configs["recommended-latest"], - reactRefresh.configs.vite, - ], + ignores: ["dist/**"], + }, + js.configs.recommended, + { + files: ["**/*.{js,jsx}"], languageOptions: { ecmaVersion: 2020, globals: globals.browser, @@ -22,8 +19,17 @@ export default defineConfig([ sourceType: "module", }, }, + plugins: { + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + }, rules: { + ...reactHooks.configs.recommended.rules, + "react-refresh/only-export-components": [ + "warn", + { allowConstantExport: true }, + ], "no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }], }, }, -]); +]; diff --git a/web-app/index.html b/frontend/index.html similarity index 100% rename from web-app/index.html rename to frontend/index.html diff --git a/web-app/package-lock.json b/frontend/package-lock.json similarity index 99% rename from web-app/package-lock.json rename to frontend/package-lock.json index aa53229..7dca15a 100644 --- a/web-app/package-lock.json +++ b/frontend/package-lock.json @@ -33,6 +33,7 @@ }, "devDependencies": { "@eslint/js": "^9.38.0", + "daisyui": "^5.4.7", "eslint": "^9.38.0", "eslint-plugin-import": "^2.32.0", "eslint-plugin-react": "^7.37.5", @@ -2906,6 +2907,16 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/daisyui": { + "version": "5.4.7", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.4.7.tgz", + "integrity": "sha512-2wYO61vTPCXk7xEBgnzLZAYoE0xS5IRLu/GSq0vORpB+cTrtubdx69NnA0loc0exvCY1s2fYL4lGZtFHe2ohNQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/saadeghi/daisyui?sponsor=1" + } + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", diff --git a/web-app/package.json b/frontend/package.json similarity index 98% rename from web-app/package.json rename to frontend/package.json index 5f5becb..b1476ae 100644 --- a/web-app/package.json +++ b/frontend/package.json @@ -40,6 +40,7 @@ "packageManager": ">=npm@10.9.0", "devDependencies": { "@eslint/js": "^9.38.0", + "daisyui": "^5.4.7", "eslint": "^9.38.0", "eslint-plugin-import": "^2.32.0", "eslint-plugin-react": "^7.37.5", diff --git a/web-app/src/app/index.jsx b/frontend/src/app/index.jsx similarity index 100% rename from web-app/src/app/index.jsx rename to frontend/src/app/index.jsx diff --git a/frontend/src/components/layouts/chat-layout.jsx b/frontend/src/components/layouts/chat-layout.jsx new file mode 100644 index 0000000..951d54c --- /dev/null +++ b/frontend/src/components/layouts/chat-layout.jsx @@ -0,0 +1,244 @@ +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import ChatHeader from "src/components/ui/chat/chat-header"; +import ChatWindow from "src/components/ui/chat/chat-window"; +import MessageInput from "src/components/ui/chat/message-input"; +import { + createQuery, + getQueryResult, + getQueryStatus, + listFiles, +} from "src/lib/api"; + +const createId = () => + globalThis.crypto?.randomUUID?.() ?? `id-${Date.now()}-${Math.random()}`; + +const INTRO_MESSAGE = { + id: "intro", + role: "assistant", + content: + "Ask me about the demo PDFs and I'll respond with the best matches pulled from the processed files.", +}; + +export default function ChatLayout() { + const [messages, setMessages] = useState([INTRO_MESSAGE]); + const [isProcessing, setIsProcessing] = useState(false); + const [files, setFiles] = useState([]); + const [errorToast, setErrorToast] = useState(""); + const pollAbortRef = useRef(null); + + const showError = useCallback((message) => { + setErrorToast(message); + window.setTimeout(() => setErrorToast(""), 5000); + }, []); + + const refreshFiles = useCallback(async () => { + try { + const latest = await listFiles(); + setFiles(latest); + } catch (error) { + showError(error.message ?? "Failed to load files"); + } + }, [showError]); + + useEffect(() => { + refreshFiles(); + }, [refreshFiles]); + + useEffect(() => { + return () => { + if (pollAbortRef.current) { + pollAbortRef.current.aborted = true; + } + }; + }, []); + + const buildAssistantMarkdown = useCallback((result) => { + if (!result || typeof result !== "object") { + return "I could not find a response for that request."; + } + + const finalAnswer = result.final_answer?.trim(); + const relationships = result.relationships?.trim(); + const relatedFiles = Array.isArray(result.related_files) + ? result.related_files + : []; + + const fileLines = relatedFiles + .filter((f) => f && typeof f === "object") + .map((file) => { + const filename = file.filename || file.id || "download"; + const storageUrl = file.storage_url || `/storage/${filename}`; + const linkTarget = storageUrl.startsWith("/storage/") + ? `/storage/${encodeURIComponent(storageUrl.replace("/storage/", ""))}` + : storageUrl; + const description = file.description?.trim(); + const score = + typeof file.score === "number" + ? ` _(score: ${file.score.toFixed(3)})_` + : ""; + const detail = description ? ` — ${description}` : ""; + return `- [${filename}](${linkTarget})${detail}${score}`; + }); + + let content = + finalAnswer || + "I could not determine an answer from the indexed documents yet."; + + if (fileLines.length) { + content += `\n\n**Related Files**\n${fileLines.join("\n")}`; + } + + if (relationships && relationships !== finalAnswer) { + content += `\n\n---\n${relationships}`; + } + + if (!fileLines.length && (!finalAnswer || finalAnswer.length < 10)) { + content += + "\n\n_No analyzed documents matched yet. Try seeding demo data or wait for processing to finish._"; + } + + return content; + }, []); + + const waitForResult = useCallback(async (id) => { + const abortState = { aborted: false }; + pollAbortRef.current = abortState; + const timeoutMs = 120_000; + const intervalMs = 1_500; + const started = Date.now(); + + while (!abortState.aborted) { + if (Date.now() - started > timeoutMs) { + throw new Error("Timed out waiting for the query to finish"); + } + + const statusPayload = await getQueryStatus(id); + const status = statusPayload?.status; + + if (status === "Completed") { + const resultPayload = await getQueryResult(id); + return resultPayload?.result; + } + + if (status === "Failed") { + const resultPayload = await getQueryResult(id); + const reason = resultPayload?.result?.error || "Query failed"; + throw new Error(reason); + } + + if (status === "Cancelled") { + throw new Error("Query was cancelled"); + } + + if (status === "not_found") { + throw new Error("Query was not found"); + } + + await new Promise((resolve) => window.setTimeout(resolve, intervalMs)); + } + + throw new Error("Query polling was aborted"); + }, []); + + const handleSend = useCallback( + async (text) => { + if (isProcessing) { + showError("Please wait for the current response to finish."); + return; + } + + const userEntry = { + id: createId(), + role: "user", + content: text, + }; + setMessages((prev) => [...prev, userEntry]); + + const placeholderId = createId(); + setMessages((prev) => [ + ...prev, + { + id: placeholderId, + role: "assistant", + content: "_Analyzing indexed documents..._", + pending: true, + }, + ]); + + setIsProcessing(true); + + try { + const payload = { q: text, top_k: 5 }; + const created = await createQuery(payload); + const result = await waitForResult(created.id); + const content = buildAssistantMarkdown(result); + setMessages((prev) => + prev.map((message) => + message.id === placeholderId + ? { ...message, content, pending: false } + : message, + ), + ); + } catch (error) { + const message = error?.message || "Something went wrong."; + setMessages((prev) => + prev.map((entry) => + entry.id === placeholderId + ? { + ...entry, + content: `⚠️ ${message}`, + pending: false, + error: true, + } + : entry, + ), + ); + showError(message); + } finally { + pollAbortRef.current = null; + setIsProcessing(false); + refreshFiles(); + } + }, + [ + isProcessing, + showError, + refreshFiles, + waitForResult, + buildAssistantMarkdown, + ], + ); + + const handleDeleteAll = useCallback(() => { + if (!window.confirm("Delete all messages?")) { + return; + } + setMessages([INTRO_MESSAGE]); + }, []); + + const latestFileSummary = useMemo(() => { + if (!files.length) return "No files indexed yet."; + const pending = files.filter((f) => f.pending_analysis).length; + const ready = files.length - pending; + return `${ready} ready • ${pending} processing`; + }, [files]); + + return ( +
+ + + +
+ ); +} diff --git a/frontend/src/components/ui/button/delete-button.jsx b/frontend/src/components/ui/button/delete-button.jsx new file mode 100644 index 0000000..37e008e --- /dev/null +++ b/frontend/src/components/ui/button/delete-button.jsx @@ -0,0 +1,19 @@ +import { Flame } from "lucide-react"; +import { motion } from "motion/react"; + +export default function FlameButton({ onClick, disabled = false }) { + return ( + + + + ); +} diff --git a/web-app/src/components/ui/button/down-button.jsx b/frontend/src/components/ui/button/down-button.jsx similarity index 100% rename from web-app/src/components/ui/button/down-button.jsx rename to frontend/src/components/ui/button/down-button.jsx diff --git a/web-app/src/components/ui/button/schematic-button.jsx b/frontend/src/components/ui/button/schematic-button.jsx similarity index 100% rename from web-app/src/components/ui/button/schematic-button.jsx rename to frontend/src/components/ui/button/schematic-button.jsx diff --git a/frontend/src/components/ui/chat/chat-header.jsx b/frontend/src/components/ui/chat/chat-header.jsx new file mode 100644 index 0000000..b5d9a63 --- /dev/null +++ b/frontend/src/components/ui/chat/chat-header.jsx @@ -0,0 +1,89 @@ +import React, { useEffect, useMemo, useState } from "react"; +import { motion } from "motion/react"; +import { Rocket } from "lucide-react"; +import DeleteButton from "src/components/ui/button/delete-button"; +import SchematicButton from "src/components/ui/button/schematic-button"; + +export default function ChatHeader({ + title = "Title of Chat", + onClear, + busy = false, + fileSummary, + errorMessage, +}) { + const isDebug = useMemo(() => { + const p = new URLSearchParams(window.location.search); + return p.get("debug") === "1"; + }, []); + const [ingesting, setIngesting] = useState(false); + const [toast, setToast] = useState(""); + const [externalToast, setExternalToast] = useState(""); + + useEffect(() => { + if (!errorMessage) return; + setExternalToast(errorMessage); + const timer = window.setTimeout(() => setExternalToast(""), 5000); + return () => window.clearTimeout(timer); + }, [errorMessage]); + + async function triggerDemoIngest() { + try { + setIngesting(true); + const res = await fetch("/api/files/import-demo", { method: "POST" }); + const json = await res.json().catch(() => ({})); + const imported = json.imported ?? "?"; + const skipped = json.skipped ?? "?"; + const summary = `Imported: ${imported}, Skipped: ${skipped}`; + setToast(json.error ? `${summary} - ${json.error}` : summary); + setTimeout(() => setToast(""), 4000); + } catch (e) { + setToast("Import failed"); + setTimeout(() => setToast(""), 4000); + } finally { + setIngesting(false); + } + } + + return ( +
+
+
+ +
+

+ {title} +

+ {fileSummary && ( +
+ {fileSummary} +
+ )} + + {isDebug && ( + + + {ingesting ? "Seeding…" : "Seed Demo Data"} + + )} +
+
+ {toast && ( +
+ {toast} +
+ )} + {externalToast && ( +
+ {externalToast} +
+ )} +
+
+ ); +} diff --git a/web-app/src/components/ui/chat/chat-window.jsx b/frontend/src/components/ui/chat/chat-window.jsx similarity index 62% rename from web-app/src/components/ui/chat/chat-window.jsx rename to frontend/src/components/ui/chat/chat-window.jsx index 2b78ce2..1252d54 100644 --- a/web-app/src/components/ui/chat/chat-window.jsx +++ b/frontend/src/components/ui/chat/chat-window.jsx @@ -4,10 +4,11 @@ import { MARKDOWN_COMPONENTS } from "src/config/markdown"; function MessageBubble({ message }) { const isUser = message.role === "user"; + const isError = !!message.error; return (
{isUser ? (
{message.content}
@@ -22,12 +23,21 @@ function MessageBubble({ message }) { } export default function ChatWindow({ messages }) { + const bottomRef = useRef(null); + + useEffect(() => { + if (bottomRef.current) { + bottomRef.current.scrollIntoView({ behavior: "smooth" }); + } + }, [messages]); + return (
{messages.map((m, i) => ( - + ))} +
); diff --git a/web-app/src/components/ui/chat/message-input.jsx b/frontend/src/components/ui/chat/message-input.jsx similarity index 86% rename from web-app/src/components/ui/chat/message-input.jsx rename to frontend/src/components/ui/chat/message-input.jsx index dc6c2aa..0f49a7f 100644 --- a/web-app/src/components/ui/chat/message-input.jsx +++ b/frontend/src/components/ui/chat/message-input.jsx @@ -3,7 +3,7 @@ import DownButton from "src/components/ui/button/down-button"; import { motion } from "motion/react"; import { BotMessageSquare } from "lucide-react"; -export default function MessageInput({ onSend, onMessage }) { +export default function MessageInput({ onSend, disabled = false }) { const [text, setText] = useState(""); const textareaRef = useRef(null); @@ -14,9 +14,7 @@ export default function MessageInput({ onSend, onMessage }) { async function handleSubmit(e) { e.preventDefault(); - if (!text.trim()) return; - - // send user message locally + if (!text.trim() || disabled) return; onSend(text.trim()); // create query on backend @@ -79,6 +77,7 @@ export default function MessageInput({ onSend, onMessage }) { ref={textareaRef} value={text} onChange={(e) => { + if (disabled) return; setText(e.target.value); // auto-resize const ta = textareaRef.current; @@ -97,12 +96,17 @@ export default function MessageInput({ onSend, onMessage }) { placeholder="Type a message..." rows={1} className="flex-1 mx-2 rounded-md shadow-2sx border-none focus:border-none focus:outline-none resize-none overflow-auto max-h-40" + disabled={disabled} /> diff --git a/web-app/src/components/ui/file/file-list.jsx b/frontend/src/components/ui/file/file-list.jsx similarity index 100% rename from web-app/src/components/ui/file/file-list.jsx rename to frontend/src/components/ui/file/file-list.jsx diff --git a/web-app/src/config/markdown.jsx b/frontend/src/config/markdown.jsx similarity index 100% rename from web-app/src/config/markdown.jsx rename to frontend/src/config/markdown.jsx diff --git a/web-app/src/features/gemini/gemini.js b/frontend/src/features/gemini/gemini.js similarity index 100% rename from web-app/src/features/gemini/gemini.js rename to frontend/src/features/gemini/gemini.js diff --git a/web-app/src/index.css b/frontend/src/index.css similarity index 95% rename from web-app/src/index.css rename to frontend/src/index.css index 2b88b1a..d513070 100644 --- a/web-app/src/index.css +++ b/frontend/src/index.css @@ -1,4 +1,5 @@ @import "tailwindcss"; +@import "daisyui"; .dark { --paragraph: 235, 236, 239; diff --git a/web-app/src/main.jsx b/frontend/src/main.jsx similarity index 100% rename from web-app/src/main.jsx rename to frontend/src/main.jsx diff --git a/web-app/tailwind.config.ts b/frontend/tailwind.config.ts similarity index 100% rename from web-app/tailwind.config.ts rename to frontend/tailwind.config.ts diff --git a/web-app/vite.config.js b/frontend/vite.config.js similarity index 95% rename from web-app/vite.config.js rename to frontend/vite.config.js index a0cbe7b..8e11c92 100644 --- a/web-app/vite.config.js +++ b/frontend/vite.config.js @@ -9,7 +9,6 @@ try { console.log("Env file not found!\n" + error) } -// https://vite.dev/config/ export default defineConfig({ plugins: [tailwindcss(), react(), jsconfigPaths()], resolve: { diff --git a/rust-engine/Cargo.lock b/rust-engine/Cargo.lock deleted file mode 100644 index 57988d6..0000000 --- a/rust-engine/Cargo.lock +++ /dev/null @@ -1,3032 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" - -[[package]] -name = "bitflags" -version = "2.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" -dependencies = [ - "serde", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "cc" -version = "1.2.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link 0.2.1", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -dependencies = [ - "serde", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "find-msvc-tools" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "h2" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" - -[[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] -name = "headers" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" -dependencies = [ - "base64", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "http" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots 1.0.3", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" - -[[package]] -name = "icu_properties" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" - -[[package]] -name = "icu_provider" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" -dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" -dependencies = [ - "equivalent", - "hashbrown 0.16.0", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "js-sys" -version = "0.3.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] - -[[package]] -name = "libc" -version = "0.2.177" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" - -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - -[[package]] -name = "libredox" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" -dependencies = [ - "bitflags", - "libc", - "redox_syscall", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" -dependencies = [ - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "litemap" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "mio" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "multer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "memchr", - "mime", - "spin", - "version_check", -] - -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "openssl" -version = "0.10.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.110" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link 0.2.1", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "potential_utf" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro2" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "reqwest" -version = "0.12.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 1.0.3", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rsa" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - -[[package]] -name = "rust-engine" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "bytes", - "chrono", - "dotenvy", - "futures-util", - "lazy_static", - "reqwest", - "serde", - "serde_json", - "sqlx", - "tokio", - "tokio-util", - "tracing", - "tracing-subscriber", - "uuid", - "warp", -] - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustix" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "schannel" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.145" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" -dependencies = [ - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -dependencies = [ - "serde", -] - -[[package]] -name = "socket2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sqlx" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" -dependencies = [ - "base64", - "bytes", - "chrono", - "crc", - "crossbeam-queue", - "either", - "event-listener", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.15.5", - "hashlink", - "indexmap", - "log", - "memchr", - "once_cell", - "percent-encoding", - "rustls", - "serde", - "serde_json", - "sha2", - "smallvec", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "url", - "uuid", - "webpki-roots 0.26.11", -] - -[[package]] -name = "sqlx-macros" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" -dependencies = [ - "dotenvy", - "either", - "heck", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" -dependencies = [ - "atoi", - "base64", - "bitflags", - "byteorder", - "bytes", - "chrono", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.5", - "rsa", - "serde", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" -dependencies = [ - "atoi", - "base64", - "bitflags", - "byteorder", - "chrono", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand 0.8.5", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" -dependencies = [ - "atoi", - "chrono", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "thiserror", - "tracing", - "url", - "uuid", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tempfile" -version = "3.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" -dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" -dependencies = [ - "nu-ansi-term", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - -[[package]] -name = "unicode-ident" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" -dependencies = [ - "getrandom 0.3.4", - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "warp" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d06d9202adc1f15d709c4f4a2069be5428aa912cc025d6f268ac441ab066b0" -dependencies = [ - "bytes", - "futures-util", - "headers", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "log", - "mime", - "mime_guess", - "multer", - "percent-encoding", - "pin-project", - "scoped-tls", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-util", - "tower-service", - "tracing", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - -[[package]] -name = "wasm-bindgen" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.3", -] - -[[package]] -name = "webpki-roots" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "whoami" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - -[[package]] -name = "yoke" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[package]] -name = "zerotrie" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/rust-engine/Cargo.toml b/rust-engine/Cargo.toml deleted file mode 100644 index 8613870..0000000 --- a/rust-engine/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -# rust-engine/Cargo.toml - -[package] -name = "rust-engine" -version = "0.1.0" -edition = "2021" - -[dependencies] -tokio = { version = "1.38.0", features = ["full"] } -warp = { version = "0.4.2", features = ["server", "multipart"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "mysql", "chrono", "uuid", "macros"] } -chrono = { version = "0.4", features = ["serde"] } -tracing = "0.1" -tracing-subscriber = "0.3" -dotenvy = "0.15.7" # Switched from unmaintained 'dotenv' -anyhow = "1.0" -uuid = { version = "1", features = ["serde", "v4"] } -reqwest = { version = "0.12.24", features = ["json", "rustls-tls"] } -async-trait = "0.1" -tokio-util = "0.7" -futures-util = "0.3" -lazy_static = "1.4" -bytes = "1.4" diff --git a/rust-engine/DEMODETAILS.md b/rust-engine/DEMODETAILS.md deleted file mode 100644 index a8a2572..0000000 --- a/rust-engine/DEMODETAILS.md +++ /dev/null @@ -1,48 +0,0 @@ -## Demo Runbook: ISS Systems (3-minute showcase) - -This demo uses ~20 public NASA PDFs covering ISS Electrical Power, ECLSS, Avionics, and Structures. They live in `rust-engine/demo-data` and are automatically ingested via the server. - -### 1) Seed demo data (one-click) - -- Trigger ingestion (cloud): POST `/api/files/import-demo` (UI button available when `?debug=1` is present) -- The backend copies PDFs into storage, inserts DB rows with `pending_analysis = true`, and the FileWorker processes them. -- Processing pipeline per file: - - Gemini Flash → comprehensive description (facts/keywords/components) - - Gemini Pro → deep vector graph data (keywords/use cases/relationships) - - Embed + upsert to Qdrant, mark file ready (`pending_analysis = false`) - -Tip: You can list files at `GET /api/files/list`. Ready files will start to appear as analysis completes. - -### 2) Showcase flow (suggested script) - -1. “We ingested real ISS technical PDFs. The worker analyzes each file with Gemini and builds vector graph data for robust retrieval.” -2. Show the files list. Point out a couple of recognizable titles. -3. Run two queries (examples below) and open their results (the app calls `POST /api/query/create` then polls `/api/query/result`). -4. Highlight the grounded answer: ‘related_files’, ‘relationships’, and ‘final_answer’ fields. -5. Call out that if info isn’t present in the PDFs, the system explicitly states uncertainty (no guessing). - -### 3) Demo queries (pick 2–3) - -- Electrical Power System (EPS) - - “Trace the power path from the P6 solar array to the BCDU. Where are likely ground fault points?” - - “What is the role of the DC Switching Unit in array power management?” -- ECLSS - - “Which modules are part of water recovery, and how does the Oxygen Generator Assembly interface?” - - “Summarize the CDRA cycle and downstream subsystems it impacts.” -- C&DH / Avionics - - “In the US Lab, a blue/white wire connects to MDM ‘LAB1’. What are possible data pathways?” - - “Describe the onboard LAN segments and links to MDMs.” -- Structures / Robotics - - “Where does the Latching End Effector connect on S1 truss?” - - “What is the Mobile Transporter’s role in SSRMS operations?” - -### 4) Reset/refresh (optional) - -- POST `/api/files/import-demo?force=1` to overwrite by filename and re-queue analysis. - -### Appendix: Example sources - -- EPS: 20110014867, 20040171627, 19900007297, 20120002931, 20100029672 -- ECLSS: 20170008316, 20070019910, 20080039691, 20100029191, 20070019929 -- C&DH: 20000012543, 20100029690, 19950014639, 20010023477, 19980227289 -- Structures/Robotics: 20020054238, 20010035542, 20140001008, Destiny fact sheet, 20020088289 \ No newline at end of file diff --git a/rust-engine/Dockerfile b/rust-engine/Dockerfile deleted file mode 100644 index 56e962d..0000000 --- a/rust-engine/Dockerfile +++ /dev/null @@ -1,90 +0,0 @@ -# syntax=docker/dockerfile:1.7 -# rust-engine/Dockerfile - -# --- Stage 1: Builder --- -# Use a stable Rust version -FROM rust:slim AS builder -WORKDIR /usr/src/app - -# Install build dependencies needed for sqlx -RUN apt-get update && apt-get install -y --no-install-recommends \ - pkg-config \ - libssl-dev \ - curl \ - build-essential \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* - - -# Allow optional override of toolchain (e.g., nightly or a pinned version). Leave empty to use image default. -ARG RUSTUP_TOOLCHAIN= - -# Use rustup and cargo from the official Rust image location -ENV PATH="/usr/local/cargo/bin:${PATH}" - -# Copy manifest files first to leverage Docker layer caching for dependencies -COPY Cargo.toml Cargo.lock rust-toolchain.toml ./ - -# Ensure the pinned toolchain from rust-toolchain.toml (or provided ARG) is installed only if missing -RUN set -eux; \ - if [ -n "${RUSTUP_TOOLCHAIN}" ]; then \ - if ! rustup toolchain list | grep -q "^${RUSTUP_TOOLCHAIN}"; then \ - rustup toolchain install "${RUSTUP_TOOLCHAIN}"; \ - fi; \ - rustup default "${RUSTUP_TOOLCHAIN}"; \ - else \ - if [ -f rust-toolchain.toml ]; then \ - TOOLCHAIN=$(sed -n 's/^channel *= *"\(.*\)"/\1/p' rust-toolchain.toml | head -n1); \ - if [ -n "$TOOLCHAIN" ]; then \ - if ! rustup toolchain list | grep -q "^$TOOLCHAIN"; then \ - rustup toolchain install "$TOOLCHAIN"; \ - fi; \ - rustup default "$TOOLCHAIN"; \ - fi; \ - fi; \ - fi; \ - rustup show active-toolchain || true - -# Create a dummy src to allow cargo to download dependencies into the cache layer -RUN mkdir -p src && echo "fn main() { println!(\"cargo cache build\"); }" > src/main.rs - -# Warm up dependency caches without compiling a dummy binary -RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ - --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \ - cargo fetch - - -# Remove dummy main.rs before copying the real source -RUN rm -f src/main.rs -COPY src ./src -# Build the real binary -RUN cargo build --release --locked - -# --- Stage 2: Final, small image --- - -FROM debian:bookworm-slim -# Install only necessary runtime dependencies (no upgrade, just ca-certificates) -RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* - -# Add a non-root user for security -RUN useradd --system --uid 10001 --no-create-home --shell /usr/sbin/nologin appuser - -# Copy the compiled binary from the builder stage - -COPY --from=builder /usr/src/app/target/release/rust-engine /usr/local/bin/rust-engine - -# Create writable storage and logs directories for appuser -RUN chown appuser:appuser /usr/local/bin/rust-engine \ - && mkdir -p /var/log /app/storage /app/demo-data \ - && touch /var/log/astra-errors.log \ - && chown -R appuser:appuser /var/log /app - -# Set working directory to a writable location -WORKDIR /app - -# Switch to non-root user -USER appuser - -EXPOSE 8000 -# Redirect all output to /var/log/astra-errors.log for easy monitoring -ENTRYPOINT ["/bin/sh", "-c", "/usr/local/bin/rust-engine >> /var/log/astra-errors.log 2>&1"] \ No newline at end of file diff --git a/rust-engine/README.md b/rust-engine/README.md deleted file mode 100644 index 1f815c4..0000000 --- a/rust-engine/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# Rust Engine API and Worker - -## Overview - -- HTTP API (warp) under /api for file management and query lifecycle -- MySQL for metadata, Qdrant for vector similarity -- Background worker resumes queued work and re-queues stale InProgress jobs at startup - -## Environment variables - -- DATABASE_URL: mysql://USER:PASS@HOST:3306/DB -- QDRANT_URL: default -- GEMINI_API_KEY: used for Gemini content generation (optional in demo) - -## Endpoints (JSON) - -- POST /api/files (multipart) - - Form: file=@path - - Response: {"success": true} - -- GET /api/files/list - - Response: {"files": [{"id","filename","path","description"}]} - -- GET /api/files/delete?id= - - Response: {"deleted": true|false} - -- POST /api/query/create - - Body: {"q": "text", "top_k": 5} - - Response: {"id": "uuid"} - -- GET /api/query/status?id= - - Response: {"status": "Queued"|"InProgress"|"Completed"|"Cancelled"|"Failed"|"not_found"} - -- GET /api/query/result?id= - - Response (Completed): - { - "result": { - "summary": "Found N related files", - "related_files": [ - {"id","filename","path","description","score"} - ], - "relationships": "...", - "final_answer": "..." - } - } - -- GET /api/query/cancel?id= - - Response: {"cancelled": true} - -## Worker behavior - -- Ensures Qdrant collection exists (dim 64, cosine) -- Re-queues InProgress older than 10 minutes -- Processing stages: - 1) Set InProgress - 2) Embed query text (demo now; pluggable Gemini later) - 3) Search Qdrant top_k (default 5) - 4) Join file metadata (MySQL) - 5) Gemini step: relationship analysis (strictly from provided files) - 6) Gemini step: final answer (no speculation; say unknown if insufficient) - 7) Persist result (JSON) and set Completed - - Checks for cancellation between stages - -## Local quickstart - -1. docker compose up -d mysql qdrant -2. set env DATABASE_URL and QDRANT_URL -3. cargo run -4. (optional) import demo PDFs - - Ensure demo files are located in `rust-engine/demo-data` (default) or set `DEMO_DATA_DIR` env var to a folder containing PDFs. - - Call the endpoint: - - POST - - Optional query `?force=1` to overwrite existing by filename - - Or run the PowerShell helper: - - `./scripts/import_demo.ps1` (adds all PDFs in demo-data) - - `./scripts/import_demo.ps1 -Force` (overwrite existing) - -## Notes - -- Replace demo embeddings with real Gemini calls for production -- Add auth to endpoints if needed (API key/JWT) diff --git a/rust-engine/demo-data/132-1-Final.pdf b/rust-engine/demo-data/132-1-Final.pdf deleted file mode 100644 index ac0ce14..0000000 Binary files a/rust-engine/demo-data/132-1-Final.pdf and /dev/null differ diff --git a/rust-engine/demo-data/179225main_iss_poster_back.pdf b/rust-engine/demo-data/179225main_iss_poster_back.pdf deleted file mode 100644 index 9386c43..0000000 Binary files a/rust-engine/demo-data/179225main_iss_poster_back.pdf and /dev/null differ diff --git a/rust-engine/demo-data/19790004570.pdf b/rust-engine/demo-data/19790004570.pdf deleted file mode 100644 index a0d22b5..0000000 Binary files a/rust-engine/demo-data/19790004570.pdf and /dev/null differ diff --git a/rust-engine/demo-data/19880012104.pdf b/rust-engine/demo-data/19880012104.pdf deleted file mode 100644 index d7f48ea..0000000 Binary files a/rust-engine/demo-data/19880012104.pdf and /dev/null differ diff --git a/rust-engine/demo-data/19890016674.pdf b/rust-engine/demo-data/19890016674.pdf deleted file mode 100644 index 0e315d6..0000000 Binary files a/rust-engine/demo-data/19890016674.pdf and /dev/null differ diff --git a/rust-engine/demo-data/19920015843.pdf b/rust-engine/demo-data/19920015843.pdf deleted file mode 100644 index 53065cf..0000000 Binary files a/rust-engine/demo-data/19920015843.pdf and /dev/null differ diff --git a/rust-engine/demo-data/19950014639.pdf b/rust-engine/demo-data/19950014639.pdf deleted file mode 100644 index 5b200c2..0000000 Binary files a/rust-engine/demo-data/19950014639.pdf and /dev/null differ diff --git a/rust-engine/demo-data/20040171627.pdf b/rust-engine/demo-data/20040171627.pdf deleted file mode 100644 index fe3eb42..0000000 Binary files a/rust-engine/demo-data/20040171627.pdf and /dev/null differ diff --git a/rust-engine/demo-data/20050207388.pdf b/rust-engine/demo-data/20050207388.pdf deleted file mode 100644 index 23ba263..0000000 Binary files a/rust-engine/demo-data/20050207388.pdf and /dev/null differ diff --git a/rust-engine/demo-data/20050210002.pdf b/rust-engine/demo-data/20050210002.pdf deleted file mode 100644 index c7d6e7e..0000000 Binary files a/rust-engine/demo-data/20050210002.pdf and /dev/null differ diff --git a/rust-engine/demo-data/20080014096.pdf b/rust-engine/demo-data/20080014096.pdf deleted file mode 100644 index 2a23fcf..0000000 Binary files a/rust-engine/demo-data/20080014096.pdf and /dev/null differ diff --git a/rust-engine/demo-data/20100029672.pdf b/rust-engine/demo-data/20100029672.pdf deleted file mode 100644 index d639021..0000000 Binary files a/rust-engine/demo-data/20100029672.pdf and /dev/null differ diff --git a/rust-engine/demo-data/20110014867.pdf b/rust-engine/demo-data/20110014867.pdf deleted file mode 100644 index 609a6df..0000000 Binary files a/rust-engine/demo-data/20110014867.pdf and /dev/null differ diff --git a/rust-engine/demo-data/20120002931.pdf b/rust-engine/demo-data/20120002931.pdf deleted file mode 100644 index deb27bf..0000000 Binary files a/rust-engine/demo-data/20120002931.pdf and /dev/null differ diff --git a/rust-engine/demo-data/20190028718.pdf b/rust-engine/demo-data/20190028718.pdf deleted file mode 100644 index d5211e8..0000000 Binary files a/rust-engine/demo-data/20190028718.pdf and /dev/null differ diff --git a/rust-engine/demo-data/20200003149.pdf b/rust-engine/demo-data/20200003149.pdf deleted file mode 100644 index 59ef117..0000000 Binary files a/rust-engine/demo-data/20200003149.pdf and /dev/null differ diff --git a/rust-engine/demo-data/473486main_iss_atcs_overview.pdf b/rust-engine/demo-data/473486main_iss_atcs_overview.pdf deleted file mode 100644 index d742279..0000000 Binary files a/rust-engine/demo-data/473486main_iss_atcs_overview.pdf and /dev/null differ diff --git a/rust-engine/demo-data/8Mod6Prob1.pdf b/rust-engine/demo-data/8Mod6Prob1.pdf deleted file mode 100644 index d719e74..0000000 Binary files a/rust-engine/demo-data/8Mod6Prob1.pdf and /dev/null differ diff --git a/rust-engine/demo-data/ICES_2023_311 final 5 15 23.pdf b/rust-engine/demo-data/ICES_2023_311 final 5 15 23.pdf deleted file mode 100644 index e10f860..0000000 Binary files a/rust-engine/demo-data/ICES_2023_311 final 5 15 23.pdf and /dev/null differ diff --git a/rust-engine/demo-data/ISwSIS Software Standard_NASA-102020_Draft.docx.pdf b/rust-engine/demo-data/ISwSIS Software Standard_NASA-102020_Draft.docx.pdf deleted file mode 100644 index 62f2eec..0000000 Binary files a/rust-engine/demo-data/ISwSIS Software Standard_NASA-102020_Draft.docx.pdf and /dev/null differ diff --git a/rust-engine/rust-toolchain.toml b/rust-engine/rust-toolchain.toml deleted file mode 100644 index 4386630..0000000 --- a/rust-engine/rust-toolchain.toml +++ /dev/null @@ -1,4 +0,0 @@ -[toolchain] -channel = "1.88.0" -# components = ["rustfmt", "clippy"] -# targets = ["x86_64-unknown-linux-gnu"] diff --git a/rust-engine/src/api.rs b/rust-engine/src/api.rs deleted file mode 100644 index 1de0c81..0000000 --- a/rust-engine/src/api.rs +++ /dev/null @@ -1,346 +0,0 @@ -use crate::vector_db::QdrantClient; -use crate::storage; -use anyhow::Result; -use bytes::Buf; -use futures_util::TryStreamExt; -use serde::Deserialize; -use sqlx::{MySqlPool, Row}; -use warp::{multipart::FormData, Filter, Rejection, Reply}; - -#[derive(Debug, Deserialize)] -struct DeleteQuery { - id: String, -} - -pub fn routes(pool: MySqlPool) -> impl Filter + Clone { - let pool_filter = warp::any().map(move || pool.clone()); - - // Import demo files from demo-data directory - let import_demo = warp::path!("files" / "import-demo") - .and(warp::post()) - .and( - warp::query::>() - .or(warp::any().map(|| std::collections::HashMap::new())) - .unify() - ) - .and(pool_filter.clone()) - .and_then(handle_import_demo); - - // Upload file - let upload = warp::path("files") - .and(warp::post()) - .and(warp::multipart::form().max_length(50_000_000)) // 50MB per part default; storage is filesystem-backed - .and(pool_filter.clone()) - .and_then(handle_upload); - - // Delete file - let delete = warp::path!("files" / "delete") - .and(warp::get()) - .and(warp::query::()) - .and(pool_filter.clone()) - .and_then(handle_delete); - - // List files - let list = warp::path!("files" / "list") - .and(warp::get()) - .and(pool_filter.clone()) - .and_then(handle_list); - - // Create query - let create_q = warp::path!("query" / "create") - .and(warp::post()) - .and(warp::body::json()) - .and(pool_filter.clone()) - .and_then(handle_create_query); - - // Query status - let status = warp::path!("query" / "status") - .and(warp::get()) - .and(warp::query::()) - .and(pool_filter.clone()) - .and_then(handle_query_status); - - // Query result - let result = warp::path!("query" / "result") - .and(warp::get()) - .and(warp::query::()) - .and(pool_filter.clone()) - .and_then(handle_query_result); - - // Cancel - let cancel = warp::path!("query" / "cancel") - .and(warp::get()) - .and(warp::query::()) - .and(pool_filter.clone()) - .and_then(handle_cancel_query); - - let api = upload.or(import_demo).or(delete).or(list).or(create_q).or(status).or(result).or(cancel); - warp::path("api").and(api) -} - -async fn handle_upload(mut form: FormData, pool: MySqlPool) -> Result { - let mut created_files = Vec::new(); - while let Some(field) = form.try_next().await.map_err(|_| warp::reject())? { - let _name = field.name().to_string(); - let filename = field - .filename() - .map(|s| s.to_string()) - .unwrap_or_else(|| format!("upload-{}", uuid::Uuid::new_v4())); - - // Read stream of Buf into a Vec - let data = field - .stream() - .map_ok(|mut buf| { - let mut v = Vec::new(); - while buf.has_remaining() { - let chunk = buf.chunk(); - v.extend_from_slice(chunk); - let n = chunk.len(); - buf.advance(n); - } - v - }) - .try_fold(Vec::new(), |mut acc, chunk_vec| async move { - acc.extend_from_slice(&chunk_vec); - Ok(acc) - }) - .await - .map_err(|_| warp::reject())?; - - // Save file - let path = storage::save_file(&filename, &data).map_err(|_| warp::reject())?; - - // Insert file record with pending_analysis = true, description = NULL - let id = uuid::Uuid::new_v4().to_string(); - sqlx::query("INSERT INTO files (id, filename, path, description, pending_analysis, analysis_status) VALUES (?, ?, ?, ?, ?, 'Queued')") - .bind(&id) - .bind(&filename) - .bind(path.to_str().unwrap()) - .bind(Option::::None) - .bind(true) - .execute(&pool) - .await - .map_err(|e| { - tracing::error!("DB insert error: {}", e); - warp::reject() - })?; - created_files.push(serde_json::json!({ - "id": id, - "filename": filename, - "pending_analysis": true, - "analysis_status": "Queued" - })); - } - - Ok(warp::reply::json(&serde_json::json!({ - "uploaded": created_files.len(), - "files": created_files - }))) -} - -async fn handle_import_demo(params: std::collections::HashMap, pool: MySqlPool) -> Result { - use std::fs; - use std::path::PathBuf; - let force = params.get("force").map(|v| v == "1" || v.eq_ignore_ascii_case("true")).unwrap_or(false); - let demo_dir_setting = std::env::var("DEMO_DATA_DIR").unwrap_or_else(|_| "demo-data".to_string()); - let base = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); - - // Build a list of plausible demo-data locations so local runs and containers both work. - let mut candidates: Vec = Vec::new(); - let configured = PathBuf::from(&demo_dir_setting); - let mut push_candidate = |path: PathBuf| { - if !candidates.iter().any(|existing| existing == &path) { - candidates.push(path); - } - }; - - push_candidate(base.join(&configured)); - push_candidate(PathBuf::from(&demo_dir_setting)); - push_candidate(base.join("rust-engine").join(&configured)); - push_candidate(base.join("rust-engine").join("demo-data")); - push_candidate(base.join("demo-data")); - if let Ok(exe_path) = std::env::current_exe() { - if let Some(exe_dir) = exe_path.parent() { - push_candidate(exe_dir.join(&configured)); - push_candidate(exe_dir.join("demo-data")); - push_candidate(exe_dir.join("rust-engine").join(&configured)); - } - } - - let mut attempted: Vec = Vec::new(); - let mut resolved_dir: Option = None; - for candidate in candidates { - if candidate.exists() && candidate.is_dir() { - resolved_dir = Some(candidate); - break; - } - attempted.push(candidate); - } - - let src_dir = match resolved_dir { - Some(path) => path, - None => { - let attempted_paths: Vec = attempted - .into_iter() - .map(|p| p.display().to_string()) - .collect(); - return Ok(warp::reply::json(&serde_json::json!({ - "imported": 0, - "skipped": 0, - "error": format!("demo dir not found (checked: {})", attempted_paths.join(", ")) - }))); - } - }; - let mut imported = 0; - let mut skipped = 0; - for entry in fs::read_dir(&src_dir).map_err(|_| warp::reject())? { - let entry = entry.map_err(|_| warp::reject())?; - let path = entry.path(); - if path.extension().and_then(|e| e.to_str()).map(|e| e.eq_ignore_ascii_case("pdf")).unwrap_or(false) { - let filename = path.file_name().and_then(|n| n.to_str()).unwrap_or("unknown.pdf").to_string(); - - // check if exists - if !force { - if let Some(_) = sqlx::query("SELECT id FROM files WHERE filename = ?") - .bind(&filename) - .fetch_optional(&pool) - .await - .map_err(|_| warp::reject())? { - skipped += 1; - continue; - } - } - - // read and save to storage - let data = fs::read(&path).map_err(|_| warp::reject())?; - let stored_path = storage::save_file(&filename, &data).map_err(|_| warp::reject())?; - - // insert or upsert db record - let id = uuid::Uuid::new_v4().to_string(); - if force { - let _ = sqlx::query("DELETE FROM files WHERE filename = ?") - .bind(&filename) - .execute(&pool) - .await; - } - sqlx::query("INSERT INTO files (id, filename, path, description, pending_analysis, analysis_status) VALUES (?, ?, ?, ?, ?, 'Queued')") - .bind(&id) - .bind(&filename) - .bind(stored_path.to_str().unwrap()) - .bind(Option::::None) - .bind(true) - .execute(&pool) - .await - .map_err(|e| { - tracing::error!("DB insert error: {}", e); - warp::reject() - })?; - imported += 1; - } - } - Ok(warp::reply::json(&serde_json::json!({ "imported": imported, "skipped": skipped }))) -} - -async fn handle_delete(q: DeleteQuery, pool: MySqlPool) -> Result { - if let Some(row) = sqlx::query("SELECT path FROM files WHERE id = ?") - .bind(&q.id) - .fetch_optional(&pool) - .await - .map_err(|_| warp::reject())? - { - let path: String = row.get("path"); - let _ = storage::delete_file(std::path::Path::new(&path)); - // Remove from Qdrant - let qdrant_url = std::env::var("QDRANT_URL").unwrap_or_else(|_| "http://qdrant:6333".to_string()); - let qdrant = QdrantClient::new(&qdrant_url); - let _ = qdrant.delete_point(&q.id).await; - let _ = sqlx::query("DELETE FROM files WHERE id = ?").bind(&q.id).execute(&pool).await; - return Ok(warp::reply::json(&serde_json::json!({"deleted": true}))); - } - Ok(warp::reply::json(&serde_json::json!({"deleted": false}))) -} - -async fn handle_list(pool: MySqlPool) -> Result { - let rows = sqlx::query("SELECT id, filename, path, description, pending_analysis, analysis_status FROM files ORDER BY created_at DESC LIMIT 500") - .fetch_all(&pool) - .await - .map_err(|e| { - tracing::error!("DB list error: {}", e); - warp::reject() - })?; - - let files: Vec = rows - .into_iter() - .map(|r| { - let id: String = r.get("id"); - let filename: String = r.get("filename"); - let path: String = r.get("path"); - let description: Option = r.get("description"); - let pending: bool = r.get("pending_analysis"); - let status: Option = r.try_get("analysis_status").ok(); - serde_json::json!({ - "id": id, - "filename": filename, - "path": path, - "description": description, - "pending_analysis": pending, - "analysis_status": status - }) - }) - .collect(); - - Ok(warp::reply::json(&serde_json::json!({"files": files}))) -} - -async fn handle_create_query(body: serde_json::Value, pool: MySqlPool) -> Result { - // Insert query as queued, worker will pick it up - let id = uuid::Uuid::new_v4().to_string(); - let payload = body; - sqlx::query("INSERT INTO queries (id, status, payload) VALUES (?, 'Queued', ?)") - .bind(&id) - .bind(payload) - .execute(&pool) - .await - .map_err(|e| { - tracing::error!("DB insert query error: {}", e); - warp::reject() - })?; - - Ok(warp::reply::json(&serde_json::json!({"id": id}))) -} - -async fn handle_query_status(q: DeleteQuery, pool: MySqlPool) -> Result { - if let Some(row) = sqlx::query("SELECT status FROM queries WHERE id = ?") - .bind(&q.id) - .fetch_optional(&pool) - .await - .map_err(|_| warp::reject())? - { - let status: String = row.get("status"); - return Ok(warp::reply::json(&serde_json::json!({"status": status}))); - } - Ok(warp::reply::json(&serde_json::json!({"status": "not_found"}))) -} - -async fn handle_query_result(q: DeleteQuery, pool: MySqlPool) -> Result { - if let Some(row) = sqlx::query("SELECT result FROM queries WHERE id = ?") - .bind(&q.id) - .fetch_optional(&pool) - .await - .map_err(|_| warp::reject())? - { - let result: Option = row.get("result"); - return Ok(warp::reply::json(&serde_json::json!({"result": result}))); - } - Ok(warp::reply::json(&serde_json::json!({"result": null}))) -} - -async fn handle_cancel_query(q: DeleteQuery, pool: MySqlPool) -> Result { - // Mark as cancelled; worker must check status before heavy steps - sqlx::query("UPDATE queries SET status = 'Cancelled' WHERE id = ?") - .bind(&q.id) - .execute(&pool) - .await - .map_err(|_| warp::reject())?; - Ok(warp::reply::json(&serde_json::json!({"cancelled": true}))) -} diff --git a/rust-engine/src/db.rs b/rust-engine/src/db.rs deleted file mode 100644 index 3245814..0000000 --- a/rust-engine/src/db.rs +++ /dev/null @@ -1,42 +0,0 @@ -use sqlx::MySqlPool; -use tracing::info; - -pub async fn init_db(database_url: &str) -> Result { - let pool = MySqlPool::connect(database_url).await?; - - // Create tables if they don't exist. Simple schema for demo/hackathon use. - // Note: MySQL requires separate statements for each CREATE TABLE - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS files ( - id VARCHAR(36) PRIMARY KEY, - filename TEXT NOT NULL, - path TEXT NOT NULL, - description TEXT, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - pending_analysis BOOLEAN DEFAULT TRUE, - analysis_status VARCHAR(32) DEFAULT 'Queued' - ) - "#, - ) - .execute(&pool) - .await?; - - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS queries ( - id VARCHAR(36) PRIMARY KEY, - status VARCHAR(32) NOT NULL, - payload JSON, - result JSON, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP - ) - "#, - ) - .execute(&pool) - .await?; - - info!("Database initialized"); - Ok(pool) -} diff --git a/rust-engine/src/file_worker.rs b/rust-engine/src/file_worker.rs deleted file mode 100644 index 0c2e4b7..0000000 --- a/rust-engine/src/file_worker.rs +++ /dev/null @@ -1,133 +0,0 @@ -use crate::gemini_client::{demo_text_embedding, generate_text_with_model, DEMO_EMBED_DIM}; -use crate::vector; -use crate::vector_db::QdrantClient; -use sqlx::MySqlPool; -use anyhow::Result; -use tracing::{info, error}; - -pub struct FileWorker { - pool: MySqlPool, - qdrant: QdrantClient, -} - -impl FileWorker { - pub fn new(pool: MySqlPool) -> Self { - let qdrant_url = std::env::var("QDRANT_URL").unwrap_or_else(|_| "http://qdrant:6333".to_string()); - let qdrant = QdrantClient::new(&qdrant_url); - Self { pool, qdrant } - } - - pub async fn run(&self) { - info!("FileWorker starting"); - if let Err(e) = self.qdrant.ensure_files_collection(DEMO_EMBED_DIM).await { - error!("Failed to ensure Qdrant collection: {}", e); - } - loop { - match self.fetch_and_claim().await { - Ok(Some(fid)) => { - info!("Processing file {}", fid); - if let Err(e) = self.process_file(&fid).await { - error!("Error processing file {}: {}", fid, e); - if let Err(mark_err) = self.mark_failed(&fid, &format!("{}", e)).await { - error!("Failed to mark file {} as failed: {}", fid, mark_err); - } - } - } - Ok(None) => { - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - } - Err(e) => { - error!("FileWorker fetch error: {}", e); - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - } - } - } - } - - async fn fetch_and_claim(&self) -> Result> { - // Claim files that are queued or stuck in progress for >10min - if let Some(row) = sqlx::query( - "SELECT id FROM files WHERE (analysis_status = 'Queued' OR (analysis_status = 'InProgress' AND created_at < (NOW() - INTERVAL 10 MINUTE))) AND pending_analysis = TRUE LIMIT 1" - ) - .fetch_optional(&self.pool) - .await? { - use sqlx::Row; - let id: String = row.get("id"); - // Mark as in-progress - let _ = sqlx::query("UPDATE files SET analysis_status = 'InProgress' WHERE id = ?") - .bind(&id) - .execute(&self.pool) - .await?; - Ok(Some(id)) - } else { - Ok(None) - } - } - - async fn process_file(&self, file_id: &str) -> Result<()> { - use sqlx::Row; - let row = sqlx::query("SELECT filename, path FROM files WHERE id = ?") - .bind(file_id) - .fetch_one(&self.pool) - .await?; - let filename: String = row.get("filename"); - let _path: String = row.get("path"); - - // Stage 1: Gemini 2.5 Flash for description - let desc = generate_text_with_model( - "gemini-2.5-flash", - &format!( - "Describe the file '{filename}' and extract all key components, keywords, and details for later vectorization. Be comprehensive and factual." - ), - ) - .await - .unwrap_or_else(|e| format!("[desc error: {}]", e)); - sqlx::query("UPDATE files SET description = ?, analysis_status = 'InProgress' WHERE id = ?") - .bind(&desc) - .bind(file_id) - .execute(&self.pool) - .await?; - - // Stage 2: Gemini 2.5 Pro for deep vector graph data - let vector_graph = generate_text_with_model( - "gemini-2.5-pro", - &format!( - "Given the file '{filename}' and its description: {desc}\nGenerate a set of vector graph data (keywords, use cases, relationships) that can be used for broad and precise search. Only include what is directly supported by the file." - ), - ) - .await - .unwrap_or_else(|e| format!("[vector error: {}]", e)); - - // Stage 3: Embed and upsert to Qdrant - let emb = demo_text_embedding(&vector_graph).await?; - match self.qdrant.upsert_point(file_id, emb.clone()).await { - Ok(_) => { - let _ = vector::store_embedding(file_id, emb.clone()); - } - Err(err) => { - error!("Qdrant upsert failed for {}: {}", file_id, err); - let _ = vector::store_embedding(file_id, emb); - } - } - - // Mark file as ready - sqlx::query("UPDATE files SET pending_analysis = FALSE, analysis_status = 'Completed' WHERE id = ?") - .bind(file_id) - .execute(&self.pool) - .await?; - Ok(()) - } - - async fn mark_failed(&self, file_id: &str, reason: &str) -> Result<()> { - sqlx::query("UPDATE files SET analysis_status = 'Failed', pending_analysis = TRUE WHERE id = ?") - .bind(file_id) - .execute(&self.pool) - .await?; - sqlx::query("UPDATE files SET description = COALESCE(description, ?) WHERE id = ?") - .bind(format!("[analysis failed: {}]", reason)) - .bind(file_id) - .execute(&self.pool) - .await?; - Ok(()) - } -} diff --git a/rust-engine/src/gemini_client.rs b/rust-engine/src/gemini_client.rs deleted file mode 100644 index 5741166..0000000 --- a/rust-engine/src/gemini_client.rs +++ /dev/null @@ -1,89 +0,0 @@ -use anyhow::Result; -use reqwest::Client; -use serde::Deserialize; -use serde_json::json; - -// NOTE: This file provides lightweight helpers around the Gemini API. For the -// hackathon demo we fall back to deterministic strings when the API key is not -// configured so the flows still work end-to-end. - -pub const DEMO_EMBED_DIM: usize = 64; - -/// Demo text embedding (replace with real Gemini text embedding API) -pub async fn demo_text_embedding(text: &str) -> Result> { - let mut v = vec![0f32; DEMO_EMBED_DIM]; - for (i, b) in text.as_bytes().iter().enumerate() { - let idx = i % v.len(); - v[idx] += (*b as f32) / 255.0; - } - Ok(v) -} - -/// Generate text using the default model (GEMINI_MODEL or gemini-2.5-pro). -#[allow(dead_code)] -pub async fn generate_text(prompt: &str) -> Result { - let model = std::env::var("GEMINI_MODEL").unwrap_or_else(|_| "gemini-2.5-pro".to_string()); - generate_text_with_model(&model, prompt).await -} - -/// Generate text with an explicit Gemini model. Falls back to a deterministic -/// response when the API key is not set so the demo still runs. -pub async fn generate_text_with_model(model: &str, prompt: &str) -> Result { - let api_key = match std::env::var("GEMINI_API_KEY") { - Ok(k) if !k.is_empty() => k, - _ => { - return Ok(format!( - "[demo] Gemini ({}) not configured. Prompt preview: {}", - model, - truncate(prompt, 240) - )); - } - }; - - let url = format!( - "https://generativelanguage.googleapis.com/v1beta/models/{}:generateContent?key={}", - model, api_key - ); - - let body = json!({ - "contents": [ { "parts": [ { "text": prompt } ] } ] - }); - - let client = Client::new(); - let resp = client.post(&url).json(&body).send().await?; - let status = resp.status(); - let txt = resp.text().await?; - if !status.is_success() { - return Ok(format!( - "[demo] Gemini ({}) error {}: {}", - model, - status, - truncate(&txt, 240) - )); - } - - #[derive(Deserialize)] - struct Part { text: Option } - #[derive(Deserialize)] - struct Content { parts: Vec } - #[derive(Deserialize)] - struct Candidate { content: Content } - #[derive(Deserialize)] - struct Response { candidates: Option> } - - let data: Response = serde_json::from_str(&txt).unwrap_or(Response { candidates: None }); - let out = data - .candidates - .and_then(|mut v| v.pop()) - .and_then(|c| c.content.parts.into_iter().find_map(|p| p.text)) - .unwrap_or_else(|| "[demo] Gemini returned empty response".to_string()); - Ok(out) -} - -fn truncate(s: &str, max: usize) -> String { - if s.len() <= max { - s.to_string() - } else { - format!("{}…", &s[..max]) - } -} diff --git a/rust-engine/src/main.rs b/rust-engine/src/main.rs deleted file mode 100644 index 68b2663..0000000 --- a/rust-engine/src/main.rs +++ /dev/null @@ -1,58 +0,0 @@ -mod file_worker; -mod api; -mod db; -mod gemini_client; -mod models; -mod storage; -mod vector; -mod worker; -mod vector_db; - -use std::env; -use std::error::Error; -use tracing::info; -use warp::Filter; - -#[tokio::main] -async fn main() -> Result<(), Box> { - // Initialize tracing - tracing_subscriber::fmt::init(); - - // Load environment variables - dotenvy::dotenv().ok(); - - let database_url = env::var("DATABASE_URL") - .unwrap_or_else(|_| "mysql://astraadmin:password@mysql:3306/astra".to_string()); - - info!("Starting Rust Engine..."); - - // Ensure storage dir - storage::ensure_storage_dir().expect("storage dir"); - - // Initialize DB - let pool = db::init_db(&database_url).await.map_err(|e| -> Box { Box::new(e) })?; - - // Spawn query worker - let worker = worker::Worker::new(pool.clone()); - tokio::spawn(async move { worker.run().await }); - - // Spawn file analysis worker - let file_worker = file_worker::FileWorker::new(pool.clone()); - tokio::spawn(async move { file_worker.run().await }); - - // API routes - let api_routes = api::routes(pool.clone()) - .with(warp::cors() - .allow_any_origin() - .allow_headers(vec!["content-type", "authorization"]) - .allow_methods(vec!["GET", "POST", "PUT", "DELETE", "OPTIONS"])) - .with(warp::log("rust_engine")); - - info!("Rust Engine started on http://0.0.0.0:8000"); - - warp::serve(api_routes) - .run(([0, 0, 0, 0], 8000)) - .await; - - Ok(()) -} \ No newline at end of file diff --git a/rust-engine/src/models.rs b/rust-engine/src/models.rs deleted file mode 100644 index 59f0e12..0000000 --- a/rust-engine/src/models.rs +++ /dev/null @@ -1,61 +0,0 @@ -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct FileRecord { - pub id: String, - pub filename: String, - pub path: String, - pub description: Option, - pub created_at: Option>, - pub pending_analysis: bool, // true if file is not yet ready for search - pub analysis_status: String, // 'Queued', 'InProgress', 'Completed', 'Failed' -} - -impl FileRecord { - #[allow(dead_code)] - pub fn new(filename: impl Into, path: impl Into, description: Option) -> Self { - Self { - id: Uuid::new_v4().to_string(), - filename: filename.into(), - path: path.into(), - description, - created_at: None, - pending_analysis: true, - analysis_status: "Queued".to_string(), - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub enum QueryStatus { - Queued, - InProgress, - Completed, - Cancelled, - Failed, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct QueryRecord { - pub id: String, - pub status: QueryStatus, - pub payload: serde_json::Value, - pub result: Option, - pub created_at: Option>, - pub updated_at: Option>, -} - -impl QueryRecord { - pub fn new(payload: serde_json::Value) -> Self { - Self { - id: Uuid::new_v4().to_string(), - status: QueryStatus::Queued, - payload, - result: None, - created_at: None, - updated_at: None, - } - } -} diff --git a/rust-engine/src/storage.rs b/rust-engine/src/storage.rs deleted file mode 100644 index 08f4ad0..0000000 --- a/rust-engine/src/storage.rs +++ /dev/null @@ -1,34 +0,0 @@ -use anyhow::Result; -use std::fs; -use std::io::Write; -use std::path::{Path, PathBuf}; - -pub fn storage_dir() -> PathBuf { - std::env::var("ASTRA_STORAGE") - .map(PathBuf::from) - .unwrap_or_else(|_| PathBuf::from("/app/storage")) -} - -pub fn ensure_storage_dir() -> Result<()> { - let dir = storage_dir(); - if !dir.exists() { - fs::create_dir_all(&dir)?; - } - Ok(()) -} - -pub fn save_file(filename: &str, contents: &[u8]) -> Result { - ensure_storage_dir()?; - let mut path = storage_dir(); - path.push(filename); - let mut f = fs::File::create(&path)?; - f.write_all(contents)?; - Ok(path) -} - -pub fn delete_file(path: &Path) -> Result<()> { - if path.exists() { - fs::remove_file(path)?; - } - Ok(()) -} diff --git a/rust-engine/src/vector.rs b/rust-engine/src/vector.rs deleted file mode 100644 index c34b0a3..0000000 --- a/rust-engine/src/vector.rs +++ /dev/null @@ -1,24 +0,0 @@ -use anyhow::Result; -use lazy_static::lazy_static; -use std::collections::HashMap; -use std::sync::Mutex; - -lazy_static! { - static ref VECTOR_STORE: Mutex>> = Mutex::new(HashMap::new()); -} - -pub fn store_embedding(id: &str, emb: Vec) -> Result<()> { - let mut s = VECTOR_STORE.lock().unwrap(); - s.insert(id.to_string(), emb); - Ok(()) -} - -pub fn query_top_k(_query_emb: &[f32], k: usize) -> Result> { - // Very naive: return up to k ids from the store. - let s = VECTOR_STORE.lock().unwrap(); - let mut out = Vec::new(); - for key in s.keys().take(k) { - out.push(key.clone()); - } - Ok(out) -} diff --git a/rust-engine/src/vector_db.rs b/rust-engine/src/vector_db.rs deleted file mode 100644 index cb09d79..0000000 --- a/rust-engine/src/vector_db.rs +++ /dev/null @@ -1,103 +0,0 @@ -use anyhow::Result; -use reqwest::Client; -use serde_json::json; -use serde::Deserialize; - -#[derive(Clone)] -pub struct QdrantClient { - base: String, - client: Client, -} - -impl QdrantClient { - - /// Delete a point from collection 'files' by id - pub async fn delete_point(&self, id: &str) -> Result<()> { - let url = format!("{}/collections/files/points/delete", self.base); - let body = json!({ - "points": [id] - }); - let resp = self.client.post(&url).json(&body).send().await?; - let status = resp.status(); - if status.is_success() { - Ok(()) - } else { - let t = resp.text().await.unwrap_or_default(); - Err(anyhow::anyhow!("qdrant delete failed: {} - {}", status, t)) - } - } - pub fn new(base: &str) -> Self { - Self { - base: base.trim_end_matches('/').to_string(), - client: Client::new(), - } - } - - /// Upsert a point into collection `files` with id and vector - pub async fn upsert_point(&self, id: &str, vector: Vec) -> Result<()> { - let url = format!("{}/collections/files/points", self.base); - let body = json!({ - "points": [{ - "id": id, - "vector": vector, - "payload": {"type": "file"} - }] - }); - - let resp = self.client.post(&url).json(&body).send().await?; - let status = resp.status(); - if status.is_success() { - Ok(()) - } else { - let t = resp.text().await.unwrap_or_default(); - Err(anyhow::anyhow!("qdrant upsert failed: {} - {}", status, t)) - } - } - - /// Ensure the 'files' collection exists with the given dimension and distance metric - pub async fn ensure_files_collection(&self, dim: usize) -> Result<()> { - let url = format!("{}/collections/files", self.base); - let body = json!({ - "vectors": {"size": dim, "distance": "Cosine"} - }); - let resp = self.client.put(&url).json(&body).send().await?; - // 200 OK or 201 Created means ready; 409 Conflict means already exists - if resp.status().is_success() || resp.status().as_u16() == 409 { - Ok(()) - } else { - let status = resp.status(); - let t = resp.text().await.unwrap_or_default(); - Err(anyhow::anyhow!("qdrant ensure collection failed: {} - {}", status, t)) - } - } - - /// Search top-k nearest points from 'files', return (id, score) - pub async fn search_top_k(&self, vector: Vec, k: usize) -> Result> { - let url = format!("{}/collections/files/points/search", self.base); - let body = json!({ - "vector": vector, - "limit": k - }); - let resp = self.client.post(&url).json(&body).send().await?; - let status = resp.status(); - if !status.is_success() { - let t = resp.text().await.unwrap_or_default(); - return Err(anyhow::anyhow!("qdrant search failed: {} - {}", status, t)); - } - #[derive(Deserialize)] - struct Hit { id: serde_json::Value, score: f32 } - #[derive(Deserialize)] - struct Data { result: Vec } - let data: Data = resp.json().await?; - let mut out = Vec::new(); - for h in data.result { - // id can be string or number; handle string - if let Some(s) = h.id.as_str() { - out.push((s.to_string(), h.score)); - } else { - out.push((h.id.to_string(), h.score)); - } - } - Ok(out) - } -} diff --git a/rust-engine/src/worker.rs b/rust-engine/src/worker.rs deleted file mode 100644 index e3e96bb..0000000 --- a/rust-engine/src/worker.rs +++ /dev/null @@ -1,266 +0,0 @@ -use crate::gemini_client::{demo_text_embedding, generate_text_with_model, DEMO_EMBED_DIM}; -use crate::models::{QueryRecord, QueryStatus}; -use crate::vector; -use crate::vector_db::QdrantClient; -use anyhow::Result; -use sqlx::MySqlPool; -use std::time::Duration; -use tracing::{error, info}; - -pub struct Worker { - pool: MySqlPool, - qdrant: QdrantClient, -} - -impl Worker { - pub fn new(pool: MySqlPool) -> Self { - let qdrant_url = std::env::var("QDRANT_URL").unwrap_or_else(|_| "http://qdrant:6333".to_string()); - let qdrant = QdrantClient::new(&qdrant_url); - Self { pool, qdrant } - } - - pub async fn run(&self) { - info!("Worker starting"); - - // Ensure qdrant collection exists - if let Err(e) = self.qdrant.ensure_files_collection(DEMO_EMBED_DIM).await { - error!("Failed to ensure Qdrant collection: {}", e); - } - - // Requeue stale InProgress jobs older than cutoff (e.g., 10 minutes) - if let Err(e) = self.requeue_stale_inprogress(10 * 60).await { - error!("Failed to requeue stale jobs: {}", e); - } - - loop { - // Claim next queued query - match self.fetch_and_claim().await { - Ok(Some(mut q)) => { - info!("Processing query {}", q.id); - if let Err(e) = self.process_query(&mut q).await { - error!("Error processing {}: {}", q.id, e); - let _ = self.mark_failed(&q.id, &format!("{}", e)).await; - } - } - Ok(None) => { - tokio::time::sleep(Duration::from_secs(2)).await; - } - Err(e) => { - error!("Worker fetch error: {}", e); - tokio::time::sleep(Duration::from_secs(5)).await; - } - } - } - } - - async fn fetch_and_claim(&self) -> Result> { - // Note: MySQL transactional SELECT FOR UPDATE handling is more complex; for this hackathon scaffold - // we do a simple two-step: select one queued id, then update it to InProgress if it is still queued. - if let Some(row) = sqlx::query("SELECT id, payload FROM queries WHERE status = 'Queued' ORDER BY created_at LIMIT 1") - .fetch_optional(&self.pool) - .await? - { - use sqlx::Row; - let id: String = row.get("id"); - let payload: serde_json::Value = row.get("payload"); - - let updated = sqlx::query("UPDATE queries SET status = 'InProgress' WHERE id = ? AND status = 'Queued'") - .bind(&id) - .execute(&self.pool) - .await?; - - if updated.rows_affected() == 1 { - let mut q = QueryRecord::new(payload); - q.id = id; - q.status = QueryStatus::InProgress; - return Ok(Some(q)); - } - } - Ok(None) - } - - async fn process_query(&self, q: &mut QueryRecord) -> Result<()> { - // Stage 1: set InProgress (idempotent) - self.update_status(&q.id, QueryStatus::InProgress).await?; - - // Stage 2: embed query text - let text = q.payload.get("q").and_then(|v| v.as_str()).unwrap_or(""); - let emb = demo_text_embedding(text).await?; - let top_k = q.payload.get("top_k").and_then(|v| v.as_u64()).unwrap_or(5) as usize; - let top_k = top_k.max(1).min(20); - - // Check cancellation - if self.is_cancelled(&q.id).await? { return Ok(()); } - - // Stage 3: search top-K in Qdrant - let hits = match self.qdrant.search_top_k(emb.clone(), top_k).await { - Ok(list) if !list.is_empty() => list, - Ok(_) => Vec::new(), - Err(err) => { - error!("Qdrant search failed for query {}: {}", q.id, err); - Vec::new() - } - }; - - let hits = if hits.is_empty() { - match vector::query_top_k(&emb, top_k) { - Ok(fallback_ids) if !fallback_ids.is_empty() => { - info!("Using in-memory fallback for query {}", q.id); - fallback_ids.into_iter().map(|id| (id, 0.0)).collect() - } - _ => Vec::new(), - } - } else { - hits - }; - - // Check cancellation - if self.is_cancelled(&q.id).await? { return Ok(()); } - - // Stage 4: fetch file metadata for IDs - let mut files_json = Vec::new(); - for (fid, score) in hits { - if let Some(row) = sqlx::query("SELECT id, filename, path, description FROM files WHERE id = ? AND pending_analysis = FALSE") - .bind(&fid) - .fetch_optional(&self.pool) - .await? { - use sqlx::Row; - let id: String = row.get("id"); - let filename: String = row.get("filename"); - let path: String = row.get("path"); - let description: Option = row.get("description"); - files_json.push(serde_json::json!({ - "id": id, "filename": filename, "path": path, "description": description, "score": score - })); - } - } - - // Stage 5: call Gemini to analyze relationships and propose follow-up details strictly from provided files - let relationships_prompt = build_relationships_prompt(text, &files_json); - let (relationships, final_answer) = if files_json.is_empty() { - ( - "No analyzed files are ready yet. Try seeding demo data or wait for processing to finish.".to_string(), - "I could not find any relevant documents yet. Once files finish analysis I will be able to answer.".to_string(), - ) - } else { - let relationships = generate_text_with_model("gemini-2.5-pro", &relationships_prompt) - .await - .unwrap_or_else(|e| format!("[demo] relationships error: {}", e)); - - // Stage 6: final answer synthesis with strict constraints (no speculation; say unknown when insufficient) - let final_prompt = build_final_answer_prompt(text, &files_json, &relationships); - let final_answer = generate_text_with_model("gemini-2.5-pro", &final_prompt) - .await - .unwrap_or_else(|e| format!("[demo] final answer error: {}", e)); - (relationships, final_answer) - }; - - // Stage 7: persist results - let result = serde_json::json!({ - "summary": format!("Found {} related files", files_json.len()), - "related_files": files_json, - "relationships": relationships, - "final_answer": final_answer, - }); - sqlx::query("UPDATE queries SET status = 'Completed', result = ? WHERE id = ?") - .bind(result) - .bind(&q.id) - .execute(&self.pool) - .await?; - Ok(()) - } - - async fn update_status(&self, id: &str, status: QueryStatus) -> Result<()> { - let s = match status { - QueryStatus::Queued => "Queued", - QueryStatus::InProgress => "InProgress", - QueryStatus::Completed => "Completed", - QueryStatus::Cancelled => "Cancelled", - QueryStatus::Failed => "Failed", - }; - sqlx::query("UPDATE queries SET status = ? WHERE id = ?") - .bind(s) - .bind(id) - .execute(&self.pool) - .await?; - Ok(()) - } - - async fn mark_failed(&self, id: &str, message: &str) -> Result<()> { - let result = serde_json::json!({"error": message}); - sqlx::query("UPDATE queries SET status = 'Failed', result = ? WHERE id = ?") - .bind(result) - .bind(id) - .execute(&self.pool) - .await?; - Ok(()) - } - - async fn requeue_stale_inprogress(&self, age_secs: i64) -> Result<()> { - // MySQL: requeue items updated_at < now()-age and status = InProgress - sqlx::query( - "UPDATE queries SET status = 'Queued' WHERE status = 'InProgress' AND updated_at < (NOW() - INTERVAL ? SECOND)" - ) - .bind(age_secs) - .execute(&self.pool) - .await?; - Ok(()) - } - - async fn is_cancelled(&self, id: &str) -> Result { - if let Some(row) = sqlx::query("SELECT status FROM queries WHERE id = ?") - .bind(id) - .fetch_optional(&self.pool) - .await? - { - use sqlx::Row; - let s: String = row.get("status"); - return Ok(s == "Cancelled"); - } - Ok(false) - } -} - -fn build_relationships_prompt(query: &str, files: &Vec) -> String { - let files_snippets: Vec = files.iter().map(|f| format!( - "- id: {id}, filename: {name}, path: {path}, desc: {desc}", - id=f.get("id").and_then(|v| v.as_str()).unwrap_or(""), - name=f.get("filename").and_then(|v| v.as_str()).unwrap_or(""), - path=f.get("path").and_then(|v| v.as_str()).unwrap_or(""), - desc=f.get("description").and_then(|v| v.as_str()).unwrap_or("") - )).collect(); - format!( - "You are an assistant analyzing relationships STRICTLY within the provided files.\n\ - Query: {query}\n\ - Files:\n{files}\n\ - Tasks:\n\ - 1) Summarize key details from the files relevant to the query.\n\ - 2) Describe relationships and linkages strictly supported by these files.\n\ - 3) List important follow-up questions that could be answered only using the provided files.\n\ - Rules: Do NOT guess or invent. If information is insufficient in the files, explicitly state that.", - query=query, - files=files_snippets.join("\n") - ) -} - -fn build_final_answer_prompt(query: &str, files: &Vec, relationships: &str) -> String { - let files_short: Vec = files.iter().map(|f| format!( - "- {name} ({id})", - id=f.get("id").and_then(|v| v.as_str()).unwrap_or(""), - name=f.get("filename").and_then(|v| v.as_str()).unwrap_or("") - )).collect(); - format!( - "You are to compose a final answer to the user query using only the information from the files.\n\ - Query: {query}\n\ - Files considered:\n{files}\n\ - Relationship analysis:\n{rels}\n\ - Requirements:\n\ - - Use only information present in the files and analysis above.\n\ - - If the answer is uncertain or cannot be determined from the files, clearly state that limitation.\n\ - - Avoid speculation or assumptions.\n\ - Provide a concise, structured answer.", - query=query, - files=files_short.join("\n"), - rels=relationships - ) -} diff --git a/src/features/gemini/gemini.js b/src/features/gemini/gemini.js deleted file mode 100644 index 60ab8c1..0000000 --- a/src/features/gemini/gemini.js +++ /dev/null @@ -1,4 +0,0 @@ -import { createPartFromUri, GoogleGenAI } from "@google/genai" -import 'dotenv/config' - -const ai = new GoogleGenAI({ apiKey: ${} }) \ No newline at end of file diff --git a/web-app/README.md b/web-app/README.md deleted file mode 100644 index 18bc70e..0000000 --- a/web-app/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# React + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## React Compiler - -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/web-app/components.json b/web-app/components.json deleted file mode 100644 index 7a2e4d8..0000000 --- a/web-app/components.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "style": "default", - "tailwind": { - "config": "tailwind.config.js", - "css": "src/app/globals.css", - "baseColor": "zinc", - "cssVariables": true - }, - "rsc": false, - "tsx": false, - "aliases": { - "utils": "~/lib/utils", - "components": "~/components" - } -} diff --git a/web-app/jsconfig.json b/web-app/jsconfig.json deleted file mode 100644 index 604c23f..0000000 --- a/web-app/jsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": "./", - "lib": ["es2015", "dom"] - }, - "include": ["src"] -} diff --git a/web-app/public/pdfs/SRHB.pdf b/web-app/public/pdfs/SRHB.pdf deleted file mode 100644 index 5bb4f78..0000000 Binary files a/web-app/public/pdfs/SRHB.pdf and /dev/null differ diff --git a/web-app/public/pdfs/falcon-users-guide-2025-05-09.pdf b/web-app/public/pdfs/falcon-users-guide-2025-05-09.pdf deleted file mode 100644 index 4d835a6..0000000 Binary files a/web-app/public/pdfs/falcon-users-guide-2025-05-09.pdf and /dev/null differ diff --git a/web-app/public/pdfs/spacex-falcon-9-data-sheet.pdf b/web-app/public/pdfs/spacex-falcon-9-data-sheet.pdf deleted file mode 100644 index d6ce2ae..0000000 Binary files a/web-app/public/pdfs/spacex-falcon-9-data-sheet.pdf and /dev/null differ diff --git a/web-app/public/pdfs/system-safety-handbook.pdf b/web-app/public/pdfs/system-safety-handbook.pdf deleted file mode 100644 index 7d076f2..0000000 Binary files a/web-app/public/pdfs/system-safety-handbook.pdf and /dev/null differ diff --git a/web-app/server.mjs b/web-app/server.mjs deleted file mode 100644 index e811db1..0000000 --- a/web-app/server.mjs +++ /dev/null @@ -1,54 +0,0 @@ -import express from 'express'; -import path from 'node:path'; -import helmet from 'helmet'; -import cors from 'cors'; -import fetch from 'node-fetch'; -import { fileURLToPath } from 'node:url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const app = express(); -const PORT = Number(process.env.PORT) || 3000; -const HOST = process.env.HOST || '0.0.0.0'; -const RUST_ENGINE_BASE = - process.env.RUST_ENGINE_BASE || - process.env.RUST_ENGINE_URL || - 'http://rust-engine:8000'; - -app.set('trust proxy', true); -app.use(helmet({ contentSecurityPolicy: false })); -app.use(cors()); -app.use(express.json()); - -app.get('/api/healthz', (_req, res) => { - res.json({ status: 'ok', upstream: RUST_ENGINE_BASE }); -}); - -// Proxy minimal API needed by the UI to the rust-engine container -app.post('/api/files/import-demo', async (req, res) => { - try { - const qs = req.url.includes('?') ? req.url.substring(req.url.indexOf('?')) : ''; - const url = `${RUST_ENGINE_BASE}/api/files/import-demo${qs}`; - const upstream = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: req.body ? JSON.stringify(req.body) : undefined }); - const text = await upstream.text(); - res.status(upstream.status).type(upstream.headers.get('content-type') || 'application/json').send(text); - } catch (err) { - console.error('import-demo proxy failed:', err); - res.status(502).json({ error: 'proxy_failed' }); - } -}); - -// Serve static frontend -const distDir = path.resolve(__dirname, 'dist'); -app.use(express.static(distDir)); - -// SPA fallback (Express 5 requires middleware instead of bare '*') -app.use((req, res) => { - res.sendFile(path.join(distDir, 'index.html')); -}); - -app.listen(PORT, HOST, () => { - console.log(`Web app server listening on http://${HOST}:${PORT}`); - console.log(`Proxying to rust engine at ${RUST_ENGINE_BASE}`); -}); diff --git a/web-app/src/components/layouts/chat-layout.jsx b/web-app/src/components/layouts/chat-layout.jsx deleted file mode 100644 index 626042b..0000000 --- a/web-app/src/components/layouts/chat-layout.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { useState } from "react"; -import ChatHeader from "src/components/ui/chat/chat-header"; -import ChatWindow from "src/components/ui/chat/chat-window"; -import MessageInput from "src/components/ui/chat/message-input"; - -export default function ChatLayout() { - const [messages, setMessages] = useState([ - { - role: "assistant", - content: "Hello — I can help you with code, explanations, and more.", - }, - ]); - - function addMessage(role, content) { - const msg = { role, content }; - setMessages((s) => [...s, msg]); - } - - function handleSend(text) { - const userMsg = { role: "user", content: text }; - setMessages((s) => [...s, userMsg]); - - // fake assistant reply after short delay - setTimeout(() => { - setMessages((s) => [ - ...s, - { role: "assistant", content: `You said: ${text}` }, - ]); - }, 600); - } - - function handleDeleteAll() { - if (!window.confirm("Delete all messages?")) return; - setMessages([]); - } - - return ( -
- - - -
- ); -} diff --git a/web-app/src/components/ui/button/delete-button.jsx b/web-app/src/components/ui/button/delete-button.jsx deleted file mode 100644 index c75c306..0000000 --- a/web-app/src/components/ui/button/delete-button.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Flame } from "lucide-react"; -import { motion } from "motion/react"; - -export default function FlameButton({ onClick }) { - return ( - - - - ); -} diff --git a/web-app/src/components/ui/chat/chat-header.jsx b/web-app/src/components/ui/chat/chat-header.jsx deleted file mode 100644 index 1f0258f..0000000 --- a/web-app/src/components/ui/chat/chat-header.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, { useMemo, useState } from "react"; -import { motion } from "motion/react"; -import { Rocket } from "lucide-react"; -import DeleteButton from "src/components/ui/button/delete-button"; -import FileList from "src/components/ui/file/file-list"; - -export default function ChatHeader({ - title = "Schematic Spelunker", - onDeleteAll, -}) { - const isDebug = useMemo(() => { - const p = new URLSearchParams(window.location.search); - return p.get("debug") === "1"; - }, []); - const [ingesting, setIngesting] = useState(false); - const [toast, setToast] = useState(""); - - async function triggerDemoIngest() { - try { - setIngesting(true); - const res = await fetch("/api/files/import-demo", { method: "POST" }); - const json = await res.json().catch(() => ({})); - const imported = json.imported ?? "?"; - const skipped = json.skipped ?? "?"; - const summary = `Imported: ${imported}, Skipped: ${skipped}`; - setToast(json.error ? `${summary} - ${json.error}` : summary); - setTimeout(() => setToast(""), 4000); - } catch (e) { - setToast("Import failed"); - setTimeout(() => setToast(""), 4000); - } finally { - setIngesting(false); - } - } - - return ( -
-
-
- -

- {title} -

- - {isDebug && ( - - - {ingesting ? "Seeding…" : "Seed Demo Data"} - - )} -
- {toast && ( -
- {toast} -
- )} -
-
- ); -} diff --git a/web-app/src/index.js b/web-app/src/index.js deleted file mode 100644 index 259ced7..0000000 --- a/web-app/src/index.js +++ /dev/null @@ -1,60 +0,0 @@ -import express from "express"; -import bodyParser from "body-parser"; -import axios from "axios"; -import multer from "multer"; -import path from "path"; -import { fileURLToPath } from 'url'; -import fs from "fs"; - -const app = new express(); -const port = 3000; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -app.use(express.static('public')); -app.use('/uploads', express.static('uploads')); -app.use(bodyParser.urlencoded({extended: true})); -app.use(bodyParser.json()); - -const storage = multer.diskStorage({ - destination: function (req, file, cb) { - cb(null, path.join(__dirname,'/uploads')); - }, - filename: function (req, file, cb) { - cb(null, file.originalname); - } -}) -const upload = multer({ storage: storage }) - - - -//Render the main page -app.get("/", async (req, res) => { - try{ - const response = await axios.get(`${API_URL}/all`); - res.render("file", { data: response.data }); - }catch(error){ - console.error(error); - res.status(500).json("Error fetching items"); - } -}) - -app.post("/upload", upload.single('image'), async (req, res) => { - const data = { - ...req.body, - fileName: req.file.originalname, - path: req.file.path - } - try{ - await axios.post(`${API_URL}/add`, data); - res.redirect("/"); - }catch(error){ - console.error(error); - res.status(500).json("Error uploading item"); - } -}) - -app.listen(port, () => { - console.log("API is listening on port " + port); -}) \ No newline at end of file