Complete the module consolidation planned in REFACTORING_PLAN.md: Modules merged: - work_orders/ + task_queue/ → swarm/ (subpackages) - self_modify/ + self_tdd/ + upgrades/ → self_coding/ (subpackages) - tools/ → creative/tools/ - chat_bridge/ + telegram_bot/ + shortcuts/ + voice/ → integrations/ (new) - ws_manager/ + notifications/ + events/ + router/ → infrastructure/ (new) - agents/ + agent_core/ + memory/ → timmy/ (subpackages) Updated across codebase: - 66 source files: import statements rewritten - 13 test files: import + patch() target strings rewritten - pyproject.toml: wheel includes (28→14), entry points updated - CLAUDE.md: singleton paths, module map, entry points table - AGENTS.md: file convention updates - REFACTORING_PLAN.md: execution status, success metrics Extras: - Module-level CLAUDE.md added to 6 key packages (Phase 6.2) - Zero test regressions: 1462 tests passing https://claude.ai/code/session_01JNjWfHqusjT3aiN4vvYgUk
157 lines
5.3 KiB
Python
157 lines
5.3 KiB
Python
"""Voice routes — /voice/* and /voice/enhanced/* endpoints.
|
|
|
|
Provides NLU intent detection, TTS control, and the full voice-to-action
|
|
pipeline (detect intent → execute → optionally speak).
|
|
"""
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Form
|
|
|
|
from integrations.voice.nlu import detect_intent, extract_command
|
|
from timmy.agent import create_timmy
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/voice", tags=["voice"])
|
|
|
|
|
|
@router.post("/nlu")
|
|
async def nlu_detect(text: str = Form(...)):
|
|
"""Detect intent from a text utterance."""
|
|
intent = detect_intent(text)
|
|
command = extract_command(text)
|
|
return {
|
|
"intent": intent.name,
|
|
"confidence": intent.confidence,
|
|
"entities": intent.entities,
|
|
"command": command,
|
|
"raw_text": intent.raw_text,
|
|
}
|
|
|
|
|
|
@router.get("/tts/status")
|
|
async def tts_status():
|
|
"""Check TTS engine availability."""
|
|
try:
|
|
from timmy_serve.voice_tts import voice_tts
|
|
return {
|
|
"available": voice_tts.available,
|
|
"voices": voice_tts.get_voices() if voice_tts.available else [],
|
|
}
|
|
except Exception:
|
|
return {"available": False, "voices": []}
|
|
|
|
|
|
@router.post("/tts/speak")
|
|
async def tts_speak(text: str = Form(...)):
|
|
"""Speak text aloud via TTS."""
|
|
try:
|
|
from timmy_serve.voice_tts import voice_tts
|
|
if not voice_tts.available:
|
|
return {"spoken": False, "reason": "TTS engine not available"}
|
|
voice_tts.speak(text)
|
|
return {"spoken": True, "text": text}
|
|
except Exception as exc:
|
|
return {"spoken": False, "reason": str(exc)}
|
|
|
|
|
|
# ── Enhanced voice pipeline ──────────────────────────────────────────────
|
|
|
|
@router.post("/enhanced/process")
|
|
async def process_voice_input(
|
|
text: str = Form(...),
|
|
speak_response: bool = Form(False),
|
|
):
|
|
"""Process a voice input: detect intent -> execute -> optionally speak.
|
|
|
|
This is the main entry point for voice-driven interaction with Timmy.
|
|
"""
|
|
intent = detect_intent(text)
|
|
response_text = None
|
|
error = None
|
|
|
|
try:
|
|
if intent.name == "status":
|
|
response_text = "Timmy is operational and running locally. All systems sovereign."
|
|
|
|
elif intent.name == "help":
|
|
response_text = (
|
|
"Available commands: chat with me, check status, "
|
|
"manage the swarm, create tasks, or adjust voice settings. "
|
|
"Everything runs locally — no cloud, no permission needed."
|
|
)
|
|
|
|
elif intent.name == "swarm":
|
|
from swarm.coordinator import coordinator
|
|
status = coordinator.status()
|
|
response_text = (
|
|
f"Swarm status: {status['agents']} agents registered, "
|
|
f"{status['agents_idle']} idle, {status['agents_busy']} busy. "
|
|
f"{status['tasks_total']} total tasks, "
|
|
f"{status['tasks_completed']} completed."
|
|
)
|
|
|
|
elif intent.name == "voice":
|
|
response_text = "Voice settings acknowledged. TTS is available for spoken responses."
|
|
|
|
elif intent.name == "code":
|
|
from config import settings as app_settings
|
|
if not app_settings.self_modify_enabled:
|
|
response_text = (
|
|
"Self-modification is disabled. "
|
|
"Set SELF_MODIFY_ENABLED=true to enable."
|
|
)
|
|
else:
|
|
import asyncio
|
|
from self_coding.self_modify.loop import SelfModifyLoop, ModifyRequest
|
|
|
|
target_files = []
|
|
if "target_file" in intent.entities:
|
|
target_files = [intent.entities["target_file"]]
|
|
|
|
loop = SelfModifyLoop()
|
|
request = ModifyRequest(
|
|
instruction=text,
|
|
target_files=target_files,
|
|
)
|
|
result = await asyncio.to_thread(loop.run, request)
|
|
|
|
if result.success:
|
|
sha_short = result.commit_sha[:8] if result.commit_sha else "none"
|
|
response_text = (
|
|
f"Code modification complete. "
|
|
f"Changed {len(result.files_changed)} file(s). "
|
|
f"Tests passed. Committed as {sha_short} "
|
|
f"on branch {result.branch_name}."
|
|
)
|
|
else:
|
|
response_text = f"Code modification failed: {result.error}"
|
|
|
|
else:
|
|
# Default: chat with Timmy
|
|
agent = create_timmy()
|
|
run = agent.run(text, stream=False)
|
|
response_text = run.content if hasattr(run, "content") else str(run)
|
|
|
|
except Exception as exc:
|
|
error = f"Processing failed: {exc}"
|
|
logger.error("Voice processing error: %s", exc)
|
|
|
|
# Optionally speak the response
|
|
if speak_response and response_text:
|
|
try:
|
|
from timmy_serve.voice_tts import voice_tts
|
|
if voice_tts.available:
|
|
voice_tts.speak(response_text)
|
|
except Exception:
|
|
pass
|
|
|
|
return {
|
|
"intent": intent.name,
|
|
"confidence": intent.confidence,
|
|
"response": response_text,
|
|
"error": error,
|
|
"spoken": speak_response and response_text is not None,
|
|
}
|