@apteva/apteva-kit 0.1.129 → 0.1.131

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.mjs CHANGED
@@ -4,7 +4,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
5
 
6
6
  // src/components/Chat/Chat.tsx
7
- import { useState as useState9, useEffect as useEffect9, useRef as useRef9, useMemo as useMemo2, useCallback as useCallback4, forwardRef, useImperativeHandle } from "react";
7
+ import { useState as useState10, useEffect as useEffect10, useRef as useRef10, useMemo as useMemo2, useCallback as useCallback5, forwardRef, useImperativeHandle } from "react";
8
8
 
9
9
  // src/components/Chat/MessageList.tsx
10
10
  import { useEffect as useEffect7, useRef as useRef6 } from "react";
@@ -2805,17 +2805,36 @@ function ToolCall({ name, status, isReceiving = false, inputLength = 0, streamOu
2805
2805
  // src/components/Chat/ToolCallGroup.tsx
2806
2806
  import { useState as useState5 } from "react";
2807
2807
  import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
2808
+ function formatResultPreview(result) {
2809
+ if (result == null) return "";
2810
+ const str = typeof result === "string" ? result : JSON.stringify(result);
2811
+ const oneLine = str.replace(/\s+/g, " ").trim();
2812
+ return oneLine.length > 120 ? oneLine.slice(0, 117) + "..." : oneLine;
2813
+ }
2808
2814
  function ToolCallGroup({ tools }) {
2809
2815
  const completed = tools.filter((t) => t.status === "completed").length;
2810
2816
  const errored = tools.filter((t) => t.status === "error").length;
2811
- const running = tools.filter((t) => t.status === "running").length;
2812
- const preparing = tools.filter((t) => t.status === "preparing").length;
2813
2817
  const total = tools.length;
2814
2818
  const allDone = completed + errored === total;
2815
2819
  const [expanded, setExpanded] = useState5(false);
2816
- const isExpanded = allDone ? expanded : true;
2817
- const activeStreamTool = tools.find((t) => t.status === "running" && t.streamOutput);
2818
- const statusText = allDone ? `Used ${total} tools` : `Using ${total} tools${completed > 0 ? ` \xB7 ${completed}/${total} done` : ""}`;
2820
+ const isExpanded = expanded;
2821
+ const activeTool = tools.find((t) => t.status === "running") || tools.find((t) => t.status === "preparing");
2822
+ let statusText;
2823
+ if (allDone) {
2824
+ if (errored > 0) {
2825
+ statusText = `Used ${total} tools \xB7 ${errored} failed`;
2826
+ } else {
2827
+ statusText = `Used ${total} tools`;
2828
+ }
2829
+ } else {
2830
+ const parts = [`Using ${total} tools`];
2831
+ if (completed > 0) parts.push(`${completed} done`);
2832
+ if (activeTool) {
2833
+ const activeLabel = activeTool.streamOutput || (activeTool.status === "preparing" ? "preparing" : "running");
2834
+ parts.push(`${activeTool.name} ${activeLabel === "preparing" || activeLabel === "running" ? activeLabel + "..." : "\xB7 " + activeLabel}`);
2835
+ }
2836
+ statusText = parts.join(" \xB7 ");
2837
+ }
2819
2838
  const cardClass = allDone ? errored > 0 ? "apteva-tool-group apteva-tool-group-error" : "apteva-tool-group apteva-tool-group-completed" : "apteva-tool-group apteva-tool-group-running";
2820
2839
  return /* @__PURE__ */ jsxs13("div", { className: cardClass, children: [
2821
2840
  /* @__PURE__ */ jsxs13(
@@ -2846,18 +2865,20 @@ function ToolCallGroup({ tools }) {
2846
2865
  ]
2847
2866
  }
2848
2867
  ),
2849
- !isExpanded && activeStreamTool && /* @__PURE__ */ jsxs13("div", { className: "apteva-tool-group-stream", children: [
2850
- /* @__PURE__ */ jsxs13("span", { className: "apteva-tool-group-stream-name", children: [
2851
- activeStreamTool.name,
2852
- ":"
2853
- ] }),
2854
- /* @__PURE__ */ jsx18("span", { className: "apteva-tool-group-stream-text", children: activeStreamTool.streamOutput })
2855
- ] }),
2856
- isExpanded && /* @__PURE__ */ jsx18("div", { className: "apteva-tool-group-list", children: tools.map((tool) => /* @__PURE__ */ jsxs13("div", { className: "apteva-tool-group-item", children: [
2857
- tool.status === "completed" ? /* @__PURE__ */ jsx18("svg", { className: "apteva-tool-group-item-icon apteva-tool-group-item-done", width: "12", height: "12", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx18("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 3, d: "M5 13l4 4L19 7" }) }) : tool.status === "error" ? /* @__PURE__ */ jsx18("svg", { className: "apteva-tool-group-item-icon apteva-tool-group-item-error", width: "12", height: "12", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx18("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 3, d: "M6 18L18 6M6 6l12 12" }) }) : /* @__PURE__ */ jsx18("div", { className: "apteva-tool-group-item-icon apteva-tool-group-item-spinner" }),
2858
- /* @__PURE__ */ jsx18("span", { className: "apteva-tool-group-item-name", children: tool.name }),
2859
- tool.streamOutput && tool.status === "running" && /* @__PURE__ */ jsx18("span", { className: "apteva-tool-group-item-stream", children: tool.streamOutput })
2860
- ] }, tool.id)) })
2868
+ isExpanded && /* @__PURE__ */ jsx18("div", { className: "apteva-tool-group-list", children: tools.map((tool) => {
2869
+ const resultPreview = tool.status === "completed" && tool.result ? formatResultPreview(tool.result) : "";
2870
+ const hasDetail = tool.status === "running" && tool.streamOutput || resultPreview;
2871
+ return /* @__PURE__ */ jsxs13("div", { className: `apteva-tool-group-item ${hasDetail ? "apteva-tool-group-item-has-detail" : ""}`, children: [
2872
+ /* @__PURE__ */ jsxs13("div", { className: "apteva-tool-group-item-row", children: [
2873
+ tool.status === "completed" ? /* @__PURE__ */ jsx18("svg", { className: "apteva-tool-group-item-icon apteva-tool-group-item-done", width: "12", height: "12", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx18("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 3, d: "M5 13l4 4L19 7" }) }) : tool.status === "error" ? /* @__PURE__ */ jsx18("svg", { className: "apteva-tool-group-item-icon apteva-tool-group-item-error", width: "12", height: "12", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx18("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 3, d: "M6 18L18 6M6 6l12 12" }) }) : /* @__PURE__ */ jsx18("div", { className: "apteva-tool-group-item-icon apteva-tool-group-item-spinner" }),
2874
+ /* @__PURE__ */ jsx18("span", { className: "apteva-tool-group-item-name", children: tool.name }),
2875
+ tool.status === "running" && !tool.streamOutput && /* @__PURE__ */ jsx18("span", { className: "apteva-tool-group-item-running-label", children: "running..." }),
2876
+ tool.status === "preparing" && /* @__PURE__ */ jsx18("span", { className: "apteva-tool-group-item-running-label", children: "preparing..." })
2877
+ ] }),
2878
+ tool.status === "running" && tool.streamOutput && /* @__PURE__ */ jsx18("div", { className: "apteva-tool-group-item-detail", children: tool.streamOutput }),
2879
+ resultPreview && /* @__PURE__ */ jsx18("div", { className: "apteva-tool-group-item-detail apteva-tool-group-item-result-text", children: resultPreview })
2880
+ ] }, tool.id);
2881
+ }) })
2861
2882
  ] });
