mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-18 14:20:42 -07:00
Mobile companion app + paired-device backend
New iOS-first companion (Expo SDK 54 + NativeWind v4) with three tabs: Captures (the hero — floating gold mic, live mic-meter waveform, expand-row playback), Generate (profile picker + speak + autoplay + recent), and Voices (searchable profile list). Pairing (V0): backend mints a one-time token, mobile scans/pastes the voicebox:// URL, server returns a long-lived bearer it stores only as a SHA-256 hash. Bearer-or-loopback auth applied to every user-data router so binding 0.0.0.0 doesn't leak existing endpoints. Loopback callers (the desktop app) keep their friction-free access. Desktop Settings → Mobile pane: live host picker (LAN / Tailscale auto- detected via the App-bundle binary path on macOS), QR rendering, 5-minute expiry countdown, copyable URL fallback, paired-device list with revoke. Auto-closes when a new device pairs. just dev now binds the backend to 0.0.0.0 so paired phones can reach it — and just setup-python pins mlx-audio==0.4.1 + mlx-lm so fresh Apple Silicon worktrees get a working STT path on first install.
This commit is contained in:
@@ -793,3 +793,61 @@ class AvailableEffectsResponse(BaseModel):
|
||||
"""Response listing all available effect types."""
|
||||
|
||||
effects: List[AvailableEffect]
|
||||
|
||||
|
||||
# --- Mobile pairing (V0) -----------------------------------------------------
|
||||
|
||||
class HostCandidate(BaseModel):
|
||||
"""A reachable address the desktop can embed in the pair QR."""
|
||||
|
||||
address: str # ``host:port``, e.g. "192.168.1.5:17493"
|
||||
label: str # human-friendly name shown in the desktop host picker
|
||||
kind: str # "lan" | "tailscale" | "loopback"
|
||||
|
||||
|
||||
class PairInitResponse(BaseModel):
|
||||
"""Response for POST /pair/init — desktop renders ``pairing_url`` as a QR."""
|
||||
|
||||
token: str
|
||||
expires_at: datetime
|
||||
pairing_url: str # full voicebox://pair?host=…&token=… URL
|
||||
|
||||
|
||||
class PairCompleteRequest(BaseModel):
|
||||
"""Mobile-side request body for POST /pair/complete."""
|
||||
|
||||
token: str = Field(..., min_length=1, max_length=128)
|
||||
device_name: str = Field(..., min_length=1, max_length=80)
|
||||
|
||||
|
||||
class PairCompleteResponse(BaseModel):
|
||||
"""One-time response after successful pairing.
|
||||
|
||||
``bearer`` is returned in plaintext exactly once and is never persisted
|
||||
server-side; the device must save it (e.g. iOS SecureStore) immediately.
|
||||
"""
|
||||
|
||||
device_id: str
|
||||
bearer: str
|
||||
device_name: str
|
||||
|
||||
|
||||
class PairedDeviceResponse(BaseModel):
|
||||
"""A row in the desktop's Settings → Mobile device list."""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
revoked: bool
|
||||
created_at: datetime
|
||||
last_seen_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class MeResponse(BaseModel):
|
||||
"""Identity of the bearer-authenticated caller."""
|
||||
|
||||
device_id: str
|
||||
device_name: str
|
||||
last_seen_at: Optional[datetime] = None
|
||||
|
||||
Reference in New Issue
Block a user