first commit
This commit is contained in:
205
PROJECT.md
Normal file
205
PROJECT.md
Normal 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
94
PROPOSAL.md
Normal 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
4
activity.log
Normal 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
21
backend/Dockerfile
Normal 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
8
backend/app/main.py
Normal 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
15
backend/app/worker.py
Normal 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
13
backend/requirements.txt
Normal 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
104
docker-compose.yml
Normal 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
24
frontend/.gitignore
vendored
Normal 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
20
frontend/Dockerfile
Normal 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
73
frontend/README.md
Normal 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
23
frontend/eslint.config.js
Normal 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
13
frontend/index.html
Normal 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
21
frontend/nginx.conf
Normal 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
3269
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
frontend/package.json
Normal file
30
frontend/package.json
Normal 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
1
frontend/public/vite.svg
Normal 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
42
frontend/src/App.css
Normal 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
35
frontend/src/App.tsx
Normal 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
|
||||
1
frontend/src/assets/react.svg
Normal file
1
frontend/src/assets/react.svg
Normal 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
68
frontend/src/index.css
Normal 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
10
frontend/src/main.tsx
Normal 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>,
|
||||
)
|
||||
28
frontend/tsconfig.app.json
Normal file
28
frontend/tsconfig.app.json
Normal 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
7
frontend/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
26
frontend/tsconfig.node.json
Normal file
26
frontend/tsconfig.node.json
Normal 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
7
frontend/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
Reference in New Issue
Block a user