first commit

This commit is contained in:
2026-01-27 14:00:02 +01:00
commit 0b310be3b2
26 changed files with 4162 additions and 0 deletions

205
PROJECT.md Normal file
View File

@@ -0,0 +1,205 @@
# Project: Auteur AI (Flow Edition) - Production Spec
**Version:** 2.1 (Expanded Production Build)
**Target Platform:** Full Stack Web Application
**Infrastructure:** Self-Hosted Linux Environment (Docker Compose / K8s)
**Goal:** Create a functional pre-production asset management suite for Google Flow (Veo 3.1).
## 1. System Architecture & Infrastructure
Since resources are unlimited and the target environment is a robust self-hosted Linux cluster, we will run a microservices-ready monolithic structure via Docker. This architecture prioritizes data privacy (keeping scripts and assets local), low latency for heavy asset manipulation, and the flexibility to scale individual components (like the AI inference engine) without refactoring the entire stack.
### The Stack
* **Frontend:** **React 18** (built via Vite). We will utilize **TypeScript** for strict type safety across the complex JSON data structures required by Google Flow.
* **Styling:** **TailwindCSS** for utility-first styling combined with **Shadcn/UI** (Radix Primitives) for accessible, keyboard-navigable components.
* **State Management:** **TanStack Query (React Query)** is critical here. It will handle server-state caching, deduping requests, and managing the "loading" and "error" states of asynchronous AI operations. We will use **Zustand** for transient client-side state (e.g., dragging an ingredient into a slot).
* **Backend:** **Python (FastAPI)**.
* *Rationale:* While Node.js is capable, Python is the native language of AI. Using FastAPI allows us to integrate directly with libraries like `langchain`, `llama-index`, or raw `transformers` pipelines if we decide to move beyond API-based LLMs in the future. FastAPI also provides automatic OpenAPI (Swagger) documentation and high-performance async support via Starlette.
* **Database:** **PostgreSQL 16**.
* *Rationale:* We need a robust relational database to manage the strict hierarchy of Projects -> Scenes -> Shots. PostgreSQL's binary JSON (JSONB) support is essential for storing the flexible metadata associated with AI assets and the complex, nested JSON payloads generated for Veo.
* **Object Storage:** **MinIO**.
* *Rationale:* A self-hosted, S3-compatible object storage server. This allows us to handle gigabytes of video references and high-res character sheets without clogging the database or the application server's file system. It supports pre-signed URLs, offloading file serving traffic directly to the client.
* **AI Inference:** **Local Ollama instance**.
* *Rationale:* Running Llama 3 (8B or 70B parameter) or Mistral locally ensures zero data leakage. The API will communicate with Ollama via HTTP requests, allowing for easy model swapping (e.g., testing `codellama` for JSON generation vs `llama3` for creative writing).
### Docker Compose Services
The production `docker-compose.yml` will orchestrate the following interconnected services:
1. `frontend`: A high-performance **Nginx** container serving the static React build. It will also act as a reverse proxy to route `/api` requests to the backend, eliminating CORS issues.
2. `backend`: The **FastAPI** application server running via `uvicorn` (Port 8000). It acts as the orchestrator.
3. `db`: **PostgreSQL 16** (Port 5432) with a persistent volume for data safety.
4. `minio`: The S3-compatible storage engine (Port 9000 for API, 9001 for Console).
5. `redis`: **Redis** (Port 6379).
* *Usage:* This is crucial for a robust production app. It will serve as the message broker for **Celery** or **ARQ** (async task queues). When a user uploads a 4K video or requests a full script breakdown, the API will offload this "heavy lifting" to a background worker to keep the interface snappy.
6. `worker`: A Python container running the background task consumer (Celery/ARQ) to process video thumbnails and long-running LLM inference tasks.
## 2. Database Schema (PostgreSQL)
The database schema needs to be robust enough to handle the relationships between creative entities. The agent must create migrations (using **Alembic** for Python) for the following schema. Note the addition of indices for performance.
```sql
-- Enum for strict typing of asset categories, critical for the "Slot" system
CREATE TYPE asset_type AS ENUM ('Character', 'Location', 'Object', 'Style');
-- Projects: The top-level container
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
resolution TEXT DEFAULT '4K', -- e.g., '3840x2160'
aspect_ratio TEXT DEFAULT '16:9',
veo_version TEXT DEFAULT '3.1',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Ingredients: The reusable assets (Actors, Sets, Props)
CREATE TABLE ingredients (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL,
type asset_type NOT NULL,
s3_key TEXT NOT NULL, -- The path in the MinIO bucket
s3_bucket TEXT DEFAULT 'auteur-assets',
thumbnail_key TEXT, -- Path to a generated low-res thumbnail
metadata JSONB DEFAULT '{}', -- Stores AI-generated tags (e.g., {"hair": "blue", "mood": "dark"})
created_at TIMESTAMP DEFAULT NOW()
);
-- Index for faster filtering by type within a project
CREATE INDEX idx_ingredients_project_type ON ingredients(project_id, type);
-- Scenes: Logical groupings within the script
CREATE TABLE scenes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
slugline TEXT NOT NULL, -- e.g., "INT. SERVER ROOM - NIGHT"
raw_content TEXT, -- The full text body of the scene
sequence_number INT NOT NULL, -- For ordering scenes in the UI
created_at TIMESTAMP DEFAULT NOW()
);
-- Shots: The atomic unit of generation
CREATE TABLE shots (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
scene_id UUID REFERENCES scenes(id) ON DELETE CASCADE,
description TEXT NOT NULL, -- The visual description
duration FLOAT, -- Estimated duration in seconds
sequence_number INT, -- Order within the scene
-- "The Slot System": JSONB array of Ingredient UUIDs assigned to slots 1, 2, 3.
-- Example: ["uuid-char-1", "uuid-loc-2", null]
assigned_ingredients JSONB DEFAULT '[]',
-- The computed prompt context sent to the LLM
llm_context_cache TEXT,
-- The final output for Google Flow/Veo
veo_json_payload JSONB,
status TEXT DEFAULT 'draft', -- 'draft', 'generating_json', 'ready'
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_shots_scene ON shots(scene_id);
```
## 3. API Module Specifications
### Module 1: Asset Library (Real Uploads & Processing)
**Endpoint:** `POST /api/assets/upload`
**Logic Flow:**
1. **Validation:** Frontend sends `FormData` containing the file and metadata (project_id, type). Backend validates file type (image/png, image/jpeg) and size limits.
2. **Storage:** Backend streams the file directly to the **MinIO** bucket `auteur-assets` using `boto3` or `minio-py`. It generates a unique object key (e.g., `proj_id/uuid.jpg`).
3. **Database:** A record is created in the `ingredients` table with the `s3_key`.
4. **Background Processing (Async):** A task is pushed to the Redis queue to:
* Generate a 200px thumbnail for the UI.
* (Optional) Run a "Vision" LLM task (using Ollama's `llava` model) to auto-caption the image and populate the `metadata` JSONB field (e.g., "A robotic dog standing in rain").
5. **Return:** The API returns the new Asset object, including a pre-signed URL for immediate display.
### Module 2: Intelligent Script Parser (The "Ingestion Engine")
**Endpoint:** `POST /api/scripts/parse`
**Logic Flow:**
1. **Ingest:** User uploads a `.txt` or `.fountain` screenplay file.
2. **Preprocessing:** Backend reads the text. If it's a large script, it chunks it by Scene Headers (`INT.`, `EXT.`).
3. **AI Analysis (Ollama):** The content is sent to the local LLM.
* *System Prompt:* "You are a Script Supervisor. Break the following screenplay text into a structured JSON array of shots. Identify the action lines that denote visual changes. Ignore dialogue unless it implies visual action."
* *Schema Enforcement:* We will use **Pydantic** models to validate that the LLM's output matches the expected JSON structure (Shot Description, Estimated Duration).
4. **Persistence:** The backend iterates through the validated JSON array and performs a bulk insert into the `scenes` and `shots` tables, ensuring `sequence_numbers` are preserved.
5. **Notification:** The frontend is notified (via polling or WebSocket) that the script is ready for review.
### Module 3: Flow Assembly & JSON Generation (The "Translator")
**Endpoint:** `POST /api/shots/:id/generate-flow`
**Logic Flow:**
1. **Data Gathering:** The endpoint fetches the `shot` record. It then queries the `ingredients` table to retrieve the full details (Name, Metadata, Visual Description) of the UUIDs stored in `assigned_ingredients`.
2. **Context Construction:** A rich text prompt is assembled.
* *Example:* "Construct a Google Veo 3.1 JSON configuration. The shot is: '{shot.description}'. The Character is '{ingredient[0].name}', described as '{ingredient[0].metadata}'. The Location is '{ingredient[1].name}'."
3. **Prompt Engineering:** The prompt explicitly forbids the LLM from adding hallucinated details and forces it to map the Ingredient characteristics to the specific JSON fields required by Veo (e.g., `subject.description`, `environment.lighting`).
4. **AI Action:** Send to Ollama. We use a low-temperature setting (e.g., 0.2) to ensure deterministic, strictly formatted JSON output.
5. **Validation:** The backend parses the returned JSON string into a Python Dictionary. If parsing fails, it retries up to 2 times.
6. **Update:** The valid JSON is saved to `shots.veo_json_payload`, and the status is updated to 'ready'.
## 4. Frontend Integration Guidelines
* **API Client:** Use `axios` with a configured base URL (e.g., `/api/v1`). Implement interceptors to handle 401/403 errors or global loading states.
* **Image Handling (Presigned URLs):** The frontend should never try to fetch images directly from the MinIO container's internal IP. Instead, the API returns a presigned URL (valid for 1 hour) that allows the browser to fetch the image directly from the MinIO public endpoint.
* **Optimistic UI:** When a user updates a shot description or drags an ingredient, the UI should update immediately (using `react-query`'s `setQueryData`) before the API call resolves. If the API call fails, the change is rolled back with a toast notification.
* **Editor Component:** For the JSON editor, use `@monaco-editor/react` to provide syntax highlighting and code folding, giving the "IDE" feel.
## 5. Implementation Prompt for Coding Agent
*Copy and paste this detailed instruction block into Antigravity/Cursor/Windsurf to begin the build process:*
> "Act as a Senior Full-Stack Software Architect. We are building 'Auteur AI', a professional video production management application.
>
> **Core Constraint:** DO NOT USE MOCK DATA. This is a real implementation meant for production deployment on a Linux cluster.
>
> **Technology Stack Definition:**
>
> 1. **Backend:** Python **FastAPI**.
> * Use `SQLAlchemy` (Async) for ORM.
> * Use `Alembic` for database migrations.
> * Use `Pydantic` for strict data validation (Models).
> * Use `boto3` for MinIO (S3) interaction.
> * Use `ollama` python library for communicating with the local LLM (`http://host.docker.internal:11434`).
>
> 2. **Frontend:** React (Vite) + TypeScript + TailwindCSS.
> * Use `axios` for API requests.
> * Use `tanstack/react-query` for data fetching and caching.
> * Use `shadcn/ui` components for the interface.
> * Use `lucide-react` for iconography.
>
> **Task 1: Infrastructure Setup**
> Create a production-ready `docker-compose.yml`. It must include:
>
> * `postgres` (v16) with a named volume for persistence.
> * `minio` with a create-bucket entrypoint script.
> * `backend` service (FastAPI) with hot-reload enabled for dev.
> * `frontend` service (Node/Vite) proxying requests to the backend.
>
> **Task 2: Database Layer**
> Define the SQLAlchemy models exactly matching the schema provided in the TDD (Projects, Ingredients, Scenes, Shots). Create the initial Alembic migration script.
>
> **Task 3: Backend API Implementation**
>
> * Implement the `POST /api/assets/upload` endpoint using `UploadFile`. It must save to MinIO and Postgres.
> * Implement the `POST /api/scripts/parse` endpoint. It must accept a text file, chunk it, send it to Ollama for analysis, and store the resulting Shots.
>
> **Task 4: Frontend Development**
>
> * Set up the React Router with layouts (Sidebar/Header).
> * Build the 'Asset Library' view: Fetch real data from the API, display images using presigned URLs, and implement a real file upload dropzone."

94
PROPOSAL.md Normal file
View File

@@ -0,0 +1,94 @@
# Project Proposal: Auteur AI (Flow Edition)
## 1. Project Overview
This project aims to build **Auteur AI**, a production-grade, self-hosted asset management suite for Google Flow (Veo 3.1). The system will be a full-stack web application designed to run on a Linux cluster using Docker Compose. It features a React frontend, Python FastAPI backend, PostgreSQL database, MinIO object storage, and integration with an external OpenAI-compatible API for script analysis and JSON generation.
## 2. Requirements
### Infrastructure & Architecture
- **Deployment**: Docker Compose (Microservices-ready monolith).
- **Services**:
- `frontend`: Nginx serving React code.
- `backend`: FastAPI (Port 8000).
- `db`: PostgreSQL 16 (Port 5432).
- `minio`: S3-compatible storage (Port 9000/9001).
- `redis`: Message broker (Port 6379).
- `worker`: Python background worker (Celery/ARQ).
- **AI Integration**: External OpenAI-compatible API (e.g., OpenAI, Anthropic, OpenRouter, or self-hosted vLLM external to this stack).
### Backend (Python/FastAPI)
- **Framework**: FastAPI with Async SQLAlchemy.
- **Database**: PostgreSQL with Alembic for migrations.
- **Storage**: `boto3` for MinIO interactions.
- **AI**: `openai` Python library (configured with `OPENAI_API_BASE` and `OPENAI_API_KEY`) to support any compatible provider.
- **Core Modules**:
- **Asset Library**: Upload handling, storage in MinIO, thumbnail generation.
- **Script Parser**: Ingest text/fountain files, chunking, AI analysis (Scene/Shot identification).
- **Flow Assembly**: Generating Google Veo compliant JSON payloads via LLM.
### Frontend (React/Vite)
- **Stack**: React 18, TypeScript, TailwindCSS.
- **UI Library**: Shadcn/UI, Lucide-react.
- **State Management**: TanStack Query (Server state), Zustand (Client state).
- **Features**:
- Asset Library with drag-and-drop uploads.
- Script ingestion and review interface.
- "IDE-like" JSON editor (Monaco Editor).
- Optimistic UI updates.
### Data Models
- **Projects**: Top-level container.
- **Ingredients**: Assets (Characters, Locations, Objects) with S3 keys and metadata.
- **Scenes**: Script sections.
- **Shots**: Atomic units with descriptions, durations, and extensive metadata (Slot system, Veo JSON).
## 3. Implementation Plan
I propose the following phased approach to build the application:
### Phase 1: Infrastructure & Initialization
- Set up the project repository structure.
- Create `docker-compose.yml` with all defined services (Db, MinIO, Redis, Backend, Frontend stub).
- Verify container orchestration and networking.
### Phase 2: Backend Core
- Initialize FastAPI project with poetry or pip.
- Configure Database connection (Async SQLAlchemy).
- Define Pydantic models and SQLAlchemy tables based on the schema.
- Set up Alembic and run initial migrations.
### Phase 3: Asset Management (MVP)
- Implement MinIO client setup.
- Build `POST /api/assets/upload`.
- Implement background worker for thumbnail generation (Redis + Worker).
- Create basic Frontend Asset Library view to test uploads and retrieval.
### Phase 4: Script Ingestion & AI Integration
- Implement `POST /api/scripts/parse`.
- Integrate `openai` client.
- Develop the prompt engineering logic for Script -> Visual Actions.
- Build Frontend interface for Script upload and Shot review.
### Phase 5: Flow Generation & UI Polish
- Implement `POST /api/shots/:id/generate-flow`.
- Integrate Monaco Editor for JSON result viewing.
- Finalize UI styling and responsiveness.
- Comprehensive documentation.
## 4. Questions & Clarifications Needed
1. **AI Provider Details**: Since we are using OpenAI-compatible endpoints, please make sure you have the `OPENAI_API_BASE` and `OPENAI_API_KEY` ready for the configuration. If you are using a specific model (e.g., `gpt-4o`, `claude-3-5-sonnet`, `deepseek-chat`), please specify it so we can set a sensible default in the `.env` file.
2. **Ports**: Are the default ports (8000, 5432, 9000, 9001, 6379) free to use on your host, or should we map them to different external ports?
3. **Credentials**: Do you have specific preference for the initial database/MinIO credentials, or should I generate secure defaults for the `.env` file?
Please review this proposal. Upon your confirmation, I will begin with Phase 1.

4
activity.log Normal file
View File

@@ -0,0 +1,4 @@
# Activity Log
2026-01-27 13:45:00 - Initialized project. Read PROJECT.md. Created PROPOSAL.md.
2026-01-27 13:50:00 - Updated PROPOSAL.md to switch from local Ollama to OpenAI-compatible endpoints as per user request.
2026-01-27 13:55:00 - Started Phase 1. Created directory structure, docker-compose.yml, and .gitignore.

21
backend/Dockerfile Normal file
View File

@@ -0,0 +1,21 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Install python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Run the application
# We use reload for development
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

8
backend/app/main.py Normal file
View File

@@ -0,0 +1,8 @@
from fastapi import FastAPI
app = FastAPI(title="Auteur AI API")
@app.get("/")
async def root():
return {"message": "Auteur AI Backend is running"}

15
backend/app/worker.py Normal file
View File

@@ -0,0 +1,15 @@
from celery import Celery
import os
redis_url = os.getenv("REDIS_URL", "redis://redis:6379/0")
celery_app = Celery(
"worker",
broker=redis_url,
backend=redis_url
)
@celery_app.task
def test_task():
return "Worker is running"

13
backend/requirements.txt Normal file
View File

@@ -0,0 +1,13 @@
fastapi
uvicorn[standard]
sqlalchemy
asyncpg
alembic
pydantic
pydantic-settings
boto3
openai
redis
celery
python-multipart

104
docker-compose.yml Normal file
View File

@@ -0,0 +1,104 @@
services:
# 1. Reverse Proxy & Frontend Serving
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:80"
depends_on:
- backend
networks:
- auteur-network
# 2. Backend Orchestrator
backend:
build:
context: ./backend
dockerfile: Dockerfile
environment:
- DATABASE_URL=postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-auteur}
- MINIO_ENDPOINT=minio:9000
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER:-minioadmin}
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD:-minioadmin}
- REDIS_URL=redis://redis:6379/0
- OPENAI_API_BASE=${OPENAI_API_BASE}
- OPENAI_API_KEY=${OPENAI_API_KEY}
volumes:
- ./backend/app:/app/app
ports:
- "8000:8000"
depends_on:
- db
- redis
- minio
networks:
- auteur-network
# 3. Database
db:
image: postgres:16-alpine
restart: always
environment:
- POSTGRES_USER=${POSTGRES_USER:-postgres}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
- POSTGRES_DB=${POSTGRES_DB:-auteur}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
networks:
- auteur-network
# 4. Object Storage
minio:
image: minio/minio
command: server /data --console-address ":9001"
environment:
- MINIO_ROOT_USER=${MINIO_ROOT_USER:-minioadmin}
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-minioadmin}
volumes:
- minio_data:/data
ports:
- "9000:9000"
- "9001:9001"
networks:
- auteur-network
# 5. Message Broker
redis:
image: redis:7-alpine
ports:
- "6379:6379"
networks:
- auteur-network
# 6. Background Worker
worker:
build:
context: ./backend
dockerfile: Dockerfile
command: celery -A app.worker worker --loglevel=info
environment:
- DATABASE_URL=postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-auteur}
- MINIO_ENDPOINT=minio:9000
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER:-minioadmin}
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD:-minioadmin}
- REDIS_URL=redis://redis:6379/0
- OPENAI_API_BASE=${OPENAI_API_BASE}
- OPENAI_API_KEY=${OPENAI_API_KEY}
volumes:
- ./backend/app:/app/app
depends_on:
- backend
- redis
networks:
- auteur-network
volumes:
postgres_data:
minio_data:
networks:
auteur-network:
driver: bridge

