Web interface merge #1

Merged
max merged 9 commits from web-interface into main 2026-08-05 08:56:17 +04:00
12 changed files with 232 additions and 11 deletions
Showing only changes of commit 6707a8e27e - Show all commits
+8
View File
@@ -0,0 +1,8 @@
# Backend
DEBUG=0
MAX_UPLOAD_SIZE_MB=500
TEMP_DIR=/tmp/audio_splitter_web
CLEANUP_AFTER_SECONDS=3600
# Frontend (development)
VITE_BACKEND_URL=http://localhost:8000
+29
View File
@@ -0,0 +1,29 @@
services:
backend:
build:
context: . # Project root (contains audio_splitter/)
dockerfile: web/backend/Dockerfile
ports:
- "8000:8000"
volumes:
- /tmp/audio_splitter_web:/tmp/audio_splitter_web
environment:
- PYTHONUNBUFFERED=1
- DEBUG=1
restart: unless-stopped
frontend:
build:
context: ./web/frontend
dockerfile: Dockerfile.dev
ports:
- "5173:5173"
volumes:
- ./web/frontend:/app
- /app/node_modules
environment:
- BACKEND_URL=http://backend:8000
- VITE_BACKEND_URL=http://backend:8000
depends_on:
- backend
restart: unless-stopped
+18
View File
@@ -0,0 +1,18 @@
__pycache__
*.pyc
*.pyo
*.pyd
.Python
*.so
*.egg
*.egg-info
dist
build
.venv
venv
.env
.git
.gitignore
README.md
Dockerfile
.dockerignore
+54
View File
@@ -0,0 +1,54 @@
FROM python:3.13-slim
# Install FFmpeg and system dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ffmpeg \
ca-certificates \
&& \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Set Python environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# Set working directory
WORKDIR /app
# ------------------------------------------------------------
# Copy and install Python dependencies
# ------------------------------------------------------------
COPY web/backend/requirements-web.txt .
RUN pip install --no-cache-dir -r requirements-web.txt
# ------------------------------------------------------------
# Copy the backend application code
# ------------------------------------------------------------
COPY web/backend /app/backend
# ------------------------------------------------------------
# Copy and install the audio_splitter package
# ------------------------------------------------------------
COPY audio_splitter /app/audio_splitter
COPY setup.py pyproject.toml README.md /app/
# Install audio_splitter as a package
RUN pip install --no-cache-dir /app
# ------------------------------------------------------------
# Create a non-root user
# ------------------------------------------------------------
RUN addgroup --system --gid 1000 appgroup && \
adduser --system --uid 1000 --ingroup appgroup appuser && \
chown -R appuser:appgroup /app
USER appuser
# ------------------------------------------------------------
# Expose port and start
# ------------------------------------------------------------
EXPOSE 8000
# PYTHONPATH is already /app by default because we set WORKDIR /app
CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"]
+7 -4
View File
@@ -7,7 +7,7 @@ from fastapi.middleware.cors import CORSMiddleware
from backend.config import settings
from backend.api import upload, split, status, download
from backend.api.websocket import router as websocket_router
from backend.services import progress_publisher # Import the module
from backend.services import progress_publisher
app = FastAPI(
title="Audio Splitter Web API",
@@ -24,9 +24,12 @@ app.add_middleware(
allow_headers=["*"],
)
# Store the main event loop in the progress_publisher module
# This avoids a circular import
progress_publisher.MAIN_LOOP = asyncio.get_running_loop()
@app.on_event("startup")
async def startup_event():
"""Store the main event loop for use in other threads."""
progress_publisher.MAIN_LOOP = asyncio.get_running_loop()
# Include routers
app.include_router(upload.router)
+8
View File
@@ -0,0 +1,8 @@
fastapi>=0.104.0
uvicorn[standard]>=0.24.0
python-multipart>=0.0.6
aiofiles>=23.2.0
pydantic>=2.5.0
pydantic-settings>=2.0.0
python-dotenv>=1.0.0
websockets>=12.0
+10 -4
View File
@@ -5,7 +5,7 @@ from typing import Dict, Set
from fastapi import WebSocket
# This will be set by main.py when the app starts
# This will be set by main.py during startup
MAIN_LOOP = None
active_connections: Dict[str, Set[WebSocket]] = {}
@@ -29,13 +29,19 @@ def publish_progress(task_id: str, progress: int, message: str, status: str = "p
}
to_remove = set()
# Get the loop to use: either the stored one or try to get the current loop
loop = MAIN_LOOP
if loop is None:
try:
loop = asyncio.get_running_loop()
except RuntimeError:
# No running loop, fallback to default event loop
loop = asyncio.get_event_loop()
for websocket in active_connections.get(task_id, set()):
try:
# Use the stored main loop, or fallback to getting the current loop
loop = MAIN_LOOP or asyncio.get_running_loop()
asyncio.run_coroutine_threadsafe(websocket.send_json(data), loop)
except Exception:
# Client disconnected or other error
to_remove.add(websocket)
# Clean up disconnected clients
+12
View File
@@ -0,0 +1,12 @@
node_modules
dist
.git
.gitignore
*.log
.env
.env.local
.DS_Store
Dockerfile
Dockerfile.dev
.dockerignore
nginx.conf
+27
View File
@@ -0,0 +1,27 @@
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
# Install dependencies
COPY package.json package-lock.json* ./
RUN npm install
# Copy the application code and build
COPY . .
RUN npm run build
# Stage 2: Production (nginx)
FROM nginx:alpine
# Copy built assets from builder
COPY --from=builder /app/dist /usr/share/nginx/html
# Copy nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Expose the port
EXPOSE 80
# Start nginx
CMD ["nginx", "-g", "daemon off;"]
+17
View File
@@ -0,0 +1,17 @@
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Install dependencies
COPY package.json package-lock.json* ./
RUN npm install
# Copy the application code
COPY . .
# Expose the port
EXPOSE 5173
# Start the development server
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
+7 -2
View File
@@ -1,18 +1,23 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// Backend URL for proxy (default to localhost for local dev)
const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8000'
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
target: BACKEND_URL,
changeOrigin: true,
secure: false,
},
'/ws': {
target: 'ws://localhost:8000',
target: BACKEND_URL.replace(/^http/, 'ws'),
ws: true,
changeOrigin: true,
secure: false,
},
},
},
+34
View File
@@ -0,0 +1,34 @@
server {
listen 80;
server_name _;
# Root directory for static files
root /usr/share/nginx/html;
index index.html;
# Serve React app
location / {
try_files $uri $uri/ /index.html;
}
# Proxy API requests to backend
location /api/ {
proxy_pass http://backend:8000/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Proxy WebSocket requests to backend
location /ws/ {
proxy_pass http://backend:8000/ws/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}