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.config import settings
from backend.api import upload, split, status, download from backend.api import upload, split, status, download
from backend.api.websocket import router as websocket_router 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( app = FastAPI(
title="Audio Splitter Web API", title="Audio Splitter Web API",
@@ -24,9 +24,12 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
# Store the main event loop in the progress_publisher module
# This avoids a circular import @app.on_event("startup")
progress_publisher.MAIN_LOOP = asyncio.get_running_loop() async def startup_event():
"""Store the main event loop for use in other threads."""
progress_publisher.MAIN_LOOP = asyncio.get_running_loop()
# Include routers # Include routers
app.include_router(upload.router) 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 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 MAIN_LOOP = None
active_connections: Dict[str, Set[WebSocket]] = {} 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() 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()): for websocket in active_connections.get(task_id, set()):
try: 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) asyncio.run_coroutine_threadsafe(websocket.send_json(data), loop)
except Exception: except Exception:
# Client disconnected or other error
to_remove.add(websocket) to_remove.add(websocket)
# Clean up disconnected clients # 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 { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' 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({ export default defineConfig({
plugins: [react()], plugins: [react()],
server: { server: {
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:8000', target: BACKEND_URL,
changeOrigin: true, changeOrigin: true,
secure: false,
}, },
'/ws': { '/ws': {
target: 'ws://localhost:8000', target: BACKEND_URL.replace(/^http/, 'ws'),
ws: true, ws: true,
changeOrigin: 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;
}
}