feat(voice): run pipeline eyes-free (STT -> Brain -> TTS), defer screen share

Make source/vision optional so the conversation loop runs with no screen
capture. Add Settings.voice() preset and `python -m wsai --voice` demo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-08-11 00:05:02 +09:00
parent b0780988a2
commit 2b6059141e
6 changed files with 60 additions and 12 deletions

View File

@@ -16,12 +16,12 @@ from dataclasses import dataclass
@dataclass
class Settings:
source: str = "mock" # mock | mss
vision: str = "mock" # mock | claude
stt: str | None = "mock" # mock | (whisper) | None
tts: str | None = "mock" # mock | (melo) | None
brain: str = "mock" # mock | claude
text: str | None = None # None | (discord)
source: str | None = "mock" # mock | mss | None (eyes-free)
vision: str | None = "mock" # mock | claude | None (eyes-free)
stt: str | None = "mock" # mock | (whisper) | None
tts: str | None = "mock" # mock | (melo) | None
brain: str = "mock" # mock | claude
text: str | None = None # None | (discord)
capture_interval: float = 1.5
anthropic_model: str = "claude-sonnet-4-5"
@@ -51,3 +51,12 @@ class Settings:
"""A realistic local config: capture this screen, Claude eyes+brain,
mock voice (until STT/TTS backends are wired)."""
return cls(source="mss", vision="claude", brain="claude", stt="mock", tts="mock")
@classmethod
def voice(cls) -> "Settings":
"""Eyes-free voice loop: no screen share, just STT -> Brain -> TTS.
Screen capture is deferred, so source/vision are off. Backends default
to mock so it runs out of the box; flip stt/tts/brain to real ones as
they land."""
return cls(source=None, vision=None, stt="mock", tts="mock", brain="mock")