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:
Jamie Pine
2026-07-26 23:16:09 -07:00
parent 766c51a8a1
commit b434db22f6
82 changed files with 970 additions and 999 deletions
+153 -153
View File
@@ -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