fix(web): clarify speech demo browser support (#449)

This commit is contained in:
Jatin K Malik
2026-06-02 13:20:24 -07:00
committed by GitHub
parent 3f5a5effff
commit b0572a36dc
15 changed files with 132 additions and 65 deletions
+5 -5
View File
@@ -64,7 +64,7 @@
</g>
</g>
<!-- GitHub CTA button — icon and text vertically centered -->
<!-- GitHub CTA button - icon and text vertically centered -->
<g transform="translate(80, 400)">
<rect width="280" height="50" rx="12" fill="#18181b"/>
<!-- GitHub icon + text centered as a group: total content ~200px wide, button 280px, offset=(280-200)/2=40 -->
@@ -95,7 +95,7 @@
<!-- URL branding -->
<text x="80" y="545" font-family="'Inter', 'Helvetica Neue', Arial, sans-serif" font-size="13" fill="#a1a1aa">vocalinux.com</text>
<!-- ==================== RIGHT SIDE — FEATURE CARDS ==================== -->
<!-- ==================== RIGHT SIDE - FEATURE CARDS ==================== -->
<!-- 4 cards, each 118px tall, with 20px gap between them -->
<!-- Total cards height: 4*118 + 3*20 = 532. Canvas usable = 630 - 8 (accent lines) = 622 -->
<!-- Top offset: (622 - 532) / 2 + 4 = 49 -->
@@ -103,7 +103,7 @@
<!-- Card 1: 100% Local Processing -->
<g transform="translate(620, 49)">
<rect width="510" height="118" rx="16" fill="white" stroke="#e4e4e7" stroke-width="1.2"/>
<!-- Icon — vertically centered: (118-50)/2 = 34 -->
<!-- Icon - vertically centered: (118-50)/2 = 34 -->
<g transform="translate(24, 34)">
<rect width="50" height="50" rx="13" fill="#f0fdf4"/>
<g transform="translate(13, 10)">
@@ -111,11 +111,11 @@
<path d="M9 12l2 2 4-4" fill="none" stroke="#16a34a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</g>
<!-- Text block — vertically centered: icon center at 34+25=59, text block ~40px tall, starts at 59-20+6=45 -->
<!-- Text block - vertically centered: icon center at 34+25=59, text block ~40px tall, starts at 59-20+6=45 -->
<text x="90" y="49" font-family="'Inter', 'Helvetica Neue', Arial, sans-serif" font-size="19" font-weight="700" fill="#18181b">100% Local Processing</text>
<text x="90" y="72" font-family="'Inter', 'Helvetica Neue', Arial, sans-serif" font-size="14" fill="#71717a">No cloud, no subscriptions, no data</text>
<text x="90" y="89" font-family="'Inter', 'Helvetica Neue', Arial, sans-serif" font-size="14" fill="#71717a">leaves your machine</text>
<!-- Badge — vertically centered alongside title -->
<!-- Badge - vertically centered alongside title -->
<rect x="418" y="37" width="72" height="26" rx="6" fill="#f0fdf4" stroke="#bbf7d0" stroke-width="1"/>
<text x="454" y="55" font-family="'Inter', 'Helvetica Neue', Arial, sans-serif" font-size="11" font-weight="700" fill="#166534" text-anchor="middle">PRIVATE</text>
</g>

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

+3 -3
View File
@@ -151,7 +151,7 @@ export default function AutostartPage() {
</h1>
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">
Have Vocalinux ready to go the moment you log in. Uses the Linux-standard XDG autostart
mechanism—works with GNOME, KDE, Xfce, and all major desktop environments.
mechanism - works with GNOME, KDE, Xfce, and all major desktop environments.
</p>
</section>
@@ -224,7 +224,7 @@ export default function AutostartPage() {
Yes
</span>
) : (
<span className="text-muted-foreground">—</span>
<span className="text-muted-foreground"> - </span>
)}
</td>
<td className="py-3 text-muted-foreground">{de.notes}</td>
@@ -241,7 +241,7 @@ export default function AutostartPage() {
</h2>
<p className="text-sm text-blue-700 dark:text-blue-400">
Vocalinux autostart uses <strong>XDG desktop entries</strong>, not systemd services. This
means it starts as a regular desktop application in your graphical session—which is the
means it starts as a regular desktop application in your graphical session - which is the
correct way to launch GUI apps. No background services, no root required.
</p>
</section>
+8 -8
View File
@@ -10,11 +10,11 @@ const releases = [
date: "2026-05-30",
type: "beta",
highlights: [
"New Advanced Settings tab with whisper.cpp anti-hallucination parameters — temperature, no_speech_threshold, max segment length, and more (PR #415)",
"New Advanced Settings tab with whisper.cpp anti-hallucination parameters - temperature, no_speech_threshold, max segment length, and more (PR #415)",
"IBus engine readiness probe at startup with hardened retries (PR #391)",
"IBus runtime failure recovery without app restart (PR #411)",
"IBus engine instance destruction handled on keyboard layout switch (fixes #388, closes #389)",
"Preserve final speech on stop — no more truncated transcriptions (fixes #401)",
"Preserve final speech on stop - no more truncated transcriptions (fixes #401)",
"Play stop sound immediately on release and after audio thread joins (PR #426, #436)",
"Repair pywhispercpp library loading in installer (PR #433)",
"Reduce whisper.cpp CPU threads and ensure GPU backend builds in dev mode (PR #439)",
@@ -41,7 +41,7 @@ const releases = [
"Add missing dependencies for Pop!_OS and Ubuntu 24.04+ including cmake, libcairo2-dev, libgirepository (PR #379)",
"Systematic code quality refactor across 20 dimensions (PR #377)",
"Clarify missing GNOME AppIndicator support on Debian (PR #385)",
"Redesigned OG image for vocalinux.com — cleaner, professional, text-based layout (PR #392)",
"Redesigned OG image for vocalinux.com - cleaner, professional, text-based layout (PR #392)",
"Test coverage improvements: mock Notify module, tray degraded-startup, IBus socket-readiness branches (PR #384, #386, #390)",
],
},
@@ -82,14 +82,14 @@ const releases = [
date: "2026-03-14",
type: "beta",
highlights: [
"Left/right modifier key distinction — choose Left Ctrl vs Right Ctrl for your shortcut",
"Sound effects toggle — enable or disable audio feedback from Settings",
"Wayland clipboard fallback — auto-copies text when virtual keyboard injection isn't available",
"Display availability check — graceful error when running in headless environments",
"Left/right modifier key distinction - choose Left Ctrl vs Right Ctrl for your shortcut",
"Sound effects toggle - enable or disable audio feedback from Settings",
"Wayland clipboard fallback - auto-copies text when virtual keyboard injection isn't available",
"Display availability check - graceful error when running in headless environments",
"Fixed unwanted leading space at the start of each new transcription session",
"Fixed shortcut mode (toggle/push-to-talk) not applying on startup",
"Improved Debian/pipx installation guidance and cross-distro error messages",
"Grouped shortcut selector UI — shortcuts organised by Either/Left/Right side",
"Grouped shortcut selector UI - shortcuts organised by Either/Left/Right side",
],
},
{
+8 -8
View File
@@ -27,7 +27,7 @@ const useCases = [
{
title: "Git Commit Messages",
description:
"Speak your commit messages naturally—no more brief, unhelpful commits because typing is slow.",
"Speak your commit messages naturally - no more brief, unhelpful commits because typing is slow.",
example: "fix authentication timeout when server is under heavy load",
icon: Terminal,
iconColor: "text-orange-500",
@@ -63,7 +63,7 @@ const useCases = [
{
title: "Pair Programming Notes",
description:
"Dictate notes while pairing—keep your hands on the keyboard for code, use voice for meta.",
"Dictate notes while pairing - keep your hands on the keyboard for code, use voice for meta.",
example: "Remember to add unit tests for the edge case we just discussed",
icon: Zap,
iconColor: "text-amber-500",
@@ -99,7 +99,7 @@ const benefits = [
},
{
title: "Maintain Flow State",
description: "Speak your thoughts as they come—no context switching to the keyboard for comments and docs.",
description: "Speak your thoughts as they come - no context switching to the keyboard for comments and docs.",
},
{
title: "Better Documentation",
@@ -107,14 +107,14 @@ const benefits = [
},
{
title: "Universal Compatibility",
description: "Works in VS Code, JetBrains IDEs, Vim, terminal, browser—every Linux application.",
description: "Works in VS Code, JetBrains IDEs, Vim, terminal, browser - every Linux application.",
},
];
export const metadata: Metadata = buildPageMetadata({
title: "Voice Dictation for Developers & Coders | Vocalinux",
description:
"Dictate code comments, documentation, and commit messages hands-free. Voice dictation for Linux developers—works in VS Code, JetBrains, terminal, and all apps.",
"Dictate code comments, documentation, and commit messages hands-free. Voice dictation for Linux developers - works in VS Code, JetBrains, terminal, and all apps.",
path: "/for-developers",
keywords: [
"voice dictation for developers",
@@ -168,7 +168,7 @@ export default function ForDevelopersPage() {
Voice Dictation for Developers
</h1>
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">
Write code comments, documentation, commit messages, and more—hands-free. Keep your hands
Write code comments, documentation, commit messages, and more - hands-free. Keep your hands
on the keyboard for code, use your voice for everything else.
</p>
</section>
@@ -236,12 +236,12 @@ export default function ForDevelopersPage() {
<section className="mb-12 rounded-2xl border border-blue-200 bg-blue-50 p-6 dark:border-blue-800 dark:bg-blue-900/20">
<h2 className="mb-3 text-xl font-bold text-blue-800 dark:text-blue-300">
Not Voice Coding—Voice Documentation
Not Voice Coding - Voice Documentation
</h2>
<p className="text-sm text-blue-700 dark:text-blue-400">
Vocalinux is designed for <strong>dictating text</strong>, not writing code syntax. For
actual code, your keyboard is still faster. But for comments, docs, messages, and
notes—voice is a game-changer. Use both together for maximum productivity.
notes - voice is a game-changer. Use both together for maximum productivity.
</p>
</section>
+1 -1
View File
@@ -187,7 +187,7 @@ export default function GpuAccelerationPage() {
</h1>
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">
Leverage your GPU for faster speech recognition. Vocalinux supports AMD, Intel, and NVIDIA
GPUs via Vulkan—with automatic fallback to CPU when needed.
GPUs via Vulkan - with automatic fallback to CPU when needed.
</p>
</section>
+1 -1
View File
@@ -169,7 +169,7 @@ export default function LanguagesPage() {
</h1>
<p className="mb-8 max-w-4xl text-lg text-muted-foreground">
Dictate in your native language. Vocalinux supports 9+ languages with full offline
processing—no cloud required, no data leaves your machine.
processing - no cloud required, no data leaves your machine.
</p>
</section>
+6 -6
View File
@@ -33,7 +33,7 @@ const benefits = [
{
title: "No Account Required",
description:
"No signup, no login, no password to forget. Download and run—that's it.",
"No signup, no login, no password to forget. Download and run - that's it.",
icon: Lock,
iconColor: "text-violet-500",
iconBg: "bg-violet-100 dark:bg-violet-900/30",
@@ -228,16 +228,16 @@ export default function OfflinePage() {
<ul className="space-y-2 text-sm text-emerald-700 dark:text-emerald-400">
<li className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
<strong>whisper.cpp</strong> — C++ port of OpenAI Whisper, runs locally with GPU
<strong>whisper.cpp</strong> - C++ port of OpenAI Whisper, runs locally with GPU
acceleration
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
<strong>VOSK</strong> — Lightweight offline engine, minimal resources
<strong>VOSK</strong> - Lightweight offline engine, minimal resources
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
<strong>OpenAI Whisper</strong> — PyTorch-based, runs on NVIDIA GPU or CPU
<strong>OpenAI Whisper</strong> - PyTorch-based, runs on NVIDIA GPU or CPU
</li>
</ul>
<p className="mt-4 text-sm text-emerald-700 dark:text-emerald-400">
@@ -251,7 +251,7 @@ export default function OfflinePage() {
Verify It Yourself
</h2>
<p className="mb-3 text-sm text-amber-700 dark:text-amber-400">
Don&apos;t trust—verify. Vocalinux is open source. You can:
Don&apos;t trust - verify. Vocalinux is open source. You can:
</p>
<ul className="space-y-2 text-sm text-amber-700 dark:text-amber-400">
<li className="flex items-start gap-2">
@@ -273,7 +273,7 @@ export default function OfflinePage() {
<li className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
Monitor with <code className="rounded bg-amber-200 px-1 dark:bg-amber-900">tcpdump</code> or{" "}
<code className="rounded bg-amber-200 px-1 dark:bg-amber-900">wireshark</code>—you&apos;ll see silence
<code className="rounded bg-amber-200 px-1 dark:bg-amber-900">wireshark</code> - you&apos;ll see silence
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
+1 -1
View File
@@ -276,7 +276,7 @@ export default function OpenSourcePage() {
</h3>
<p className="mb-4 text-sm text-blue-700 dark:text-blue-400">
We welcome contributions! Whether it&apos;s bug fixes, features, documentation, or
translations—join the community.
translations - join the community.
</p>
<a
href="https://github.com/jatinkrmalik/vocalinux"
+3 -2
View File
@@ -565,10 +565,11 @@ export default function HomePage() {
<FadeInSection>
<div className="text-center mb-12">
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4">
Try Linux Speech-to-Text in Your Browser
Try a Speech-to-Text Preview in Your Browser
</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Experience voice-to-text right here in your browser. Click the microphone and start speaking!
This preview runs when your browser exposes SpeechRecognition. Vocalinux itself
runs offline after install.
</p>
</div>
</FadeInSection>
+4 -4
View File
@@ -144,19 +144,19 @@ export default function PrivacyPage() {
<p>When you dictate, here's exactly what happens:</p>
<ol className="space-y-3 pl-4">
<li className="border-l-2 border-primary pl-4">
<strong className="text-foreground">1. Audio Capture</strong> — Your microphone
<strong className="text-foreground">1. Audio Capture</strong> - Your microphone
records audio temporarily in RAM
</li>
<li className="border-l-2 border-primary pl-4">
<strong className="text-foreground">2. Local Processing</strong> — Audio is
<strong className="text-foreground">2. Local Processing</strong> - Audio is
processed by whisper.cpp, Whisper, or VOSK on your CPU/GPU
</li>
<li className="border-l-2 border-primary pl-4">
<strong className="text-foreground">3. Text Output</strong> — Transcribed text is
<strong className="text-foreground">3. Text Output</strong> - Transcribed text is
injected into your active application
</li>
<li className="border-l-2 border-primary pl-4">
<strong className="text-foreground">4. Immediate Deletion</strong> — Audio data is
<strong className="text-foreground">4. Immediate Deletion</strong> - Audio data is
discarded immediately after processing
</li>
</ol>
+4 -4
View File
@@ -24,7 +24,7 @@ const benefits = [
{
title: "Privacy for Health",
description:
"Your health data stays private. All voice processing happens locally—no cloud uploads, no third parties.",
"Your health data stays private. All voice processing happens locally - no cloud uploads, no third parties.",
icon: Shield,
iconColor: "text-blue-500",
iconBg: "bg-blue-100 dark:bg-blue-900/30",
@@ -222,7 +222,7 @@ export default function RsiPreventionPage() {
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
Take breaks—voice dictation is still work, and vocal rest matters too
Take breaks - voice dictation is still work, and vocal rest matters too
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
@@ -235,7 +235,7 @@ export default function RsiPreventionPage() {
<ul className="space-y-2 text-sm text-muted-foreground">
<li className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
Toggle mode or push-to-talk control—no mouse required
Toggle mode or push-to-talk control - no mouse required
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
@@ -247,7 +247,7 @@ export default function RsiPreventionPage() {
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-500" />
Works in ALL Linux applications—universal compatibility
Works in ALL Linux applications - universal compatibility
</li>
</ul>
</div>
+3 -3
View File
@@ -241,17 +241,17 @@ export default function WaylandPage() {
<ul className="space-y-2 text-sm text-blue-700 dark:text-blue-400">
<li className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
<strong>IBus Engine</strong> — A custom IBus input method that receives text from
<strong>IBus Engine</strong> - A custom IBus input method that receives text from
Vocalinux and types it into the focused application
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
<strong>wtype</strong> — Native Wayland typing tool for compositors that support the
<strong>wtype</strong> - Native Wayland typing tool for compositors that support the
virtual keyboard protocol
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 flex-shrink-0" />
<strong>X11 Fallback</strong> — xdotool for XWayland applications
<strong>X11 Fallback</strong> - xdotool for XWayland applications
</li>
</ul>
<p className="mt-4 text-sm text-blue-700 dark:text-blue-400">
+1 -1
View File
@@ -247,7 +247,7 @@ export default function WritersPage() {
<h2 className="mb-3 text-2xl font-bold">Start Writing with Your Voice</h2>
<p className="mb-6 max-w-3xl text-muted-foreground">
Install Vocalinux and start dictating novels, blog posts, academic notes, and newsroom
drafts directly on Linux—without cloud lock-in.
drafts directly on Linux - without cloud lock-in.
</p>
<Link
href="/install/"
@@ -0,0 +1,52 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { LiveDemo } from "../live-demo";
describe("LiveDemo unsupported browser state", () => {
const originalSpeechRecognition = window.SpeechRecognition;
const originalWebkitSpeechRecognition = window.webkitSpeechRecognition;
beforeEach(() => {
Object.defineProperty(window, "SpeechRecognition", {
configurable: true,
writable: true,
value: undefined,
});
Object.defineProperty(window, "webkitSpeechRecognition", {
configurable: true,
writable: true,
value: undefined,
});
});
afterEach(() => {
Object.defineProperty(window, "SpeechRecognition", {
configurable: true,
writable: true,
value: originalSpeechRecognition,
});
Object.defineProperty(window, "webkitSpeechRecognition", {
configurable: true,
writable: true,
value: originalWebkitSpeechRecognition,
});
});
it("explains missing speech recognition support and links to compatibility references", async () => {
render(<LiveDemo />);
expect(
await screen.findByText("Browser Speech Demo Not Available"),
).toBeInTheDocument();
expect(
screen.getByText(/does not support the SpeechRecognition API/i),
).toBeInTheDocument();
expect(screen.getByText("Chrome")).toBeInTheDocument();
expect(screen.getByText("Edge")).toBeInTheDocument();
expect(screen.getByText("Safari")).toBeInTheDocument();
expect(screen.getByRole("link", { name: "MDN" })).toHaveAttribute(
"href",
"https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition",
);
});
});
+32 -18
View File
@@ -2,7 +2,7 @@
import React, { useState, useEffect, useRef, useCallback } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Mic, MicOff, AlertCircle, Chrome, Globe, Volume2, Sparkles, Keyboard } from "lucide-react";
import { Mic, MicOff, AlertCircle, Volume2, Sparkles, Keyboard } from "lucide-react";
// Define types for Web Speech API
interface SpeechRecognitionEvent {
@@ -306,28 +306,41 @@ export function LiveDemo() {
if (isSupported === false) {
return (
<div className="bg-zinc-900 rounded-2xl p-6 sm:p-8 border border-zinc-800">
<div className="text-center">
<div className="text-center max-w-3xl mx-auto">
<div className="inline-flex items-center justify-center h-16 w-16 rounded-full bg-yellow-500/10 mb-4">
<AlertCircle className="h-8 w-8 text-yellow-500" />
</div>
<h3 className="text-xl font-semibold text-white mb-2">
Browser Demo Not Available
Browser Speech Demo Not Available
</h3>
<p className="text-zinc-400 mb-6 max-w-md mx-auto">
Your browser doesn&apos;t support the Web Speech API. Try opening this page in:
<p className="text-zinc-400 mb-4 max-w-2xl mx-auto leading-relaxed">
Your browser does not support the SpeechRecognition API used by this
live demo. Firefox supports parts of Web Speech, but speech recognition
is still disabled for regular pages.
</p>
<div className="flex justify-center gap-4 mb-6">
<div className="flex items-center gap-2 text-zinc-300">
<Chrome className="h-5 w-5" />
<span>Chrome</span>
</div>
<div className="flex items-center gap-2 text-zinc-300">
<Globe className="h-5 w-5" />
<span>Edge</span>
</div>
<p className="text-sm text-zinc-500 mb-3">
Try a browser listed by MDN with SpeechRecognition support:
</p>
<div className="flex flex-wrap justify-center gap-2 mb-4 text-sm max-w-2xl mx-auto">
{["Chrome", "Edge", "Safari", "Chrome Android", "Safari iOS", "Samsung Internet"].map((browser) => (
<span key={browser} className="px-3 py-1 rounded-full bg-zinc-800 text-zinc-300 border border-zinc-700">
{browser}
</span>
))}
</div>
<p className="text-sm text-zinc-500 mb-6">
Check compatibility on{" "}
<a
href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition"
className="text-primary hover:underline"
target="_blank"
rel="noreferrer"
>
MDN
</a>.
</p>
<p className="text-sm text-zinc-500">
Or better yet — <a href="#install" className="text-primary hover:underline">install Vocalinux</a> for
You can still <a href="#install" className="text-primary hover:underline">install Vocalinux</a> for
the full offline experience with Whisper AI!
</p>
</div>
@@ -360,7 +373,7 @@ export function LiveDemo() {
Live Browser Demo
</span>
<span className="text-xs bg-primary/20 text-primary px-2 py-0.5 rounded-full">
Web Speech API
SpeechRecognition API
</span>
{(() => {
const langInfo = getLanguageInfo(detectedLanguage);
@@ -514,10 +527,11 @@ export function LiveDemo() {
<Sparkles className="h-5 w-5 text-primary flex-shrink-0 mt-0.5" />
<div className="text-sm">
<p className="text-zinc-300">
<strong>This is just a preview!</strong> The browser demo uses Web Speech API (cloud-based).
<strong>This is just a preview!</strong> The browser demo uses browser
SpeechRecognition, which is cloud-based in many browsers.
</p>
<p className="text-zinc-500 mt-1">
Vocalinux runs <strong>100% offline</strong> using Whisper AI — no cloud, no data sharing,
Vocalinux runs <strong>100% offline</strong> using Whisper AI - no cloud, no data sharing,
complete privacy. <a href="#install" className="text-primary hover:underline">Install it</a> to
experience the real deal!
</p>