ProgramAT is a mobile-first assistive technology platform for blind and visually impaired users. It combines a React Native app with a Python WebSocket backend to run AI-powered vision tools in real-time. Camera frames stream from the device to the server, where Python tools process them using ML models (YOLO, Gemini, Google Vision) and return audio-friendly results that are spoken aloud via TTS. The platform also supports GitHub issue/PR management and live Copilot session monitoring.
┌──────────────────────────────────────────────────────────────────┐
│ Mobile App (React Native / iOS & Android) │
│ │
│ ┌───────────────┐ ┌───────────────┐ ┌──────────┐ ┌────────┐ │
│ │ PRs & Text │ │ Tools/Runner │ │ Chat │ │Settings│ │
│ │ (Dev mode) │ │ (both modes) │ │ │ │ │ │
│ └───────┬───────┘ └───────┬───────┘ └────┬─────┘ └────────┘ │
│ │ │ │ │
│ └──────────────────┴───────────────┘ │
│ │ │
│ ┌─────────────▼──────────────┐ │
│ │ WebSocketService │ │
│ │ (native WebSocket, not │ │
│ │ Socket.IO) │ │
│ └─────────────┬──────────────┘ │
│ │ │
│ ┌─────────────────────┤ │
│ │ │ │
│ ┌────▼──────┐ ┌───────────▼────────┐ ┌────────────────────┐ │
│ │CameraView │ │ AudioOutputService │ │TextToSpeechService │ │
│ │(frame │ │ (TTS, beeps, │ │ (react-native-tts) │ │
│ │ capture) │ │ earcons, haptics) │ │ │ │
│ └───────────┘ └────────────────────┘ └────────────────────┘ │
└──────────────────────────────┬───────────────────────────────────┘
│
│ WebSocket (ws://server:8081)
│ Binary / JSON messages
│
┌──────────────────────────────▼───────────────────────────────────┐
│ Python Backend (GCP VM) │
│ stream_server.py │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Message Router (async) │ │
│ │ frame | text | run_tool | start_streaming | stop | │ │
│ │ request_issue_list | request_pr_list | ping | ... │ │
│ └───┬──────────────┬───────────────────┬────────────────────┘ │
│ │ │ │ │
│ ┌───▼────┐ ┌──────▼──────┐ ┌────────▼─────────────────────┐ │
│ │ Frame │ │ Text / Issue │ │ Tool Executor │ │
│ │ Store │ │ Processor │ │ │ │
│ │(disk + │ │ (Gemini AI │ │ • exec() Python tool code │ │
│ │ last │ │ parsing, │ │ • YOLO model cache │ │
│ │ frame) │ │ GitHub ops) │ │ • Module auto-install │ │
│ └────────┘ └─────────────┘ │ • Streaming (per-frame loop)│ │
│ │ • Gemini Live (custom GPT) │ │
│ └──────────────────────────────┘ │
│ │
│ ┌────────────────────┐ ┌────────────────┐ ┌─────────────────┐ │
│ │ GeminiLiveManager │ │ copilot_db.py │ │GeminiSummarizer │ │
│ │ (native audio, │ │ (SQLite, │ │(summarize logs │ │
│ │ real-time conv.) │ │ sessions, │ │ for TTS) │ │
│ │ │ │ logs, │ │ │ │
│ └────────────────────┘ │ summaries) │ └─────────────────┘ │
│ └────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
External Services:
• GitHub API (PyGithub) — issue/PR/comment management
• Google Gemini API — AI parsing, tool execution, live audio
• Google Cloud Vision API — OCR / text extraction (in tools)
• GCP Secret Manager — secret storage (GitHub token, etc.)
- Framework: React Native (iOS & Android)
- Language: TypeScript
- Camera: react-native-vision-camera
- Speech-to-Text: @react-native-voice/voice (used in ToolRunner for follow-up queries)
- Text-to-Speech: react-native-tts +
AudioOutputService.ts - WebSocket: Native WebSocket (not Socket.IO)
- Storage: AsyncStorage (persisted tool selections, chat sessions)
- UI: React Native core components + react-native-safe-area-context
The app runs in one of two modes configured in config.ts:
| Feature | Development | Production |
|---|---|---|
| PRs & Text tab | ✅ | ❌ |
| Issues tab | ✅ | ❌ |
| PR / branch selection | ✅ | ❌ |
| Tools tab | ✅ | ✅ |
| Chat tab | ✅ | ✅ |
| Tools loaded from | Any PR/branch | main only |
| Default tab on launch | PRs & Text | Tools |
- Bottom tab bar with: PRs & Text (dev only), Tools, Chat, Settings
- Passes
issueTools,prList,copilotSessions/Logs/Summariesdown to children - Handles mode switching (Dev ↔ Production)
- Coordinates cross-tab navigation (e.g. ToolRunner → Chat)
- Three sub-views: PR list (
IssueSelector), text input (TextInput), log viewer (ViewLogsScreen) - Browse open PRs, select one, compose issue text, watch Copilot logs
- Composes
ToolSelector+ToolRunner - Persists selected tool via AsyncStorage across app restarts
- Refreshes tool definition when code or
gpt_querychanges on server
- Fetches available tools from backend via
request_tool_listWebSocket message - Displays tools from the
tools/directory (filtered by mode) - Plays loading beep if fetch takes > 3 seconds
- Sends
run_tool/start_streaming_tool/stop_streaming_toolmessages - Displays text output from tool results
- Audio output: passes result through
AudioOutputService(TTS, beeps, earcons, haptics) - Streaming mode: continuous per-frame execution with throttling and similarity filtering
- Custom GPT mode: activates
GeminiLiveSessionon the server; supports voice follow-up questions via@react-native-voice/voice; auto-re-queries everyquery_intervalseconds - Camera frames captured via
CameraViewref and attached to each run
- Live preview via react-native-vision-camera
- Captures single frames (base64 JPEG) on demand or on a timer
- Exposes
CameraViewHandleref so parent components can trigger captures
- Stores chat sessions in AsyncStorage
- Shows image thumbnail, tool name, and full message history per session
- Supports follow-up questions that are sent to the server as
chat_followupmessages - Auto-navigated to from ToolRunner when a Gemini Live conversation produces a conversation ID
- Native WebSocket connection to
ws://server:8081 - 25-second heartbeat ping with 8-second pong timeout and auto-reconnect
- 15-second connection timeout
- Single
onMessagecallback dispatched to all registered listeners - Sends:
StreamFrame(base64 image),StreamText(text), and arbitrary JSON commands
- Unified interface for TTS, beeps (200 / 440 / 880 Hz), earcons (success/error/warning), and haptic patterns
interruptflag stops current speech before starting new audio- Tools return a dict with
audio.typeto control which output mode is used
- Browse open GitHub issues and PRs
- Select to enter update mode, view Copilot logs, or navigate to Tools
- Shows live Copilot agent logs for a selected PR
- Displays rolling summaries generated by
GeminiSummarizer
User selects tool in ToolSelector
│
▼
ToolRunner sends start_streaming_tool (or run_tool for one-shot)
│
▼
CameraView captures frame (base64 JPEG)
│
▼
WebSocket sends frame + run command to backend
│
▼
Backend exec()s tool code with (image, input_data)
│
▼
Tool returns string or dict with 'audio'/'text' keys
│
▼
Backend sends tool_result WebSocket message
│
▼
ToolRunner receives result
│
┌────┴────────────────────────────┐
│ │
▼ ▼
AudioOutputService.speak() setToolOutput() → UI text
(TTS / beep / earcon)
- Language: Python 3.11
- WebSocket Server:
websocketslibrary (asyncio) - AI – General: Google Gemini API (
gemini-2.5-flash-litedefault; configurable) - AI – Live Audio:
gemini-2.5-flash-native-audio-previewviagoogle-genaiSDK - Object Detection: YOLO11 (
yolo11n.pt) and YOLOWorld (yolov8s-world.pt) via Ultralytics - OCR: Google Cloud Vision API
- GitHub: PyGithub
- Image Processing: OpenCV, NumPy, Pillow
- Secrets: GCP Secret Manager (with
.envfallback) - Database: SQLite via
copilot_db.py - Deployment: GCP Compute Engine VM
- Single asyncio event loop; handles all connected clients concurrently
- 20 MB max message size; 20s ping/pong keepalive
- Per-client
SessionLoggerwrites timestamped.logfiles touser_logs/ - Global state:
last_frame,active_streaming_tools,yolo_model_cache,conversation_images,active_copilot_streams,pending_copilot_issues
- Tools from the
tools/directory are fetched from GitHub andexec()-ed directly in Python - Each tool must expose a
main(image, input_data)function - Returns a
str(spoken via TTS) ordictwithaudio/textkeys - YOLO model cache (
yolo_model_cache) is shared across all tool runs to avoid reloading weights on every frame - Module auto-install:
ModuleManagerinstalls missing pip packages at runtime and updatesrequirements.txt - Streaming tools:
run_streaming_tools()loops on every incoming frame; per-client throttle (default 500 ms) - Gemini Live tools (
custom_gpt=True): handled byGeminiLiveManager, bypass theexec()loop
install_module(name)runspip installin a subprocessload_module(name)imports and caches; installs if missing- Tracks installed modules per session to avoid duplicate installs
- Maps common import aliases (
cv2→opencv-python,PIL→pillow, etc.)
GeminiLiveSession: connects to Gemini Live API with native audio response modality- Background
_receive_loopcollects audio transcription and text parts send_image_query(base64_image, query)sends a frame + prompt; awaitsturn_complete- Images are downscaled to ≤ 1024 px on longest edge before transmission
GeminiLiveManager(instream_server.py) manages per-client sessions and the periodic re-query loop
- Tables:
copilot_sessions,copilot_logs,copilot_summaries - Tracks GitHub Copilot agent subprocess sessions by PR number
- Bulk log inserts run in a
ThreadPoolExecutorto avoid blocking the asyncio loop
- Summarizes batches of Copilot log entries into 1–3 sentence TTS-friendly strings
- Uses
system_llm_call(...)with the fixedSYSTEM_LLM_MODEL; internal summaries do not use capability routing - Called periodically while a Copilot session is streaming
- Creates issues from
.github/ISSUE_TEMPLATE/templates - Fills templates with Gemini-parsed field data
- Adds comments to existing issues (update mode)
- Mentions
@copilotfor code-change requests - Polls GitHub for new PRs after Copilot issue creation (
pending_copilot_issues)
| Message type | Key fields | Purpose |
|---|---|---|
frame |
data.base64Image |
Camera frame for active streaming tool |
text |
data.text |
Issue/PR text input (delta-tracked) |
run_tool |
tool, image |
One-shot tool execution |
start_streaming_tool |
tool |
Begin continuous per-frame tool |
stop_streaming_tool |
— | Stop streaming tool |
request_tool_list |
productionMode, issueNumber |
Fetch available tools |
request_issue_list |
— | Fetch open GitHub issues |
request_pr_list |
— | Fetch open PRs |
start_copilot_stream |
pr_number |
Stream Copilot agent logs for a PR |
stop_copilot_stream |
— | Stop Copilot log stream |
chat_followup |
conversationId, message |
Follow-up question on a tool result |
resume_live_query |
— | Resume Gemini Live query loop after voice input |
ping |
— | Keepalive |
| Message type | Key fields | Purpose |
|---|---|---|
tool_result |
result, audio |
Tool execution output |
tool_list |
tools[] |
Available tools |
streaming_started |
tool_name |
Streaming tool activated |
streaming_stopped |
— | Streaming tool deactivated |
issue_created |
issue_number, issue_url |
New GitHub issue created |
issue_updated |
issue_number, message |
Comment added to issue |
issue_list |
issues[] |
Open GitHub issues |
pr_list |
prs[] |
Open PRs |
copilot_log |
log_line, session_id |
Live Copilot agent log line |
copilot_summary |
summary, session_id |
Gemini summary of log batch |
feedback |
message, missing_fields |
Request more info from user |
ack |
frame_number |
Frame acknowledged |
pong |
— | Keepalive response |
Each tool is a standalone Python file with:
main(image, input_data)— entry point called by the server- Returns
str(auto-TTS) ordictwithaudio/textkeys - Tools share building-block patterns but are fully self-contained (no cross-imports at runtime)
| Tool | Description | Model |
|---|---|---|
object_recognition.py |
Detect objects in scene | YOLO11 + COCO |
scene_description.py |
Describe full scene | Gemini Vision |
live_ocr.py |
Real-time text extraction | Google Cloud Vision |
door_detection.py |
Detect doors and openings | YOLO11 |
empty_seat_detection.py |
Find empty seats | YOLO11 |
clothing_recognition.py |
Identify clothing items | YOLOWorld |
camera_aiming.py |
Guide camera alignment (clock-face directions) | YOLO11 |
run_tool message received (with base64 image)
│
▼
Decode image → PIL Image
│
▼
exec() tool code in isolated namespace
│
▼
Call main(image, input_data)
│
▼
Serialize result (str or dict)
│
▼
Send tool_result to client → AudioOutputService → TTS
start_streaming_tool received
│
▼
Register in active_streaming_tools[client_id]
│
▼
Each incoming frame triggers run_streaming_tools()
│ (throttled per tool config, default 500 ms)
▼
exec() tool → result → tool_result message
start_streaming_tool with custom_gpt=True
│
▼
GeminiLiveManager.start_session(client_id, system_instruction)
│
▼
Background query loop every query_interval seconds:
send_image_query(last_frame, gpt_query)
│
▼
Receive audio transcription + turn_complete
│
▼
Send tool_result with Gemini's spoken response
│
▼
Client receives → AudioOutputService plays native audio or TTS
│
▼
User can ask follow-up via voice (ToolRunner speech-to-text)
→ resume_live_query restarts the query loop
Text received → delta extraction (new words only)
│
▼
5-second pause detection
│
▼
Gemini parses transcript → structured issue fields
│
▼
Missing fields? → send feedback → wait for more input
│
▼
All fields present → fill .github/ISSUE_TEMPLATE → create GitHub issue
│
▼
Poll for Copilot PR creation → stream logs to client
- React Native app built for iOS/Android
- Distributed via App Store / TestFlight / APK
- GCP Compute Engine VM
- WebSocket server:
ws://<SERVER_IP>:8081 - Python 3.11 virtual environment (
venv) restart_server.sh/clear_frames.shutility scripts- Logs: per-session files in
backend/user_logs/; SQLite DB atbackend/copilot_logs.db
Backend .env (or GCP Secret Manager):
GITHUB_TOKEN=<GitHub PAT>
GITHUB_REPO=owner/repo
GEMINI_API_KEY=<Google AI API key>
ROUTING_MODE=semantic # optional; semantic is the default
GCP_PROJECT=<project-id> # for Secret Manager fallback
PAUSE_DURATION=5.0 # seconds before issue creationFrontend config.ts:
export const WEBSOCKET_SERVER_URL = 'ws://<SERVER_IP>:8081';
export const APP_MODE: AppMode = 'development'; // or 'production'- GitHub PAT stored in GCP Secret Manager (
.envfallback for local dev) - WebSocket runs over plain WS — use WSS/TLS behind a reverse proxy for production
- Input validation on backend; tool code is fetched from a trusted GitHub repo
- Rate limiting should be applied at the reverse-proxy level
- Frame storage in
received_frames/should be cleaned up periodically (clear_frames.sh)
Last Updated: March 24, 2026
Architecture Version: 3.0 (Vision Tools + Gemini Live)