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

+66
View File
@@ -0,0 +1,66 @@
import { useEffect, useState } from 'react';
import type { SystemMetrics } from '../../shared/ipc';
import { bridge } from '../lib/bridge';
export interface LiveMetrics extends SystemMetrics {
fps: number;
}
const EMPTY: LiveMetrics = {
hostname: 'LOCALHOST',
platform: navigator.platform,
cpuModel: '',
cpuCores: navigator.hardwareConcurrency || 0,
cpuLoad: 0,
cpuFreqMhz: 0,
memUsedPct: 0,
memTotalGb: 0,
uptimeSec: 0,
fps: 60
};
export function useMetrics(enabled = true, intervalMs = 2000): LiveMetrics {
const [metrics, setMetrics] = useState<LiveMetrics>(EMPTY);
useEffect(() => {
if (!enabled) return;
let frames = 0;
let lastFpsTick = performance.now();
let raf = 0;
const loop = () => {
frames++;
const now = performance.now();
if (now - lastFpsTick >= 1000) {
const fps = Math.round((frames * 1000) / (now - lastFpsTick));
frames = 0;
lastFpsTick = now;
setMetrics((m) => (m.fps === fps ? m : { ...m, fps }));
}
raf = requestAnimationFrame(loop);
};
raf = requestAnimationFrame(loop);
const api = bridge();
const poll = () => {
if (!api) return;
api.system.metrics().then((m) => setMetrics((prev) => ({ ...prev, ...m }))).catch(() => undefined);
};
poll();
const timer = setInterval(poll, intervalMs);
return () => {
cancelAnimationFrame(raf);
clearInterval(timer);
};
}, [enabled, intervalMs]);
return metrics;
}
export function useClock(): Date {
const [now, setNow] = useState(() => new Date());
useEffect(() => {
const timer = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(timer);
}, []);
return now;
}