Files
voicebox/landing/src/app/capture/page.tsx
T
James PineandClaude Opus 4.7 6b75e097e1 feat(mcp): Rust-owned speaking pill with self-contained audio playback
The pill window now surfaces for agent-initiated speech without main-window
involvement. Rust subscribes to /events/speak via a tokio task + reqwest
streaming body (speak_monitor.rs), shows the pill, and forwards events to
the dictate webview over Tauri's event bus. The pill plays audio via a
plain HTMLAudioElement and emits dictate:hide when playback ends. The
pill stays hidden through the ~1 s generation wait and only surfaces when
audio actually starts, with the counter armed at that moment.

Fixes a shared-dict mutation in mcp_server/events.publish() that caused
the second subscriber (Rust speak_monitor) to receive `event: message`
instead of named speak-start/speak-end frames. Also teaches the speak_monitor
parser to handle CRLF framing (sse-starlette default). Main-window
AudioPlayer now skips autoplay for source in {mcp, rest} to avoid
double-play when both windows are alive.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-23 01:46:17 -07:00

178 lines
8.4 KiB
TypeScript

'use client';
import { Github } from 'lucide-react';
import { useEffect, useState } from 'react';
import { AgentIntegration } from '@/components/AgentIntegration';
import { CaptureHero } from '@/components/CaptureHero';
import { CapturesMockup } from '@/components/CapturesMockup';
import { Footer } from '@/components/Footer';
import { Navbar } from '@/components/Navbar';
import { AppleIcon, LinuxIcon, WindowsIcon } from '@/components/PlatformIcons';
import { GITHUB_REPO } from '@/lib/constants';
export default function CapturePage() {
const [version, setVersion] = useState<string | null>(null);
const [totalDownloads, setTotalDownloads] = useState<number | null>(null);
useEffect(() => {
fetch('/api/releases')
.then((res) => {
if (!res.ok) throw new Error('Failed to fetch releases');
return res.json();
})
.then((data) => {
if (data.version) setVersion(data.version);
if (data.totalDownloads != null) setTotalDownloads(data.totalDownloads);
})
.catch((error) => {
console.error('Failed to fetch release info:', error);
});
}, []);
return (
<>
<Navbar />
{/* ── Hero ─────────────────────────────────────────────────── */}
<CaptureHero version={version} totalDownloads={totalDownloads} />
{/* ── Captures mockup ─────────────────────────────────────── */}
<section className="relative border-t border-border py-24">
<div className="mx-auto max-w-5xl px-6 text-center mb-14">
<div className="text-[11px] font-semibold uppercase tracking-[0.22em] text-accent mb-4">
The Captures tab
</div>
<h2 className="text-3xl md:text-4xl font-semibold tracking-tight text-foreground mb-4">
Every capture, paired with audio and transcript.
</h2>
<p className="text-muted-foreground max-w-2xl mx-auto">
Hold the shortcut, speak, release a capture lands in the Captures tab. Replay the
original audio, re-transcribe with a different model, refine with a local LLM, copy to
clipboard, or send it straight to any MCP-aware agent. Nothing leaves your machine.
</p>
</div>
<CapturesMockup />
</section>
{/* ── Feature bullets ─────────────────────────────────────── */}
<section className="border-t border-border py-24">
<div className="mx-auto max-w-6xl px-6">
<div className="grid md:grid-cols-3 gap-6">
<div className="rounded-xl border border-border bg-card/40 backdrop-blur-sm p-6">
<h3 className="text-[15px] font-semibold text-foreground mb-2">
Whisper, sized for every machine
</h3>
<p className="text-sm leading-relaxed text-muted-foreground">
Base, Small, Medium, Large, and Turbo. Pick per-capture 99 languages at every
tier, all local, all downloadable from inside the app.
</p>
</div>
<div className="rounded-xl border border-border bg-card/40 backdrop-blur-sm p-6">
<h3 className="text-[15px] font-semibold text-foreground mb-2">
LLM refinement that respects your words
</h3>
<p className="text-sm leading-relaxed text-muted-foreground">
A local Qwen model cleans ums, self-corrections, and punctuation without
rephrasing. Keep raw and refined side-by-side; the original audio is always kept.
</p>
</div>
<div className="rounded-xl border border-border bg-card/40 backdrop-blur-sm p-6">
<h3 className="text-[15px] font-semibold text-foreground mb-2">
Archived by default
</h3>
<p className="text-sm leading-relaxed text-muted-foreground">
Every dictation keeps both the audio and the transcript. Search, re-run, or turn
any capture into a voice sample for cloning. Configurable retention auto-expire
or keep forever.
</p>
</div>
</div>
</div>
</section>
{/* ── Agent voice output ──────────────────────────────────── */}
<AgentIntegration />
{/* ── Bottom CTA ──────────────────────────────────────────── */}
<section id="download" className="border-t border-border py-24">
<div className="mx-auto max-w-4xl px-6">
<div className="text-center mb-12">
<h2 className="text-3xl font-semibold tracking-tight text-foreground md:text-4xl mb-4">
Install Voicebox, start dictating.
</h2>
<p className="text-muted-foreground">
Free, open-source, local. No account, no API keys, no per-character fees.
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 max-w-2xl mx-auto">
<a
href="/download?platform=macArm"
className="flex items-center rounded-xl border border-border bg-card/60 backdrop-blur-sm px-5 py-4 transition-all hover:border-accent/30 hover:bg-card group"
>
<AppleIcon className="h-6 w-6 shrink-0 text-muted-foreground group-hover:text-foreground transition-colors" />
<div className="ml-4">
<div className="text-sm font-medium">macOS</div>
<div className="text-xs text-muted-foreground">Apple Silicon (ARM)</div>
</div>
</a>
<a
href="/download?platform=macIntel"
className="flex items-center rounded-xl border border-border bg-card/60 backdrop-blur-sm px-5 py-4 transition-all hover:border-accent/30 hover:bg-card group"
>
<AppleIcon className="h-6 w-6 shrink-0 text-muted-foreground group-hover:text-foreground transition-colors" />
<div className="ml-4">
<div className="text-sm font-medium">macOS</div>
<div className="text-xs text-muted-foreground">Intel (x64)</div>
</div>
</a>
<a
href="/download?platform=windows"
className="flex items-center rounded-xl border border-border bg-card/60 backdrop-blur-sm px-5 py-4 transition-all hover:border-accent/30 hover:bg-card group"
>
<WindowsIcon className="h-6 w-6 shrink-0 text-muted-foreground group-hover:text-foreground transition-colors" />
<div className="ml-4">
<div className="text-sm font-medium">Windows</div>
<div className="text-xs text-muted-foreground">64-bit (MSI)</div>
</div>
</a>
<a
href="/linux-install"
className="flex items-center rounded-xl border border-border bg-card/60 backdrop-blur-sm px-5 py-4 transition-all hover:border-accent/30 hover:bg-card group"
>
<LinuxIcon className="h-6 w-6 shrink-0 text-muted-foreground group-hover:text-foreground transition-colors" />
<div className="ml-4">
<div className="text-sm font-medium">Linux</div>
<div className="text-xs text-muted-foreground">Build from source</div>
</div>
</a>
</div>
<div className="mt-6 text-center">
<a
href={`${GITHUB_REPO}/releases`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
>
<Github className="h-4 w-4" />
View all releases on GitHub
</a>
</div>
<div className="mt-10 text-center">
<a
href="/"
className="text-sm text-muted-foreground/70 hover:text-foreground transition-colors"
>
See everything Voicebox can do
</a>
</div>
</div>
</section>
<Footer />
</>
);
}