From dea5ea2bb7c8d0606a8bd935b0c57ef5baa7da3d Mon Sep 17 00:00:00 2001 From: Nexus Dev Date: Wed, 1 Apr 2026 23:59:08 +0000 Subject: [PATCH] feat(25-08): wire VoiceRecordButton into ChatInput and mark INPUT-02/03/04 complete - Add enableVoiceInput prop to ChatInput props interface - Add handleTranscription callback that appends transcription text to textarea state - Render VoiceRecordButton conditionally when enableVoiceInput is true - Pass enableVoiceInput={true} from ChatPanel to ChatInput - Mark INPUT-02, INPUT-03, INPUT-04 as Complete in REQUIREMENTS.md traceability table --- .planning/REQUIREMENTS.md | 12 ++++++------ ui/src/components/ChatInput.tsx | 19 ++++++++++++++++++- ui/src/components/ChatPanel.tsx | 1 + 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 174e5380..a6aa857a 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -30,9 +30,9 @@ ### Input (7) - [x] **INPUT-01** — Multi-line text input with auto-resize: grows with content up to a max height before scrolling -- [ ] **INPUT-02** — File/image upload via drag-and-drop or button with inline preview before sending -- [ ] **INPUT-03** — Paste image from clipboard directly into the chat input -- [ ] **INPUT-04** — Voice input via Whisper (when local AI is enabled): record button with transcription preview before sending +- [x] **INPUT-02** — File/image upload via drag-and-drop or button with inline preview before sending +- [x] **INPUT-03** — Paste image from clipboard directly into the chat input +- [x] **INPUT-04** — Voice input via Whisper (when local AI is enabled): record button with transcription preview before sending - [x] **INPUT-05** — Slash commands: `/brainstorm`, `/ask-pm`, `/ask-engineer`, `/task`, `/search` - [x] **INPUT-06** — `@mention` agents: type `@engineer` to route a message to a specific agent - [x] **INPUT-07** — Keyboard shortcuts: Enter to send, Shift+Enter for newline, Cmd+K for search, Escape to cancel @@ -133,9 +133,9 @@ The following are explicitly deferred: | CHAT-13 | Phase 24 | Complete | | CHAT-14 | Phase 24 | Complete | | INPUT-01 | Phase 21 | Complete | -| INPUT-02 | Phase 25 | Pending | -| INPUT-03 | Phase 25 | Pending | -| INPUT-04 | Phase 25 | Pending | +| INPUT-02 | Phase 25 | Complete | +| INPUT-03 | Phase 25 | Complete | +| INPUT-04 | Phase 25 | Complete | | INPUT-05 | Phase 22 | Complete | | INPUT-06 | Phase 22 | Complete | | INPUT-07 | Phase 21 | Complete | diff --git a/ui/src/components/ChatInput.tsx b/ui/src/components/ChatInput.tsx index c44ec9ab..979bdd90 100644 --- a/ui/src/components/ChatInput.tsx +++ b/ui/src/components/ChatInput.tsx @@ -1,9 +1,10 @@ -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { Send, Loader2, Paperclip, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { ChatSlashCommandPopover } from "./ChatSlashCommandPopover"; import { ChatMentionPopover } from "./ChatMentionPopover"; import { ChatFileDropZone } from "./ChatFileDropZone"; +import { VoiceRecordButton } from "./VoiceRecordButton"; import { cn } from "../lib/utils"; import type { Agent } from "@paperclipai/shared"; import type { PendingFile } from "../hooks/useChatFileUpload"; @@ -20,6 +21,8 @@ interface ChatInputProps { onFilesPicked?: (files: File[]) => void; pendingFiles?: PendingFile[]; onRemoveFile?: (id: string) => void; + // Voice input support + enableVoiceInput?: boolean; } export function ChatInput({ @@ -32,6 +35,7 @@ export function ChatInput({ onFilesPicked, pendingFiles, onRemoveFile, + enableVoiceInput = false, }: ChatInputProps) { const [value, setValue] = useState(""); const textareaRef = useRef(null); @@ -100,6 +104,11 @@ export function ChatInput({ textareaRef.current?.focus(); } + const handleTranscription = useCallback((text: string) => { + setValue((current) => (current ? `${current} ${text}` : text)); + textareaRef.current?.focus(); + }, []); + function handleKeyDown(e: React.KeyboardEvent) { if (e.key === "Escape") { if (slashOpen) { @@ -232,6 +241,14 @@ export function ChatInput({ + {/* Voice input button */} + {enableVoiceInput && ( + + )} +