fix: Remove hardcoded ports, support custom port configuration

- Replace all hardcoded localhost:8000/3000/8501 with environment variables
- Frontend: Use API_PORT env var in next.config.ts
- Backend: Use API_PORT env var in editor.py and quality.py
- Web UI: Use API_PORT and EDITOR_PORT env vars in all Streamlit pages
- Update dev.sh to pass environment variables to all services
- Add .env.example with port configuration template

Now supports custom ports via environment variables:
  API_PORT=8080 EDITOR_PORT=3001 WEB_PORT=8502 ./dev.sh

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-10 16:13:02 +08:00
parent 6bf16936af
commit 3f59b324ad
8 changed files with 61 additions and 18 deletions

21
.env.example Normal file
View File

@@ -0,0 +1,21 @@
# Pixelle-Video Environment Configuration
# Copy this file to .env and customize as needed
# ============================================================================
# Port Configuration
# ============================================================================
# FastAPI Backend Port
API_PORT=8000
# Next.js Editor Port
EDITOR_PORT=3000
# Streamlit Web UI Port
WEB_PORT=8501
# ============================================================================
# Other Configuration
# ============================================================================
# Add other environment variables here as needed

View File

@@ -47,24 +47,31 @@ from api.schemas.editor import (
from fastapi import BackgroundTasks from fastapi import BackgroundTasks
import asyncio import asyncio
import uuid as uuid_module import uuid as uuid_module
import os
router = APIRouter(prefix="/editor", tags=["Editor"]) router = APIRouter(prefix="/editor", tags=["Editor"])
# Export task storage # Export task storage
_export_tasks: dict = {} _export_tasks: dict = {}
# Get API port from environment
API_PORT = os.getenv("API_PORT", "8000")
def _path_to_url(file_path: str, base_url: str = "http://localhost:8000") -> str:
def _path_to_url(file_path: str, base_url: str = None) -> str:
"""Convert local file path to URL accessible through API""" """Convert local file path to URL accessible through API"""
if not file_path: if not file_path:
return None return None
if base_url is None:
base_url = f"http://localhost:{API_PORT}"
import os import os
from pathlib import Path from pathlib import Path
# Normalize path separators # Normalize path separators
file_path = file_path.replace("\\", "/") file_path = file_path.replace("\\", "/")
# Extract relative path from output directory # Extract relative path from output directory
parts = file_path.split("/") parts = file_path.split("/")
try: try:
@@ -73,7 +80,7 @@ def _path_to_url(file_path: str, base_url: str = "http://localhost:8000") -> str
relative_path = "/".join(relative_parts) relative_path = "/".join(relative_parts)
except ValueError: except ValueError:
relative_path = Path(file_path).name relative_path = Path(file_path).name
return f"{base_url}/api/files/{relative_path}" return f"{base_url}/api/files/{relative_path}"
@@ -951,8 +958,9 @@ async def export_video(
for frame in sorted_frames: for frame in sorted_frames:
path = frame.get("video_segment_path", "") path = frame.get("video_segment_path", "")
if path.startswith("http"): if path.startswith("http"):
# Extract path from URL # Extract path from URL (format: http://localhost:{port}/api/files/{relative_path})
path = path.replace("http://localhost:8000/api/files/", "output/") if "/api/files/" in path:
path = "output/" + path.split("/api/files/")[-1]
video_segments.append(path) video_segments.append(path)
_export_tasks[task_id]["progress"] = 0.3 _export_tasks[task_id]["progress"] = 0.3

View File

@@ -349,8 +349,9 @@ async def extract_style(
# Convert URL to file path if needed # Convert URL to file path if needed
actual_path = image_path actual_path = image_path
if image_path.startswith("http"): if image_path.startswith("http"):
# Extract path from URL like http://localhost:8000/api/files/... # Extract path from URL (format: http://localhost:{port}/api/files/{relative_path})
actual_path = image_path.replace("http://localhost:8000/api/files/", "output/") if "/api/files/" in image_path:
actual_path = "output/" + image_path.split("/api/files/")[-1]
# Check if file exists # Check if file exists
import os import os

6
dev.sh
View File

@@ -54,7 +54,7 @@ print_banner() {
start_api() { start_api() {
echo -e "${GREEN}🚀 Starting FastAPI Backend...${NC}" echo -e "${GREEN}🚀 Starting FastAPI Backend...${NC}"
uv run python api/app.py --port $API_PORT --reload & API_PORT=$API_PORT uv run python api/app.py --port $API_PORT --reload &
echo $! > "$PID_DIR/api.pid" echo $! > "$PID_DIR/api.pid"
echo -e " ${GREEN}${NC} API running at: ${YELLOW}http://localhost:$API_PORT${NC}" echo -e " ${GREEN}${NC} API running at: ${YELLOW}http://localhost:$API_PORT${NC}"
echo -e " ${GREEN}${NC} API Docs at: ${YELLOW}http://localhost:$API_PORT/docs${NC}" echo -e " ${GREEN}${NC} API Docs at: ${YELLOW}http://localhost:$API_PORT/docs${NC}"
@@ -63,7 +63,7 @@ start_api() {
start_editor() { start_editor() {
echo -e "${GREEN}🎬 Starting Next.js Editor...${NC}" echo -e "${GREEN}🎬 Starting Next.js Editor...${NC}"
cd "$PROJECT_ROOT/frontend" cd "$PROJECT_ROOT/frontend"
PORT=$EDITOR_PORT npm run dev & API_PORT=$API_PORT PORT=$EDITOR_PORT npm run dev &
echo $! > "$PID_DIR/editor.pid" echo $! > "$PID_DIR/editor.pid"
cd "$PROJECT_ROOT" cd "$PROJECT_ROOT"
echo -e " ${GREEN}${NC} Editor running at: ${YELLOW}http://localhost:$EDITOR_PORT${NC}" echo -e " ${GREEN}${NC} Editor running at: ${YELLOW}http://localhost:$EDITOR_PORT${NC}"
@@ -71,7 +71,7 @@ start_editor() {
start_web() { start_web() {
echo -e "${GREEN}🌐 Starting Streamlit Web UI...${NC}" echo -e "${GREEN}🌐 Starting Streamlit Web UI...${NC}"
uv run streamlit run web/app.py --server.port $WEB_PORT & API_PORT=$API_PORT EDITOR_PORT=$EDITOR_PORT uv run streamlit run web/app.py --server.port $WEB_PORT &
echo $! > "$PID_DIR/web.pid" echo $! > "$PID_DIR/web.pid"
echo -e " ${GREEN}${NC} Web UI running at: ${YELLOW}http://localhost:$WEB_PORT${NC}" echo -e " ${GREEN}${NC} Web UI running at: ${YELLOW}http://localhost:$WEB_PORT${NC}"
} }

View File

@@ -2,10 +2,11 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
async rewrites() { async rewrites() {
const apiPort = process.env.API_PORT || '8000';
return [ return [
{ {
source: '/api/:path*', source: '/api/:path*',
destination: 'http://localhost:8000/api/:path*', destination: `http://localhost:${apiPort}/api/:path*`,
}, },
] ]
}, },

View File

@@ -26,6 +26,10 @@ from web.utils.async_helpers import run_async
from pixelle_video.models.progress import ProgressEvent from pixelle_video.models.progress import ProgressEvent
from pixelle_video.config import config_manager from pixelle_video.config import config_manager
# Get ports from environment
API_PORT = os.getenv("API_PORT", "8000")
EDITOR_PORT = os.getenv("EDITOR_PORT", "3000")
def render_output_preview(pixelle_video, video_params): def render_output_preview(pixelle_video, video_params):
"""Render output preview section (right column)""" """Render output preview section (right column)"""
@@ -135,7 +139,7 @@ def render_single_output(pixelle_video, video_params):
# Submit to async API # Submit to async API
response = requests.post( response = requests.post(
"http://localhost:8000/api/video/generate/async", f"http://localhost:{API_PORT}/api/video/generate/async",
json=api_payload, json=api_payload,
timeout=30 timeout=30
) )
@@ -309,7 +313,7 @@ def render_single_output(pixelle_video, video_params):
pass pass
if task_id: if task_id:
editor_url = f"http://localhost:3000/editor?storyboard_id={task_id}" editor_url = f"http://localhost:{EDITOR_PORT}/editor?storyboard_id={task_id}"
st.markdown( st.markdown(
f''' f'''
<a href="{editor_url}" target="_blank" style="text-decoration: none;"> <a href="{editor_url}" target="_blank" style="text-decoration: none;">

View File

@@ -33,6 +33,9 @@ from web.components.header import render_header
from web.i18n import tr from web.i18n import tr
from web.utils.async_helpers import run_async from web.utils.async_helpers import run_async
# Get ports from environment
EDITOR_PORT = os.getenv("EDITOR_PORT", "3000")
# Page config # Page config
st.set_page_config( st.set_page_config(
page_title="History - Pixelle-Video", page_title="History - Pixelle-Video",
@@ -363,7 +366,7 @@ def render_task_detail_modal(task_id: str, pixelle_video):
) )
# Open in Editor button # Open in Editor button
editor_url = f"http://localhost:3000/editor?storyboard_id={task_id}" editor_url = f"http://localhost:{EDITOR_PORT}/editor?storyboard_id={task_id}"
st.markdown( st.markdown(
f''' f'''
<a href="{editor_url}" target="_blank" style="text-decoration: none;"> <a href="{editor_url}" target="_blank" style="text-decoration: none;">

View File

@@ -22,6 +22,7 @@ Features:
import streamlit as st import streamlit as st
import requests import requests
import time import time
import os
from datetime import datetime from datetime import datetime
from web.i18n import tr, get_language from web.i18n import tr, get_language
@@ -33,8 +34,12 @@ st.set_page_config(
layout="wide", layout="wide",
) )
# Get ports from environment
API_PORT = os.getenv("API_PORT", "8000")
EDITOR_PORT = os.getenv("EDITOR_PORT", "3000")
# API endpoint # API endpoint
API_BASE = "http://localhost:8000/api" API_BASE = f"http://localhost:{API_PORT}/api"
def get_all_tasks(): def get_all_tasks():
@@ -183,7 +188,7 @@ def render_task_card(task):
with col_a: with col_a:
st.success("✨ 视频生成成功") st.success("✨ 视频生成成功")
with col_b: with col_b:
editor_url = f"http://localhost:3000/editor?storyboard_id={task_id}" editor_url = f"http://localhost:{EDITOR_PORT}/editor?storyboard_id={task_id}"
st.markdown( st.markdown(
f''' f'''
<a href="{editor_url}" target="_blank" style="text-decoration: none;"> <a href="{editor_url}" target="_blank" style="text-decoration: none;">