@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.cjs.js
CHANGED
|
@@ -4422,17 +4422,21 @@ const AssistantMessage = ({ ToolFallback: ToolFallbackCustom }) => {
|
|
|
4422
4422
|
] });
|
|
4423
4423
|
};
|
|
4424
4424
|
const AssistantActionBar$1 = () => {
|
|
4425
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
4425
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4426
4426
|
react.ActionBarPrimitive.Root,
|
|
4427
4427
|
{
|
|
4428
4428
|
hideWhenRunning: true,
|
|
4429
4429
|
autohide: "always",
|
|
4430
4430
|
autohideFloat: "single-branch",
|
|
4431
4431
|
className: "flex gap-1 items-center transition-all relative",
|
|
4432
|
-
children:
|
|
4433
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.MessagePrimitive.If, {
|
|
4434
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.MessagePrimitive.If, {
|
|
4435
|
-
|
|
4432
|
+
children: [
|
|
4433
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.MessagePrimitive.If, { speaking: false, children: /* @__PURE__ */ jsxRuntime.jsx(react.ActionBarPrimitive.Speak, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(TooltipIconButton, { tooltip: "Read aloud", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AudioLinesIcon, {}) }) }) }),
|
|
4434
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.MessagePrimitive.If, { speaking: true, children: /* @__PURE__ */ jsxRuntime.jsx(react.ActionBarPrimitive.StopSpeaking, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(TooltipIconButton, { tooltip: "Stop", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.StopCircleIcon, {}) }) }) }),
|
|
4435
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.ActionBarPrimitive.Copy, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(TooltipIconButton, { tooltip: "Copy", className: "bg-transparent text-icon3 hover:text-icon6", children: [
|
|
4436
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.MessagePrimitive.If, { copied: true, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckIcon, {}) }),
|
|
4437
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.MessagePrimitive.If, { copied: false, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CopyIcon, {}) })
|
|
4438
|
+
] }) })
|
|
4439
|
+
]
|
|
4436
4440
|
}
|
|
4437
4441
|
);
|
|
4438
4442
|
};
|
|
@@ -4703,7 +4707,67 @@ const Txt = ({ as: Root = "p", className, variant = "ui-md", font, ...props }) =
|
|
|
4703
4707
|
return /* @__PURE__ */ jsxRuntime.jsx(Root, { className: clsx(variants[variant], font && fonts[font], className), ...props });
|
|
4704
4708
|
};
|
|
4705
4709
|
|
|
4706
|
-
|
|
4710
|
+
async function recordMicrophoneToFile(onFinish) {
|
|
4711
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
4712
|
+
const mediaRecorder = new MediaRecorder(stream);
|
|
4713
|
+
let chunks = [];
|
|
4714
|
+
mediaRecorder.ondataavailable = (e) => {
|
|
4715
|
+
chunks.push(e.data);
|
|
4716
|
+
};
|
|
4717
|
+
mediaRecorder.onstop = () => {
|
|
4718
|
+
const blob = new Blob(chunks, { type: "audio/webm" });
|
|
4719
|
+
const file = new File([blob], `recording-${Date.now()}.webm`, {
|
|
4720
|
+
type: "audio/webm",
|
|
4721
|
+
lastModified: Date.now()
|
|
4722
|
+
});
|
|
4723
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
4724
|
+
onFinish(file);
|
|
4725
|
+
};
|
|
4726
|
+
return mediaRecorder;
|
|
4727
|
+
}
|
|
4728
|
+
|
|
4729
|
+
const useSpeechRecognition = ({
|
|
4730
|
+
language = "en-US",
|
|
4731
|
+
agentId
|
|
4732
|
+
}) => {
|
|
4733
|
+
const client = useMastraClient();
|
|
4734
|
+
const [agent, setAgent] = React.useState(null);
|
|
4735
|
+
React.useEffect(() => {
|
|
4736
|
+
if (!agentId) return;
|
|
4737
|
+
const agent2 = client.getAgent(agentId);
|
|
4738
|
+
const check = async () => {
|
|
4739
|
+
try {
|
|
4740
|
+
await agent2.voice.getSpeakers();
|
|
4741
|
+
setAgent(agent2);
|
|
4742
|
+
} catch (error) {
|
|
4743
|
+
setAgent(null);
|
|
4744
|
+
}
|
|
4745
|
+
};
|
|
4746
|
+
check();
|
|
4747
|
+
}, [agentId]);
|
|
4748
|
+
const {
|
|
4749
|
+
start: startBrowser,
|
|
4750
|
+
stop: stopBrowser,
|
|
4751
|
+
isListening: isListeningBrowser,
|
|
4752
|
+
transcript: transcriptBrowser
|
|
4753
|
+
} = useBrowserSpeechRecognition({ language });
|
|
4754
|
+
const {
|
|
4755
|
+
start: startMastra,
|
|
4756
|
+
stop: stopMastra,
|
|
4757
|
+
isListening: isListeningMastra,
|
|
4758
|
+
transcript: transcriptMastra
|
|
4759
|
+
} = useMastraSpeechToText({ agent });
|
|
4760
|
+
if (!agent) {
|
|
4761
|
+
return {
|
|
4762
|
+
start: startBrowser,
|
|
4763
|
+
stop: stopBrowser,
|
|
4764
|
+
isListening: isListeningBrowser,
|
|
4765
|
+
transcript: transcriptBrowser
|
|
4766
|
+
};
|
|
4767
|
+
}
|
|
4768
|
+
return { start: startMastra, stop: stopMastra, isListening: isListeningMastra, transcript: transcriptMastra };
|
|
4769
|
+
};
|
|
4770
|
+
const useBrowserSpeechRecognition = ({ language = "en-US" }) => {
|
|
4707
4771
|
const speechRecognitionRef = React.useRef(null);
|
|
4708
4772
|
const [state, setState] = React.useState({
|
|
4709
4773
|
isListening: false,
|
|
@@ -4752,6 +4816,41 @@ const useSpeechRecognition = ({ language = "en-US" } = {}) => {
|
|
|
4752
4816
|
stop
|
|
4753
4817
|
};
|
|
4754
4818
|
};
|
|
4819
|
+
const useMastraSpeechToText = ({ agent }) => {
|
|
4820
|
+
const [transcript, setTranscript] = React.useState("");
|
|
4821
|
+
const [recorder, setRecorder] = React.useState(null);
|
|
4822
|
+
if (!agent) {
|
|
4823
|
+
return {
|
|
4824
|
+
start: () => {
|
|
4825
|
+
},
|
|
4826
|
+
stop: () => {
|
|
4827
|
+
},
|
|
4828
|
+
isListening: false,
|
|
4829
|
+
transcript: ""
|
|
4830
|
+
};
|
|
4831
|
+
}
|
|
4832
|
+
const handleFinish = (file) => {
|
|
4833
|
+
agent.voice.listen(file).then((res) => {
|
|
4834
|
+
setTranscript(res.text);
|
|
4835
|
+
});
|
|
4836
|
+
};
|
|
4837
|
+
const start = () => {
|
|
4838
|
+
recordMicrophoneToFile(handleFinish).then((recorder2) => {
|
|
4839
|
+
setRecorder(recorder2);
|
|
4840
|
+
recorder2.start();
|
|
4841
|
+
});
|
|
4842
|
+
};
|
|
4843
|
+
const stop = () => {
|
|
4844
|
+
recorder?.stop();
|
|
4845
|
+
setRecorder(null);
|
|
4846
|
+
};
|
|
4847
|
+
return {
|
|
4848
|
+
start,
|
|
4849
|
+
stop,
|
|
4850
|
+
isListening: Boolean(recorder),
|
|
4851
|
+
transcript
|
|
4852
|
+
};
|
|
4853
|
+
};
|
|
4755
4854
|
|
|
4756
4855
|
const useHasAttachments = () => {
|
|
4757
4856
|
const composer = react.useComposerRuntime();
|
|
@@ -4870,7 +4969,7 @@ const ComposerAttachments = () => {
|
|
|
4870
4969
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex w-full flex-row items-center gap-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx(react.ComposerPrimitive.Attachments, { components: { Attachment: AttachmentThumbnail } }) });
|
|
4871
4970
|
};
|
|
4872
4971
|
|
|
4873
|
-
const Thread = ({ ToolFallback, agentName, hasMemory, onInputChange }) => {
|
|
4972
|
+
const Thread = ({ ToolFallback, agentName, agentId, hasMemory, onInputChange }) => {
|
|
4874
4973
|
const areaRef = React.useRef(null);
|
|
4875
4974
|
useAutoscroll(areaRef, { enabled: true });
|
|
4876
4975
|
const WrappedAssistantMessage = (props) => {
|
|
@@ -4891,7 +4990,7 @@ const Thread = ({ ToolFallback, agentName, hasMemory, onInputChange }) => {
|
|
|
4891
4990
|
) }),
|
|
4892
4991
|
/* @__PURE__ */ jsxRuntime.jsx(react.ThreadPrimitive.If, { empty: false, children: /* @__PURE__ */ jsxRuntime.jsx("div", {}) })
|
|
4893
4992
|
] }),
|
|
4894
|
-
/* @__PURE__ */ jsxRuntime.jsx(Composer$1, { hasMemory, onInputChange })
|
|
4993
|
+
/* @__PURE__ */ jsxRuntime.jsx(Composer$1, { hasMemory, onInputChange, agentId })
|
|
4895
4994
|
] });
|
|
4896
4995
|
};
|
|
4897
4996
|
const ThreadWrapper$1 = ({ children }) => {
|
|
@@ -4913,7 +5012,7 @@ const ThreadWelcome$1 = ({ agentName }) => {
|
|
|
4913
5012
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-4 font-medium", children: "How can I help you today?" })
|
|
4914
5013
|
] }) });
|
|
4915
5014
|
};
|
|
4916
|
-
const Composer$1 = ({ hasMemory, onInputChange }) => {
|
|
5015
|
+
const Composer$1 = ({ hasMemory, onInputChange, agentId }) => {
|
|
4917
5016
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-4", children: [
|
|
4918
5017
|
/* @__PURE__ */ jsxRuntime.jsxs(react.ComposerPrimitive.Root, { children: [
|
|
4919
5018
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-[568px] w-full mx-auto pb-2", children: /* @__PURE__ */ jsxRuntime.jsx(ComposerAttachments, {}) }),
|
|
@@ -4930,7 +5029,7 @@ const Composer$1 = ({ hasMemory, onInputChange }) => {
|
|
|
4930
5029
|
}
|
|
4931
5030
|
) }),
|
|
4932
5031
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
4933
|
-
/* @__PURE__ */ jsxRuntime.jsx(SpeechInput$1, {}),
|
|
5032
|
+
/* @__PURE__ */ jsxRuntime.jsx(SpeechInput$1, { agentId }),
|
|
4934
5033
|
/* @__PURE__ */ jsxRuntime.jsx(ComposerAction$1, {})
|
|
4935
5034
|
] })
|
|
4936
5035
|
] })
|
|
@@ -4941,9 +5040,9 @@ const Composer$1 = ({ hasMemory, onInputChange }) => {
|
|
|
4941
5040
|
] })
|
|
4942
5041
|
] });
|
|
4943
5042
|
};
|
|
4944
|
-
const SpeechInput$1 = () => {
|
|
5043
|
+
const SpeechInput$1 = ({ agentId }) => {
|
|
4945
5044
|
const composerRuntime = react.useComposerRuntime();
|
|
4946
|
-
const { start, stop, isListening, transcript } = useSpeechRecognition();
|
|
5045
|
+
const { start, stop, isListening, transcript } = useSpeechRecognition({ agentId });
|
|
4947
5046
|
React.useEffect(() => {
|
|
4948
5047
|
if (!transcript) return;
|
|
4949
5048
|
composerRuntime.setText(transcript);
|
|
@@ -5090,6 +5189,74 @@ function useWorkingMemory() {
|
|
|
5090
5189
|
return ctx;
|
|
5091
5190
|
}
|
|
5092
5191
|
|
|
5192
|
+
class VoiceAttachmentAdapter {
|
|
5193
|
+
constructor(agent) {
|
|
5194
|
+
this.agent = agent;
|
|
5195
|
+
}
|
|
5196
|
+
speak(text) {
|
|
5197
|
+
let _cleanup = () => {
|
|
5198
|
+
};
|
|
5199
|
+
const handleEnd = (reason, error) => {
|
|
5200
|
+
if (res.status.type === "ended") return;
|
|
5201
|
+
res.status = { type: "ended", reason, error };
|
|
5202
|
+
_cleanup();
|
|
5203
|
+
};
|
|
5204
|
+
const res = {
|
|
5205
|
+
status: { type: "running" },
|
|
5206
|
+
cancel: () => {
|
|
5207
|
+
handleEnd("cancelled");
|
|
5208
|
+
},
|
|
5209
|
+
subscribe: (callback) => {
|
|
5210
|
+
this.agent.voice.speak(text).then((res2) => {
|
|
5211
|
+
if (res2) {
|
|
5212
|
+
return res2.body;
|
|
5213
|
+
}
|
|
5214
|
+
}).then((readableStream) => {
|
|
5215
|
+
if (readableStream) {
|
|
5216
|
+
return playStreamWithWebAudio(readableStream);
|
|
5217
|
+
}
|
|
5218
|
+
}).then((cleanup) => {
|
|
5219
|
+
if (cleanup) {
|
|
5220
|
+
_cleanup = cleanup;
|
|
5221
|
+
}
|
|
5222
|
+
callback();
|
|
5223
|
+
}).catch((error) => {
|
|
5224
|
+
handleEnd("error", error);
|
|
5225
|
+
});
|
|
5226
|
+
return () => {
|
|
5227
|
+
};
|
|
5228
|
+
}
|
|
5229
|
+
};
|
|
5230
|
+
return res;
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
5233
|
+
async function playStreamWithWebAudio(stream) {
|
|
5234
|
+
const audioContext = new window.AudioContext();
|
|
5235
|
+
const reader = stream.getReader();
|
|
5236
|
+
const chunks = [];
|
|
5237
|
+
while (true) {
|
|
5238
|
+
const { done, value } = await reader.read();
|
|
5239
|
+
if (done) break;
|
|
5240
|
+
chunks.push(value);
|
|
5241
|
+
}
|
|
5242
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
5243
|
+
const combinedBuffer = new Uint8Array(totalLength);
|
|
5244
|
+
let offset = 0;
|
|
5245
|
+
for (const chunk of chunks) {
|
|
5246
|
+
combinedBuffer.set(chunk, offset);
|
|
5247
|
+
offset += chunk.length;
|
|
5248
|
+
}
|
|
5249
|
+
const audioBuffer = await audioContext.decodeAudioData(combinedBuffer.buffer);
|
|
5250
|
+
const source = audioContext.createBufferSource();
|
|
5251
|
+
source.buffer = audioBuffer;
|
|
5252
|
+
source.connect(audioContext.destination);
|
|
5253
|
+
source.start();
|
|
5254
|
+
return () => {
|
|
5255
|
+
source.stop();
|
|
5256
|
+
audioContext.close();
|
|
5257
|
+
};
|
|
5258
|
+
}
|
|
5259
|
+
|
|
5093
5260
|
class PDFAttachmentAdapter {
|
|
5094
5261
|
accept = "application/pdf";
|
|
5095
5262
|
async add({ file }) {
|
|
@@ -5143,6 +5310,37 @@ class PDFAttachmentAdapter {
|
|
|
5143
5310
|
}
|
|
5144
5311
|
}
|
|
5145
5312
|
|
|
5313
|
+
const useAdapters = (agentId) => {
|
|
5314
|
+
const [isReady, setIsReady] = React.useState(false);
|
|
5315
|
+
const [speechAdapter, setSpeechAdapter] = React.useState(void 0);
|
|
5316
|
+
const baseClient = useMastraClient();
|
|
5317
|
+
React.useEffect(() => {
|
|
5318
|
+
const check = async () => {
|
|
5319
|
+
const agent = baseClient.getAgent(agentId);
|
|
5320
|
+
try {
|
|
5321
|
+
await agent.voice.getSpeakers();
|
|
5322
|
+
setSpeechAdapter(new VoiceAttachmentAdapter(agent));
|
|
5323
|
+
setIsReady(true);
|
|
5324
|
+
} catch {
|
|
5325
|
+
setSpeechAdapter(new react.WebSpeechSynthesisAdapter());
|
|
5326
|
+
setIsReady(true);
|
|
5327
|
+
}
|
|
5328
|
+
};
|
|
5329
|
+
check();
|
|
5330
|
+
}, [agentId]);
|
|
5331
|
+
return {
|
|
5332
|
+
isReady,
|
|
5333
|
+
adapters: {
|
|
5334
|
+
attachments: new react.CompositeAttachmentAdapter([
|
|
5335
|
+
new react.SimpleImageAttachmentAdapter(),
|
|
5336
|
+
new react.SimpleTextAttachmentAdapter(),
|
|
5337
|
+
new PDFAttachmentAdapter()
|
|
5338
|
+
]),
|
|
5339
|
+
speech: speechAdapter
|
|
5340
|
+
}
|
|
5341
|
+
};
|
|
5342
|
+
};
|
|
5343
|
+
|
|
5146
5344
|
const convertMessage$2 = (message) => {
|
|
5147
5345
|
return message;
|
|
5148
5346
|
};
|
|
@@ -5567,20 +5765,16 @@ function MastraRuntimeProvider({
|
|
|
5567
5765
|
setIsRunning(false);
|
|
5568
5766
|
}
|
|
5569
5767
|
};
|
|
5768
|
+
const { adapters, isReady } = useAdapters(agentId);
|
|
5570
5769
|
const runtime = react.useExternalStoreRuntime({
|
|
5571
5770
|
isRunning,
|
|
5572
5771
|
messages,
|
|
5573
5772
|
convertMessage: convertMessage$2,
|
|
5574
5773
|
onNew,
|
|
5575
5774
|
onCancel,
|
|
5576
|
-
adapters:
|
|
5577
|
-
attachments: new react.CompositeAttachmentAdapter([
|
|
5578
|
-
new react.SimpleImageAttachmentAdapter(),
|
|
5579
|
-
new react.SimpleTextAttachmentAdapter(),
|
|
5580
|
-
new PDFAttachmentAdapter()
|
|
5581
|
-
])
|
|
5582
|
-
}
|
|
5775
|
+
adapters: isReady ? adapters : void 0
|
|
5583
5776
|
});
|
|
5777
|
+
if (!isReady) return null;
|
|
5584
5778
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.AssistantRuntimeProvider, { runtime, children: [
|
|
5585
5779
|
" ",
|
|
5586
5780
|
children,
|
|
@@ -5681,7 +5875,7 @@ const AgentChat = ({
|
|
|
5681
5875
|
refreshThreadList,
|
|
5682
5876
|
settings,
|
|
5683
5877
|
runtimeContext,
|
|
5684
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(Thread, { agentName: agentName ?? "", hasMemory: memory, onInputChange })
|
|
5878
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Thread, { agentName: agentName ?? "", hasMemory: memory, onInputChange, agentId })
|
|
5685
5879
|
}
|
|
5686
5880
|
);
|
|
5687
5881
|
};
|
|
@@ -10487,7 +10681,7 @@ const Composer = ({ hasMemory }) => {
|
|
|
10487
10681
|
};
|
|
10488
10682
|
const SpeechInput = () => {
|
|
10489
10683
|
const composerRuntime = react.useComposerRuntime();
|
|
10490
|
-
const { start, stop, isListening, transcript } = useSpeechRecognition();
|
|
10684
|
+
const { start, stop, isListening, transcript } = useSpeechRecognition({});
|
|
10491
10685
|
React.useEffect(() => {
|
|
10492
10686
|
if (!transcript) return;
|
|
10493
10687
|
composerRuntime.setText(transcript);
|