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

+81
View File
@@ -0,0 +1,81 @@
import { useEffect, useRef, useState } from 'react';
import { MessageSquare, Trash2, X } from 'lucide-react';
import { useChat } from '../../state/chat';
import { useSettings } from '../../state/settings';
import { MessageBubble } from './MessageBubble';
import { CommandBar } from './CommandBar';
interface Props {
compact?: boolean;
}
export function ChatPanel({ compact }: Props) {
const messages = useChat((s) => s.messages);
const clear = useChat((s) => s.clear);
const settings = useSettings((s) => s.settings);
const listRef = useRef<HTMLDivElement>(null);
const [zoom, setZoom] = useState<string | null>(null);
const [stick, setStick] = useState(true);
useEffect(() => {
if (!stick) return;
const el = listRef.current;
if (el) el.scrollTop = el.scrollHeight;
}, [messages, stick]);
const onScroll = () => {
const el = listRef.current;
if (!el) return;
setStick(el.scrollHeight - el.scrollTop - el.clientHeight < 80);
};
const visible = compact ? messages.slice(-6) : messages;
return (
<section className={`panel bracket chat-panel${compact ? ' compact' : ''}`}>
{!compact && (
<header className="panel-head">
<MessageSquare size={14} />
<span>Transmission</span>
<span className="spacer" />
<span className="chip">{messages.length} msg</span>
<button className="icon-btn" title="Effacer la conversation" onClick={clear} disabled={messages.length === 0}>
<Trash2 size={14} />
</button>
</header>
)}
<div ref={listRef} className={`chat-list${compact ? ' compact' : ''}`} onScroll={onScroll}>
{visible.length === 0 && (
<div className="empty">
{compact ? 'En attente…' : `Canal ouvert. Parlez ou écrivez à ${settings.assistantName}.`}
{!compact && (
<div className="muted" style={{ marginTop: 8, fontSize: 12 }}>
<span className="kbd">Ctrl</span> + <span className="kbd">Shift</span> + <span className="kbd">Espace</span> active le micro depuis n’importe où.
</div>
)}
</div>
)}
{visible.map((m) => (
<MessageBubble
key={m.id}
message={m}
assistantName={settings.assistantName}
userName={settings.userName}
showReasoning={settings.ui.showReasoning}
compact={compact}
onOpenImage={setZoom}
/>
))}
</div>
<CommandBar compact={compact} />
{zoom && (
<div className="overlay" onClick={() => setZoom(null)}>
<img src={zoom} alt="aperçu" style={{ maxWidth: '92%', maxHeight: '90%', borderRadius: 10, border: '1px solid var(--line-strong)' }} />
<button className="icon-btn" style={{ position: 'absolute', top: 12, right: 12 }} onClick={() => setZoom(null)}>
<X size={16} />
</button>
</div>
)}
</section>
);
}