mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-19 23:00:45 -07:00
chore(backend): repair test suite and bring ruff to green
The suite hadn't run green since the routes refactor: - test_profile_duplicate_names.py imported the pre-refactor module layout and broke collection; now imports backend.services.profiles - tests/conftest.py puts the repo root and backend dir on sys.path so files collect standalone instead of depending on run order - test_cors.py tested a hand-copied mirror of the origin list that had drifted from app.py (missing http://tauri.localhost); it now builds the app via the real create_app() factory - test_progress.py simulated a 1KB download, below the tracker's 1MB reporting threshold; simulation raised to 5MB - slow/timeout markers registered in pyproject Ruff: ~900 violations auto-fixed (typing modernization, import sorting, unused imports, whitespace). The remaining rules are baselined in pyproject.toml with per-rule counts to burn down, plus per-file carve-outs for deliberate env-before-import ordering. ruff check is now clean; suite is 134 passed, 2 skipped.
This commit is contained in:
+153
-153
@@ -2,10 +2,10 @@
|
||||
Pydantic models for request/response validation.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .utils.capture_chords import (
|
||||
default_push_to_talk_chord,
|
||||
default_toggle_to_talk_chord,
|
||||
@@ -16,16 +16,16 @@ class VoiceProfileCreate(BaseModel):
|
||||
"""Request model for creating a voice profile."""
|
||||
|
||||
name: str = Field(..., min_length=1, max_length=100)
|
||||
description: Optional[str] = Field(None, max_length=500)
|
||||
description: str | None = Field(None, max_length=500)
|
||||
language: str = Field(
|
||||
default="en", pattern="^(zh|en|ja|ko|de|fr|ru|pt|es|it|he|ar|da|el|fi|hi|ms|nl|no|pl|sv|sw|tr)$"
|
||||
)
|
||||
voice_type: Optional[str] = Field(default="cloned", pattern="^(cloned|preset|designed)$")
|
||||
preset_engine: Optional[str] = Field(None, max_length=50)
|
||||
preset_voice_id: Optional[str] = Field(None, max_length=100)
|
||||
design_prompt: Optional[str] = Field(None, max_length=2000)
|
||||
default_engine: Optional[str] = Field(None, max_length=50)
|
||||
personality: Optional[str] = Field(None, max_length=2000)
|
||||
voice_type: str | None = Field(default="cloned", pattern="^(cloned|preset|designed)$")
|
||||
preset_engine: str | None = Field(None, max_length=50)
|
||||
preset_voice_id: str | None = Field(None, max_length=100)
|
||||
design_prompt: str | None = Field(None, max_length=2000)
|
||||
default_engine: str | None = Field(None, max_length=50)
|
||||
personality: str | None = Field(None, max_length=2000)
|
||||
|
||||
|
||||
class VoiceProfileResponse(BaseModel):
|
||||
@@ -33,16 +33,16 @@ class VoiceProfileResponse(BaseModel):
|
||||
|
||||
id: str
|
||||
name: str
|
||||
description: Optional[str]
|
||||
description: str | None
|
||||
language: str
|
||||
avatar_path: Optional[str] = None
|
||||
effects_chain: Optional[List["EffectConfig"]] = None
|
||||
avatar_path: str | None = None
|
||||
effects_chain: list["EffectConfig"] | None = None
|
||||
voice_type: str = "cloned"
|
||||
preset_engine: Optional[str] = None
|
||||
preset_voice_id: Optional[str] = None
|
||||
design_prompt: Optional[str] = None
|
||||
default_engine: Optional[str] = None
|
||||
personality: Optional[str] = None
|
||||
preset_engine: str | None = None
|
||||
preset_voice_id: str | None = None
|
||||
design_prompt: str | None = None
|
||||
default_engine: str | None = None
|
||||
personality: str | None = None
|
||||
generation_count: int = 0
|
||||
sample_count: int = 0
|
||||
created_at: datetime
|
||||
@@ -82,10 +82,10 @@ class GenerationRequest(BaseModel):
|
||||
profile_id: str
|
||||
text: str = Field(..., min_length=1, max_length=50000)
|
||||
language: str = Field(default="en", pattern="^(zh|en|ja|ko|de|fr|ru|pt|es|it|he|ar|da|el|fi|hi|ms|nl|no|pl|sv|sw|tr)$")
|
||||
seed: Optional[int] = Field(None, ge=0)
|
||||
model_size: Optional[str] = Field(default="1.7B", pattern="^(1\\.7B|0\\.6B|1B|3B)$")
|
||||
instruct: Optional[str] = Field(None, max_length=500)
|
||||
engine: Optional[str] = Field(default="qwen", pattern="^(qwen|qwen_custom_voice|luxtts|chatterbox|chatterbox_turbo|tada|kokoro)$")
|
||||
seed: int | None = Field(None, ge=0)
|
||||
model_size: str | None = Field(default="1.7B", pattern="^(1\\.7B|0\\.6B|1B|3B)$")
|
||||
instruct: str | None = Field(None, max_length=500)
|
||||
engine: str | None = Field(default="qwen", pattern="^(qwen|qwen_custom_voice|luxtts|chatterbox|chatterbox_turbo|tada|kokoro)$")
|
||||
personality: bool = Field(
|
||||
default=False,
|
||||
description="When true and the profile has a personality prompt, the input text is rewritten in-character before TTS.",
|
||||
@@ -97,7 +97,7 @@ class GenerationRequest(BaseModel):
|
||||
default=50, ge=0, le=500, description="Crossfade duration in ms between chunks (0 for hard cut)"
|
||||
)
|
||||
normalize: bool = Field(default=True, description="Normalize output audio volume")
|
||||
effects_chain: Optional[List["EffectConfig"]] = Field(
|
||||
effects_chain: list["EffectConfig"] | None = Field(
|
||||
None, description="Effects chain to apply after generation (overrides profile default)"
|
||||
)
|
||||
|
||||
@@ -109,19 +109,19 @@ class GenerationResponse(BaseModel):
|
||||
profile_id: str
|
||||
text: str
|
||||
language: str
|
||||
audio_path: Optional[str] = None
|
||||
duration: Optional[float] = None
|
||||
seed: Optional[int] = None
|
||||
instruct: Optional[str] = None
|
||||
engine: Optional[str] = "qwen"
|
||||
model_size: Optional[str] = None
|
||||
audio_path: str | None = None
|
||||
duration: float | None = None
|
||||
seed: int | None = None
|
||||
instruct: str | None = None
|
||||
engine: str | None = "qwen"
|
||||
model_size: str | None = None
|
||||
status: str = "completed"
|
||||
error: Optional[str] = None
|
||||
error: str | None = None
|
||||
is_favorited: bool = False
|
||||
source: str = "manual"
|
||||
created_at: datetime
|
||||
versions: Optional[List["GenerationVersionResponse"]] = None
|
||||
active_version_id: Optional[str] = None
|
||||
versions: list["GenerationVersionResponse"] | None = None
|
||||
active_version_id: str | None = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -130,8 +130,8 @@ class GenerationResponse(BaseModel):
|
||||
class HistoryQuery(BaseModel):
|
||||
"""Query model for generation history."""
|
||||
|
||||
profile_id: Optional[str] = None
|
||||
search: Optional[str] = None
|
||||
profile_id: str | None = None
|
||||
search: str | None = None
|
||||
limit: int = Field(default=50, ge=1, le=100)
|
||||
offset: int = Field(default=0, ge=0)
|
||||
|
||||
@@ -144,18 +144,18 @@ class HistoryResponse(BaseModel):
|
||||
profile_name: str
|
||||
text: str
|
||||
language: str
|
||||
audio_path: Optional[str] = None
|
||||
duration: Optional[float] = None
|
||||
seed: Optional[int] = None
|
||||
instruct: Optional[str] = None
|
||||
engine: Optional[str] = "qwen"
|
||||
model_size: Optional[str] = None
|
||||
audio_path: str | None = None
|
||||
duration: float | None = None
|
||||
seed: int | None = None
|
||||
instruct: str | None = None
|
||||
engine: str | None = "qwen"
|
||||
model_size: str | None = None
|
||||
status: str = "completed"
|
||||
error: Optional[str] = None
|
||||
error: str | None = None
|
||||
is_favorited: bool = False
|
||||
created_at: datetime
|
||||
versions: Optional[List["GenerationVersionResponse"]] = None
|
||||
active_version_id: Optional[str] = None
|
||||
versions: list["GenerationVersionResponse"] | None = None
|
||||
active_version_id: str | None = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -164,15 +164,15 @@ class HistoryResponse(BaseModel):
|
||||
class HistoryListResponse(BaseModel):
|
||||
"""Response model for history list."""
|
||||
|
||||
items: List[HistoryResponse]
|
||||
items: list[HistoryResponse]
|
||||
total: int
|
||||
|
||||
|
||||
class TranscriptionRequest(BaseModel):
|
||||
"""Request model for audio transcription."""
|
||||
|
||||
language: Optional[str] = Field(None, pattern="^(en|zh|ja|ko|de|fr|ru|pt|es|it)$")
|
||||
model: Optional[str] = Field(None, pattern="^(base|small|medium|large|turbo)$")
|
||||
language: str | None = Field(None, pattern="^(en|zh|ja|ko|de|fr|ru|pt|es|it)$")
|
||||
model: str | None = Field(None, pattern="^(base|small|medium|large|turbo)$")
|
||||
|
||||
|
||||
class TranscriptionResponse(BaseModel):
|
||||
@@ -196,13 +196,13 @@ class CaptureResponse(BaseModel):
|
||||
id: str
|
||||
audio_path: str
|
||||
source: str
|
||||
language: Optional[str] = None
|
||||
duration_ms: Optional[int] = None
|
||||
language: str | None = None
|
||||
duration_ms: int | None = None
|
||||
transcript_raw: str
|
||||
transcript_refined: Optional[str] = None
|
||||
stt_model: Optional[str] = None
|
||||
llm_model: Optional[str] = None
|
||||
refinement_flags: Optional[RefinementFlagsModel] = None
|
||||
transcript_refined: str | None = None
|
||||
stt_model: str | None = None
|
||||
llm_model: str | None = None
|
||||
refinement_flags: RefinementFlagsModel | None = None
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
@@ -212,7 +212,7 @@ class CaptureResponse(BaseModel):
|
||||
class CaptureListResponse(BaseModel):
|
||||
"""Response model for paginated capture list."""
|
||||
|
||||
items: List[CaptureResponse]
|
||||
items: list[CaptureResponse]
|
||||
total: int
|
||||
|
||||
|
||||
@@ -234,15 +234,15 @@ class CaptureCreateResponse(CaptureResponse):
|
||||
class CaptureRefineRequest(BaseModel):
|
||||
"""Request to refine a capture's transcript via the LLM."""
|
||||
|
||||
flags: Optional[RefinementFlagsModel] = None
|
||||
model_size: Optional[str] = Field(default=None, pattern="^(0\\.6B|1\\.7B|4B)$")
|
||||
flags: RefinementFlagsModel | None = None
|
||||
model_size: str | None = Field(default=None, pattern="^(0\\.6B|1\\.7B|4B)$")
|
||||
|
||||
|
||||
class CaptureRetranscribeRequest(BaseModel):
|
||||
"""Request to re-run STT on a capture's audio with a different model."""
|
||||
|
||||
model: Optional[str] = Field(None, pattern="^(base|small|medium|large|turbo)$")
|
||||
language: Optional[str] = Field(None, pattern="^(en|zh|ja|ko|de|fr|ru|pt|es|it)$")
|
||||
model: str | None = Field(None, pattern="^(base|small|medium|large|turbo)$")
|
||||
language: str | None = Field(None, pattern="^(en|zh|ja|ko|de|fr|ru|pt|es|it)$")
|
||||
|
||||
|
||||
class CaptureSettingsResponse(BaseModel):
|
||||
@@ -256,13 +256,13 @@ class CaptureSettingsResponse(BaseModel):
|
||||
self_correction: bool = True
|
||||
preserve_technical: bool = True
|
||||
allow_auto_paste: bool = True
|
||||
default_playback_voice_id: Optional[str] = None
|
||||
default_playback_voice_id: str | None = None
|
||||
hotkey_enabled: bool = False
|
||||
keep_mic_warm: bool = False
|
||||
chord_push_to_talk_keys: List[str] = Field(
|
||||
chord_push_to_talk_keys: list[str] = Field(
|
||||
default_factory=default_push_to_talk_chord
|
||||
)
|
||||
chord_toggle_to_talk_keys: List[str] = Field(
|
||||
chord_toggle_to_talk_keys: list[str] = Field(
|
||||
default_factory=default_toggle_to_talk_chord
|
||||
)
|
||||
|
||||
@@ -273,19 +273,19 @@ class CaptureSettingsResponse(BaseModel):
|
||||
class CaptureSettingsUpdate(BaseModel):
|
||||
"""Partial update for capture settings — every field is optional."""
|
||||
|
||||
stt_model: Optional[str] = Field(default=None, pattern="^(base|small|medium|large|turbo)$")
|
||||
language: Optional[str] = None
|
||||
auto_refine: Optional[bool] = None
|
||||
llm_model: Optional[str] = Field(default=None, pattern="^(0\\.6B|1\\.7B|4B)$")
|
||||
smart_cleanup: Optional[bool] = None
|
||||
self_correction: Optional[bool] = None
|
||||
preserve_technical: Optional[bool] = None
|
||||
allow_auto_paste: Optional[bool] = None
|
||||
default_playback_voice_id: Optional[str] = None
|
||||
hotkey_enabled: Optional[bool] = None
|
||||
keep_mic_warm: Optional[bool] = None
|
||||
chord_push_to_talk_keys: Optional[List[str]] = Field(default=None, min_length=1, max_length=6)
|
||||
chord_toggle_to_talk_keys: Optional[List[str]] = Field(default=None, min_length=1, max_length=6)
|
||||
stt_model: str | None = Field(default=None, pattern="^(base|small|medium|large|turbo)$")
|
||||
language: str | None = None
|
||||
auto_refine: bool | None = None
|
||||
llm_model: str | None = Field(default=None, pattern="^(0\\.6B|1\\.7B|4B)$")
|
||||
smart_cleanup: bool | None = None
|
||||
self_correction: bool | None = None
|
||||
preserve_technical: bool | None = None
|
||||
allow_auto_paste: bool | None = None
|
||||
default_playback_voice_id: str | None = None
|
||||
hotkey_enabled: bool | None = None
|
||||
keep_mic_warm: bool | None = None
|
||||
chord_push_to_talk_keys: list[str] | None = Field(default=None, min_length=1, max_length=6)
|
||||
chord_toggle_to_talk_keys: list[str] | None = Field(default=None, min_length=1, max_length=6)
|
||||
|
||||
|
||||
class GenerationSettingsResponse(BaseModel):
|
||||
@@ -303,10 +303,10 @@ class GenerationSettingsResponse(BaseModel):
|
||||
class GenerationSettingsUpdate(BaseModel):
|
||||
"""Partial update for generation settings — every field is optional."""
|
||||
|
||||
max_chunk_chars: Optional[int] = Field(default=None, ge=100, le=5000)
|
||||
crossfade_ms: Optional[int] = Field(default=None, ge=0, le=500)
|
||||
normalize_audio: Optional[bool] = None
|
||||
autoplay_on_generate: Optional[bool] = None
|
||||
max_chunk_chars: int | None = Field(default=None, ge=100, le=5000)
|
||||
crossfade_ms: int | None = Field(default=None, ge=0, le=500)
|
||||
normalize_audio: bool | None = None
|
||||
autoplay_on_generate: bool | None = None
|
||||
|
||||
|
||||
class MCPClientBindingResponse(BaseModel):
|
||||
@@ -315,14 +315,14 @@ class MCPClientBindingResponse(BaseModel):
|
||||
opt-in personality-rewrite default."""
|
||||
|
||||
client_id: str
|
||||
label: Optional[str] = None
|
||||
profile_id: Optional[str] = None
|
||||
default_engine: Optional[str] = Field(
|
||||
label: str | None = None
|
||||
profile_id: str | None = None
|
||||
default_engine: str | None = Field(
|
||||
None,
|
||||
pattern="^(qwen|qwen_custom_voice|luxtts|chatterbox|chatterbox_turbo|tada|kokoro)$",
|
||||
)
|
||||
default_personality: bool = False
|
||||
last_seen_at: Optional[datetime] = None
|
||||
last_seen_at: datetime | None = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
@@ -334,9 +334,9 @@ class MCPClientBindingUpsert(BaseModel):
|
||||
"""Create or update a binding. Matched by ``client_id``."""
|
||||
|
||||
client_id: str = Field(..., min_length=1, max_length=64)
|
||||
label: Optional[str] = Field(None, max_length=128)
|
||||
profile_id: Optional[str] = None
|
||||
default_engine: Optional[str] = Field(
|
||||
label: str | None = Field(None, max_length=128)
|
||||
profile_id: str | None = None
|
||||
default_engine: str | None = Field(
|
||||
None,
|
||||
pattern="^(qwen|qwen_custom_voice|luxtts|chatterbox|chatterbox_turbo|tada|kokoro)$",
|
||||
)
|
||||
@@ -344,26 +344,26 @@ class MCPClientBindingUpsert(BaseModel):
|
||||
|
||||
|
||||
class MCPClientBindingListResponse(BaseModel):
|
||||
items: List[MCPClientBindingResponse]
|
||||
items: list[MCPClientBindingResponse]
|
||||
|
||||
|
||||
class SpeakRequest(BaseModel):
|
||||
"""Body for POST /speak — non-MCP REST surface that mirrors voicebox.speak."""
|
||||
|
||||
text: str = Field(..., min_length=1, max_length=10000)
|
||||
profile: Optional[str] = Field(
|
||||
profile: str | None = Field(
|
||||
None,
|
||||
description="Voice profile name or id. Falls back to per-client binding, then default.",
|
||||
)
|
||||
engine: Optional[str] = Field(
|
||||
engine: str | None = Field(
|
||||
None,
|
||||
pattern="^(qwen|qwen_custom_voice|luxtts|chatterbox|chatterbox_turbo|tada|kokoro)$",
|
||||
)
|
||||
personality: Optional[bool] = Field(
|
||||
personality: bool | None = Field(
|
||||
None,
|
||||
description="When true and the profile has a personality prompt, the input text is rewritten in-character before TTS. When null, the per-client binding's default_personality flag decides.",
|
||||
)
|
||||
language: Optional[str] = Field(
|
||||
language: str | None = Field(
|
||||
None,
|
||||
pattern="^(zh|en|ja|ko|de|fr|ru|pt|es|it|he|ar|da|el|fi|hi|ms|nl|no|pl|sv|sw|tr)$",
|
||||
)
|
||||
@@ -373,15 +373,15 @@ class LLMGenerateRequest(BaseModel):
|
||||
"""Request model for LLM text generation."""
|
||||
|
||||
prompt: str = Field(..., min_length=1, max_length=50000)
|
||||
system: Optional[str] = Field(None, max_length=4000)
|
||||
model_size: Optional[str] = Field(default="0.6B", pattern="^(0\\.6B|1\\.7B|4B)$")
|
||||
system: str | None = Field(None, max_length=4000)
|
||||
model_size: str | None = Field(default="0.6B", pattern="^(0\\.6B|1\\.7B|4B)$")
|
||||
max_tokens: int = Field(default=512, ge=1, le=4096)
|
||||
temperature: float = Field(default=0.7, ge=0.0, le=2.0)
|
||||
# Few-shot (user, assistant) pairs prepended as real chat turns.
|
||||
# Used by the refinement service to pin tricky rules (imperatives
|
||||
# staying imperatives, technical-term punctuation) that small models
|
||||
# lose when the examples live inline in the system prompt.
|
||||
examples: Optional[List[List[str]]] = Field(default=None, max_length=8)
|
||||
examples: list[list[str]] | None = Field(default=None, max_length=8)
|
||||
|
||||
|
||||
class LLMGenerateResponse(BaseModel):
|
||||
@@ -418,7 +418,7 @@ class ModelReadiness(BaseModel):
|
||||
model_name: str
|
||||
display_name: str
|
||||
size: str
|
||||
size_mb: Optional[int] = None
|
||||
size_mb: int | None = None
|
||||
|
||||
|
||||
class CaptureReadinessResponse(BaseModel):
|
||||
@@ -438,15 +438,15 @@ class HealthResponse(BaseModel):
|
||||
|
||||
status: str
|
||||
model_loaded: bool
|
||||
model_downloaded: Optional[bool] = None # Whether model is cached/downloaded
|
||||
model_size: Optional[str] = None # Current model size if loaded
|
||||
model_downloaded: bool | None = None # Whether model is cached/downloaded
|
||||
model_size: str | None = None # Current model size if loaded
|
||||
gpu_available: bool
|
||||
gpu_type: Optional[str] = None # GPU type (CUDA, MPS, or None)
|
||||
vram_used_mb: Optional[float] = None
|
||||
backend_type: Optional[str] = None # Backend type (mlx or pytorch)
|
||||
backend_variant: Optional[str] = None # Binary variant (cpu, cuda, or rocm)
|
||||
gpu_type: str | None = None # GPU type (CUDA, MPS, or None)
|
||||
vram_used_mb: float | None = None
|
||||
backend_type: str | None = None # Backend type (mlx or pytorch)
|
||||
backend_variant: str | None = None # Binary variant (cpu, cuda, or rocm)
|
||||
supports_rocm: bool = False # AMD GPU on Windows — the ROCm backend is applicable
|
||||
gpu_compatibility_warning: Optional[str] = None # Warning if GPU arch unsupported
|
||||
gpu_compatibility_warning: str | None = None # Warning if GPU arch unsupported
|
||||
|
||||
|
||||
class DirectoryCheck(BaseModel):
|
||||
@@ -455,16 +455,16 @@ class DirectoryCheck(BaseModel):
|
||||
path: str
|
||||
exists: bool
|
||||
writable: bool
|
||||
error: Optional[str] = None
|
||||
error: str | None = None
|
||||
|
||||
|
||||
class FilesystemHealthResponse(BaseModel):
|
||||
"""Response model for filesystem health check."""
|
||||
|
||||
healthy: bool
|
||||
disk_free_mb: Optional[float] = None
|
||||
disk_total_mb: Optional[float] = None
|
||||
directories: List[DirectoryCheck]
|
||||
disk_free_mb: float | None = None
|
||||
disk_total_mb: float | None = None
|
||||
directories: list[DirectoryCheck]
|
||||
|
||||
|
||||
class ModelStatus(BaseModel):
|
||||
@@ -472,17 +472,17 @@ class ModelStatus(BaseModel):
|
||||
|
||||
model_name: str
|
||||
display_name: str
|
||||
hf_repo_id: Optional[str] = None # HuggingFace repository ID
|
||||
hf_repo_id: str | None = None # HuggingFace repository ID
|
||||
downloaded: bool
|
||||
downloading: bool = False # True if download is in progress
|
||||
size_mb: Optional[float] = None
|
||||
size_mb: float | None = None
|
||||
loaded: bool = False
|
||||
|
||||
|
||||
class ModelStatusListResponse(BaseModel):
|
||||
"""Response model for model status list."""
|
||||
|
||||
models: List[ModelStatus]
|
||||
models: list[ModelStatus]
|
||||
|
||||
|
||||
class ModelDownloadRequest(BaseModel):
|
||||
@@ -503,11 +503,11 @@ class ActiveDownloadTask(BaseModel):
|
||||
model_name: str
|
||||
status: str
|
||||
started_at: datetime
|
||||
error: Optional[str] = None
|
||||
progress: Optional[float] = None # 0-100 percentage
|
||||
current: Optional[int] = None # bytes downloaded
|
||||
total: Optional[int] = None # total bytes
|
||||
filename: Optional[str] = None # current file being downloaded
|
||||
error: str | None = None
|
||||
progress: float | None = None # 0-100 percentage
|
||||
current: int | None = None # bytes downloaded
|
||||
total: int | None = None # total bytes
|
||||
filename: str | None = None # current file being downloaded
|
||||
|
||||
|
||||
class ActiveGenerationTask(BaseModel):
|
||||
@@ -522,22 +522,22 @@ class ActiveGenerationTask(BaseModel):
|
||||
class ActiveTasksResponse(BaseModel):
|
||||
"""Response model for active tasks."""
|
||||
|
||||
downloads: List[ActiveDownloadTask]
|
||||
generations: List[ActiveGenerationTask]
|
||||
downloads: list[ActiveDownloadTask]
|
||||
generations: list[ActiveGenerationTask]
|
||||
|
||||
|
||||
class AudioChannelCreate(BaseModel):
|
||||
"""Request model for creating an audio channel."""
|
||||
|
||||
name: str = Field(..., min_length=1, max_length=100)
|
||||
device_ids: List[str] = Field(default_factory=list)
|
||||
device_ids: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class AudioChannelUpdate(BaseModel):
|
||||
"""Request model for updating an audio channel."""
|
||||
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=100)
|
||||
device_ids: Optional[List[str]] = None
|
||||
name: str | None = Field(None, min_length=1, max_length=100)
|
||||
device_ids: list[str] | None = None
|
||||
|
||||
|
||||
class AudioChannelResponse(BaseModel):
|
||||
@@ -546,7 +546,7 @@ class AudioChannelResponse(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
is_default: bool
|
||||
device_ids: List[str]
|
||||
device_ids: list[str]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
@@ -556,20 +556,20 @@ class AudioChannelResponse(BaseModel):
|
||||
class ChannelVoiceAssignment(BaseModel):
|
||||
"""Request model for assigning voices to a channel."""
|
||||
|
||||
profile_ids: List[str]
|
||||
profile_ids: list[str]
|
||||
|
||||
|
||||
class ProfileChannelAssignment(BaseModel):
|
||||
"""Request model for assigning channels to a profile."""
|
||||
|
||||
channel_ids: List[str]
|
||||
channel_ids: list[str]
|
||||
|
||||
|
||||
class StoryCreate(BaseModel):
|
||||
"""Request model for creating a story."""
|
||||
|
||||
name: str = Field(..., min_length=1, max_length=100)
|
||||
description: Optional[str] = Field(None, max_length=500)
|
||||
description: str | None = Field(None, max_length=500)
|
||||
|
||||
|
||||
class StoryResponse(BaseModel):
|
||||
@@ -577,7 +577,7 @@ class StoryResponse(BaseModel):
|
||||
|
||||
id: str
|
||||
name: str
|
||||
description: Optional[str]
|
||||
description: str | None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
item_count: int = 0
|
||||
@@ -592,7 +592,7 @@ class StoryItemDetail(BaseModel):
|
||||
id: str
|
||||
story_id: str
|
||||
generation_id: str
|
||||
version_id: Optional[str] = None
|
||||
version_id: str | None = None
|
||||
start_time_ms: int
|
||||
track: int = 0
|
||||
trim_start_ms: int = 0
|
||||
@@ -605,14 +605,14 @@ class StoryItemDetail(BaseModel):
|
||||
language: str
|
||||
audio_path: str
|
||||
duration: float
|
||||
seed: Optional[int]
|
||||
instruct: Optional[str]
|
||||
engine: Optional[str] = None
|
||||
seed: int | None
|
||||
instruct: str | None
|
||||
engine: str | None = None
|
||||
volume: float = 1.0
|
||||
generation_created_at: datetime
|
||||
# Versions available for this generation
|
||||
versions: Optional[List["GenerationVersionResponse"]] = None
|
||||
active_version_id: Optional[str] = None
|
||||
versions: list["GenerationVersionResponse"] | None = None
|
||||
active_version_id: str | None = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -623,10 +623,10 @@ class StoryDetailResponse(BaseModel):
|
||||
|
||||
id: str
|
||||
name: str
|
||||
description: Optional[str]
|
||||
description: str | None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
items: List[StoryItemDetail] = []
|
||||
items: list[StoryItemDetail] = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -636,8 +636,8 @@ class StoryItemCreate(BaseModel):
|
||||
"""Request model for adding a generation to a story."""
|
||||
|
||||
generation_id: str
|
||||
start_time_ms: Optional[int] = None # If not provided, will be calculated automatically
|
||||
track: Optional[int] = 0 # Track number (0 = main track)
|
||||
start_time_ms: int | None = None # If not provided, will be calculated automatically
|
||||
track: int | None = 0 # Track number (0 = main track)
|
||||
|
||||
|
||||
class StoryItemUpdateTime(BaseModel):
|
||||
@@ -650,13 +650,13 @@ class StoryItemUpdateTime(BaseModel):
|
||||
class StoryItemBatchUpdate(BaseModel):
|
||||
"""Request model for batch updating story item timecodes."""
|
||||
|
||||
updates: List[StoryItemUpdateTime]
|
||||
updates: list[StoryItemUpdateTime]
|
||||
|
||||
|
||||
class StoryItemReorder(BaseModel):
|
||||
"""Request model for reordering story items."""
|
||||
|
||||
generation_ids: List[str] = Field(..., min_length=1)
|
||||
generation_ids: list[str] = Field(..., min_length=1)
|
||||
|
||||
|
||||
class StoryItemMove(BaseModel):
|
||||
@@ -682,7 +682,7 @@ class StoryItemSplit(BaseModel):
|
||||
class StoryItemVersionUpdate(BaseModel):
|
||||
"""Request model for setting a story item's pinned version."""
|
||||
|
||||
version_id: Optional[str] = None # null = use generation default
|
||||
version_id: str | None = None # null = use generation default
|
||||
|
||||
|
||||
class StoryItemVolumeUpdate(BaseModel):
|
||||
@@ -707,23 +707,23 @@ class EffectConfig(BaseModel):
|
||||
class EffectsChain(BaseModel):
|
||||
"""An ordered list of effects to apply."""
|
||||
|
||||
effects: List[EffectConfig] = Field(default_factory=list)
|
||||
effects: list[EffectConfig] = Field(default_factory=list)
|
||||
|
||||
|
||||
class EffectPresetCreate(BaseModel):
|
||||
"""Request model for creating an effect preset."""
|
||||
|
||||
name: str = Field(..., min_length=1, max_length=100)
|
||||
description: Optional[str] = Field(None, max_length=500)
|
||||
effects_chain: List[EffectConfig]
|
||||
description: str | None = Field(None, max_length=500)
|
||||
effects_chain: list[EffectConfig]
|
||||
|
||||
|
||||
class EffectPresetUpdate(BaseModel):
|
||||
"""Request model for updating an effect preset."""
|
||||
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=100)
|
||||
description: Optional[str] = None
|
||||
effects_chain: Optional[List[EffectConfig]] = None
|
||||
name: str | None = Field(None, min_length=1, max_length=100)
|
||||
description: str | None = None
|
||||
effects_chain: list[EffectConfig] | None = None
|
||||
|
||||
|
||||
class EffectPresetResponse(BaseModel):
|
||||
@@ -731,8 +731,8 @@ class EffectPresetResponse(BaseModel):
|
||||
|
||||
id: str
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
effects_chain: List[EffectConfig]
|
||||
description: str | None = None
|
||||
effects_chain: list[EffectConfig]
|
||||
is_builtin: bool = False
|
||||
created_at: datetime
|
||||
|
||||
@@ -747,8 +747,8 @@ class GenerationVersionResponse(BaseModel):
|
||||
generation_id: str
|
||||
label: str
|
||||
audio_path: str
|
||||
effects_chain: Optional[List[EffectConfig]] = None
|
||||
source_version_id: Optional[str] = None
|
||||
effects_chain: list[EffectConfig] | None = None
|
||||
source_version_id: str | None = None
|
||||
is_default: bool
|
||||
created_at: datetime
|
||||
|
||||
@@ -759,18 +759,18 @@ class GenerationVersionResponse(BaseModel):
|
||||
class ApplyEffectsRequest(BaseModel):
|
||||
"""Request to apply effects to an existing generation."""
|
||||
|
||||
effects_chain: List[EffectConfig]
|
||||
source_version_id: Optional[str] = Field(
|
||||
effects_chain: list[EffectConfig]
|
||||
source_version_id: str | None = Field(
|
||||
None, description="Version to use as source audio (defaults to clean/original)"
|
||||
)
|
||||
label: Optional[str] = Field(None, max_length=100, description="Label for this version (auto-generated if omitted)")
|
||||
label: str | None = Field(None, max_length=100, description="Label for this version (auto-generated if omitted)")
|
||||
set_as_default: bool = Field(default=True, description="Set this version as the default")
|
||||
|
||||
|
||||
class ProfileEffectsUpdate(BaseModel):
|
||||
"""Request to update the default effects chain on a profile."""
|
||||
|
||||
effects_chain: Optional[List[EffectConfig]] = Field(None, description="Effects chain (null to remove)")
|
||||
effects_chain: list[EffectConfig] | None = Field(None, description="Effects chain (null to remove)")
|
||||
|
||||
|
||||
class AvailableEffectParam(BaseModel):
|
||||
@@ -795,7 +795,7 @@ class AvailableEffect(BaseModel):
|
||||
class AvailableEffectsResponse(BaseModel):
|
||||
"""Response listing all available effect types."""
|
||||
|
||||
effects: List[AvailableEffect]
|
||||
effects: list[AvailableEffect]
|
||||
|
||||
|
||||
# ─── Cloud (backup & sync) ──────────────────────────────────────────────
|
||||
@@ -812,8 +812,8 @@ class CloudStatusResponse(BaseModel):
|
||||
"""Current link between this device and a Voicebox Cloud account."""
|
||||
|
||||
connected: bool
|
||||
device_name: Optional[str] = None
|
||||
account_user_id: Optional[str] = None
|
||||
key_prefix: Optional[str] = None
|
||||
connected_at: Optional[datetime] = None
|
||||
device_name: str | None = None
|
||||
account_user_id: str | None = None
|
||||
key_prefix: str | None = None
|
||||
connected_at: datetime | None = None
|
||||
dashboard_url: str
|
||||
|
||||
Reference in New Issue
Block a user