feat!: rebuild EveFlow as a JARVIS-style voice HUD for Hermes Agent (v2.0.0)

Complete rewrite of the application:

- Toolchain: Electron 44, Vite 8, React 19, TypeScript 5.9, Zustand, Vitest;
  main process rewritten in TypeScript (electron/), typed IPC contract (shared/),
  sandboxed renderer with webSecurity on and an HTTP/SSE proxy in the main process.
- Voice: AudioWorklet microphone capture at 16 kHz with adaptive energy VAD and
  auto-stop, WAV encoder, OpenAI-compatible STT, TTS queue with sentence-level
  streaming, prefetch and Web Audio playback feeding an analyser; hands-free mode,
  global push-to-talk hotkey, system-voice fallback.
- Hermes: full API client (capabilities, health, models, skills, toolsets,
  sessions, jobs) with automatic transport selection: runs API (SSE lifecycle,
  approvals, steer, stop) > sessions stream > chat completions with local tools;
  tolerant event normalisation; webhook receiver hardened (secret, size limit).
- UI: JARVIS arc-reactor core on Canvas 2D reacting to the real audio signal,
  holographic HUD layout (transcript, core, Hermes ops panel with tools/crons/
  skills/sessions/link tabs, telemetry), settings drawer, approval modals,
  compact floating widget, four themes, tray icon and global shortcuts.
- Removed the 3D Eve robot, three.js and legacy assets; migrated 1.x settings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Wn5VX9HNbJ7N54hR24u9Y
This commit is contained in:
Claude committed 2026-09-03 13:40:59 +00:00
1 parent 5217eee53a
commit 127a2083a1
239 files changed
+11351 -86495

No files matched your search

+65
View File
@@ -0,0 +1,65 @@
/**
* Singleton facade over the TTS engine bound to the settings store, exposing speaking state
* to the voice and chat stores.
*/
import { useChat } from '../../state/chat';
import { useSettings } from '../../state/settings';
import { useVoice } from '../../state/voice';
import { TtsEngine } from './tts';
class SpeechFacade {
private engine: TtsEngine | null = null;
private streaming = false;
private streamEnabled = true;
private get tts(): TtsEngine {
if (!this.engine) {
const settings = useSettings.getState().settings;
this.engine = new TtsEngine(settings.speech);
this.engine.onState((state) => {
useVoice.getState().setTts(state);
const chat = useChat.getState();
if (state === 'speaking') chat.setHud('speaking');
else if (state === 'idle' && chat.hud === 'speaking') chat.setHud(chat.isSending ? 'thinking' : 'idle');
});
useSettings.subscribe((s) => this.engine?.updateConfig(s.settings.speech));
}
return this.engine;
}
init(): void {
this.tts;
}
isSpeaking(): boolean {
return this.engine?.isActive ?? false;
}
say(text: string): void {
if (useSettings.getState().settings.speech.provider === 'off') return;
this.tts.speak(text);
}
pushStream(delta: string): void {
if (!useSettings.getState().settings.speech.autoSpeak) return;
this.streaming = true;
if (this.streamEnabled) this.tts.pushStream(delta);
}
endStream(): void {
if (this.streaming) this.tts.endStream();
this.streaming = false;
}
discardStream(): void {
this.streaming = false;
this.tts.stop();
}
stop(): void {
this.streaming = false;
this.engine?.stop();
}
}
export const speech = new SpeechFacade();