refactor: centralize DIFY settings in config.py and cleanup env files
- Update config.py to read both .env and .env.local (with .env.local priority) - Move DIFY API settings from hardcoded values to environment configuration - Remove unused PADDLEOCR_MODEL_DIR setting (models stored in ~/.paddleocr/) - Remove deprecated argostranslate translation settings - Add DIFY settings: base_url, api_key, timeout, max_retries, batch limits - Update dify_client.py to use settings from config.py - Update translation_service.py to use settings instead of constants - Fix frontend env files to use correct variable name VITE_API_BASE_URL - Update setup_dev_env.sh with correct PaddlePaddle version (3.2.0) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -5,9 +5,13 @@ Loads environment variables and provides centralized configuration
|
||||
|
||||
from typing import List, Optional
|
||||
from pydantic_settings import BaseSettings
|
||||
from pydantic import Field
|
||||
from pydantic import Field, model_validator
|
||||
from pathlib import Path
|
||||
|
||||
# Anchor all default paths to the backend directory to avoid scattering runtime folders
|
||||
BACKEND_ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
PROJECT_ROOT = BACKEND_ROOT.parent
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Application settings loaded from environment variables"""
|
||||
@@ -28,8 +32,8 @@ class Settings(BaseSettings):
|
||||
)
|
||||
|
||||
# ===== Application Configuration =====
|
||||
backend_port: int = Field(default=12010)
|
||||
frontend_port: int = Field(default=12011)
|
||||
backend_port: int = Field(default=8000)
|
||||
frontend_port: int = Field(default=5173)
|
||||
secret_key: str = Field(default="your-secret-key-change-this")
|
||||
algorithm: str = Field(default="HS256")
|
||||
access_token_expire_minutes: int = Field(default=1440) # 24 hours
|
||||
@@ -52,7 +56,7 @@ class Settings(BaseSettings):
|
||||
max_tasks_per_user: int = Field(default=1000)
|
||||
|
||||
# ===== OCR Configuration =====
|
||||
paddleocr_model_dir: str = Field(default="./models/paddleocr")
|
||||
# Note: PaddleOCR models are stored in ~/.paddleocr/ and ~/.paddlex/ by default
|
||||
ocr_languages: str = Field(default="ch,en,japan,korean")
|
||||
ocr_confidence_threshold: float = Field(default=0.5)
|
||||
max_ocr_workers: int = Field(default=4)
|
||||
@@ -323,10 +327,10 @@ class Settings(BaseSettings):
|
||||
# ===== File Upload Configuration =====
|
||||
max_upload_size: int = Field(default=52428800) # 50MB
|
||||
allowed_extensions: str = Field(default="png,jpg,jpeg,pdf,bmp,tiff,doc,docx,ppt,pptx")
|
||||
upload_dir: str = Field(default="./uploads")
|
||||
temp_dir: str = Field(default="./uploads/temp")
|
||||
processed_dir: str = Field(default="./uploads/processed")
|
||||
images_dir: str = Field(default="./uploads/images")
|
||||
upload_dir: str = Field(default=str(BACKEND_ROOT / "uploads"))
|
||||
temp_dir: str = Field(default=str(BACKEND_ROOT / "uploads" / "temp"))
|
||||
processed_dir: str = Field(default=str(BACKEND_ROOT / "uploads" / "processed"))
|
||||
images_dir: str = Field(default=str(BACKEND_ROOT / "uploads" / "images"))
|
||||
|
||||
@property
|
||||
def allowed_extensions_list(self) -> List[str]:
|
||||
@@ -334,11 +338,11 @@ class Settings(BaseSettings):
|
||||
return [ext.strip() for ext in self.allowed_extensions.split(",")]
|
||||
|
||||
# ===== Export Configuration =====
|
||||
storage_dir: str = Field(default="./storage")
|
||||
markdown_dir: str = Field(default="./storage/markdown")
|
||||
json_dir: str = Field(default="./storage/json")
|
||||
exports_dir: str = Field(default="./storage/exports")
|
||||
result_dir: str = Field(default="./storage/results")
|
||||
storage_dir: str = Field(default=str(BACKEND_ROOT / "storage"))
|
||||
markdown_dir: str = Field(default=str(BACKEND_ROOT / "storage" / "markdown"))
|
||||
json_dir: str = Field(default=str(BACKEND_ROOT / "storage" / "json"))
|
||||
exports_dir: str = Field(default=str(BACKEND_ROOT / "storage" / "exports"))
|
||||
result_dir: str = Field(default=str(BACKEND_ROOT / "storage" / "results"))
|
||||
|
||||
# ===== PDF Generation Configuration =====
|
||||
pandoc_path: str = Field(default="/opt/homebrew/bin/pandoc")
|
||||
@@ -350,21 +354,25 @@ class Settings(BaseSettings):
|
||||
pdf_margin_right: int = Field(default=20)
|
||||
|
||||
# ===== Layout-Preserving PDF Configuration =====
|
||||
chinese_font_path: str = Field(default="./backend/fonts/NotoSansSC-Regular.ttf")
|
||||
chinese_font_path: str = Field(default=str(BACKEND_ROOT / "fonts" / "NotoSansSC-Regular.ttf"))
|
||||
pdf_font_size_base: int = Field(default=12)
|
||||
pdf_enable_bbox_debug: bool = Field(default=False) # Draw bounding boxes for debugging
|
||||
|
||||
# ===== Translation Configuration (Reserved) =====
|
||||
enable_translation: bool = Field(default=False)
|
||||
translation_engine: str = Field(default="offline")
|
||||
argostranslate_models_dir: str = Field(default="./models/argostranslate")
|
||||
# ===== Translation Configuration (DIFY API) =====
|
||||
enable_translation: bool = Field(default=True)
|
||||
dify_base_url: str = Field(default="https://dify.theaken.com/v1")
|
||||
dify_api_key: str = Field(default="") # Required: set in .env.local
|
||||
dify_timeout: float = Field(default=120.0) # seconds
|
||||
dify_max_retries: int = Field(default=3)
|
||||
dify_max_batch_chars: int = Field(default=5000) # Max characters per batch
|
||||
dify_max_batch_items: int = Field(default=20) # Max items per batch
|
||||
|
||||
# ===== Background Tasks Configuration =====
|
||||
task_queue_type: str = Field(default="memory")
|
||||
redis_url: str = Field(default="redis://localhost:6379/0")
|
||||
|
||||
# ===== CORS Configuration =====
|
||||
cors_origins: str = Field(default="http://localhost:12011,http://127.0.0.1:12011")
|
||||
cors_origins: str = Field(default="http://localhost:5173,http://127.0.0.1:5173")
|
||||
|
||||
@property
|
||||
def cors_origins_list(self) -> List[str]:
|
||||
@@ -373,14 +381,52 @@ class Settings(BaseSettings):
|
||||
|
||||
# ===== Logging Configuration =====
|
||||
log_level: str = Field(default="INFO")
|
||||
log_file: str = Field(default="./logs/app.log")
|
||||
log_file: str = Field(default=str(BACKEND_ROOT / "logs" / "app.log"))
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _normalize_paths(self):
|
||||
"""Resolve all runtime paths to backend-rooted absolutes"""
|
||||
path_fields = [
|
||||
"upload_dir",
|
||||
"temp_dir",
|
||||
"processed_dir",
|
||||
"images_dir",
|
||||
"storage_dir",
|
||||
"markdown_dir",
|
||||
"json_dir",
|
||||
"exports_dir",
|
||||
"result_dir",
|
||||
"log_file",
|
||||
"chinese_font_path",
|
||||
]
|
||||
|
||||
for field in path_fields:
|
||||
value = getattr(self, field)
|
||||
if value:
|
||||
setattr(self, field, str(self._resolve_path(str(value))))
|
||||
|
||||
return self
|
||||
|
||||
class Config:
|
||||
# Look for .env in project root (one level up from backend/)
|
||||
env_file = str(Path(__file__).resolve().parent.parent.parent.parent / ".env")
|
||||
# Look for .env files in project root (one level up from backend/)
|
||||
# .env.local has higher priority and overrides .env
|
||||
env_file = (
|
||||
str(PROJECT_ROOT / ".env"),
|
||||
str(PROJECT_ROOT / ".env.local"),
|
||||
)
|
||||
env_file_encoding = "utf-8"
|
||||
case_sensitive = False
|
||||
|
||||
def _resolve_path(self, path_value: str) -> Path:
|
||||
"""
|
||||
Convert relative paths to backend-rooted absolute paths.
|
||||
|
||||
This keeps runtime artifacts contained under backend/ even when the app
|
||||
is launched from different working directories.
|
||||
"""
|
||||
path = Path(path_value)
|
||||
return path if path.is_absolute() else BACKEND_ROOT / path
|
||||
|
||||
def ensure_directories(self):
|
||||
"""Create all necessary directories if they don't exist"""
|
||||
dirs = [
|
||||
@@ -393,15 +439,11 @@ class Settings(BaseSettings):
|
||||
self.json_dir,
|
||||
self.exports_dir,
|
||||
self.result_dir,
|
||||
self.paddleocr_model_dir,
|
||||
Path(self.log_file).parent,
|
||||
]
|
||||
|
||||
if self.enable_translation and self.translation_engine == "offline":
|
||||
dirs.append(self.argostranslate_models_dir)
|
||||
|
||||
for dir_path in dirs:
|
||||
Path(dir_path).mkdir(parents=True, exist_ok=True)
|
||||
self._resolve_path(str(dir_path)).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
# Global settings instance
|
||||
|
||||
Reference in New Issue
Block a user