From dbcb83d035bd15431c1304f10c83ac2daef27970 Mon Sep 17 00:00:00 2001 From: kartikmanimuthu Date: Sat, 25 Jul 2026 10:14:33 +0530 Subject: [PATCH] feat(ui): voice dictation on AI prompt inputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a mic button to every surface where a natural-language prompt is typed, backed by the browser's Web Speech API — no new dependencies, no API route, no AWS cost. New modules: - hooks/use-dictation.ts — binds speech recognition to an already-controlled text field. Snapshots the value at session start, then rewrites `snapshot + finalSoFar + interim` on each result event, so words appear live while the interim tail is swapped for the final transcript instead of duplicating after it. Continuous + interim recognition, mic released on unmount, session stopped when the field disables, and permission/capture errors surfaced as toasts while no-speech/aborted stay silent. - components/voice/mic-button.tsx — ghost icon button that pulses and reports aria-pressed while listening; renders nothing on browsers without the API rather than showing a dead control. - components/voice/dictation-textarea.tsx — drop-in Textarea wrapper that tucks the mic into the bottom-right corner. Wired into nine surfaces: AI Ops composer, Inventory Ask AI, KB chat, Deep Agent chat, Agent Ops new-run dialog, scheduled-task objective, both HIL respond textareas, and the chat clarification card. Dictation appends to existing text everywhere rather than replacing it. Also fixes Deep Agent chat auto-resizing its textarea inside the onChange handler off e.target, which meant programmatic writes never resized it. Moved to an effect keyed on the value, which also fixes the box not shrinking when cleared on send. The engine is isolated inside useDictation, so a server-side model (Parakeet, Transcribe) can be swapped in later without touching any call site. Tests: 21 new (append-not-replace, interim to final swap, cumulative multi-segment accumulation, maxLength clamping, unsupported browser, disabled-mid-session, silence timeout, permission errors, unmount cleanup). Co-Authored-By: Claude Opus 5 (1M context) --- .../web-ui/app/app/agent-ops/[runId]/page.tsx | 6 +- .../app/agent-ops/[runId]/respond/page.tsx | 7 +- .../components/agent-ops/new-run-dialog.tsx | 6 +- .../agent-ops/scheduled-task-dialog.tsx | 6 +- .../agent/chat/clarification-card.tsx | 2 + .../components/agent/workspace/composer.tsx | 8 + .../components/deep-agent/deep-agent-chat.tsx | 14 +- .../components/inventory/ask-ai-dialog.tsx | 7 + .../components/knowledge-base/kb-chat.tsx | 2 + .../voice/__tests__/mic-button.test.tsx | 95 +++++++ .../components/voice/dictation-textarea.tsx | 50 ++++ apps/web-ui/components/voice/mic-button.tsx | 72 +++++ .../hooks/__tests__/use-dictation.test.tsx | 214 +++++++++++++++ apps/web-ui/hooks/use-dictation.ts | 246 ++++++++++++++++++ 14 files changed, 720 insertions(+), 15 deletions(-) create mode 100644 apps/web-ui/components/voice/__tests__/mic-button.test.tsx create mode 100644 apps/web-ui/components/voice/dictation-textarea.tsx create mode 100644 apps/web-ui/components/voice/mic-button.tsx create mode 100644 apps/web-ui/hooks/__tests__/use-dictation.test.tsx create mode 100644 apps/web-ui/hooks/use-dictation.ts diff --git a/apps/web-ui/app/app/agent-ops/[runId]/page.tsx b/apps/web-ui/app/app/agent-ops/[runId]/page.tsx index 859bd156..2a96f8c4 100644 --- a/apps/web-ui/app/app/agent-ops/[runId]/page.tsx +++ b/apps/web-ui/app/app/agent-ops/[runId]/page.tsx @@ -5,7 +5,7 @@ import { useParams, useRouter } from "next/navigation" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { Textarea } from "@/components/ui/textarea" +import { DictationTextarea } from "@/components/voice/dictation-textarea" import { MarkdownContent } from "@/components/ui/markdown-content" import { Spinner } from "@/components/ui/spinner" import { @@ -187,10 +187,10 @@ export default function RunDetailPage() {
-