Backend: - tiers.py: real license validation via Heimdall (self-hosted key or cloud user_id), 30-min cache, BYOK exception for translation - session_store.py: create_session() accepts tier/user_id/window_ms/transcribe_lang/ num_speakers; tier-aware _should_reap() (Free: reap on subscriber leave, Paid: activity TTL); auto-snapshot paid sessions with history on reap; voice-ready probe on session start - services/translator.py: DeepL-backed label translation, CF-managed and BYOK key paths, per-session label cache - models/session.py, scene_event.py, service_event.py: new event types and session fields - api/snapshots.py: GET /sessions/history + POST /session/resume (Paid tier only) - api/dev.py: dev-only /dev/voice/stop|start|restart proxy to cf-orch agent - migrations/002_session_snapshots.sql: session_snapshots table Frontend: - ComposeBar.vue: pre-session settings panel (window, language, speakers, Elcor toggle) with slide transition; passes options to startSession() - stores/settings.ts: persistent user preferences (localStorage), survives page reload - stores/session.ts: voiceReady/voiceError/voiceWarning states; SceneEvent/AccentEvent/ ServiceEvent types; TTL timers for environ/queue badges; startSession() accepts opts - composables/useToneStream.ts: handlers for scene-event, accent-event, service-event SSE - NowPanel.vue: diarization speaker identity badge, acoustic scene badge, privacy risk indicator - HistoryStrip.vue: per-speaker colour chips in history strip - components/DevPanel.vue: dev-only cf-voice stop/restart controls + thread view toggle - App.vue: DevPanel in footer (DEV only), threadView flag switches v0.1.x/v0.2.x preview Tests: - tests/test_pinning.py: 20 tests covering tier-aware reaping, snapshot creation, and translator behaviour - tests/test_tiers.py: 17 tests covering key validation, cloud resolution, caching, require_paid() gate, BYOK exception - All 72 tests passing
61 lines
3.1 KiB
Python
61 lines
3.1 KiB
Python
# app/config.py — runtime mode settings
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
|
|
class Settings:
|
|
"""Read-once settings from environment variables."""
|
|
|
|
demo_mode: bool = os.getenv("DEMO_MODE", "").lower() in ("1", "true", "yes")
|
|
cloud_mode: bool = os.getenv("CLOUD_MODE", "").lower() in ("1", "true", "yes")
|
|
|
|
# DEMO: max simultaneous active sessions (prevents resource abuse on the demo server)
|
|
demo_max_sessions: int = int(os.getenv("DEMO_MAX_SESSIONS", "3"))
|
|
# DEMO: auto-kill sessions after this many seconds of inactivity
|
|
demo_session_ttl_s: int = int(os.getenv("DEMO_SESSION_TTL_S", "300")) # 5 min
|
|
|
|
# Free tier: kill session this many seconds after the last SSE subscriber leaves.
|
|
# Covers mobile tab timeout / screen lock / crash.
|
|
session_idle_ttl_s: int = int(os.getenv("SESSION_IDLE_TTL_S", "90"))
|
|
# Paid tier: hard idle timeout — kill session this many seconds after the last
|
|
# annotation event, regardless of subscriber state. 30 minutes default.
|
|
session_paid_ttl_s: int = int(os.getenv("SESSION_PAID_TTL_S", "1800"))
|
|
|
|
# DeepL API key for CF-managed cloud translation (Paid tier).
|
|
# BYOK users supply their own key per-session in StartRequest.byok_deepl_key.
|
|
deepl_api_key: str = os.getenv("DEEPL_API_KEY", "")
|
|
|
|
# CLOUD: where Caddy injects the cf_session user token
|
|
cloud_session_header: str = os.getenv("CLOUD_SESSION_HEADER", "X-CF-Session")
|
|
cloud_data_root: str = os.getenv("CLOUD_DATA_ROOT", "/devl/linnet-cloud-data")
|
|
|
|
heimdall_url: str = os.getenv("HEIMDALL_URL", "https://license.circuitforge.tech")
|
|
# Admin token for cloud-mode user tier resolution (/admin/cloud/resolve).
|
|
# Not required for self-hosted installs; required when CLOUD_MODE=true.
|
|
heimdall_admin_token: str = os.getenv("HEIMDALL_ADMIN_TOKEN", "")
|
|
# Self-hosted license key. Cloud mode resolves tier via user_id instead.
|
|
linnet_license_key: str = os.getenv("LINNET_LICENSE_KEY", "")
|
|
|
|
linnet_port: int = int(os.getenv("LINNET_PORT", "8522"))
|
|
linnet_frontend_port: int = int(os.getenv("LINNET_FRONTEND_PORT", "8521"))
|
|
linnet_base_url: str = os.getenv("LINNET_BASE_URL", "")
|
|
|
|
# cf-orch coordinator URL — used to request a managed cf-voice instance per session.
|
|
# When set, each session allocates a cf-voice sidecar via the coordinator REST API.
|
|
cf_orch_url: str = os.getenv("CF_ORCH_URL", "")
|
|
|
|
# cf-orch agent URL — local service lifecycle (start/stop). Separate from the
|
|
# coordinator: coordinator is read/scheduling (:7700), agent is write (:7701).
|
|
cf_orch_agent_url: str = os.getenv("CF_ORCH_AGENT_URL", "")
|
|
|
|
# Static cf-voice sidecar URL — legacy override (bypasses cf-orch, points directly
|
|
# at a running cf-voice process). Takes precedence over cf-orch allocation when set.
|
|
cf_voice_url: str = os.getenv("CF_VOICE_URL", "")
|
|
|
|
# Local SQLite DB for corrections storage. Shared across users on a single instance;
|
|
# corrections contain text only (no audio). Cloud mode uses cloud_data_root prefix.
|
|
linnet_db: str = os.getenv("LINNET_DB", "data/linnet.db")
|
|
|
|
|
|
settings = Settings()
|