@apteva/apteva-kit 0.1.129 → 0.1.130

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";
@@ -3370,7 +3370,7 @@ var getSpeechRecognition = () => {
3370
3370
  if (typeof window === "undefined") return null;
3371
3371
  return window.SpeechRecognition || window.webkitSpeechRecognition || null;
3372
3372
  };
3373
- function Composer({ onSendMessage, placeholder = "Type a message...", disabled = false, isLoading = false, onStop, onFileUpload, onSwitchMode, speechToText }) {
3373
+ 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
3374
  const [text, setText] = useState6("");
3375
3375
  const [showMenu, setShowMenu] = useState6(false);
3376
3376
  const [pendingFiles, setPendingFiles] = useState6([]);
@@ -3663,6 +3663,31 @@ function Composer({ onSendMessage, placeholder = "Type a message...", disabled =
3663
3663
  const gridCols = hasMic ? "auto 1fr auto auto" : "auto 1fr auto";
3664
3664
  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
3665
  const gridColsRecording = "auto 1fr auto";
3666
+ const voiceActive = voiceState === "active" || voiceState === "connecting";
3667
+ const formatVoiceDuration = (s) => {
3668
+ const m = Math.floor(s / 60);
3669
+ const sec = s % 60;
3670
+ return `${m}:${sec.toString().padStart(2, "0")}`;
3671
+ };
3672
+ const showMicButton = enableVoice && !text.trim() && pendingFiles.length === 0 && !isLoading && !voiceActive;
3673
+ if (voiceActive) {
3674
+ return /* @__PURE__ */ jsx23("div", { className: "px-4 py-3 relative", children: /* @__PURE__ */ jsxs17("div", { className: "apteva-voice-overlay", children: [
3675
+ /* @__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..." }) }),
3676
+ /* @__PURE__ */ jsxs17("div", { className: "apteva-voice-controls", children: [
3677
+ /* @__PURE__ */ jsx23("span", { className: "apteva-voice-duration", children: formatVoiceDuration(voiceDuration) }),
3678
+ /* @__PURE__ */ jsx23(
3679
+ "button",
3680
+ {
3681
+ onClick: onVoiceStop,
3682
+ className: "apteva-voice-stop-btn",
3683
+ title: "Stop voice mode",
3684
+ 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" }) })
3685
+ }
3686
+ )
3687
+ ] }),
3688
+ /* @__PURE__ */ jsx23("div", { className: "apteva-voice-indicator", children: /* @__PURE__ */ jsx23("div", { className: `apteva-voice-pulse ${voiceState === "active" ? "apteva-voice-pulse-active" : ""}` }) })
3689
+ ] }) });
3690
+ }
3666
3691
  return /* @__PURE__ */ jsxs17("div", { className: "px-4 py-3 relative", children: [
3667
3692
  fileError && /* @__PURE__ */ jsx23("div", { className: "apteva-file-error", children: /* @__PURE__ */ jsxs17("div", { className: "apteva-file-error-content", children: [
3668
3693
  /* @__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 +3831,18 @@ function Composer({ onSendMessage, placeholder = "Type a message...", disabled =
3806
3831
  title: "Stop generation",
3807
3832
  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
3833
  }
3834
+ ) : showMicButton ? /* @__PURE__ */ jsx23(
3835
+ "button",
3836
+ {
3837
+ onClick: onVoiceStart,
3838
+ className: "apteva-composer-voice-btn w-8 h-8 rounded-lg flex items-center justify-center transition-all flex-shrink-0",
3839
+ title: "Start voice mode",
3840
+ children: /* @__PURE__ */ jsxs17("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
3841
+ /* @__PURE__ */ jsx23("path", { d: "M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z", fill: "currentColor" }),
3842
+ /* @__PURE__ */ jsx23("path", { d: "M19 10v2a7 7 0 01-14 0v-2", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
3843
+ /* @__PURE__ */ jsx23("path", { d: "M12 19v4M8 23h8", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
3844
+ ] })
3845
+ }
3809
3846
  ) : /* @__PURE__ */ jsx23(
3810
3847
  "button",
3811
3848
  {
@@ -4366,6 +4403,261 @@ function PersistentWidgetPanel({ widgets, onAction }) {
4366
4403
  ] });
4367
4404
  }
4368
4405
 
4406
+ // src/hooks/useVoiceSession.ts
4407
+ import { useState as useState9, useRef as useRef9, useCallback as useCallback4, useEffect as useEffect9 } from "react";
4408
+
4409
+ // src/utils/audio-utils.ts
4410
+ function float32ToInt16(float32Array) {
4411
+ const int16Array = new Int16Array(float32Array.length);
4412
+ for (let i = 0; i < float32Array.length; i++) {
4413
+ const s = Math.max(-1, Math.min(1, float32Array[i]));
4414
+ int16Array[i] = s < 0 ? s * 32768 : s * 32767;
4415
+ }
4416
+ return int16Array;
4417
+ }
4418
+ function int16ToBase64(int16Array) {
4419
+ const uint8Array = new Uint8Array(int16Array.buffer);
4420
+ let binary = "";
4421
+ for (let i = 0; i < uint8Array.length; i++) {
4422
+ binary += String.fromCharCode(uint8Array[i]);
4423
+ }
4424
+ return btoa(binary);
4425
+ }
4426
+ function base64ToFloat32(base64) {
4427
+ const binaryString = atob(base64);
4428
+ const int16Array = new Int16Array(binaryString.length / 2);
4429
+ for (let i = 0; i < int16Array.length; i++) {
4430
+ int16Array[i] = binaryString.charCodeAt(i * 2 + 1) << 8 | binaryString.charCodeAt(i * 2);
4431
+ }
4432
+ const float32Array = new Float32Array(int16Array.length);
4433
+ for (let i = 0; i < int16Array.length; i++) {
4434
+ float32Array[i] = int16Array[i] / (int16Array[i] < 0 ? 32768 : 32767);
4435
+ }
4436
+ return float32Array;
4437
+ }
4438
+ function resampleAudio(inputData, inputSampleRate, outputSampleRate) {
4439
+ if (inputSampleRate === outputSampleRate) {
4440
+ return inputData;
4441
+ }
4442
+ const ratio = inputSampleRate / outputSampleRate;
4443
+ const outputLength = Math.floor(inputData.length / ratio);
4444
+ const output = new Float32Array(outputLength);
4445
+ for (let i = 0; i < outputLength; i++) {
4446
+ const srcIndex = i * ratio;
4447
+ const srcIndexFloor = Math.floor(srcIndex);
4448
+ const srcIndexCeil = Math.min(srcIndexFloor + 1, inputData.length - 1);
4449
+ const t = srcIndex - srcIndexFloor;
4450
+ output[i] = inputData[srcIndexFloor] * (1 - t) + inputData[srcIndexCeil] * t;
4451
+ }
4452
+ return output;
4453
+ }
4454
+
4455
+ // src/hooks/useVoiceSession.ts
4456
+ function useVoiceSession(config) {
4457
+ const [state, setState] = useState9("idle");
4458
+ const [partialTranscript, setPartialTranscript] = useState9("");
4459
+ const [duration, setDuration] = useState9(0);
4460
+ const wsRef = useRef9(null);
4461
+ const captureCtxRef = useRef9(null);
4462
+ const playbackCtxRef = useRef9(null);
4463
+ const mediaStreamRef = useRef9(null);
4464
+ const processorRef = useRef9(null);
4465
+ const nextPlayTimeRef = useRef9(0);
4466
+ const durationIntervalRef = useRef9(null);
4467
+ const startTimeRef = useRef9(0);
4468
+ const configRef = useRef9(config);
4469
+ configRef.current = config;
4470
+ const cleanup = useCallback4(() => {
4471
+ if (durationIntervalRef.current) {
4472
+ clearInterval(durationIntervalRef.current);
4473
+ durationIntervalRef.current = null;
4474
+ }
4475
+ if (processorRef.current) {
4476
+ processorRef.current.disconnect();
4477
+ processorRef.current = null;
4478
+ }
4479
+ if (mediaStreamRef.current) {
4480
+ mediaStreamRef.current.getTracks().forEach((t) => t.stop());
4481
+ mediaStreamRef.current = null;
4482
+ }
4483
+ if (captureCtxRef.current) {
4484
+ try {
4485
+ captureCtxRef.current.close();
4486
+ } catch (_) {
4487
+ }
4488
+ captureCtxRef.current = null;
4489
+ }
4490
+ if (playbackCtxRef.current) {
4491
+ try {
4492
+ playbackCtxRef.current.close();
4493
+ } catch (_) {
4494
+ }
4495
+ playbackCtxRef.current = null;
4496
+ }
4497
+ if (wsRef.current) {
4498
+ try {
4499
+ wsRef.current.close();
4500
+ } catch (_) {
4501
+ }
4502
+ wsRef.current = null;
4503
+ }
4504
+ nextPlayTimeRef.current = 0;
4505
+ setPartialTranscript("");
4506
+ setDuration(0);
4507
+ }, []);
4508
+ useEffect9(() => {
4509
+ return () => {
4510
+ cleanup();
4511
+ };
4512
+ }, [cleanup]);
4513
+ const playAudioChunk = useCallback4((base64Audio) => {
4514
+ if (!playbackCtxRef.current) {
4515
+ playbackCtxRef.current = new AudioContext({ sampleRate: 24e3 });
4516
+ }
4517
+ const ctx = playbackCtxRef.current;
4518
+ if (ctx.state === "suspended") {
4519
+ ctx.resume();
4520
+ }
4521
+ const float32Data = base64ToFloat32(base64Audio);
4522
+ const audioBuffer = ctx.createBuffer(1, float32Data.length, 24e3);
4523
+ audioBuffer.getChannelData(0).set(float32Data);
4524
+ const source = ctx.createBufferSource();
4525
+ source.buffer = audioBuffer;
4526
+ source.connect(ctx.destination);
4527
+ const currentTime = ctx.currentTime;
4528
+ const startTime = Math.max(currentTime, nextPlayTimeRef.current);
4529
+ source.start(startTime);
4530
+ nextPlayTimeRef.current = startTime + audioBuffer.duration;
4531
+ }, []);
4532
+ const handleMessage = useCallback4((msg) => {
4533
+ const cfg = configRef.current;
4534
+ switch (msg.type) {
4535
+ case "session_created":
4536
+ setState("active");
4537
+ startTimeRef.current = Date.now();
4538
+ durationIntervalRef.current = setInterval(() => {
4539
+ setDuration(Math.floor((Date.now() - startTimeRef.current) / 1e3));
4540
+ }, 1e3);
4541
+ break;
4542
+ case "audio_delta":
4543
+ if (msg.data?.chunk) {
4544
+ playAudioChunk(msg.data.chunk);
4545
+ }
4546
+ break;
4547
+ case "transcript":
4548
+ if (msg.data) {
4549
+ if (msg.data.partial) {
4550
+ setPartialTranscript(msg.data.content);
4551
+ } else {
4552
+ setPartialTranscript("");
4553
+ cfg.onTranscript?.({
4554
+ id: `vt-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
4555
+ role: msg.data.role,
4556
+ content: msg.data.content,
4557
+ partial: false,
4558
+ timestamp: /* @__PURE__ */ new Date()
4559
+ });
4560
+ }
4561
+ }
4562
+ break;
4563
+ case "tool_call":
4564
+ if (msg.data) {
4565
+ nextPlayTimeRef.current = 0;
4566
+ cfg.onTranscript?.({
4567
+ id: `vt-tool-${Date.now()}`,
4568
+ role: "system",
4569
+ content: `Using ${msg.data.name}...`,
4570
+ partial: false,
4571
+ timestamp: /* @__PURE__ */ new Date()
4572
+ });
4573
+ }
4574
+ break;
4575
+ case "tool_result":
4576
+ if (msg.data) {
4577
+ nextPlayTimeRef.current = 0;
4578
+ }
4579
+ break;
4580
+ case "error":
4581
+ setState("error");
4582
+ cfg.onError?.(new Error(msg.data?.message || "Voice session error"));
4583
+ break;
4584
+ }
4585
+ }, [playAudioChunk]);
4586
+ const startCapture = useCallback4(async () => {
4587
+ const ws = wsRef.current;
4588
+ if (!ws) return;
4589
+ try {
4590
+ captureCtxRef.current = new AudioContext();
4591
+ const nativeSampleRate = captureCtxRef.current.sampleRate;
4592
+ mediaStreamRef.current = await navigator.mediaDevices.getUserMedia({ audio: true });
4593
+ const source = captureCtxRef.current.createMediaStreamSource(mediaStreamRef.current);
4594
+ processorRef.current = captureCtxRef.current.createScriptProcessor(2048, 1, 1);
4595
+ processorRef.current.onaudioprocess = (e) => {
4596
+ if (!ws || ws.readyState !== WebSocket.OPEN) return;
4597
+ const inputData = e.inputBuffer.getChannelData(0);
4598
+ const resampledData = resampleAudio(inputData, nativeSampleRate, 16e3);
4599
+ const int16Data = float32ToInt16(resampledData);
4600
+ const base64Data = int16ToBase64(int16Data);
4601
+ ws.send(JSON.stringify({
4602
+ type: "audio",
4603
+ data: { chunk: base64Data }
4604
+ }));
4605
+ };
4606
+ source.connect(processorRef.current);
4607
+ processorRef.current.connect(captureCtxRef.current.destination);
4608
+ } catch (e) {
4609
+ configRef.current.onError?.(new Error("Microphone access denied"));
4610
+ cleanup();
4611
+ setState("idle");
4612
+ }
4613
+ }, [cleanup]);
4614
+ const start = useCallback4(() => {
4615
+ if (state !== "idle") return;
4616
+ setState("connecting");
4617
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
4618
+ const wsUrl = `${protocol}//${window.location.host}${config.apiUrl}/voice`;
4619
+ const ws = new WebSocket(wsUrl);
4620
+ wsRef.current = ws;
4621
+ ws.onopen = () => {
4622
+ const provider = configRef.current.provider || "openai";
4623
+ const voice = configRef.current.voice || "ash";
4624
+ ws.send(JSON.stringify({
4625
+ type: "start",
4626
+ data: { provider, voice }
4627
+ }));
4628
+ startCapture();
4629
+ };
4630
+ ws.onmessage = (event) => {
4631
+ try {
4632
+ const msg = JSON.parse(event.data);
4633
+ handleMessage(msg);
4634
+ } catch (_) {
4635
+ }
4636
+ };
4637
+ ws.onerror = () => {
4638
+ setState("error");
4639
+ configRef.current.onError?.(new Error("WebSocket connection failed"));
4640
+ };
4641
+ ws.onclose = () => {
4642
+ cleanup();
4643
+ setState("idle");
4644
+ };
4645
+ }, [state, config.apiUrl, startCapture, handleMessage, cleanup]);
4646
+ const stop = useCallback4(() => {
4647
+ cleanup();
4648
+ setState("idle");
4649
+ }, [cleanup]);
4650
+ const sendText = useCallback4((text) => {
4651
+ const ws = wsRef.current;
4652
+ if (!ws || ws.readyState !== WebSocket.OPEN) return;
4653
+ ws.send(JSON.stringify({
4654
+ type: "text",
4655
+ data: { content: text }
4656
+ }));
4657
+ }, []);
4658
+ return { state, partialTranscript, duration, start, stop, sendText };
4659
+ }
4660
+
4369
4661
  // src/components/Chat/Chat.tsx
4370
4662
  import { Fragment as Fragment6, jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
4371
4663
  var Chat = forwardRef(function Chat2({
@@ -4418,28 +4710,49 @@ var Chat = forwardRef(function Chat2({
4418
4710
  onWidgetRender,
4419
4711
  // Speech to text
4420
4712
  speechToText,
4713
+ // Realtime voice
4714
+ enableVoice = false,
4715
+ voiceProvider,
4716
+ voiceId,
4421
4717
  className
4422
4718
  }, 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) => {
4719
+ const [messages, setMessages] = useState10(initialMessages);
4720
+ const [isLoading, setIsLoading] = useState10(false);
4721
+ const [currentThreadId, setCurrentThreadId] = useState10(threadId || null);
4722
+ const [mode, setMode] = useState10(initialMode);
4723
+ const [chatToolName, setChatToolName] = useState10(null);
4724
+ const [commandState, setCommandState] = useState10("idle");
4725
+ const [commandResult, setCommandResult] = useState10(null);
4726
+ const [commandError, setCommandError] = useState10(null);
4727
+ const [progress, setProgress] = useState10(0);
4728
+ const [commandInput, setCommandInput] = useState10("");
4729
+ const [streamedContent, setStreamedContent] = useState10("");
4730
+ const [currentToolName, setCurrentToolName] = useState10(null);
4731
+ const [currentRequestId, setCurrentRequestId] = useState10(null);
4732
+ const [plan, setPlan] = useState10("");
4733
+ const [pendingCommand, setPendingCommand] = useState10("");
4734
+ const [internalPlanMode, setInternalPlanMode] = useState10(planMode);
4735
+ const [showSettingsMenu, setShowSettingsMenu] = useState10(false);
4736
+ const fileInputRef = useRef10(null);
4737
+ const handleVoiceTranscript = useCallback5((entry) => {
4738
+ const msg = {
4739
+ id: entry.id,
4740
+ role: entry.role === "system" ? "assistant" : entry.role,
4741
+ content: entry.content,
4742
+ timestamp: entry.timestamp,
4743
+ metadata: entry.role === "system" ? { isVoiceSystem: true } : { isVoice: true }
4744
+ };
4745
+ setMessages((prev) => [...prev, msg]);
4746
+ }, []);
4747
+ const voice = useVoiceSession({
4748
+ apiUrl: apiUrl || "",
4749
+ provider: voiceProvider,
4750
+ voice: voiceId,
4751
+ onTranscript: handleVoiceTranscript,
4752
+ onError
4753
+ });
4754
+ const [persistentWidgets, setPersistentWidgets] = useState10(/* @__PURE__ */ new Map());
4755
+ const updatePersistentWidgets = useCallback5((msgs) => {
4443
4756
  setPersistentWidgets((prev) => {
4444
4757
  const next = new Map(prev);
4445
4758
  let changed = false;
@@ -4457,12 +4770,12 @@ var Chat = forwardRef(function Chat2({
4457
4770
  return changed ? next : prev;
4458
4771
  });
4459
4772
  }, []);
4460
- useEffect9(() => {
4773
+ useEffect10(() => {
4461
4774
  updatePersistentWidgets(messages);
4462
4775
  }, [messages, updatePersistentWidgets]);
4463
4776
  const persistentWidgetList = useMemo2(() => Array.from(persistentWidgets.values()), [persistentWidgets]);
4464
4777
  const persistentWidgetIds = useMemo2(() => new Set(persistentWidgets.keys()), [persistentWidgets]);
4465
- const handleSendMessageRef = useRef9(null);
4778
+ const handleSendMessageRef = useRef10(null);
4466
4779
  useImperativeHandle(ref, () => ({
4467
4780
  sendMessage: async (text) => {
4468
4781
  if (handleSendMessageRef.current) {
@@ -4483,7 +4796,7 @@ var Chat = forwardRef(function Chat2({
4483
4796
  return context ? `${context}
4484
4797
  ${widgetContext}` : widgetContext;
4485
4798
  }, [context, enableWidgets, availableWidgets, compactWidgetContext]);
4486
- useEffect9(() => {
4799
+ useEffect10(() => {
4487
4800
  if (apiUrl || apiKey) {
4488
4801
  aptevaClient.configure({
4489
4802
  ...apiUrl && { apiUrl },
@@ -4491,15 +4804,15 @@ ${widgetContext}` : widgetContext;
4491
4804
  });
4492
4805
  }
4493
4806
  }, [apiUrl, apiKey]);
4494
- useEffect9(() => {
4807
+ useEffect10(() => {
4495
4808
  if (threadId) {
4496
4809
  onThreadChange?.(threadId);
4497
4810
  }
4498
4811
  }, [threadId, onThreadChange]);
4499
- useEffect9(() => {
4812
+ useEffect10(() => {
4500
4813
  setInternalPlanMode(planMode);
4501
4814
  }, [planMode]);
4502
- useEffect9(() => {
4815
+ useEffect10(() => {
4503
4816
  const handleClickOutside = (event) => {
4504
4817
  const target = event.target;
4505
4818
  if (showSettingsMenu && !target.closest(".settings-menu-container")) {
@@ -4519,7 +4832,7 @@ ${widgetContext}` : widgetContext;
4519
4832
  }
4520
4833
  };
4521
4834
  const defaultPlaceholder = mode === "chat" ? "Type a message..." : "Enter your command...";
4522
- const handleWidgetAction = useCallback4((action) => {
4835
+ const handleWidgetAction = useCallback5((action) => {
4523
4836
  onAction?.(action);
4524
4837
  if (action.type === "submit" && action.payload?.formData) {
4525
4838
  const formData = action.payload.formData;
@@ -5040,8 +5353,8 @@ ${planToExecute}`;
5040
5353
  /* @__PURE__ */ jsx26("div", { className: "apteva-chat-title", children: headerTitle }),
5041
5354
  /* @__PURE__ */ jsx26("div", { className: cn(
5042
5355
  "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" })
5356
+ 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"
5357
+ ), children: voice.state === "active" ? "Voice active" : voice.state === "connecting" ? "Connecting voice..." : isLoading ? chatToolName ? `Using ${chatToolName}...` : "Thinking..." : "Ready" })
5045
5358
  ] })
5046
5359
  ] }) }),
5047
5360
  mode === "chat" && /* @__PURE__ */ jsxs20(Fragment6, { children: [
@@ -5074,7 +5387,13 @@ ${planToExecute}`;
5074
5387
  onStop: handleStop,
5075
5388
  onFileUpload,
5076
5389
  onSwitchMode: showModeToggle ? () => handleModeChange("command") : void 0,
5077
- speechToText
5390
+ speechToText,
5391
+ enableVoice,
5392
+ voiceState: voice.state,
5393
+ voicePartialTranscript: voice.partialTranscript,
5394
+ voiceDuration: voice.duration,
5395
+ onVoiceStart: voice.start,
5396
+ onVoiceStop: voice.stop
5078
5397
  }
5079
5398
  )
5080
5399
  ] }),
@@ -5117,11 +5436,11 @@ ${planToExecute}`;
5117
5436
  });
5118
5437
 
5119
5438
  // src/components/Chat/CommandOutput.tsx
5120
- import { useState as useState10 } from "react";
5439
+ import { useState as useState11 } from "react";
5121
5440
  import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
5122
5441
 
5123
5442
  // src/components/Command/Command.tsx
5124
- import React, { useState as useState11, useEffect as useEffect10 } from "react";
5443
+ import React, { useState as useState12, useEffect as useEffect11 } from "react";
5125
5444
  import { Fragment as Fragment7, jsx as jsx28, jsxs as jsxs22 } from "react/jsx-runtime";
5126
5445
  function Command({
5127
5446
  agentId,
@@ -5149,28 +5468,28 @@ function Command({
5149
5468
  resultRenderer,
5150
5469
  className
5151
5470
  }) {
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);
5471
+ const [state, setState] = useState12("idle");
5472
+ const [result, setResult] = useState12(null);
5473
+ const [error, setError] = useState12(null);
5474
+ const [progress, setProgress] = useState12(0);
5475
+ const [command, setCommand] = useState12(initialCommand || "");
5476
+ const [streamedContent, setStreamedContent] = useState12("");
5477
+ const [plan, setPlan] = useState12("");
5478
+ const [pendingCommand, setPendingCommand] = useState12("");
5479
+ const [showPlanDetails, setShowPlanDetails] = useState12(false);
5480
+ const [uploadedFiles, setUploadedFiles] = useState12([]);
5481
+ const [showSettingsMenu, setShowSettingsMenu] = useState12(false);
5482
+ const [internalPlanMode, setInternalPlanMode] = useState12(planMode);
5164
5483
  const fileInputRef = React.useRef(null);
5165
- useEffect10(() => {
5484
+ useEffect11(() => {
5166
5485
  if (autoExecute && state === "idle" && command) {
5167
5486
  executeCommand();
5168
5487
  }
5169
5488
  }, [autoExecute]);
5170
- useEffect10(() => {
5489
+ useEffect11(() => {
5171
5490
  setInternalPlanMode(planMode);
5172
5491
  }, [planMode]);
5173
- useEffect10(() => {
5492
+ useEffect11(() => {
5174
5493
  const handleClickOutside = (event) => {
5175
5494
  const target = event.target;
5176
5495
  if (showSettingsMenu && !target.closest(".settings-menu-container")) {
@@ -6092,7 +6411,7 @@ ${planToExecute}`;
6092
6411
  }
6093
6412
 
6094
6413
  // src/components/Prompt/Prompt.tsx
6095
- import { useState as useState12 } from "react";
6414
+ import { useState as useState13 } from "react";
6096
6415
  import { jsx as jsx29, jsxs as jsxs23 } from "react/jsx-runtime";
6097
6416
  function Prompt({
6098
6417
  agentId,
@@ -6110,9 +6429,9 @@ function Prompt({
6110
6429
  showSuggestions = false,
6111
6430
  className
6112
6431
  }) {
6113
- const [value, setValue] = useState12(initialValue);
6114
- const [isLoading, setIsLoading] = useState12(false);
6115
- const [suggestions] = useState12(["Plan a trip", "Write a description", "Analyze data"]);
6432
+ const [value, setValue] = useState13(initialValue);
6433
+ const [isLoading, setIsLoading] = useState13(false);
6434
+ const [suggestions] = useState13(["Plan a trip", "Write a description", "Analyze data"]);
6116
6435
  const handleChange = (e) => {
6117
6436
  const newValue = e.target.value;
6118
6437
  if (!maxLength || newValue.length <= maxLength) {
@@ -6203,7 +6522,7 @@ function Prompt({
6203
6522
  }
6204
6523
 
6205
6524
  // src/components/Stream/Stream.tsx
6206
- import { useState as useState13, useEffect as useEffect11 } from "react";
6525
+ import { useState as useState14, useEffect as useEffect12 } from "react";
6207
6526
  import { jsx as jsx30, jsxs as jsxs24 } from "react/jsx-runtime";
6208
6527
  function Stream({
6209
6528
  agentId,
@@ -6220,10 +6539,10 @@ function Stream({
6220
6539
  typingSpeed = 30,
6221
6540
  className
6222
6541
  }) {
6223
- const [text, setText] = useState13("");
6224
- const [isStreaming, setIsStreaming] = useState13(false);
6225
- const [isComplete, setIsComplete] = useState13(false);
6226
- useEffect11(() => {
6542
+ const [text, setText] = useState14("");
6543
+ const [isStreaming, setIsStreaming] = useState14(false);
6544
+ const [isComplete, setIsComplete] = useState14(false);
6545
+ useEffect12(() => {
6227
6546
  if (autoStart && !isStreaming && !isComplete) {
6228
6547
  startStreaming();
6229
6548
  }
@@ -6300,7 +6619,7 @@ function Stream({
6300
6619
  }
6301
6620
 
6302
6621
  // src/components/Threads/ThreadList.tsx
6303
- import { useState as useState14 } from "react";
6622
+ import { useState as useState15 } from "react";
6304
6623
 
6305
6624
  // src/components/Threads/ThreadItem.tsx
6306
6625
  import { jsx as jsx31, jsxs as jsxs25 } from "react/jsx-runtime";
@@ -6364,7 +6683,7 @@ function ThreadList({
6364
6683
  showSearch = false,
6365
6684
  groupBy = "none"
6366
6685
  }) {
6367
- const [searchQuery, setSearchQuery] = useState14("");
6686
+ const [searchQuery, setSearchQuery] = useState15("");
6368
6687
  const filteredThreads = threads.filter(
6369
6688
  (thread) => thread.title.toLowerCase().includes(searchQuery.toLowerCase()) || thread.preview?.toLowerCase().includes(searchQuery.toLowerCase())
6370
6689
  );
@@ -6494,10 +6813,10 @@ function Threads({
6494
6813
  }
6495
6814
 
6496
6815
  // src/components/AutoInterface/AutoInterface.tsx
6497
- import { useState as useState16, useRef as useRef10, useCallback as useCallback5, useEffect as useEffect12 } from "react";
6816
+ import { useState as useState17, useRef as useRef11, useCallback as useCallback6, useEffect as useEffect13 } from "react";
6498
6817
 
6499
6818
  // src/components/AutoInterface/LayoutRenderer.tsx
6500
- import { useState as useState15 } from "react";
6819
+ import { useState as useState16 } from "react";
6501
6820
  import { Fragment as Fragment8, jsx as jsx34, jsxs as jsxs28 } from "react/jsx-runtime";
6502
6821
  var gapClasses = {
6503
6822
  none: "gap-0",
@@ -6613,7 +6932,7 @@ function SidebarLayout({ node, renderNode }) {
6613
6932
  function TabsLayout({ node, renderNode }) {
6614
6933
  const { labels = [], defaultTab = 0 } = node.props || {};
6615
6934
  const children = node.children || [];
6616
- const [activeTab, setActiveTab] = useState15(defaultTab);
6935
+ const [activeTab, setActiveTab] = useState16(defaultTab);
6617
6936
  return /* @__PURE__ */ jsxs28("div", { children: [
6618
6937
  /* @__PURE__ */ jsx34("div", { className: "flex border-b border-neutral-200 dark:border-neutral-700 mb-4", children: labels.map((label, idx) => /* @__PURE__ */ jsx34(
6619
6938
  "button",
@@ -6764,19 +7083,19 @@ function AutoInterface({
6764
7083
  theme,
6765
7084
  className
6766
7085
  }) {
6767
- const [interfaceSpec, setInterfaceSpec] = useState16(initialInterface || null);
6768
- const [isGenerating, setIsGenerating] = useState16(false);
6769
- const [chatCollapsed, setChatCollapsed] = useState16(false);
6770
- const chatRef = useRef10(null);
7086
+ const [interfaceSpec, setInterfaceSpec] = useState17(initialInterface || null);
7087
+ const [isGenerating, setIsGenerating] = useState17(false);
7088
+ const [chatCollapsed, setChatCollapsed] = useState17(false);
7089
+ const chatRef = useRef11(null);
6771
7090
  const systemContext = [
6772
7091
  generateInterfaceContext(),
6773
7092
  context || ""
6774
7093
  ].filter(Boolean).join("\n\n");
6775
- const updateInterface = useCallback5((newSpec) => {
7094
+ const updateInterface = useCallback6((newSpec) => {
6776
7095
  setInterfaceSpec(newSpec);
6777
7096
  onInterfaceChange?.(newSpec);
6778
7097
  }, [onInterfaceChange]);
6779
- const handleAction = useCallback5((action) => {
7098
+ const handleAction = useCallback6((action) => {
6780
7099
  onAction?.(action);
6781
7100
  if (chatRef.current) {
6782
7101
  chatRef.current.sendMessage(
@@ -6784,7 +7103,7 @@ function AutoInterface({
6784
7103
  );
6785
7104
  }
6786
7105
  }, [onAction]);
6787
- const handleMessageComplete = useCallback5((result) => {
7106
+ const handleMessageComplete = useCallback6((result) => {
6788
7107
  if (!result?.data) return;
6789
7108
  const text = typeof result.data === "string" ? result.data : result.data.message || "";
6790
7109
  console.log("[AutoInterface] Chat message complete, text (" + text.length + " chars):", text.substring(0, 300));
@@ -6805,7 +7124,7 @@ function AutoInterface({
6805
7124
  }
6806
7125
  setIsGenerating(false);
6807
7126
  }, [interfaceSpec, updateInterface]);
6808
- useEffect12(() => {
7127
+ useEffect13(() => {
6809
7128
  if (!initialPrompt || initialInterface || useMock) return;
6810
7129
  if (!apiUrl) return;
6811
7130
  let cancelled = false;
@@ -6930,29 +7249,29 @@ function getThemeScript() {
6930
7249
  }
6931
7250
 
6932
7251
  // src/hooks/useInterfaceState.ts
6933
- import { useState as useState17, useCallback as useCallback6 } from "react";
7252
+ import { useState as useState18, useCallback as useCallback7 } from "react";
6934
7253
  function useInterfaceState(initialSpec) {
6935
- const [spec, setSpec] = useState17(initialSpec || null);
6936
- const [isStreaming, setIsStreaming] = useState17(false);
6937
- const setInterface = useCallback6((newSpec) => {
7254
+ const [spec, setSpec] = useState18(initialSpec || null);
7255
+ const [isStreaming, setIsStreaming] = useState18(false);
7256
+ const setInterface = useCallback7((newSpec) => {
6938
7257
  setSpec(newSpec);
6939
7258
  }, []);
6940
- const clearInterface = useCallback6(() => {
7259
+ const clearInterface = useCallback7(() => {
6941
7260
  setSpec(null);
6942
7261
  }, []);
6943
- const applyInterfaceUpdate = useCallback6((update) => {
7262
+ const applyInterfaceUpdate = useCallback7((update) => {
6944
7263
  setSpec((prev) => {
6945
7264
  if (!prev) return prev;
6946
7265
  return applyUpdate(prev, update);
6947
7266
  });
6948
7267
  }, []);
6949
- const applyInterfaceUpdates = useCallback6((updates) => {
7268
+ const applyInterfaceUpdates = useCallback7((updates) => {
6950
7269
  setSpec((prev) => {
6951
7270
  if (!prev) return prev;
6952
7271
  return applyUpdates(prev, updates);
6953
7272
  });
6954
7273
  }, []);
6955
- const getNode = useCallback6((id) => {
7274
+ const getNode = useCallback7((id) => {
6956
7275
  if (!spec) return null;
6957
7276
  return findNode(spec.root, id);
6958
7277
  }, [spec]);
@@ -6969,7 +7288,7 @@ function useInterfaceState(initialSpec) {
6969
7288
  }
6970
7289
 
6971
7290
  // src/hooks/useInterfaceAI.ts
6972
- import { useCallback as useCallback7, useRef as useRef11 } from "react";
7291
+ import { useCallback as useCallback8, useRef as useRef12 } from "react";
6973
7292
  function useInterfaceAI({
6974
7293
  agentId,
6975
7294
  apiUrl,
@@ -6981,15 +7300,15 @@ function useInterfaceAI({
6981
7300
  onStreamStart,
6982
7301
  onStreamEnd
6983
7302
  }) {
6984
- const threadIdRef = useRef11(null);
6985
- const accumulatedTextRef = useRef11("");
7303
+ const threadIdRef = useRef12(null);
7304
+ const accumulatedTextRef = useRef12("");
6986
7305
  if (apiUrl || apiKey) {
6987
7306
  aptevaClient.configure({
6988
7307
  ...apiUrl && { apiUrl },
6989
7308
  ...apiKey && { apiKey }
6990
7309
  });
6991
7310
  }
6992
- const sendMessage = useCallback7(async (message) => {
7311
+ const sendMessage = useCallback8(async (message) => {
6993
7312
  accumulatedTextRef.current = "";
6994
7313
  onStreamStart?.();
6995
7314
  const systemPrompt = [