Add Discord-native hybrid front-end for Jarvis (bot + bridge)
Some checks failed
Release / semantic-release (push) Successful in 59s
tests / Unit tests (Linux, Python 3.11) (push) Successful in 13m45s
Release / build-linux (push) Failing after 7m47s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled

Transform isair/jarvis into a Discord-controlled voice assistant running on
the Ubuntu VNC desktop, keeping the mature ~39k-line Python brain intact.

- bot/ (Node + bun, discord.js): /자비스 slash commands (ephemeral),
  voice channel join + voice receive/playback, pluggable VNC screen broadcast
  (selfbot live / noVNC / screenshot)
- bridge/ (Python, Flask): wraps jarvis STT + run_reply_engine + Piper TTS
  behind a thin localhost HTTP API
- .env.example, scripts/ (start_bridge/start_bot/dev), README rewrite,
  docs/language-comparison.md and docs/vnc-xfce-setup.md

Language decision: hybrid (Python brain + Node/bun Discord layer) because
Discord blocks bot video; native screen broadcast only works via a Node
selfbot library.
This commit is contained in:
javis-bot
2026-06-09 14:51:05 +09:00
parent a5bf8d1826
commit c4abf63f38
308 changed files with 94135 additions and 1 deletions

View File

@@ -0,0 +1,120 @@
"""
Dictation history — persists transcription results to a local JSON file.
Privacy-first: all data stays on disk, never leaves the machine.
"""
from __future__ import annotations
import json
import threading
import time
import uuid
from pathlib import Path
from typing import Any, Dict, List, Optional
def _default_history_path() -> Path:
"""Return the default path for dictation history storage."""
base = Path.home() / ".local" / "share" / "jarvis"
base.mkdir(parents=True, exist_ok=True)
return base / "dictation_history.json"
class DictationHistory:
"""Thread-safe, file-backed dictation history.
Each entry is a dict with keys:
id unique identifier (UUID4 hex)
text transcribed text
timestamp epoch seconds (float)
duration recording duration in seconds (float)
"""
def __init__(self, path: Optional[Path] = None, max_entries: int = 500) -> None:
self._path = path or _default_history_path()
self._max_entries = max_entries
self._lock = threading.Lock()
self._entries: List[Dict[str, Any]] = self._load()
# ------------------------------------------------------------------
# Public API
# ------------------------------------------------------------------
def add(self, text: str, duration: float = 0.0) -> Dict[str, Any]:
"""Append a new dictation entry and persist. Returns the new entry."""
entry: Dict[str, Any] = {
"id": uuid.uuid4().hex,
"text": text,
"timestamp": time.time(),
"duration": round(duration, 1),
}
with self._lock:
# Re-read from disk to pick up external changes (e.g. deletions
# made by the desktop app while the daemon runs in a subprocess).
self._entries = self._load()
self._entries.append(entry)
# Trim oldest entries if over limit
if len(self._entries) > self._max_entries:
self._entries = self._entries[-self._max_entries:]
self._save()
return entry
def get_all(self) -> List[Dict[str, Any]]:
"""Return all entries, newest first."""
with self._lock:
return list(reversed(self._entries))
def delete(self, entry_id: str) -> bool:
"""Delete an entry by ID. Returns True if found and removed."""
with self._lock:
before = len(self._entries)
self._entries = [e for e in self._entries if e["id"] != entry_id]
if len(self._entries) < before:
self._save()
return True
return False
def clear(self) -> None:
"""Delete all entries."""
with self._lock:
self._entries = []
self._save()
def reload_from_disk(self) -> None:
"""Re-read entries from the JSON file (thread-safe).
Useful for external consumers (e.g. the desktop app) that need to
pick up changes written by another process.
"""
with self._lock:
self._entries = self._load()
@property
def count(self) -> int:
with self._lock:
return len(self._entries)
# ------------------------------------------------------------------
# Persistence
# ------------------------------------------------------------------
def _load(self) -> List[Dict[str, Any]]:
try:
if self._path.exists():
with self._path.open("r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, list):
return data
except Exception:
pass
return []
def _save(self) -> None:
try:
self._path.parent.mkdir(parents=True, exist_ok=True)
with self._path.open("w", encoding="utf-8") as f:
json.dump(self._entries, f, ensure_ascii=False, indent=2)
except Exception as exc:
from jarvis.debug import debug_log
debug_log(f"failed to save dictation history: {exc}", "dictation")