24
frontend/.gitignore vendored Normal file
View File

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

20
frontend/Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
# Stage 1: Build
FROM node:20-alpine as build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build
# Stage 2: Serve
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

73
frontend/README.md Normal file
View File

@@ -0,0 +1,73 @@
# React + TypeScript + 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 updating the configuration to enable type-aware lint rules:
```js
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```

23
frontend/eslint.config.js Normal file
View File

@@ -0,0 +1,23 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])

13
frontend/index.html Normal file
View File

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

21
frontend/nginx.conf Normal file
View File

@@ -0,0 +1,21 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Serve static assets
location / {
try_files $uri $uri/ /index.html;
}
# Proxy API requests to the backend service
location /api/ {
proxy_pass http://backend:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

3269
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
frontend/package.json Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.4",
"vite": "^7.2.4"
}
}

1
frontend/public/vite.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

42
frontend/src/App.css Normal file
View File

@@ -0,0 +1,42 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

35
frontend/src/App.tsx Normal file
View File

@@ -0,0 +1,35 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
)
}
export default App

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

68
frontend/src/index.css Normal file
View File

@@ -0,0 +1,68 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

10
frontend/src/main.tsx Normal file
View File

@@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)

View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}

7
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"types": ["node"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

7
frontend/vite.config.ts Normal file
View File

@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})