interface / externals / clockwork_ghost
PyQt6 desktop client (Raspberry Pi cyberdeck) with 3D visualisation, voice I/O, camera capture, and TTS playback.
client.py
Outbound HTTP
| Endpoint | Purpose |
|---|---|
POST /api/chat | Streaming chat. SSE chunks accumulated in conversation_history; sentence boundaries (.?!) push to TTS queue. |
POST /api/workspace/save · /load | State zip. |
POST $STT_SERVER_URL/stt | multipart audio upload. |
POST $TTS_SERVER_URL/tts | JSON text → audio bytes. |
GET /api/download/{image_path} | Fetch agent-generated images. |
UI layout
- Left: chat display + text input with up/down history.
- Right: stacked widget — MatrixFaceWidget · QWebEngineView dashboard · QWebEngineView 3D face.
- Top: workspace, face toggle, fullscreen.
- Bottom: camera snap, push-to-talk, TTS toggle + stats.
Voice queues
audio_queue(asyncio.Queue) — text chunks to TTS.playback_queue— audio bytes awaitingaplay.audio_fetch_task()— POSTs each chunk to TTS, queues result.audio_worker_task()— pipes audio bytes toaplaysubprocess.
Camera
CameraPreviewDialog via OpenCV VideoCapture on /dev/video0 at 1920×1080 60 fps MJPG. Ctrl+Esc snaps a frame; the user can optionally upload + add a prompt.
Input shortcuts
Esc— toggle PTT recording.Alt+Esc— toggle TTS.Ctrl+Esc— snap camera.- Slash commands:
/clear,/shutdown,/reboot,/exit.
Stats
Timer every 5 s reads battery from /sys/class/power_supply/*/capacity and shows time + battery + network icon.
Concurrency
qasync.QEventLoop wraps the QApplication so asyncio and Qt cooperate. Async tasks for audio_fetch_task(), audio_worker_task(), send_chat_request(). Signal/slot pattern for cross-thread UI updates.
matrix_face.py — 3D Animated Visualization
- OpenGL ES 3.0 inside
QOpenGLWidget. - 75 nodes on a random sphere; idle vs. busy speeds: 0.75 vs. 2.75.
- Three render passes: scene → bloom blur → tone-mapped composite.
- State controlled by
set_state(active=bool, error=bool)— error flips all nodes magenta. - Physics: each node = base sphere position + 3 phase-shifted sine waves; lines drawn between nodes within 2.5 distance.
- Driven by a
QTimerfiring every 16 ms.
External dependencies
- PyQt6 + qasync + PyOpenGL.
- httpx, OpenCV (cv2), markdown, subprocess (arecord/aplay).
Heads-up: URLs and the
X-Ghost-Key in client.py are hardcoded placeholders ("YOUR_KEY_HERE", "http://eva:8000", "http://192.168.0.24:8000"). These should be wired to env vars before deployment.