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
This commit is contained in:
Nexus Dev 2026-04-01 23:59:08 +00:00
parent 64a90c284e
commit dea5ea2bb7
3 changed files with 25 additions and 7 deletions

View file

@ -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 |

View file

@ -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<HTMLTextAreaElement>(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<HTMLTextAreaElement>) {
if (e.key === "Escape") {
if (slashOpen) {
@ -232,6 +241,14 @@ export function ChatInput({
</Button>
</label>
{/* Voice input button */}
{enableVoiceInput && (
<VoiceRecordButton
onTranscription={handleTranscription}
disabled={disabled}
/>
)}
<Button
type="submit"
variant="ghost"

View file

@ -388,6 +388,7 @@ export function ChatPanel() {
pendingFiles={pendingFiles}
onRemoveFile={removeFile}
onFilesPicked={(files) => files.forEach(addFile)}
enableVoiceInput={true}
/>
</div>
</div>