2862
2883
  }
2863
2884
 
@@ -3108,7 +3129,8 @@ function Message({ message, onAction, enableWidgets, onWidgetRender, persistentW
3108
3129
  name: tool.name,
3109
3130
  status: tool.status || (tool.result !== void 0 ? "completed" : "running"),
3110
3131
  isReceiving: tool.isReceiving,
3111
- streamOutput: tool.streamOutput
3132
+ streamOutput: tool.streamOutput,
3133
+ result: tool.result
3112
3134
  };
3113
3135
  });
3114
3136
  elements.push(
@@ -3370,7 +3392,7 @@ var getSpeechRecognition = () => {
3370
3392
  if (typeof window === "undefined") return null;
3371
3393
  return window.SpeechRecognition || window.webkitSpeechRecognition || null;
3372
3394
  };
3373
- function Composer({ onSendMessage, placeholder = "Type a message...", disabled = false, isLoading = false, onStop, onFileUpload, onSwitchMode, speechToText }) {
3395
+ function Composer({ onSendMessage, placeholder = "Type a message...", disabled = false, isLoading = false, onStop, onFileUpload, onSwitchMode, speechToText, enableVoice = false, voiceState = "idle", voicePartialTranscript = "", voiceDuration = 0, onVoiceStart, onVoiceStop }) {
3374
3396
  const [text, setText] = useState6("");
3375
3397
  const [showMenu, setShowMenu] = useState6(false);
3376
3398
  const [pendingFiles, setPendingFiles] = useState6([]);
@@ -3663,6 +3685,31 @@ function Composer({ onSendMessage, placeholder = "Type a message...", disabled =
3663
3685
  const gridCols = hasMic ? "auto 1fr auto auto" : "auto 1fr auto";
3664
3686
  const gridAreas = isRecording ? '"plus waveform waveform stop"' : isMultiLine ? hasMic ? '"textarea textarea textarea textarea" "plus . mic send"' : '"textarea textarea textarea" "plus . send"' : hasMic ? '"plus textarea mic send"' : '"plus textarea send"';
3665
3687
  const gridColsRecording = "auto 1fr auto";
3688
+ const voiceActive = voiceState === "active" || voiceState === "connecting";
3689
+ const formatVoiceDuration = (s) => {
3690
+ const m = Math.floor(s / 60);
3691
+ const sec = s % 60;
3692
+ return `${m}:${sec.toString().padStart(2, "0")}`;
3693
+ };
3694
+ const showMicButton = enableVoice && !text.trim() && pendingFiles.length === 0 && !isLoading && !voiceActive;
3695
+ if (voiceActive) {
3696
+ return /* @__PURE__ */ jsx23("div", { className: "px-4 py-3 relative", children: /* @__PURE__ */ jsxs17("div", { className: "apteva-voice-overlay", children: [
3697
+ /* @__PURE__ */ jsx23("div", { className: "apteva-voice-transcript-area", children: voicePartialTranscript ? /* @__PURE__ */ jsx23("span", { className: "apteva-voice-partial", children: voicePartialTranscript }) : voiceState === "connecting" ? /* @__PURE__ */ jsx23("span", { className: "apteva-voice-connecting", children: "Connecting..." }) : /* @__PURE__ */ jsx23("span", { className: "apteva-voice-listening", children: "Listening..." }) }),
3698
+ /* @__PURE__ */ jsxs17("div", { className: "apteva-voice-controls", children: [
3699
+ /* @__PURE__ */ jsx23("span", { className: "apteva-voice-duration", children: formatVoiceDuration(voiceDuration) }),
3700
+ /* @__PURE__ */ jsx23(
3701
+ "button",
3702
+ {
3703
+ onClick: onVoiceStop,
3704
+ className: "apteva-voice-stop-btn",
3705
+ title: "Stop voice mode",
3706
+ children: /* @__PURE__ */ jsx23("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx23("rect", { x: "3", y: "3", width: "10", height: "10", rx: "1", fill: "currentColor" }) })
3707
+ }
3708
+ )
3709
+ ] }),
3710
+ /* @__PURE__ */ jsx23("div", { className: "apteva-voice-indicator", children: /* @__PURE__ */ jsx23("div", { className: `apteva-voice-pulse ${voiceState === "active" ? "apteva-voice-pulse-active" : ""}` }) })
3711
+ ] }) });
3712
+ }
3666
3713
  return /* @__PURE__ */ jsxs17("div", { className: "px-4 py-3 relative", children: [
3667
3714
  fileError && /* @__PURE__ */ jsx23("div", { className: "apteva-file-error", children: /* @__PURE__ */ jsxs17("div", { className: "apteva-file-error-content", children: [
3668
3715
  /* @__PURE__ */ jsx23("svg", { fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx23("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
@@ -3806,6 +3853,18 @@ function Composer({ onSendMessage, placeholder = "Type a message...", disabled =
3806
3853
  title: "Stop generation",
3807
3854
  children: /* @__PURE__ */ jsx23("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx23("rect", { x: "2", y: "2", width: "10", height: "10", rx: "1", fill: "currentColor" }) })
3808
3855
  }
3856
+ ) : showMicButton ? /* @__PURE__ */ jsx23(
3857
+ "button",
3858
+ {
3859
+ onClick: onVoiceStart,
3860
+ className: "apteva-composer-voice-btn w-8 h-8 rounded-lg flex items-center justify-center transition-all flex-shrink-0",
3861
+ title: "Start voice mode",
3862
+ children: /* @__PURE__ */ jsxs17("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
3863
+ /* @__PURE__ */ jsx23("path", { d: "M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z", fill: "currentColor" }),
3864
+ /* @__PURE__ */ jsx23("path", { d: "M19 10v2a7 7 0 01-14 0v-2", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
3865
+ /* @__PURE__ */ jsx23("path", { d: "M12 19v4M8 23h8", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
3866
+ ] })
3867
+ }
3809
3868
  ) : /* @__PURE__ */ jsx23(
3810
3869
  "button",
3811
3870
  {
@@ -4366,6 +4425,261 @@ function PersistentWidgetPanel({ widgets, onAction }) {
4366
4425
  ] });
4367
4426
  }
4368
4427
 
4428
+ // src/hooks/useVoiceSession.ts
4429
+ import { useState as useState9, useRef as useRef9, useCallback as useCallback4, useEffect as useEffect9 } from "react";
4430
+
4431
+ // src/utils/audio-utils.ts
4432
+ function float32ToInt16(float32Array) {
4433
+ const int16Array = new Int16Array(float32Array.length);
4434
+ for (let i = 0; i < float32Array.length; i++) {
4435
+ const s = Math.max(-1, Math.min(1, float32Array[i]));
4436
+ int16Array[i] = s < 0 ? s * 32768 : s * 32767;
4437
+ }
4438
+ return int16Array;
4439
+ }
4440
+ function int16ToBase64(int16Array) {
4441
+ const uint8Array = new Uint8Array(int16Array.buffer);
4442
+ let binary = "";
4443
+ for (let i = 0; i < uint8Array.length; i++) {
4444
+ binary += String.fromCharCode(uint8Array[i]);
4445
+ }
4446
+ return btoa(binary);
4447
+ }
4448
+ function base64ToFloat32(base64) {
4449
+ const binaryString = atob(base64);
4450
+ const int16Array = new Int16Array(binaryString.length / 2);
4451
+ for (let i = 0; i < int16Array.length; i++) {
4452
+ int16Array[i] = binaryString.charCodeAt(i * 2 + 1) << 8 | binaryString.charCodeAt(i * 2);
4453
+ }
4454
+ const float32Array = new Float32Array(int16Array.length);
4455
+ for (let i = 0; i < int16Array.length; i++) {
4456
+ float32Array[i] = int16Array[i] / (int16Array[i] < 0 ? 32768 : 32767);
4457
+ }
4458
+ return float32Array;
4459
+ }
4460
+ function resampleAudio(inputData, inputSampleRate, outputSampleRate) {
4461
+ if (inputSampleRate === outputSampleRate) {
4462
+ return inputData;
4463
+ }
4464
+ const ratio = inputSampleRate / outputSampleRate;
4465
+ const outputLength = Math.floor(inputData.length / ratio);
4466
+ const output = new Float32Array(outputLength);
4467
+ for (let i = 0; i < outputLength; i++) {
4468
+ const srcIndex = i * ratio;
4469
+ const srcIndexFloor = Math.floor(srcIndex);
4470
+ const srcIndexCeil = Math.min(srcIndexFloor + 1, inputData.length - 1);
4471
+ const t = srcIndex - srcIndexFloor;
4472
+ output[i] = inputData[srcIndexFloor] * (1 - t) + inputData[srcIndexCeil] * t;
4473
+ }
4474
+ return output;
4475
+ }
4476
+
4477
+ // src/hooks/useVoiceSession.ts
4478
+ function useVoiceSession(config) {
4479
+ const [state, setState] = useState9("idle");
4480
+ const [partialTranscript, setPartialTranscript] = useState9("");
4481
+ const [duration, setDuration] = useState9(0);
4482
+ const wsRef = useRef9(null);
4483
+ const captureCtxRef = useRef9(null);
4484
+ const playbackCtxRef = useRef9(null);
4485
+ const mediaStreamRef = useRef9(null);
4486
+ const processorRef = useRef9(null);
4487
+ const nextPlayTimeRef = useRef9(0);
4488
+ const durationIntervalRef = useRef9(null);
4489
+ const startTimeRef = useRef9(0);
4490
+ const configRef = useRef9(config);
4491
+ configRef.current = config;
4492
+ const cleanup = useCallback4(() => {
4493
+ if (durationIntervalRef.current) {
4494
+ clearInterval(durationIntervalRef.current);
4495
+ durationIntervalRef.current = null;
4496
+ }
4497
+ if (processorRef.current) {
4498
+ processorRef.current.disconnect();
4499
+ processorRef.current = null;
4500
+ }
4501
+ if (mediaStreamRef.current) {
4502
+ mediaStreamRef.current.getTracks().forEach((t) => t.stop());
4503
+ mediaStreamRef.current = null;
4504
+ }
4505
+ if (captureCtxRef.current) {
4506
+ try {
4507
+ captureCtxRef.current.close();
4508
+ } catch (_) {
4509
+ }
4510
+ captureCtxRef.current = null;
4511
+ }
4512
+ if (playbackCtxRef.current) {
4513
+ try {
4514
+ playbackCtxRef.current.close();
4515
+ } catch (_) {
4516
+ }
4517
+ playbackCtxRef.current = null;
4518
+ }
4519
+ if (wsRef.current) {
4520
+ try {
4521
+ wsRef.current.close();
4522
+ } catch (_) {
4523
+ }
4524
+ wsRef.current = null;
4525
+ }
4526
+ nextPlayTimeRef.current = 0;
4527
+ setPartialTranscript("");
4528
+ setDuration(0);
4529
+ }, []);
4530
+ useEffect9(() => {
4531
+ return () => {
4532
+ cleanup();
4533
+ };
4534
+ }, [cleanup]);
4535
+ const playAudioChunk = useCallback4((base64Audio) => {
4536
+ if (!playbackCtxRef.current) {
4537
+ playbackCtxRef.current = new AudioContext({ sampleRate: 24e3 });
4538
+ }
4539
+ const ctx = playbackCtxRef.current;
4540
+ if (ctx.state === "suspended") {
4541
+ ctx.resume();
4542
+ }
4543
+ const float32Data = base64ToFloat32(base64Audio);
4544
+ const audioBuffer = ctx.createBuffer(1, float32Data.length, 24e3);
4545
+ audioBuffer.getChannelData(0).set(float32Data);
4546
+ const source = ctx.createBufferSource();
4547
+ source.buffer = audioBuffer;
4548
+ source.connect(ctx.destination);
4549
+ const currentTime = ctx.currentTime;
4550
+ const startTime = Math.max(currentTime, nextPlayTimeRef.current);
4551
+ source.start(startTime);
4552
+ nextPlayTimeRef.current = startTime + audioBuffer.duration;
4553
+ }, []);
4554
+ const handleMessage = useCallback4((msg) => {
4555
+ const cfg = configRef.current;
4556
+ switch (msg.type) {
4557
+ case "session_created":
4558
+ setState("active");
4559
+ startTimeRef.current = Date.now();
4560
+ durationIntervalRef.current = setInterval(() => {
4561
+ setDuration(Math.floor((Date.now() - startTimeRef.current) / 1e3));
4562
+ }, 1e3);
4563
+ break;
4564
+ case "audio_delta":
4565
+ if (msg.data?.chunk) {
4566
+ playAudioChunk(msg.data.chunk);
4567
+ }
4568
+ break;
4569
+ case "transcript":
4570
+ if (msg.data) {
4571
+ if (msg.data.partial) {
4572
+ setPartialTranscript(msg.data.content);
4573
+ } else {
4574
+ setPartialTranscript("");
4575
+ cfg.onTranscript?.({
4576
+ id: `vt-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
4577
+ role: msg.data.role,
4578
+ content: msg.data.content,
4579
+ partial: false,
4580
+ timestamp: /* @__PURE__ */ new Date()
4581
+ });
4582
+ }
4583
+ }
4584
+ break;
4585
+ case "tool_call":
4586
+ if (msg.data) {
4587
+ nextPlayTimeRef.current = 0;
4588
+ cfg.onTranscript?.({
4589
+ id: `vt-tool-${Date.now()}`,
4590
+ role: "system",
4591
+ content: `Using ${msg.data.name}...`,
4592
+ partial: false,
4593
+ timestamp: /* @__PURE__ */ new Date()
4594
+ });
4595
+ }
4596
+ break;
4597
+ case "tool_result":
4598
+ if (msg.data) {
4599
+ nextPlayTimeRef.current = 0;
4600
+ }
4601
+ break;
4602
+ case "error":
4603
+ setState("error");
4604
+ cfg.onError?.(new Error(msg.data?.message || "Voice session error"));
4605
+ break;
4606
+ }
4607
+ }, [playAudioChunk]);
4608
+ const startCapture = useCallback4(async () => {
4609
+ const ws = wsRef.current;
4610
+ if (!ws) return;
4611
+ try {
4612
+ captureCtxRef.current = new AudioContext();
4613
+ const nativeSampleRate = captureCtxRef.current.sampleRate;
4614
+ mediaStreamRef.current = await navigator.mediaDevices.getUserMedia({ audio: true });
4615
+ const source = captureCtxRef.current.createMediaStreamSource(mediaStreamRef.current);
4616
+ processorRef.current = captureCtxRef.current.createScriptProcessor(2048, 1, 1);
4617
+ processorRef.current.onaudioprocess = (e) => {
4618
+ if (!ws || ws.readyState !== WebSocket.OPEN) return;
4619
+ const inputData = e.inputBuffer.getChannelData(0);
4620
+ const resampledData = resampleAudio(inputData, nativeSampleRate, 16e3);
4621
+ const int16Data = float32ToInt16(resampledData);
4622
+ const base64Data = int16ToBase64(int16Data);
4623
+ ws.send(JSON.stringify({
4624
+ type: "audio",
4625
+ data: { chunk: base64Data }
4626
+ }));
4627
+ };
4628
+ source.connect(processorRef.current);
4629
+ processorRef.current.connect(captureCtxRef.current.destination);
4630
+ } catch (e) {
4631
+ configRef.current.onError?.(new Error("Microphone access denied"));
4632
+ cleanup();
4633
+ setState("idle");
4634
+ }
4635
+ }, [cleanup]);
4636
+ const start = useCallback4(() => {
4637
+ if (state !== "idle") return;
4638
+ setState("connecting");
4639
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
4640
+ const wsUrl = `${protocol}//${window.location.host}${config.apiUrl}/voice`;
4641
+ const ws = new WebSocket(wsUrl);
4642
+ wsRef.current = ws;
4643
+ ws.onopen = () => {
4644
+ const provider = configRef.current.provider || "openai";
4645
+ const voice = configRef.current.voice || "ash";
4646
+ ws.send(JSON.stringify({
4647
+ type: "start",
4648
+ data: { provider, voice }
4649
+ }));
4650
+ startCapture();
4651
+ };
4652
+ ws.onmessage = (event) => {
4653
+ try {
4654
+ const msg = JSON.parse(event.data);
4655
+ handleMessage(msg);
4656
+ } catch (_) {
4657
+ }
4658
+ };
4659
+ ws.onerror = () => {
4660
+ setState("error");
4661
+ configRef.current.onError?.(new Error("WebSocket connection failed"));
4662
+ };
4663
+ ws.onclose = () => {
4664
+ cleanup();
4665
+ setState("idle");
4666
+ };
4667
+ }, [state, config.apiUrl, startCapture, handleMessage, cleanup]);
4668
+ const stop = useCallback4(() => {
4669
+ cleanup();
4670
+ setState("idle");
4671
+ }, [cleanup]);
4672
+ const sendText = useCallback4((text) => {
4673
+ const ws = wsRef.current;
4674
+ if (!ws || ws.readyState !== WebSocket.OPEN) return;
4675
+ ws.send(JSON.stringify({
4676
+ type: "text",
4677
+ data: { content: text }
4678
+ }));
4679
+ }, []);
4680
+ return { state, partialTranscript, duration, start, stop, sendText };
4681
+ }
4682
+
4369
4683
  // src/components/Chat/Chat.tsx
4370
4684
  import { Fragment as Fragment6, jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
4371
4685
  var Chat = forwardRef(function Chat2({
@@ -4418,28 +4732,49 @@ var Chat = forwardRef(function Chat2({
4418
4732
  onWidgetRender,
4419
4733
  // Speech to text
4420
4734
  speechToText,
4735
+ // Realtime voice
4736
+ enableVoice = false,
4737
+ voiceProvider,
4738
+ voiceId,
4421
4739
  className
4422
4740
  }, ref) {
4423
- const [messages, setMessages] = useState9(initialMessages);
4424
- const [isLoading, setIsLoading] = useState9(false);
4425
- const [currentThreadId, setCurrentThreadId] = useState9(threadId || null);
4426
- const [mode, setMode] = useState9(initialMode);
4427
- const [chatToolName, setChatToolName] = useState9(null);
4428
- const [commandState, setCommandState] = useState9("idle");
4429
- const [commandResult, setCommandResult] = useState9(null);
4430
- const [commandError, setCommandError] = useState9(null);
4431
- const [progress, setProgress] = useState9(0);
4432
- const [commandInput, setCommandInput] = useState9("");
4433
- const [streamedContent, setStreamedContent] = useState9("");
4434
- const [currentToolName, setCurrentToolName] = useState9(null);
4435
- const [currentRequestId, setCurrentRequestId] = useState9(null);
4436
- const [plan, setPlan] = useState9("");
4437
- const [pendingCommand, setPendingCommand] = useState9("");
4438
- const [internalPlanMode, setInternalPlanMode] = useState9(planMode);
4439
- const [showSettingsMenu, setShowSettingsMenu] = useState9(false);
4440
- const fileInputRef = useRef9(null);
4441
- const [persistentWidgets, setPersistentWidgets] = useState9(/* @__PURE__ */ new Map());
4442
- const updatePersistentWidgets = useCallback4((msgs) => {
4741
+ const [messages, setMessages] = useState10(initialMessages);
4742
+ const [isLoading, setIsLoading] = useState10(false);
4743
+ const [currentThreadId, setCurrentThreadId] = useState10(threadId || null);
4744
+ const [mode, setMode] = useState10(initialMode);
4745
+ const [chatToolName, setChatToolName] = useState10(null);
4746
+ const [commandState, setCommandState] = useState10("idle");
4747
+ const [commandResult, setCommandResult] = useState10(null);
4748
+ const [commandError, setCommandError] = useState10(null);
4749
+ const [progress, setProgress] = useState10(0);
4750
+ const [commandInput, setCommandInput] = useState10("");
4751
+ const [streamedContent, setStreamedContent] = useState10("");
4752
+ const [currentToolName, setCurrentToolName] = useState10(null);
4753
+ const [currentRequestId, setCurrentRequestId] = useState10(null);
4754
+ const [plan, setPlan] = useState10("");
4755
+ const [pendingCommand, setPendingCommand] = useState10("");
4756
+ const [internalPlanMode, setInternalPlanMode] = useState10(planMode);
4757
+ const [showSettingsMenu, setShowSettingsMenu] = useState10(false);
4758
+ const fileInputRef = useRef10(null);
4759
+ const handleVoiceTranscript = useCallback5((entry) => {
4760
+ const msg = {
4761
+ id: entry.id,
4762
+ role: entry.role === "system" ? "assistant" : entry.role,
4763
+ content: entry.content,
4764
+ timestamp: entry.timestamp,
4765
+ metadata: entry.role === "system" ? { isVoiceSystem: true } : { isVoice: true }
4766
+ };
4767
+ setMessages((prev) => [...prev, msg]);
4768
+ }, []);
4769
+ const voice = useVoiceSession({
4770
+ apiUrl: apiUrl || "",
4771
+ provider: voiceProvider,
4772
+ voice: voiceId,
4773
+ onTranscript: handleVoiceTranscript,
4774
+ onError
4775
+ });
4776
+ const [persistentWidgets, setPersistentWidgets] = useState10(/* @__PURE__ */ new Map());
4777
+ const updatePersistentWidgets = useCallback5((msgs) => {
4443
4778
  setPersistentWidgets((prev) => {
4444
4779
  const next = new Map(prev);
4445
4780
  let changed = false;
@@ -4457,12 +4792,12 @@ var Chat = forwardRef(function Chat2({
4457
4792
  return changed ? next : prev;
4458
4793
  });
4459
4794
  }, []);
4460
- useEffect9(() => {
4795
+ useEffect10(() => {
4461
4796
  updatePersistentWidgets(messages);
4462
4797
  }, [messages, updatePersistentWidgets]);
4463
4798
  const persistentWidgetList = useMemo2(() => Array.from(persistentWidgets.values()), [persistentWidgets]);
4464
4799
  const persistentWidgetIds = useMemo2(() => new Set(persistentWidgets.keys()), [persistentWidgets]);
4465
- const handleSendMessageRef = useRef9(null);
4800
+ const handleSendMessageRef = useRef10(null);
4466
4801
  useImperativeHandle(ref, () => ({
4467
4802
  sendMessage: async (text) => {
4468
4803
  if (handleSendMessageRef.current) {
@@ -4483,7 +4818,7 @@ var Chat = forwardRef(function Chat2({
4483
4818
  return context ? `${context}
4484
4819
  ${widgetContext}` : widgetContext;
4485
4820
  }, [context, enableWidgets, availableWidgets, compactWidgetContext]);
4486
- useEffect9(() => {
4821
+ useEffect10(() => {
4487
4822
  if (apiUrl || apiKey) {
4488
4823
  aptevaClient.configure({
4489
4824
  ...apiUrl && { apiUrl },
@@ -4491,15 +4826,15 @@ ${widgetContext}` : widgetContext;
4491
4826
  });
4492
4827
  }
4493
4828
  }, [apiUrl, apiKey]);
4494
- useEffect9(() => {
4829
+ useEffect10(() => {
4495
4830
  if (threadId) {
4496
4831
  onThreadChange?.(threadId);
4497
4832
  }
4498
4833
  }, [threadId, onThreadChange]);
4499
- useEffect9(() => {
4834
+ useEffect10(() => {
4500
4835
  setInternalPlanMode(planMode);
4501
4836
  }, [planMode]);
4502
- useEffect9(() => {
4837
+ useEffect10(() => {
4503
4838
  const handleClickOutside = (event) => {
4504
4839
  const target = event.target;
4505
4840
  if (showSettingsMenu && !target.closest(".settings-menu-container")) {
@@ -4519,7 +4854,7 @@ ${widgetContext}` : widgetContext;
4519
4854
  }
4520
4855
  };
4521
4856
  const defaultPlaceholder = mode === "chat" ? "Type a message..." : "Enter your command...";
4522
- const handleWidgetAction = useCallback4((action) => {
4857
+ const handleWidgetAction = useCallback5((action) => {
4523
4858
  onAction?.(action);
4524
4859
  if (action.type === "submit" && action.payload?.formData) {
4525
4860
  const formData = action.payload.formData;
@@ -5040,8 +5375,8 @@ ${planToExecute}`;
5040
5375
  /* @__PURE__ */ jsx26("div", { className: "apteva-chat-title", children: headerTitle }),
5041
5376
  /* @__PURE__ */ jsx26("div", { className: cn(
5042
5377
  "apteva-chat-status",
5043
- isLoading ? chatToolName ? "apteva-chat-status-tool" : "apteva-chat-status-thinking" : "apteva-chat-status-ready"
5044
- ), children: isLoading ? chatToolName ? `Using ${chatToolName}...` : "Thinking..." : "Ready" })
5378
+ voice.state === "active" ? "apteva-chat-status-voice" : voice.state === "connecting" ? "apteva-chat-status-thinking" : isLoading ? chatToolName ? "apteva-chat-status-tool" : "apteva-chat-status-thinking" : "apteva-chat-status-ready"
5379
+ ), children: voice.state === "active" ? "Voice active" : voice.state === "connecting" ? "Connecting voice..." : isLoading ? chatToolName ? `Using ${chatToolName}...` : "Thinking..." : "Ready" })
5045
5380
  ] })
5046
5381
  ] }) }),
5047
5382
  mode === "chat" && /* @__PURE__ */ jsxs20(Fragment6, { children: [
@@ -5074,7 +5409,13 @@ ${planToExecute}`;
5074
5409
  onStop: handleStop,
5075
5410
  onFileUpload,
5076
5411
  onSwitchMode: showModeToggle ? () => handleModeChange("command") : void 0,
5077
- speechToText
5412
+ speechToText,
5413
+ enableVoice,
5414
+ voiceState: voice.state,
5415
+ voicePartialTranscript: voice.partialTranscript,
5416
+ voiceDuration: voice.duration,
5417
+ onVoiceStart: voice.start,
5418
+ onVoiceStop: voice.stop
5078
5419
  }
5079
5420
  )
5080
5421
  ] }),
@@ -5117,11 +5458,11 @@ ${planToExecute}`;
5117
5458
  });
5118
5459
 
5119
5460
  // src/components/Chat/CommandOutput.tsx
5120
- import { useState as useState10 } from "react";
5461
+ import { useState as useState11 } from "react";
5121
5462
  import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
5122
5463
 
5123
5464
  // src/components/Command/Command.tsx
5124
- import React, { useState as useState11, useEffect as useEffect10 } from "react";
5465
+ import React, { useState as useState12, useEffect as useEffect11 } from "react";
5125
5466
  import { Fragment as Fragment7, jsx as jsx28, jsxs as jsxs22 } from "react/jsx-runtime";
5126
5467
  function Command({
5127
5468
  agentId,
@@ -5149,28 +5490,28 @@ function Command({
5149
5490
  resultRenderer,
5150
5491
  className
5151
5492
  }) {
5152
- const [state, setState] = useState11("idle");
5153
- const [result, setResult] = useState11(null);
5154
- const [error, setError] = useState11(null);
5155
- const [progress, setProgress] = useState11(0);
5156
- const [command, setCommand] = useState11(initialCommand || "");
5157
- const [streamedContent, setStreamedContent] = useState11("");
5158
- const [plan, setPlan] = useState11("");
5159
- const [pendingCommand, setPendingCommand] = useState11("");
5160
- const [showPlanDetails, setShowPlanDetails] = useState11(false);
5161
- const [uploadedFiles, setUploadedFiles] = useState11([]);
5162
- const [showSettingsMenu, setShowSettingsMenu] = useState11(false);
5163
- const [internalPlanMode, setInternalPlanMode] = useState11(planMode);
5493
+ const [state, setState] = useState12("idle");
5494
+ const [result, setResult] = useState12(null);
5495
+ const [error, setError] = useState12(null);
5496
+ const [progress, setProgress] = useState12(0);
5497
+ const [command, setCommand] = useState12(initialCommand || "");
5498
+ const [streamedContent, setStreamedContent] = useState12("");
5499
+ const [plan, setPlan] = useState12("");
5500
+ const [pendingCommand, setPendingCommand] = useState12("");
5501
+ const [showPlanDetails, setShowPlanDetails] = useState12(false);
5502
+ const [uploadedFiles, setUploadedFiles] = useState12([]);
5503
+ const [showSettingsMenu, setShowSettingsMenu] = useState12(false);
5504
+ const [internalPlanMode, setInternalPlanMode] = useState12(planMode);
5164
5505
  const fileInputRef = React.useRef(null);
5165
- useEffect10(() => {
5506
+ useEffect11(() => {
5166
5507
  if (autoExecute && state === "idle" && command) {
5167
5508
  executeCommand();
5168
5509
  }
5169
5510
  }, [autoExecute]);
5170
- useEffect10(() => {
5511
+ useEffect11(() => {
5171
5512
  setInternalPlanMode(planMode);
5172
5513
  }, [planMode]);
5173
- useEffect10(() => {
5514
+ useEffect11(() => {
5174
5515
  const handleClickOutside = (event) => {
5175
5516
  const target = event.target;
5176
5517
  if (showSettingsMenu && !target.closest(".settings-menu-container")) {
@@ -6092,7 +6433,7 @@ ${planToExecute}`;
6092
6433
  }
6093
6434
 
6094
6435
  // src/components/Prompt/Prompt.tsx
6095
- import { useState as useState12 } from "react";
6436
+ import { useState as useState13 } from "react";
6096
6437
  import { jsx as jsx29, jsxs as jsxs23 } from "react/jsx-runtime";
6097
6438
  function Prompt({
6098
6439
  agentId,
@@ -6110,9 +6451,9 @@ function Prompt({
6110
6451
  showSuggestions = false,
6111
6452
  className
6112
6453
  }) {
6113
- const [value, setValue] = useState12(initialValue);
6114
- const [isLoading, setIsLoading] = useState12(false);
6115
- const [suggestions] = useState12(["Plan a trip", "Write a description", "Analyze data"]);
6454
+ const [value, setValue] = useState13(initialValue);
6455
+ const [isLoading, setIsLoading] = useState13(false);
6456
+ const [suggestions] = useState13(["Plan a trip", "Write a description", "Analyze data"]);
6116
6457
  const handleChange = (e) => {
6117
6458
  const newValue = e.target.value;
6118
6459
  if (!maxLength || newValue.length <= maxLength) {
@@ -6203,7 +6544,7 @@ function Prompt({
6203
6544
  }
6204
6545
 
6205
6546
  // src/components/Stream/Stream.tsx
6206
- import { useState as useState13, useEffect as useEffect11 } from "react";
6547
+ import { useState as useState14, useEffect as useEffect12 } from "react";
6207
6548
  import { jsx as jsx30, jsxs as jsxs24 } from "react/jsx-runtime";
6208
6549
  function Stream({
6209
6550
  agentId,
@@ -6220,10 +6561,10 @@ function Stream({
6220
6561
  typingSpeed = 30,
6221
6562
  className
6222
6563
  }) {
6223
- const [text, setText] = useState13("");
6224
- const [isStreaming, setIsStreaming] = useState13(false);
6225
- const [isComplete, setIsComplete] = useState13(false);
6226
- useEffect11(() => {
6564
+ const [text, setText] = useState14("");
6565
+ const [isStreaming, setIsStreaming] = useState14(false);
6566
+ const [isComplete, setIsComplete] = useState14(false);
6567
+ useEffect12(() => {
6227
6568
  if (autoStart && !isStreaming && !isComplete) {
6228
6569
  startStreaming();
6229
6570
  }
@@ -6300,7 +6641,7 @@ function Stream({
6300
6641
  }
6301
6642
 
6302
6643
  // src/components/Threads/ThreadList.tsx
6303
- import { useState as useState14 } from "react";
6644
+ import { useState as useState15 } from "react";
6304
6645
 
6305
6646
  // src/components/Threads/ThreadItem.tsx
6306
6647
  import { jsx as jsx31, jsxs as jsxs25 } from "react/jsx-runtime";
@@ -6364,7 +6705,7 @@ function ThreadList({
6364
6705
  showSearch = false,
6365
6706
  groupBy = "none"
6366
6707
  }) {
6367
- const [searchQuery, setSearchQuery] = useState14("");
6708
+ const [searchQuery, setSearchQuery] = useState15("");
6368
6709
  const filteredThreads = threads.filter(
6369
6710
  (thread) => thread.title.toLowerCase().includes(searchQuery.toLowerCase()) || thread.preview?.toLowerCase().includes(searchQuery.toLowerCase())
6370
6711
  );
@@ -6494,10 +6835,10 @@ function Threads({
6494
6835
  }
6495
6836
 
6496
6837
  // src/components/AutoInterface/AutoInterface.tsx
6497
- import { useState as useState16, useRef as useRef10, useCallback as useCallback5, useEffect as useEffect12 } from "react";
6838
+ import { useState as useState17, useRef as useRef11, useCallback as useCallback6, useEffect as useEffect13 } from "react";
6498
6839
 
6499
6840
  // src/components/AutoInterface/LayoutRenderer.tsx
6500
- import { useState as useState15 } from "react";
6841
+ import { useState as useState16 } from "react";
6501
6842
  import { Fragment as Fragment8, jsx as jsx34, jsxs as jsxs28 } from "react/jsx-runtime";
6502
6843
  var gapClasses = {
6503
6844
  none: "gap-0",
@@ -6613,7 +6954,7 @@ function SidebarLayout({ node, renderNode }) {
6613
6954
  function TabsLayout({ node, renderNode }) {
6614
6955
  const { labels = [], defaultTab = 0 } = node.props || {};
6615
6956
  const children = node.children || [];
6616
- const [activeTab, setActiveTab] = useState15(defaultTab);
6957
+ const [activeTab, setActiveTab] = useState16(defaultTab);
6617
6958
  return /* @__PURE__ */ jsxs28("div", { children: [
6618
6959
  /* @__PURE__ */ jsx34("div", { className: "flex border-b border-neutral-200 dark:border-neutral-700 mb-4", children: labels.map((label, idx) => /* @__PURE__ */ jsx34(
6619
6960
  "button",
@@ -6764,19 +7105,19 @@ function AutoInterface({
6764
7105
  theme,
6765
7106
  className
6766
7107
  }) {
6767
- const [interfaceSpec, setInterfaceSpec] = useState16(initialInterface || null);
6768
- const [isGenerating, setIsGenerating] = useState16(false);
6769
- const [chatCollapsed, setChatCollapsed] = useState16(false);
6770
- const chatRef = useRef10(null);
7108
+ const [interfaceSpec, setInterfaceSpec] = useState17(initialInterface || null);
7109
+ const [isGenerating, setIsGenerating] = useState17(false);
7110
+ const [chatCollapsed, setChatCollapsed] = useState17(false);
7111
+ const chatRef = useRef11(null);
6771
7112
  const systemContext = [
6772
7113
  generateInterfaceContext(),
6773
7114
  context || ""
6774
7115
  ].filter(Boolean).join("\n\n");
6775
- const updateInterface = useCallback5((newSpec) => {
7116
+ const updateInterface = useCallback6((newSpec) => {
6776
7117
  setInterfaceSpec(newSpec);
6777
7118
  onInterfaceChange?.(newSpec);
6778
7119
  }, [onInterfaceChange]);
6779
- const handleAction = useCallback5((action) => {
7120
+ const handleAction = useCallback6((action) => {
6780
7121
  onAction?.(action);
6781
7122
  if (chatRef.current) {
6782
7123
  chatRef.current.sendMessage(
@@ -6784,7 +7125,7 @@ function AutoInterface({
6784
7125
  );
6785
7126
  }
6786
7127
  }, [onAction]);
6787
- const handleMessageComplete = useCallback5((result) => {
7128
+ const handleMessageComplete = useCallback6((result) => {
6788
7129
  if (!result?.data) return;
6789
7130
  const text = typeof result.data === "string" ? result.data : result.data.message || "";
6790
7131
  console.log("[AutoInterface] Chat message complete, text (" + text.length + " chars):", text.substring(0, 300));
@@ -6805,7 +7146,7 @@ function AutoInterface({
6805
7146
  }
6806
7147
  setIsGenerating(false);
6807
7148
  }, [interfaceSpec, updateInterface]);
6808
- useEffect12(() => {
7149
+ useEffect13(() => {
6809
7150
  if (!initialPrompt || initialInterface || useMock) return;
6810
7151
  if (!apiUrl) return;
6811
7152
  let cancelled = false;
@@ -6930,29 +7271,29 @@ function getThemeScript() {
6930
7271
  }
6931
7272
 
6932
7273
  // src/hooks/useInterfaceState.ts
6933
- import { useState as useState17, useCallback as useCallback6 } from "react";
7274
+ import { useState as useState18, useCallback as useCallback7 } from "react";
6934
7275
  function useInterfaceState(initialSpec) {
6935
- const [spec, setSpec] = useState17(initialSpec || null);
6936
- const [isStreaming, setIsStreaming] = useState17(false);
6937
- const setInterface = useCallback6((newSpec) => {
7276
+ const [spec, setSpec] = useState18(initialSpec || null);
7277
+ const [isStreaming, setIsStreaming] = useState18(false);
7278
+ const setInterface = useCallback7((newSpec) => {
6938
7279
  setSpec(newSpec);
6939
7280
  }, []);
6940
- const clearInterface = useCallback6(() => {
7281
+ const clearInterface = useCallback7(() => {
6941
7282
  setSpec(null);
6942
7283
  }, []);
6943
- const applyInterfaceUpdate = useCallback6((update) => {
7284
+ const applyInterfaceUpdate = useCallback7((update) => {
6944
7285
  setSpec((prev) => {
6945
7286
  if (!prev) return prev;
6946
7287
  return applyUpdate(prev, update);
6947
7288
  });
6948
7289
  }, []);
6949
- const applyInterfaceUpdates = useCallback6((updates) => {
7290
+ const applyInterfaceUpdates = useCallback7((updates) => {
6950
7291
  setSpec((prev) => {
6951
7292
  if (!prev) return prev;
6952
7293
  return applyUpdates(prev, updates);
6953
7294
  });
6954
7295
  }, []);
6955
- const getNode = useCallback6((id) => {
7296
+ const getNode = useCallback7((id) => {
6956
7297
  if (!spec) return null;
6957
7298
  return findNode(spec.root, id);
6958
7299
  }, [spec]);
@@ -6969,7 +7310,7 @@ function useInterfaceState(initialSpec) {
6969
7310
  }
6970
7311
 
6971
7312
  // src/hooks/useInterfaceAI.ts
6972
- import { useCallback as useCallback7, useRef as useRef11 } from "react";
7313
+ import { useCallback as useCallback8, useRef as useRef12 } from "react";
6973
7314
  function useInterfaceAI({
6974
7315
  agentId,
6975
7316
  apiUrl,
@@ -6981,15 +7322,15 @@ function useInterfaceAI({
6981
7322
  onStreamStart,
6982
7323
  onStreamEnd
6983
7324
  }) {
6984
- const threadIdRef = useRef11(null);
6985
- const accumulatedTextRef = useRef11("");
7325
+ const threadIdRef = useRef12(null);
7326
+ const accumulatedTextRef = useRef12("");
6986
7327
  if (apiUrl || apiKey) {
6987
7328
  aptevaClient.configure({
6988
7329
  ...apiUrl && { apiUrl },
6989
7330
  ...apiKey && { apiKey }
6990
7331
  });
6991
7332
  }
6992
- const sendMessage = useCallback7(async (message) => {
7333
+ const sendMessage = useCallback8(async (message) => {
6993
7334
  accumulatedTextRef.current = "";
6994
7335
  onStreamStart?.();
6995
7336
  const systemPrompt = [