@mastra/playground-ui 5.1.14 → 5.1.16-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +215 -21
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +217 -23
- package/dist/index.es.js.map +1 -1
- package/dist/src/components/assistant-ui/attachments/voice-adapter.d.ts +8 -0
- package/dist/src/components/assistant-ui/hooks/use-adapters.d.ts +9 -0
- package/dist/src/components/assistant-ui/thread.d.ts +2 -1
- package/dist/src/{hooks → domains/voice/hooks}/use-speech-recognition.d.ts +4 -2
- package/dist/src/domains/voice/utils/record-mic-to-file.d.ts +1 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/services/mastra-runtime-provider.d.ts +1 -1
- package/package.json +5 -5
package/dist/index.es.js
CHANGED
|
@@ -2,8 +2,8 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import React__default, { createContext, useContext, forwardRef, memo, useState, useEffect, useRef, useCallback, useMemo, Suspense, Fragment as Fragment$1, startTransition } from 'react';
|
|
4
4
|
import { MastraClient } from '@mastra/client-js';
|
|
5
|
-
import { useMessage, MessagePrimitive, ActionBarPrimitive, useAttachment, AttachmentPrimitive, useComposerRuntime, ComposerPrimitive, ThreadPrimitive,
|
|
6
|
-
import { CheckIcon as CheckIcon$1, CopyIcon, Check, Copy, ChevronUpIcon, BrainIcon, X, FileText, CircleXIcon, Mic, PlusIcon, ArrowUp, Search, RefreshCcwIcon, ChevronRight, SortAsc, SortDesc, Circle, ChevronDown, Braces, SaveIcon, RefreshCw, ExternalLink, InfoIcon as InfoIcon$1, GaugeIcon, Plus, LoaderCircle, ChevronDownIcon, ExternalLinkIcon, Loader2, Network, PauseIcon, HourglassIcon, CircleDashed, Footprints, CircleCheck, CircleX, Minus, Maximize, Workflow, AlertCircleIcon, Users, Brain, NetworkIcon, SearchIcon, AlertCircle, CalendarIcon, Brackets, TrashIcon, CirclePause, StopCircle, ChevronUp } from 'lucide-react';
|
|
5
|
+
import { useMessage, MessagePrimitive, ActionBarPrimitive, useAttachment, AttachmentPrimitive, useComposerRuntime, ComposerPrimitive, ThreadPrimitive, CompositeAttachmentAdapter, SimpleImageAttachmentAdapter, SimpleTextAttachmentAdapter, WebSpeechSynthesisAdapter, useExternalStoreRuntime, AssistantRuntimeProvider } from '@assistant-ui/react';
|
|
6
|
+
import { CheckIcon as CheckIcon$1, CopyIcon, Check, Copy, ChevronUpIcon, BrainIcon, AudioLinesIcon, StopCircleIcon, X, FileText, CircleXIcon, Mic, PlusIcon, ArrowUp, Search, RefreshCcwIcon, ChevronRight, SortAsc, SortDesc, Circle, ChevronDown, Braces, SaveIcon, RefreshCw, ExternalLink, InfoIcon as InfoIcon$1, GaugeIcon, Plus, LoaderCircle, ChevronDownIcon, ExternalLinkIcon, Loader2, Network, PauseIcon, HourglassIcon, CircleDashed, Footprints, CircleCheck, CircleX, Minus, Maximize, Workflow, AlertCircleIcon, Users, Brain, NetworkIcon, SearchIcon, AlertCircle, CalendarIcon, Brackets, TrashIcon, CirclePause, StopCircle, ChevronUp } from 'lucide-react';
|
|
7
7
|
import { Slot } from '@radix-ui/react-slot';
|
|
8
8
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
9
9
|
import { TooltipProvider as TooltipProvider$1 } from '@radix-ui/react-tooltip';
|
|
@@ -4389,17 +4389,21 @@ const AssistantMessage = ({ ToolFallback: ToolFallbackCustom }) => {
|
|
|
4389
4389
|
] });
|
|
4390
4390
|
};
|
|
4391
4391
|
const AssistantActionBar$1 = () => {
|
|
4392
|
-
return /* @__PURE__ */
|
|
4392
|
+
return /* @__PURE__ */ jsxs(
|
|
4393
4393
|
ActionBarPrimitive.Root,
|
|
4394
4394
|
{
|
|
4395
4395
|
hideWhenRunning: true,
|
|
4396
4396
|
autohide: "always",
|
|
4397
4397
|
autohideFloat: "single-branch",
|
|
4398
4398
|
className: "flex gap-1 items-center transition-all relative",
|
|
4399
|
-
children:
|
|
4400
|
-
/* @__PURE__ */ jsx(MessagePrimitive.If, {
|
|
4401
|
-
/* @__PURE__ */ jsx(MessagePrimitive.If, {
|
|
4402
|
-
|
|
4399
|
+
children: [
|
|
4400
|
+
/* @__PURE__ */ jsx(MessagePrimitive.If, { speaking: false, children: /* @__PURE__ */ jsx(ActionBarPrimitive.Speak, { asChild: true, children: /* @__PURE__ */ jsx(TooltipIconButton, { tooltip: "Read aloud", children: /* @__PURE__ */ jsx(AudioLinesIcon, {}) }) }) }),
|
|
4401
|
+
/* @__PURE__ */ jsx(MessagePrimitive.If, { speaking: true, children: /* @__PURE__ */ jsx(ActionBarPrimitive.StopSpeaking, { asChild: true, children: /* @__PURE__ */ jsx(TooltipIconButton, { tooltip: "Stop", children: /* @__PURE__ */ jsx(StopCircleIcon, {}) }) }) }),
|
|
4402
|
+
/* @__PURE__ */ jsx(ActionBarPrimitive.Copy, { asChild: true, children: /* @__PURE__ */ jsxs(TooltipIconButton, { tooltip: "Copy", className: "bg-transparent text-icon3 hover:text-icon6", children: [
|
|
4403
|
+
/* @__PURE__ */ jsx(MessagePrimitive.If, { copied: true, children: /* @__PURE__ */ jsx(CheckIcon$1, {}) }),
|
|
4404
|
+
/* @__PURE__ */ jsx(MessagePrimitive.If, { copied: false, children: /* @__PURE__ */ jsx(CopyIcon, {}) })
|
|
4405
|
+
] }) })
|
|
4406
|
+
]
|
|
4403
4407
|
}
|
|
4404
4408
|
);
|
|
4405
4409
|
};
|
|
@@ -4670,7 +4674,67 @@ const Txt = ({ as: Root = "p", className, variant = "ui-md", font, ...props }) =
|
|
|
4670
4674
|
return /* @__PURE__ */ jsx(Root, { className: clsx(variants[variant], font && fonts[font], className), ...props });
|
|
4671
4675
|
};
|
|
4672
4676
|
|
|
4673
|
-
|
|
4677
|
+
async function recordMicrophoneToFile(onFinish) {
|
|
4678
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
4679
|
+
const mediaRecorder = new MediaRecorder(stream);
|
|
4680
|
+
let chunks = [];
|
|
4681
|
+
mediaRecorder.ondataavailable = (e) => {
|
|
4682
|
+
chunks.push(e.data);
|
|
4683
|
+
};
|
|
4684
|
+
mediaRecorder.onstop = () => {
|
|
4685
|
+
const blob = new Blob(chunks, { type: "audio/webm" });
|
|
4686
|
+
const file = new File([blob], `recording-${Date.now()}.webm`, {
|
|
4687
|
+
type: "audio/webm",
|
|
4688
|
+
lastModified: Date.now()
|
|
4689
|
+
});
|
|
4690
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
4691
|
+
onFinish(file);
|
|
4692
|
+
};
|
|
4693
|
+
return mediaRecorder;
|
|
4694
|
+
}
|
|
4695
|
+
|
|
4696
|
+
const useSpeechRecognition = ({
|
|
4697
|
+
language = "en-US",
|
|
4698
|
+
agentId
|
|
4699
|
+
}) => {
|
|
4700
|
+
const client = useMastraClient();
|
|
4701
|
+
const [agent, setAgent] = useState(null);
|
|
4702
|
+
useEffect(() => {
|
|
4703
|
+
if (!agentId) return;
|
|
4704
|
+
const agent2 = client.getAgent(agentId);
|
|
4705
|
+
const check = async () => {
|
|
4706
|
+
try {
|
|
4707
|
+
await agent2.voice.getSpeakers();
|
|
4708
|
+
setAgent(agent2);
|
|
4709
|
+
} catch (error) {
|
|
4710
|
+
setAgent(null);
|
|
4711
|
+
}
|
|
4712
|
+
};
|
|
4713
|
+
check();
|
|
4714
|
+
}, [agentId]);
|
|
4715
|
+
const {
|
|
4716
|
+
start: startBrowser,
|
|
4717
|
+
stop: stopBrowser,
|
|
4718
|
+
isListening: isListeningBrowser,
|
|
4719
|
+
transcript: transcriptBrowser
|
|
4720
|
+
} = useBrowserSpeechRecognition({ language });
|
|
4721
|
+
const {
|
|
4722
|
+
start: startMastra,
|
|
4723
|
+
stop: stopMastra,
|
|
4724
|
+
isListening: isListeningMastra,
|
|
4725
|
+
transcript: transcriptMastra
|
|
4726
|
+
} = useMastraSpeechToText({ agent });
|
|
4727
|
+
if (!agent) {
|
|
4728
|
+
return {
|
|
4729
|
+
start: startBrowser,
|
|
4730
|
+
stop: stopBrowser,
|
|
4731
|
+
isListening: isListeningBrowser,
|
|
4732
|
+
transcript: transcriptBrowser
|
|
4733
|
+
};
|
|
4734
|
+
}
|
|
4735
|
+
return { start: startMastra, stop: stopMastra, isListening: isListeningMastra, transcript: transcriptMastra };
|
|
4736
|
+
};
|
|
4737
|
+
const useBrowserSpeechRecognition = ({ language = "en-US" }) => {
|
|
4674
4738
|
const speechRecognitionRef = useRef(null);
|
|
4675
4739
|
const [state, setState] = useState({
|
|
4676
4740
|
isListening: false,
|
|
@@ -4719,6 +4783,41 @@ const useSpeechRecognition = ({ language = "en-US" } = {}) => {
|
|
|
4719
4783
|
stop
|
|
4720
4784
|
};
|
|
4721
4785
|
};
|
|
4786
|
+
const useMastraSpeechToText = ({ agent }) => {
|
|
4787
|
+
const [transcript, setTranscript] = useState("");
|
|
4788
|
+
const [recorder, setRecorder] = useState(null);
|
|
4789
|
+
if (!agent) {
|
|
4790
|
+
return {
|
|
4791
|
+
start: () => {
|
|
4792
|
+
},
|
|
4793
|
+
stop: () => {
|
|
4794
|
+
},
|
|
4795
|
+
isListening: false,
|
|
4796
|
+
transcript: ""
|
|
4797
|
+
};
|
|
4798
|
+
}
|
|
4799
|
+
const handleFinish = (file) => {
|
|
4800
|
+
agent.voice.listen(file).then((res) => {
|
|
4801
|
+
setTranscript(res.text);
|
|
4802
|
+
});
|
|
4803
|
+
};
|
|
4804
|
+
const start = () => {
|
|
4805
|
+
recordMicrophoneToFile(handleFinish).then((recorder2) => {
|
|
4806
|
+
setRecorder(recorder2);
|
|
4807
|
+
recorder2.start();
|
|
4808
|
+
});
|
|
4809
|
+
};
|
|
4810
|
+
const stop = () => {
|
|
4811
|
+
recorder?.stop();
|
|
4812
|
+
setRecorder(null);
|
|
4813
|
+
};
|
|
4814
|
+
return {
|
|
4815
|
+
start,
|
|
4816
|
+
stop,
|
|
4817
|
+
isListening: Boolean(recorder),
|
|
4818
|
+
transcript
|
|
4819
|
+
};
|
|
4820
|
+
};
|
|
4722
4821
|
|
|
4723
4822
|
const useHasAttachments = () => {
|
|
4724
4823
|
const composer = useComposerRuntime();
|
|
@@ -4837,7 +4936,7 @@ const ComposerAttachments = () => {
|
|
|
4837
4936
|
return /* @__PURE__ */ jsx("div", { className: "flex w-full flex-row items-center gap-4 pb-2", children: /* @__PURE__ */ jsx(ComposerPrimitive.Attachments, { components: { Attachment: AttachmentThumbnail } }) });
|
|
4838
4937
|
};
|
|
4839
4938
|
|
|
4840
|
-
const Thread = ({ ToolFallback, agentName, hasMemory, onInputChange }) => {
|
|
4939
|
+
const Thread = ({ ToolFallback, agentName, agentId, hasMemory, onInputChange }) => {
|
|
4841
4940
|
const areaRef = useRef(null);
|
|
4842
4941
|
useAutoscroll(areaRef, { enabled: true });
|
|
4843
4942
|
const WrappedAssistantMessage = (props) => {
|
|
@@ -4858,7 +4957,7 @@ const Thread = ({ ToolFallback, agentName, hasMemory, onInputChange }) => {
|
|
|
4858
4957
|
) }),
|
|
4859
4958
|
/* @__PURE__ */ jsx(ThreadPrimitive.If, { empty: false, children: /* @__PURE__ */ jsx("div", {}) })
|
|
4860
4959
|
] }),
|
|
4861
|
-
/* @__PURE__ */ jsx(Composer$1, { hasMemory, onInputChange })
|
|
4960
|
+
/* @__PURE__ */ jsx(Composer$1, { hasMemory, onInputChange, agentId })
|
|
4862
4961
|
] });
|
|
4863
4962
|
};
|
|
4864
4963
|
const ThreadWrapper$1 = ({ children }) => {
|
|
@@ -4880,7 +4979,7 @@ const ThreadWelcome$1 = ({ agentName }) => {
|
|
|
4880
4979
|
/* @__PURE__ */ jsx("p", { className: "mt-4 font-medium", children: "How can I help you today?" })
|
|
4881
4980
|
] }) });
|
|
4882
4981
|
};
|
|
4883
|
-
const Composer$1 = ({ hasMemory, onInputChange }) => {
|
|
4982
|
+
const Composer$1 = ({ hasMemory, onInputChange, agentId }) => {
|
|
4884
4983
|
return /* @__PURE__ */ jsxs("div", { className: "mx-4", children: [
|
|
4885
4984
|
/* @__PURE__ */ jsxs(ComposerPrimitive.Root, { children: [
|
|
4886
4985
|
/* @__PURE__ */ jsx("div", { className: "max-w-[568px] w-full mx-auto pb-2", children: /* @__PURE__ */ jsx(ComposerAttachments, {}) }),
|
|
@@ -4897,7 +4996,7 @@ const Composer$1 = ({ hasMemory, onInputChange }) => {
|
|
|
4897
4996
|
}
|
|
4898
4997
|
) }),
|
|
4899
4998
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
4900
|
-
/* @__PURE__ */ jsx(SpeechInput$1, {}),
|
|
4999
|
+
/* @__PURE__ */ jsx(SpeechInput$1, { agentId }),
|
|
4901
5000
|
/* @__PURE__ */ jsx(ComposerAction$1, {})
|
|
4902
5001
|
] })
|
|
4903
5002
|
] })
|
|
@@ -4908,9 +5007,9 @@ const Composer$1 = ({ hasMemory, onInputChange }) => {
|
|
|
4908
5007
|
] })
|
|
4909
5008
|
] });
|
|
4910
5009
|
};
|
|
4911
|
-
const SpeechInput$1 = () => {
|
|
5010
|
+
const SpeechInput$1 = ({ agentId }) => {
|
|
4912
5011
|
const composerRuntime = useComposerRuntime();
|
|
4913
|
-
const { start, stop, isListening, transcript } = useSpeechRecognition();
|
|
5012
|
+
const { start, stop, isListening, transcript } = useSpeechRecognition({ agentId });
|
|
4914
5013
|
useEffect(() => {
|
|
4915
5014
|
if (!transcript) return;
|
|
4916
5015
|
composerRuntime.setText(transcript);
|
|
@@ -5057,6 +5156,74 @@ function useWorkingMemory() {
|
|
|
5057
5156
|
return ctx;
|
|
5058
5157
|
}
|
|
5059
5158
|
|
|
5159
|
+
class VoiceAttachmentAdapter {
|
|
5160
|
+
constructor(agent) {
|
|
5161
|
+
this.agent = agent;
|
|
5162
|
+
}
|
|
5163
|
+
speak(text) {
|
|
5164
|
+
let _cleanup = () => {
|
|
5165
|
+
};
|
|
5166
|
+
const handleEnd = (reason, error) => {
|
|
5167
|
+
if (res.status.type === "ended") return;
|
|
5168
|
+
res.status = { type: "ended", reason, error };
|
|
5169
|
+
_cleanup();
|
|
5170
|
+
};
|
|
5171
|
+
const res = {
|
|
5172
|
+
status: { type: "running" },
|
|
5173
|
+
cancel: () => {
|
|
5174
|
+
handleEnd("cancelled");
|
|
5175
|
+
},
|
|
5176
|
+
subscribe: (callback) => {
|
|
5177
|
+
this.agent.voice.speak(text).then((res2) => {
|
|
5178
|
+
if (res2) {
|
|
5179
|
+
return res2.body;
|
|
5180
|
+
}
|
|
5181
|
+
}).then((readableStream) => {
|
|
5182
|
+
if (readableStream) {
|
|
5183
|
+
return playStreamWithWebAudio(readableStream);
|
|
5184
|
+
}
|
|
5185
|
+
}).then((cleanup) => {
|
|
5186
|
+
if (cleanup) {
|
|
5187
|
+
_cleanup = cleanup;
|
|
5188
|
+
}
|
|
5189
|
+
callback();
|
|
5190
|
+
}).catch((error) => {
|
|
5191
|
+
handleEnd("error", error);
|
|
5192
|
+
});
|
|
5193
|
+
return () => {
|
|
5194
|
+
};
|
|
5195
|
+
}
|
|
5196
|
+
};
|
|
5197
|
+
return res;
|
|
5198
|
+
}
|
|
5199
|
+
}
|
|
5200
|
+
async function playStreamWithWebAudio(stream) {
|
|
5201
|
+
const audioContext = new window.AudioContext();
|
|
5202
|
+
const reader = stream.getReader();
|
|
5203
|
+
const chunks = [];
|
|
5204
|
+
while (true) {
|
|
5205
|
+
const { done, value } = await reader.read();
|
|
5206
|
+
if (done) break;
|
|
5207
|
+
chunks.push(value);
|
|
5208
|
+
}
|
|
5209
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
5210
|
+
const combinedBuffer = new Uint8Array(totalLength);
|
|
5211
|
+
let offset = 0;
|
|
5212
|
+
for (const chunk of chunks) {
|
|
5213
|
+
combinedBuffer.set(chunk, offset);
|
|
5214
|
+
offset += chunk.length;
|
|
5215
|
+
}
|
|
5216
|
+
const audioBuffer = await audioContext.decodeAudioData(combinedBuffer.buffer);
|
|
5217
|
+
const source = audioContext.createBufferSource();
|
|
5218
|
+
source.buffer = audioBuffer;
|
|
5219
|
+
source.connect(audioContext.destination);
|
|
5220
|
+
source.start();
|
|
5221
|
+
return () => {
|
|
5222
|
+
source.stop();
|
|
5223
|
+
audioContext.close();
|
|
5224
|
+
};
|
|
5225
|
+
}
|
|
5226
|
+
|
|
5060
5227
|
class PDFAttachmentAdapter {
|
|
5061
5228
|
accept = "application/pdf";
|
|
5062
5229
|
async add({ file }) {
|
|
@@ -5110,6 +5277,37 @@ class PDFAttachmentAdapter {
|
|
|
5110
5277
|
}
|
|
5111
5278
|
}
|
|
5112
5279
|
|
|
5280
|
+
const useAdapters = (agentId) => {
|
|
5281
|
+
const [isReady, setIsReady] = useState(false);
|
|
5282
|
+
const [speechAdapter, setSpeechAdapter] = useState(void 0);
|
|
5283
|
+
const baseClient = useMastraClient();
|
|
5284
|
+
useEffect(() => {
|
|
5285
|
+
const check = async () => {
|
|
5286
|
+
const agent = baseClient.getAgent(agentId);
|
|
5287
|
+
try {
|
|
5288
|
+
await agent.voice.getSpeakers();
|
|
5289
|
+
setSpeechAdapter(new VoiceAttachmentAdapter(agent));
|
|
5290
|
+
setIsReady(true);
|
|
5291
|
+
} catch {
|
|
5292
|
+
setSpeechAdapter(new WebSpeechSynthesisAdapter());
|
|
5293
|
+
setIsReady(true);
|
|
5294
|
+
}
|
|
5295
|
+
};
|
|
5296
|
+
check();
|
|
5297
|
+
}, [agentId]);
|
|
5298
|
+
return {
|
|
5299
|
+
isReady,
|
|
5300
|
+
adapters: {
|
|
5301
|
+
attachments: new CompositeAttachmentAdapter([
|
|
5302
|
+
new SimpleImageAttachmentAdapter(),
|
|
5303
|
+
new SimpleTextAttachmentAdapter(),
|
|
5304
|
+
new PDFAttachmentAdapter()
|
|
5305
|
+
]),
|
|
5306
|
+
speech: speechAdapter
|
|
5307
|
+
}
|
|
5308
|
+
};
|
|
5309
|
+
};
|
|
5310
|
+
|
|
5113
5311
|
const convertMessage$2 = (message) => {
|
|
5114
5312
|
return message;
|
|
5115
5313
|
};
|
|
@@ -5534,20 +5732,16 @@ function MastraRuntimeProvider({
|
|
|
5534
5732
|
setIsRunning(false);
|
|
5535
5733
|
}
|
|
5536
5734
|
};
|
|
5735
|
+
const { adapters, isReady } = useAdapters(agentId);
|
|
5537
5736
|
const runtime = useExternalStoreRuntime({
|
|
5538
5737
|
isRunning,
|
|
5539
5738
|
messages,
|
|
5540
5739
|
convertMessage: convertMessage$2,
|
|
5541
5740
|
onNew,
|
|
5542
5741
|
onCancel,
|
|
5543
|
-
adapters:
|
|
5544
|
-
attachments: new CompositeAttachmentAdapter([
|
|
5545
|
-
new SimpleImageAttachmentAdapter(),
|
|
5546
|
-
new SimpleTextAttachmentAdapter(),
|
|
5547
|
-
new PDFAttachmentAdapter()
|
|
5548
|
-
])
|
|
5549
|
-
}
|
|
5742
|
+
adapters: isReady ? adapters : void 0
|
|
5550
5743
|
});
|
|
5744
|
+
if (!isReady) return null;
|
|
5551
5745
|
return /* @__PURE__ */ jsxs(AssistantRuntimeProvider, { runtime, children: [
|
|
5552
5746
|
" ",
|
|
5553
5747
|
children,
|
|
@@ -5648,7 +5842,7 @@ const AgentChat = ({
|
|
|
5648
5842
|
refreshThreadList,
|
|
5649
5843
|
settings,
|
|
5650
5844
|
runtimeContext,
|
|
5651
|
-
children: /* @__PURE__ */ jsx(Thread, { agentName: agentName ?? "", hasMemory: memory, onInputChange })
|
|
5845
|
+
children: /* @__PURE__ */ jsx(Thread, { agentName: agentName ?? "", hasMemory: memory, onInputChange, agentId })
|
|
5652
5846
|
}
|
|
5653
5847
|
);
|
|
5654
5848
|
};
|
|
@@ -10454,7 +10648,7 @@ const Composer = ({ hasMemory }) => {
|
|
|
10454
10648
|
};
|
|
10455
10649
|
const SpeechInput = () => {
|
|
10456
10650
|
const composerRuntime = useComposerRuntime();
|
|
10457
|
-
const { start, stop, isListening, transcript } = useSpeechRecognition();
|
|
10651
|
+
const { start, stop, isListening, transcript } = useSpeechRecognition({});
|
|
10458
10652
|
useEffect(() => {
|
|
10459
10653
|
if (!transcript) return;
|
|
10460
10654
|
composerRuntime.setText(transcript);
|