add dictation

This commit is contained in:
Nate Kelley 2025-09-29 15:12:52 -06:00
parent 8e1b106d9c
commit d5db629b22
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
5 changed files with 78 additions and 4 deletions

View File

@ -172,6 +172,7 @@
"react-markdown": "^10.1.0",
"react-mentions": "^4.4.10",
"react-player": "^3.3.3",
"react-speech-recognition": "^4.0.1",
"react-textarea-autosize": "^8.5.9",
"remark-gfm": "^4.0.1",
"scheduler": "^0.26.0",
@ -210,6 +211,7 @@
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/react-mentions": "^4.4.1",
"@types/react-speech-recognition": "^3.9.6",
"@vitejs/plugin-react": "^5.0.3",
"@vitest/browser": "3.2.4",
"@vitest/coverage-v8": "3.2.4",

View File

@ -80,6 +80,7 @@ export const BusterChatInputBase: React.FC<BusterChatInput> = React.memo(
disabled={disabled}
mode="auto"
onModeChange={() => {}}
onDictate={() => {}}
/>
</MentionInputSuggestions>
);

View File

@ -1,7 +1,10 @@
import React from 'react';
import type React from 'react';
import { useEffect } from 'react';
import SpeechRecognition, { useSpeechRecognition } from 'react-speech-recognition';
import { Button } from '@/components/ui/buttons';
import { ArrowUp, Magnifier, Sparkle2 } from '@/components/ui/icons';
import Atom from '@/components/ui/icons/NucleoIconOutlined/atom';
import Microphone from '@/components/ui/icons/NucleoIconOutlined/microphone';
import { Popover } from '@/components/ui/popover';
import { AppSegmented, type AppSegmentedProps } from '@/components/ui/segmented';
import { Text } from '@/components/ui/typography';
@ -16,6 +19,7 @@ type BusterChatInputButtons = {
disabled: boolean;
mode: BusterChatInputMode;
onModeChange: (mode: BusterChatInputMode) => void;
onDictate: (transcript: string) => void;
};
export const BusterChatInputButtons = ({
@ -25,15 +29,48 @@ export const BusterChatInputButtons = ({
disabled,
mode,
onModeChange,
onDictate,
}: BusterChatInputButtons) => {
const { transcript, listening, resetTranscript, browserSupportsSpeechRecognition } =
useSpeechRecognition();
const startListening = () => {
SpeechRecognition.startListening({ continuous: true });
};
const stopListening = () => {
SpeechRecognition.stopListening();
};
useEffect(() => {
if (listening && transcript) {
onDictate(transcript);
}
}, [listening, transcript, onDictate]);
return (
<div className="flex justify-between items-center gap-2">
<AppSegmented value={mode} options={modesOptions} onChange={(v) => onModeChange(v.value)} />
<div>
<div className="flex items-center gap-2">
{browserSupportsSpeechRecognition && (
<Button
rounding={'large'}
variant={'ghost'}
prefix={<Microphone />}
onClick={listening ? stopListening : startListening}
loading={submitting}
disabled={disabled}
className={cn(
'origin-center transform-gpu transition-all duration-300 ease-out will-change-transform text-text-secondary',
!disabled && 'hover:scale-110 active:scale-95',
listening && 'bg-item-select text-foreground'
)}
/>
)}
<Button
rounding={'large'}
variant="black"
variant={'default'}
prefix={<ArrowUp />}
onClick={submitting ? onStop : onSubmit}
loading={submitting}

View File

@ -48,7 +48,6 @@ export const MentionInputSuggestions = ({
const commandListNavigatedRef = useRef(false);
const commandRef = useRef<HTMLDivElement>(null);
const mentionsInputRef = useRef<MentionInputRef>(null);
console.log(hasResults);
const showSuggestionList = !hasClickedSelect && suggestionItems.length > 0;
@ -117,6 +116,8 @@ export const MentionInputSuggestions = ({
}
}, [showSuggestionList]);
useImperativeHandle(ref, () => ({}), []);
return (
<Command
ref={commandRef}

View File

@ -838,6 +838,9 @@ importers:
react-player:
specifier: ^3.3.3
version: 3.3.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-speech-recognition:
specifier: ^4.0.1
version: 4.0.1(react@19.1.1)
react-textarea-autosize:
specifier: ^8.5.9
version: 8.5.9(@types/react@19.1.13)(react@19.1.1)
@ -947,6 +950,9 @@ importers:
'@types/react-mentions':
specifier: ^4.4.1
version: 4.4.1
'@types/react-speech-recognition':
specifier: ^3.9.6
version: 3.9.6
'@vitejs/plugin-react':
specifier: ^5.0.3
version: 5.0.3(vite@7.1.4(@types/node@24.3.1)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.93.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))
@ -6268,6 +6274,9 @@ packages:
'@types/doctrine@0.0.9':
resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==}
'@types/dom-speech-recognition@0.0.6':
resolution: {integrity: sha512-o7pAVq9UQPJL5RDjO1f/fcpfFHdgiMnR4PoIU2N/ZQrYOS3C5rzdOJMsrpqeBCbii2EE9mERXgqspQqPDdPahw==}
'@types/eslint-scope@3.7.7':
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
@ -6381,6 +6390,9 @@ packages:
'@types/react-mentions@4.4.1':
resolution: {integrity: sha512-65QdcZYkGe2I4GnOLY2OhlXCGz/Csd8NhytwE5r59CoFeYafMltAE/WqFB/Y6SoPU8LvF7EyUrq6Rxrf0Kzxkg==}
'@types/react-speech-recognition@3.9.6':
resolution: {integrity: sha512-cdzwXIZXWyp8zfM2XI7APDW1rZf4Nz73T4SIS2y+cC7zHnZluCdumYKH6HacxgxJH+zemAq2oXbHWXcyW0eT3A==}
'@types/react@19.1.13':
resolution: {integrity: sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==}
@ -9513,6 +9525,9 @@ packages:
lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
lodash.defaults@4.2.0:
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
@ -11090,6 +11105,11 @@ packages:
'@types/react':
optional: true
react-speech-recognition@4.0.1:
resolution: {integrity: sha512-0fIqzLtfY8vuYA6AmJVK7qiabZx0oFKOO+rbiBgFI3COWVGREy0A+gdU16hWXmFebeyrI8JsOLYsWk6WaHUXRw==}
peerDependencies:
react: '>=16.8.0'
react-style-singleton@2.2.3:
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
engines: {node: '>=10'}
@ -19220,6 +19240,8 @@ snapshots:
'@types/doctrine@0.0.9': {}
'@types/dom-speech-recognition@0.0.6': {}
'@types/eslint-scope@3.7.7':
dependencies:
'@types/eslint': 9.6.1
@ -19354,6 +19376,10 @@ snapshots:
dependencies:
'@types/react': 19.1.13
'@types/react-speech-recognition@3.9.6':
dependencies:
'@types/dom-speech-recognition': 0.0.6
'@types/react@19.1.13':
dependencies:
csstype: 3.1.3
@ -22862,6 +22888,8 @@ snapshots:
lodash-es@4.17.21: {}
lodash.debounce@4.0.8: {}
lodash.defaults@4.2.0: {}
lodash.flattendeep@4.4.0: {}
@ -25044,6 +25072,11 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.13
react-speech-recognition@4.0.1(react@19.1.1):
dependencies:
lodash.debounce: 4.0.8
react: 19.1.1
react-style-singleton@2.2.3(@types/react@19.1.13)(react@19.1.1):
dependencies:
get-nonce: 1.0.1