@apteva/apteva-kit 0.1.111 → 0.1.113

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 useEffect8, useRef as useRef9, useMemo as useMemo2, useCallback as useCallback3, forwardRef, useImperativeHandle } from "react";
7
+ import { useState as useState9, useEffect as useEffect9, useRef as useRef9, useMemo as useMemo2, useCallback as useCallback4, forwardRef, useImperativeHandle } from "react";
8
8
 
9
9
  // src/components/Chat/MessageList.tsx
10
10
  import { useEffect as useEffect7, useRef as useRef6 } from "react";
@@ -1439,7 +1439,74 @@ function Table({ widget, onAction }) {
1439
1439
 
1440
1440
  // src/components/Widgets/widget-library/Form.tsx
1441
1441
  import { useState as useState3, useRef as useRef3, useEffect as useEffect3, useCallback } from "react";
1442
+ import { createPortal } from "react-dom";
1442
1443
  import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1444
+ function PortalDropdown({
1445
+ anchorRef,
1446
+ open,
1447
+ onClose,
1448
+ width,
1449
+ children
1450
+ }) {
1451
+ const dropdownRef = useRef3(null);
1452
+ const [pos, setPos] = useState3({ top: 0, left: 0, width: 0 });
1453
+ useEffect3(() => {
1454
+ if (!open || !anchorRef.current) return;
1455
+ const update = () => {
1456
+ const rect = anchorRef.current.getBoundingClientRect();
1457
+ setPos({
1458
+ top: rect.bottom + window.scrollY + 4,
1459
+ left: rect.left + window.scrollX,
1460
+ width: rect.width
1461
+ });
1462
+ };
1463
+ update();
1464
+ window.addEventListener("scroll", update, true);
1465
+ window.addEventListener("resize", update);
1466
+ return () => {
1467
+ window.removeEventListener("scroll", update, true);
1468
+ window.removeEventListener("resize", update);
1469
+ };
1470
+ }, [open, anchorRef]);
1471
+ useEffect3(() => {
1472
+ if (!open) return;
1473
+ const handler = (e) => {
1474
+ const target = e.target;
1475
+ if (dropdownRef.current && !dropdownRef.current.contains(target) && anchorRef.current && !anchorRef.current.contains(target)) {
1476
+ onClose();
1477
+ }
1478
+ };
1479
+ document.addEventListener("mousedown", handler);
1480
+ return () => document.removeEventListener("mousedown", handler);
1481
+ }, [open, onClose, anchorRef]);
1482
+ useEffect3(() => {
1483
+ if (!open) return;
1484
+ const handler = (e) => {
1485
+ if (e.key === "Escape") onClose();
1486
+ };
1487
+ document.addEventListener("keydown", handler);
1488
+ return () => document.removeEventListener("keydown", handler);
1489
+ }, [open, onClose]);
1490
+ if (!open) return null;
1491
+ return createPortal(
1492
+ /* @__PURE__ */ jsx6(
1493
+ "div",
1494
+ {
1495
+ ref: dropdownRef,
1496
+ className: "apteva-widget",
1497
+ style: {
1498
+ position: "absolute",
1499
+ top: pos.top,
1500
+ left: pos.left,
1501
+ width: width === "match" ? pos.width : void 0,
1502
+ zIndex: 99999
1503
+ },
1504
+ children
1505
+ }
1506
+ ),
1507
+ document.body
1508
+ );
1509
+ }
1443
1510
  function CustomSelect({
1444
1511
  name,
1445
1512
  value,
@@ -1449,17 +1516,10 @@ function CustomSelect({
1449
1516
  onChange
1450
1517
  }) {
1451
1518
  const [open, setOpen] = useState3(false);
1452
- const ref = useRef3(null);
1519
+ const triggerRef = useRef3(null);
1453
1520
  const selected = options?.find((o) => o.value === value);
1454
- useEffect3(() => {
1455
- if (!open) return;
1456
- const handler = (e) => {
1457
- if (ref.current && !ref.current.contains(e.target)) setOpen(false);
1458
- };
1459
- document.addEventListener("mousedown", handler);
1460
- return () => document.removeEventListener("mousedown", handler);
1461
- }, [open]);
1462
- return /* @__PURE__ */ jsxs4("div", { ref, className: "apteva-select relative", children: [
1521
+ const close = useCallback(() => setOpen(false), []);
1522
+ return /* @__PURE__ */ jsxs4("div", { className: "apteva-select relative", children: [
1463
1523
  /* @__PURE__ */ jsxs4(
1464
1524
  "select",
1465
1525
  {
@@ -1480,6 +1540,7 @@ function CustomSelect({
1480
1540
  /* @__PURE__ */ jsxs4(
1481
1541
  "button",
1482
1542
  {
1543
+ ref: triggerRef,
1483
1544
  type: "button",
1484
1545
  onClick: () => setOpen(!open),
1485
1546
  className: "apteva-select-trigger w-full flex items-center justify-between px-3 py-2 rounded-lg border transition-colors text-left",
@@ -1498,14 +1559,14 @@ function CustomSelect({
1498
1559
  ]
1499
1560
  }
1500
1561
  ),
1501
- open && /* @__PURE__ */ jsx6("div", { className: "apteva-select-dropdown absolute z-50 w-full mt-1 rounded-lg border shadow-lg overflow-hidden", children: /* @__PURE__ */ jsxs4("div", { className: "max-h-48 overflow-y-auto py-1", children: [
1562
+ /* @__PURE__ */ jsx6(PortalDropdown, { anchorRef: triggerRef, open, onClose: close, width: "match", children: /* @__PURE__ */ jsx6("div", { className: "apteva-select-dropdown rounded-lg border shadow-lg overflow-hidden", children: /* @__PURE__ */ jsxs4("div", { className: "max-h-48 overflow-y-auto py-1", children: [
1502
1563
  options?.map((opt) => /* @__PURE__ */ jsx6(
1503
1564
  "button",
1504
1565
  {
1505
1566
  type: "button",
1506
1567
  onClick: () => {
1507
1568
  onChange(opt.value);
1508
- setOpen(false);
1569
+ close();
1509
1570
  },
1510
1571
  className: `apteva-select-option w-full text-left px-3 py-2 text-sm transition-colors ${opt.value === value ? "apteva-select-option-active" : ""}`,
1511
1572
  children: opt.label
@@ -1513,7 +1574,7 @@ function CustomSelect({
1513
1574
  opt.value
1514
1575
  )),
1515
1576
  (!options || options.length === 0) && /* @__PURE__ */ jsx6("div", { className: "px-3 py-2 text-sm apteva-select-placeholder", children: "No options" })
1516
- ] }) })
1577
+ ] }) }) })
1517
1578
  ] });
1518
1579
  }
1519
1580
  function CustomDatePicker({
@@ -1524,7 +1585,8 @@ function CustomDatePicker({
1524
1585
  onChange
1525
1586
  }) {
1526
1587
  const [open, setOpen] = useState3(false);
1527
- const ref = useRef3(null);
1588
+ const triggerRef = useRef3(null);
1589
+ const close = useCallback(() => setOpen(false), []);
1528
1590
  const parseDate = (v) => {
1529
1591
  if (v) {
1530
1592
  const [y, m, d] = v.split("-").map(Number);
@@ -1536,14 +1598,6 @@ function CustomDatePicker({
1536
1598
  const parsed = parseDate(value);
1537
1599
  const [viewYear, setViewYear] = useState3(parsed.year);
1538
1600
  const [viewMonth, setViewMonth] = useState3(parsed.month);
1539
- useEffect3(() => {
1540
- if (!open) return;
1541
- const handler = (e) => {
1542
- if (ref.current && !ref.current.contains(e.target)) setOpen(false);
1543
- };
1544
- document.addEventListener("mousedown", handler);
1545
- return () => document.removeEventListener("mousedown", handler);
1546
- }, [open]);
1547
1601
  useEffect3(() => {
1548
1602
  if (value) {
1549
1603
  const p = parseDate(value);
@@ -1570,7 +1624,7 @@ function CustomDatePicker({
1570
1624
  const m = String(viewMonth + 1).padStart(2, "0");
1571
1625
  const d = String(day).padStart(2, "0");
1572
1626
  onChange(`${viewYear}-${m}-${d}`);
1573
- setOpen(false);
1627
+ close();
1574
1628
  };
1575
1629
  const formatDisplay = (v) => {
1576
1630
  if (!v) return "";
@@ -1588,7 +1642,7 @@ function CustomDatePicker({
1588
1642
  const now = /* @__PURE__ */ new Date();
1589
1643
  return now.getFullYear() === viewYear && now.getMonth() === viewMonth && now.getDate() === day;
1590
1644
  };
1591
- return /* @__PURE__ */ jsxs4("div", { ref, className: "apteva-datepicker relative", children: [
1645
+ return /* @__PURE__ */ jsxs4("div", { className: "apteva-datepicker relative", children: [
1592
1646
  /* @__PURE__ */ jsx6(
1593
1647
  "input",
1594
1648
  {
@@ -1606,6 +1660,7 @@ function CustomDatePicker({
1606
1660
  /* @__PURE__ */ jsxs4(
1607
1661
  "button",
1608
1662
  {
1663
+ ref: triggerRef,
1609
1664
  type: "button",
1610
1665
  onClick: () => setOpen(!open),
1611
1666
  className: "apteva-datepicker-trigger w-full flex items-center justify-between px-3 py-2 rounded-lg border transition-colors text-left",
@@ -1615,7 +1670,7 @@ function CustomDatePicker({
1615
1670
  ]
1616
1671
  }
1617
1672
  ),
1618
- open && /* @__PURE__ */ jsxs4("div", { className: "apteva-datepicker-dropdown absolute z-50 mt-1 rounded-lg border shadow-lg overflow-hidden", children: [
1673
+ /* @__PURE__ */ jsx6(PortalDropdown, { anchorRef: triggerRef, open, onClose: close, width: "auto", children: /* @__PURE__ */ jsxs4("div", { className: "apteva-datepicker-dropdown rounded-lg border shadow-lg overflow-hidden", children: [
1619
1674
  /* @__PURE__ */ jsxs4("div", { className: "apteva-datepicker-header flex items-center justify-between px-3 py-2", children: [
1620
1675
  /* @__PURE__ */ jsx6("button", { type: "button", onClick: prevMonth, className: "apteva-datepicker-nav p-1 rounded transition-colors", children: /* @__PURE__ */ jsx6("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx6("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) }) }),
1621
1676
  /* @__PURE__ */ jsxs4("span", { className: "apteva-datepicker-title text-sm font-semibold", children: [
@@ -1648,7 +1703,7 @@ function CustomDatePicker({
1648
1703
  );
1649
1704
  })
1650
1705
  ] })
1651
- ] })
1706
+ ] }) })
1652
1707
  ] });
1653
1708
  }
1654
1709
  function Form({ widget, onAction }) {
@@ -3281,9 +3336,13 @@ function MessageList({
3281
3336
  }
3282
3337
 
3283
3338
  // src/components/Chat/Composer.tsx
3284
- import { useState as useState6, useRef as useRef7 } from "react";
3339
+ import { useState as useState6, useEffect as useEffect8, useCallback as useCallback3, useRef as useRef7 } from "react";
3285
3340
  import { Fragment as Fragment4, jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
3286
- function Composer({ onSendMessage, placeholder = "Type a message...", disabled = false, isLoading = false, onStop, onFileUpload, onSwitchMode }) {
3341
+ var getSpeechRecognition = () => {
3342
+ if (typeof window === "undefined") return null;
3343
+ return window.SpeechRecognition || window.webkitSpeechRecognition || null;
3344
+ };
3345
+ function Composer({ onSendMessage, placeholder = "Type a message...", disabled = false, isLoading = false, onStop, onFileUpload, onSwitchMode, speechToText }) {
3287
3346
  const [text, setText] = useState6("");
3288
3347
  const [showMenu, setShowMenu] = useState6(false);
3289
3348
  const [pendingFiles, setPendingFiles] = useState6([]);
@@ -3292,6 +3351,190 @@ function Composer({ onSendMessage, placeholder = "Type a message...", disabled =
3292
3351
  const textareaRef = useRef7(null);
3293
3352
  const fileInputRef = useRef7(null);
3294
3353
  const menuButtonRef = useRef7(null);
3354
+ const [isRecording, setIsRecording] = useState6(false);
3355
+ const [recordingTime, setRecordingTime] = useState6(0);
3356
+ const [transcriptFlash, setTranscriptFlash] = useState6(null);
3357
+ const recognitionRef = useRef7(null);
3358
+ const mediaStreamRef = useRef7(null);
3359
+ const audioContextRef = useRef7(null);
3360
+ const analyserRef = useRef7(null);
3361
+ const canvasRef = useRef7(null);
3362
+ const animFrameRef = useRef7(0);
3363
+ const silenceTimerRef = useRef7(null);
3364
+ const recordingTimerRef = useRef7(null);
3365
+ const finalTranscriptRef = useRef7("");
3366
+ const manualStopRef = useRef7(false);
3367
+ const sttConfig = speechToText ? typeof speechToText === "object" ? speechToText : {} : null;
3368
+ const sttSupported = !!sttConfig && !!getSpeechRecognition();
3369
+ const silenceTimeout = sttConfig?.silenceTimeout ?? 2e3;
3370
+ const autoSend = sttConfig?.autoSend !== false;
3371
+ useEffect8(() => {
3372
+ return () => {
3373
+ stopRecording(true);
3374
+ };
3375
+ }, []);
3376
+ const drawWaveform = useCallback3(() => {
3377
+ const canvas = canvasRef.current;
3378
+ const analyser = analyserRef.current;
3379
+ if (!canvas || !analyser) return;
3380
+ const ctx = canvas.getContext("2d");
3381
+ if (!ctx) return;
3382
+ const bufferLength = analyser.frequencyBinCount;
3383
+ const dataArray = new Uint8Array(bufferLength);
3384
+ const draw = () => {
3385
+ animFrameRef.current = requestAnimationFrame(draw);
3386
+ analyser.getByteTimeDomainData(dataArray);
3387
+ const { width, height } = canvas;
3388
+ ctx.clearRect(0, 0, width, height);
3389
+ const barCount = 32;
3390
+ const barWidth = Math.max(2, (width - (barCount - 1) * 2) / barCount);
3391
+ const gap = 2;
3392
+ const samplesPerBar = Math.floor(bufferLength / barCount);
3393
+ for (let i = 0; i < barCount; i++) {
3394
+ let sum = 0;
3395
+ for (let j = 0; j < samplesPerBar; j++) {
3396
+ const val = dataArray[i * samplesPerBar + j] - 128;
3397
+ sum += Math.abs(val);
3398
+ }
3399
+ const avg = sum / samplesPerBar;
3400
+ const barHeight = Math.max(3, avg / 128 * height * 2.5);
3401
+ const x = i * (barWidth + gap);
3402
+ const y = (height - barHeight) / 2;
3403
+ ctx.fillStyle = "#3b82f6";
3404
+ ctx.beginPath();
3405
+ ctx.roundRect(x, y, barWidth, barHeight, barWidth / 2);
3406
+ ctx.fill();
3407
+ }
3408
+ };
3409
+ draw();
3410
+ }, []);
3411
+ const startRecording = useCallback3(async () => {
3412
+ const SpeechRecognitionCtor = getSpeechRecognition();
3413
+ if (!SpeechRecognitionCtor) return;
3414
+ try {
3415
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
3416
+ mediaStreamRef.current = stream;
3417
+ const audioCtx = new AudioContext();
3418
+ audioContextRef.current = audioCtx;
3419
+ const source = audioCtx.createMediaStreamSource(stream);
3420
+ const analyser = audioCtx.createAnalyser();
3421
+ analyser.fftSize = 256;
3422
+ source.connect(analyser);
3423
+ analyserRef.current = analyser;
3424
+ const recognition = new SpeechRecognitionCtor();
3425
+ recognition.continuous = true;
3426
+ recognition.interimResults = true;
3427
+ recognition.lang = sttConfig?.language || navigator.language || "en-US";
3428
+ recognitionRef.current = recognition;
3429
+ finalTranscriptRef.current = "";
3430
+ manualStopRef.current = false;
3431
+ recognition.onresult = (event) => {
3432
+ let final = "";
3433
+ let interim = "";
3434
+ for (let i = 0; i < event.results.length; i++) {
3435
+ const result = event.results[i];
3436
+ if (result.isFinal) {
3437
+ final += result[0].transcript;
3438
+ } else {
3439
+ interim += result[0].transcript;
3440
+ }
3441
+ }
3442
+ finalTranscriptRef.current = final;
3443
+ if (silenceTimerRef.current) {
3444
+ clearTimeout(silenceTimerRef.current);
3445
+ }
3446
+ silenceTimerRef.current = setTimeout(() => {
3447
+ stopRecording(false);
3448
+ }, silenceTimeout);
3449
+ };
3450
+ recognition.onerror = (event) => {
3451
+ if (event.error !== "aborted") {
3452
+ console.warn("Speech recognition error:", event.error);
3453
+ }
3454
+ stopRecording(true);
3455
+ };
3456
+ recognition.onend = () => {
3457
+ if (!manualStopRef.current && isRecording) {
3458
+ finishRecording();
3459
+ }
3460
+ };
3461
+ recognition.start();
3462
+ setIsRecording(true);
3463
+ setRecordingTime(0);
3464
+ recordingTimerRef.current = setInterval(() => {
3465
+ setRecordingTime((t) => t + 1);
3466
+ }, 1e3);
3467
+ requestAnimationFrame(() => drawWaveform());
3468
+ silenceTimerRef.current = setTimeout(() => {
3469
+ stopRecording(false);
3470
+ }, silenceTimeout + 1e3);
3471
+ } catch (err) {
3472
+ console.warn("Microphone access denied or error:", err);
3473
+ setFileError("Microphone access denied");
3474
+ setTimeout(() => setFileError(null), 3e3);
3475
+ }
3476
+ }, [sttConfig?.language, silenceTimeout, drawWaveform]);
3477
+ const finishRecording = useCallback3(() => {
3478
+ const transcript = finalTranscriptRef.current.trim();
3479
+ setIsRecording(false);
3480
+ setRecordingTime(0);
3481
+ if (transcript) {
3482
+ if (autoSend) {
3483
+ setTranscriptFlash(transcript);
3484
+ setTimeout(() => {
3485
+ setTranscriptFlash(null);
3486
+ onSendMessage(transcript);
3487
+ }, 600);
3488
+ } else {
3489
+ setText((prev) => prev ? `${prev} ${transcript}` : transcript);
3490
+ }
3491
+ }
3492
+ }, [autoSend, onSendMessage]);
3493
+ const stopRecording = useCallback3((isCleanupOnly) => {
3494
+ manualStopRef.current = true;
3495
+ if (silenceTimerRef.current) {
3496
+ clearTimeout(silenceTimerRef.current);
3497
+ silenceTimerRef.current = null;
3498
+ }
3499
+ if (recordingTimerRef.current) {
3500
+ clearInterval(recordingTimerRef.current);
3501
+ recordingTimerRef.current = null;
3502
+ }
3503
+ if (animFrameRef.current) {
3504
+ cancelAnimationFrame(animFrameRef.current);
3505
+ animFrameRef.current = 0;
3506
+ }
3507
+ if (recognitionRef.current) {
3508
+ try {
3509
+ recognitionRef.current.stop();
3510
+ } catch (_e) {
3511
+ }
3512
+ recognitionRef.current = null;
3513
+ }
3514
+ if (mediaStreamRef.current) {
3515
+ mediaStreamRef.current.getTracks().forEach((t) => t.stop());
3516
+ mediaStreamRef.current = null;
3517
+ }
3518
+ if (audioContextRef.current) {
3519
+ try {
3520
+ audioContextRef.current.close();
3521
+ } catch (_e) {
3522
+ }
3523
+ audioContextRef.current = null;
3524
+ }
3525
+ analyserRef.current = null;
3526
+ if (!isCleanupOnly) {
3527
+ finishRecording();
3528
+ } else {
3529
+ setIsRecording(false);
3530
+ setRecordingTime(0);
3531
+ }
3532
+ }, [finishRecording]);
3533
+ const formatTime = (seconds) => {
3534
+ const m = Math.floor(seconds / 60);
3535
+ const s = seconds % 60;
3536
+ return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
3537
+ };
3295
3538
  const handleKeyDown = (e) => {
3296
3539
  if (e.key === "Enter" && !e.shiftKey) {
3297
3540
  e.preventDefault();
@@ -3374,12 +3617,17 @@ function Composer({ onSendMessage, placeholder = "Type a message...", disabled =
3374
3617
  }
3375
3618
  return /* @__PURE__ */ jsx23("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx23("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" }) });
3376
3619
  };
3620
+ const hasMic = sttSupported && !isRecording;
3621
+ const gridCols = hasMic ? "auto 1fr auto auto" : "auto 1fr auto";
3622
+ 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"';
3623
+ const gridColsRecording = "auto 1fr auto";
3377
3624
  return /* @__PURE__ */ jsxs17("div", { className: "px-4 py-3 relative", children: [
3378
3625
  fileError && /* @__PURE__ */ jsx23("div", { className: "apteva-file-error", children: /* @__PURE__ */ jsxs17("div", { className: "apteva-file-error-content", children: [
3379
3626
  /* @__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" }) }),
3380
3627
  /* @__PURE__ */ jsx23("span", { children: fileError })
3381
3628
  ] }) }),
3382
- pendingFiles.length > 0 && /* @__PURE__ */ jsx23("div", { className: "apteva-file-preview", children: pendingFiles.map((pf, index) => /* @__PURE__ */ jsxs17("div", { className: "apteva-file-item", children: [
3629
+ transcriptFlash && /* @__PURE__ */ jsx23("div", { className: "apteva-transcript-flash", children: /* @__PURE__ */ jsx23("span", { children: transcriptFlash }) }),
3630
+ pendingFiles.length > 0 && !isRecording && /* @__PURE__ */ jsx23("div", { className: "apteva-file-preview", children: pendingFiles.map((pf, index) => /* @__PURE__ */ jsxs17("div", { className: "apteva-file-item", children: [
3383
3631
  pf.preview ? /* @__PURE__ */ jsx23("img", { src: pf.preview, alt: pf.file.name, className: "apteva-file-thumb" }) : /* @__PURE__ */ jsx23("div", { className: "apteva-file-icon", children: getFileIcon(pf.file.type) }),
3384
3632
  /* @__PURE__ */ jsxs17("div", { className: "apteva-file-info", children: [
3385
3633
  /* @__PURE__ */ jsx23("span", { className: "apteva-file-name", children: pf.file.name }),
@@ -3400,12 +3648,12 @@ function Composer({ onSendMessage, placeholder = "Type a message...", disabled =
3400
3648
  {
3401
3649
  className: "apteva-composer",
3402
3650
  style: {
3403
- gridTemplateColumns: "auto 1fr auto",
3404
- gridTemplateAreas: isMultiLine ? '"textarea textarea textarea" "plus . send"' : '"plus textarea send"',
3651
+ gridTemplateColumns: isRecording ? gridColsRecording : gridCols,
3652
+ gridTemplateAreas: isRecording ? '"plus waveform stop"' : gridAreas,
3405
3653
  alignItems: "end"
3406
3654
  },
3407
3655
  children: [
3408
- /* @__PURE__ */ jsxs17("div", { className: "relative flex-shrink-0 self-end", style: { gridArea: "plus" }, children: [
3656
+ /* @__PURE__ */ jsx23("div", { className: "relative flex-shrink-0 self-end", style: { gridArea: "plus" }, children: isRecording ? /* @__PURE__ */ jsx23("div", { className: "apteva-composer-rec-dot", title: "Recording...", children: /* @__PURE__ */ jsx23("span", {}) }) : /* @__PURE__ */ jsxs17(Fragment4, { children: [
3409
3657
  /* @__PURE__ */ jsx23(
3410
3658
  "button",
3411
3659
  {
@@ -3459,39 +3707,77 @@ function Composer({ onSendMessage, placeholder = "Type a message...", disabled =
3459
3707
  }
3460
3708
  )
3461
3709
  ] })
3462
- ] }),
3463
- /* @__PURE__ */ jsx23(
3464
- "textarea",
3465
- {
3466
- ref: textareaRef,
3467
- value: text,
3468
- onChange: handleChange,
3469
- onKeyDown: handleKeyDown,
3470
- placeholder,
3471
- disabled,
3472
- className: "apteva-composer-textarea resize-none bg-transparent border-none focus:outline-none !text-neutral-900 dark:!text-neutral-100 placeholder-neutral-400 dark:placeholder-neutral-500 py-1 disabled:opacity-50 disabled:cursor-not-allowed overflow-y-auto max-h-[200px]",
3473
- style: { gridArea: "textarea" },
3474
- rows: 1
3475
- }
3476
- ),
3477
- /* @__PURE__ */ jsx23("div", { className: "self-end", style: { gridArea: "send" }, children: isLoading && onStop ? /* @__PURE__ */ jsx23(
3478
- "button",
3479
- {
3480
- onClick: onStop,
3481
- className: "apteva-composer-stop-btn",
3482
- title: "Stop generation",
3483
- 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" }) })
3484
- }
3485
- ) : /* @__PURE__ */ jsx23(
3486
- "button",
3487
- {
3488
- onClick: handleSend,
3489
- disabled: !text.trim() && pendingFiles.length === 0 || disabled,
3490
- className: "apteva-composer-send-btn w-8 h-8 rounded-lg flex items-center justify-center font-bold transition-all flex-shrink-0 border border-neutral-300 dark:border-neutral-600 bg-white dark:bg-neutral-800 !text-neutral-700 dark:!text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700 disabled:opacity-30 disabled:cursor-not-allowed !text-lg",
3491
- title: "Send message",
3492
- 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("path", { d: "M8 3L8 13M8 3L4 7M8 3L12 7", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
3493
- }
3494
- ) })
3710
+ ] }) }),
3711
+ isRecording ? /* @__PURE__ */ jsxs17(Fragment4, { children: [
3712
+ /* @__PURE__ */ jsxs17("div", { className: "apteva-composer-waveform", style: { gridArea: "waveform" }, children: [
3713
+ /* @__PURE__ */ jsx23(
3714
+ "canvas",
3715
+ {
3716
+ ref: canvasRef,
3717
+ width: 300,
3718
+ height: 36,
3719
+ className: "apteva-composer-waveform-canvas"
3720
+ }
3721
+ ),
3722
+ /* @__PURE__ */ jsx23("span", { className: "apteva-composer-recording-timer", children: formatTime(recordingTime) })
3723
+ ] }),
3724
+ /* @__PURE__ */ jsx23("div", { className: "self-end", style: { gridArea: "stop" }, children: /* @__PURE__ */ jsx23(
3725
+ "button",
3726
+ {
3727
+ onClick: () => stopRecording(false),
3728
+ className: "apteva-composer-stop-btn",
3729
+ title: "Stop recording",
3730
+ 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" }) })
3731
+ }
3732
+ ) })
3733
+ ] }) : /* @__PURE__ */ jsxs17(Fragment4, { children: [
3734
+ /* @__PURE__ */ jsx23(
3735
+ "textarea",
3736
+ {
3737
+ ref: textareaRef,
3738
+ value: text,
3739
+ onChange: handleChange,
3740
+ onKeyDown: handleKeyDown,
3741
+ placeholder,
3742
+ disabled,
3743
+ className: "apteva-composer-textarea resize-none bg-transparent border-none focus:outline-none !text-neutral-900 dark:!text-neutral-100 placeholder-neutral-400 dark:placeholder-neutral-500 py-1 disabled:opacity-50 disabled:cursor-not-allowed overflow-y-auto max-h-[200px]",
3744
+ style: { gridArea: "textarea" },
3745
+ rows: 1
3746
+ }
3747
+ ),
3748
+ sttSupported && /* @__PURE__ */ jsx23("div", { className: "self-end", style: { gridArea: "mic" }, children: /* @__PURE__ */ jsx23(
3749
+ "button",
3750
+ {
3751
+ onClick: startRecording,
3752
+ disabled: disabled || isLoading,
3753
+ className: "apteva-composer-mic-btn",
3754
+ title: "Voice input",
3755
+ children: /* @__PURE__ */ jsxs17("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
3756
+ /* @__PURE__ */ jsx23("path", { d: "M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z", fill: "currentColor" }),
3757
+ /* @__PURE__ */ jsx23("path", { d: "M19 10v2a7 7 0 01-14 0v-2", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
3758
+ /* @__PURE__ */ jsx23("path", { d: "M12 19v4M8 23h8", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
3759
+ ] })
3760
+ }
3761
+ ) }),
3762
+ /* @__PURE__ */ jsx23("div", { className: "self-end", style: { gridArea: "send" }, children: isLoading && onStop ? /* @__PURE__ */ jsx23(
3763
+ "button",
3764
+ {
3765
+ onClick: onStop,
3766
+ className: "apteva-composer-stop-btn",
3767
+ title: "Stop generation",
3768
+ 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" }) })
3769
+ }
3770
+ ) : /* @__PURE__ */ jsx23(
3771
+ "button",
3772
+ {
3773
+ onClick: handleSend,
3774
+ disabled: !text.trim() && pendingFiles.length === 0 || disabled,
3775
+ className: "apteva-composer-send-btn w-8 h-8 rounded-lg flex items-center justify-center font-bold transition-all flex-shrink-0 border border-neutral-300 dark:border-neutral-600 bg-white dark:bg-neutral-800 !text-neutral-700 dark:!text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700 disabled:opacity-30 disabled:cursor-not-allowed !text-lg",
3776
+ title: "Send message",
3777
+ 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("path", { d: "M8 3L8 13M8 3L4 7M8 3L12 7", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
3778
+ }
3779
+ ) })
3780
+ ] })
3495
3781
  ]
3496
3782
  }
3497
3783
  ),
@@ -4090,6 +4376,8 @@ var Chat = forwardRef(function Chat2({
4090
4376
  availableWidgets,
4091
4377
  compactWidgetContext = false,
4092
4378
  onWidgetRender,
4379
+ // Speech to text
4380
+ speechToText,
4093
4381
  className
4094
4382
  }, ref) {
4095
4383
  const [messages, setMessages] = useState9(initialMessages);
@@ -4111,7 +4399,7 @@ var Chat = forwardRef(function Chat2({
4111
4399
  const [showSettingsMenu, setShowSettingsMenu] = useState9(false);
4112
4400
  const fileInputRef = useRef9(null);
4113
4401
  const [persistentWidgets, setPersistentWidgets] = useState9(/* @__PURE__ */ new Map());
4114
- const updatePersistentWidgets = useCallback3((msgs) => {
4402
+ const updatePersistentWidgets = useCallback4((msgs) => {
4115
4403
  setPersistentWidgets((prev) => {
4116
4404
  const next = new Map(prev);
4117
4405
  let changed = false;
@@ -4129,7 +4417,7 @@ var Chat = forwardRef(function Chat2({
4129
4417
  return changed ? next : prev;
4130
4418
  });
4131
4419
  }, []);
4132
- useEffect8(() => {
4420
+ useEffect9(() => {
4133
4421
  updatePersistentWidgets(messages);
4134
4422
  }, [messages, updatePersistentWidgets]);
4135
4423
  const persistentWidgetList = useMemo2(() => Array.from(persistentWidgets.values()), [persistentWidgets]);
@@ -4155,7 +4443,7 @@ var Chat = forwardRef(function Chat2({
4155
4443
  return context ? `${context}
4156
4444
  ${widgetContext}` : widgetContext;
4157
4445
  }, [context, enableWidgets, availableWidgets, compactWidgetContext]);
4158
- useEffect8(() => {
4446
+ useEffect9(() => {
4159
4447
  if (apiUrl || apiKey) {
4160
4448
  aptevaClient.configure({
4161
4449
  ...apiUrl && { apiUrl },
@@ -4163,15 +4451,15 @@ ${widgetContext}` : widgetContext;
4163
4451
  });
4164
4452
  }
4165
4453
  }, [apiUrl, apiKey]);
4166
- useEffect8(() => {
4454
+ useEffect9(() => {
4167
4455
  if (threadId) {
4168
4456
  onThreadChange?.(threadId);
4169
4457
  }
4170
4458
  }, [threadId, onThreadChange]);
4171
- useEffect8(() => {
4459
+ useEffect9(() => {
4172
4460
  setInternalPlanMode(planMode);
4173
4461
  }, [planMode]);
4174
- useEffect8(() => {
4462
+ useEffect9(() => {
4175
4463
  const handleClickOutside = (event) => {
4176
4464
  const target = event.target;
4177
4465
  if (showSettingsMenu && !target.closest(".settings-menu-container")) {
@@ -4191,7 +4479,7 @@ ${widgetContext}` : widgetContext;
4191
4479
  }
4192
4480
  };
4193
4481
  const defaultPlaceholder = mode === "chat" ? "Type a message..." : "Enter your command...";
4194
- const handleWidgetAction = useCallback3((action) => {
4482
+ const handleWidgetAction = useCallback4((action) => {
4195
4483
  onAction?.(action);
4196
4484
  if (action.type === "submit" && action.payload?.formData) {
4197
4485
  const formData = action.payload.formData;
@@ -4745,7 +5033,8 @@ ${planToExecute}`;
4745
5033
  isLoading,
4746
5034
  onStop: handleStop,
4747
5035
  onFileUpload,
4748
- onSwitchMode: showModeToggle ? () => handleModeChange("command") : void 0
5036
+ onSwitchMode: showModeToggle ? () => handleModeChange("command") : void 0,
5037
+ speechToText
4749
5038
  }
4750
5039
  )
4751
5040
  ] }),
@@ -4792,7 +5081,7 @@ import { useState as useState10 } from "react";
4792
5081
  import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
4793
5082
 
4794
5083
  // src/components/Command/Command.tsx
4795
- import React, { useState as useState11, useEffect as useEffect9 } from "react";
5084
+ import React, { useState as useState11, useEffect as useEffect10 } from "react";
4796
5085
  import { Fragment as Fragment7, jsx as jsx28, jsxs as jsxs22 } from "react/jsx-runtime";
4797
5086
  function Command({
4798
5087
  agentId,
@@ -4833,15 +5122,15 @@ function Command({
4833
5122
  const [showSettingsMenu, setShowSettingsMenu] = useState11(false);
4834
5123
  const [internalPlanMode, setInternalPlanMode] = useState11(planMode);
4835
5124
  const fileInputRef = React.useRef(null);
4836
- useEffect9(() => {
5125
+ useEffect10(() => {
4837
5126
  if (autoExecute && state === "idle" && command) {
4838
5127
  executeCommand();
4839
5128
  }
4840
5129
  }, [autoExecute]);
4841
- useEffect9(() => {
5130
+ useEffect10(() => {
4842
5131
  setInternalPlanMode(planMode);
4843
5132
  }, [planMode]);
4844
- useEffect9(() => {
5133
+ useEffect10(() => {
4845
5134
  const handleClickOutside = (event) => {
4846
5135
  const target = event.target;
4847
5136
  if (showSettingsMenu && !target.closest(".settings-menu-container")) {
@@ -5874,7 +6163,7 @@ function Prompt({
5874
6163
  }
5875
6164
 
5876
6165
  // src/components/Stream/Stream.tsx
5877
- import { useState as useState13, useEffect as useEffect10 } from "react";
6166
+ import { useState as useState13, useEffect as useEffect11 } from "react";
5878
6167
  import { jsx as jsx30, jsxs as jsxs24 } from "react/jsx-runtime";
5879
6168
  function Stream({
5880
6169
  agentId,
@@ -5894,7 +6183,7 @@ function Stream({
5894
6183
  const [text, setText] = useState13("");
5895
6184
  const [isStreaming, setIsStreaming] = useState13(false);
5896
6185
  const [isComplete, setIsComplete] = useState13(false);
5897
- useEffect10(() => {
6186
+ useEffect11(() => {
5898
6187
  if (autoStart && !isStreaming && !isComplete) {
5899
6188
  startStreaming();
5900
6189
  }
@@ -6165,7 +6454,7 @@ function Threads({
6165
6454
  }
6166
6455
 
6167
6456
  // src/components/AutoInterface/AutoInterface.tsx
6168
- import { useState as useState16, useRef as useRef10, useCallback as useCallback4, useEffect as useEffect11 } from "react";
6457
+ import { useState as useState16, useRef as useRef10, useCallback as useCallback5, useEffect as useEffect12 } from "react";
6169
6458
 
6170
6459
  // src/components/AutoInterface/LayoutRenderer.tsx
6171
6460
  import { useState as useState15 } from "react";
@@ -6443,11 +6732,11 @@ function AutoInterface({
6443
6732
  generateInterfaceContext(),
6444
6733
  context || ""
6445
6734
  ].filter(Boolean).join("\n\n");
6446
- const updateInterface = useCallback4((newSpec) => {
6735
+ const updateInterface = useCallback5((newSpec) => {
6447
6736
  setInterfaceSpec(newSpec);
6448
6737
  onInterfaceChange?.(newSpec);
6449
6738
  }, [onInterfaceChange]);
6450
- const handleAction = useCallback4((action) => {
6739
+ const handleAction = useCallback5((action) => {
6451
6740
  onAction?.(action);
6452
6741
  if (chatRef.current) {
6453
6742
  chatRef.current.sendMessage(
@@ -6455,7 +6744,7 @@ function AutoInterface({
6455
6744
  );
6456
6745
  }
6457
6746
  }, [onAction]);
6458
- const handleMessageComplete = useCallback4((result) => {
6747
+ const handleMessageComplete = useCallback5((result) => {
6459
6748
  if (!result?.data) return;
6460
6749
  const text = typeof result.data === "string" ? result.data : result.data.message || "";
6461
6750
  console.log("[AutoInterface] Chat message complete, text (" + text.length + " chars):", text.substring(0, 300));
@@ -6476,7 +6765,7 @@ function AutoInterface({
6476
6765
  }
6477
6766
  setIsGenerating(false);
6478
6767
  }, [interfaceSpec, updateInterface]);
6479
- useEffect11(() => {
6768
+ useEffect12(() => {
6480
6769
  if (!initialPrompt || initialInterface || useMock) return;
6481
6770
  if (!apiUrl) return;
6482
6771
  let cancelled = false;
@@ -6601,29 +6890,29 @@ function getThemeScript() {
6601
6890
  }
6602
6891
 
6603
6892
  // src/hooks/useInterfaceState.ts
6604
- import { useState as useState17, useCallback as useCallback5 } from "react";
6893
+ import { useState as useState17, useCallback as useCallback6 } from "react";
6605
6894
  function useInterfaceState(initialSpec) {
6606
6895
  const [spec, setSpec] = useState17(initialSpec || null);
6607
6896
  const [isStreaming, setIsStreaming] = useState17(false);
6608
- const setInterface = useCallback5((newSpec) => {
6897
+ const setInterface = useCallback6((newSpec) => {
6609
6898
  setSpec(newSpec);
6610
6899
  }, []);
6611
- const clearInterface = useCallback5(() => {
6900
+ const clearInterface = useCallback6(() => {
6612
6901
  setSpec(null);
6613
6902
  }, []);
6614
- const applyInterfaceUpdate = useCallback5((update) => {
6903
+ const applyInterfaceUpdate = useCallback6((update) => {
6615
6904
  setSpec((prev) => {
6616
6905
  if (!prev) return prev;
6617
6906
  return applyUpdate(prev, update);
6618
6907
  });
6619
6908
  }, []);
6620
- const applyInterfaceUpdates = useCallback5((updates) => {
6909
+ const applyInterfaceUpdates = useCallback6((updates) => {
6621
6910
  setSpec((prev) => {
6622
6911
  if (!prev) return prev;
6623
6912
  return applyUpdates(prev, updates);
6624
6913
  });
6625
6914
  }, []);
6626
- const getNode = useCallback5((id) => {
6915
+ const getNode = useCallback6((id) => {
6627
6916
  if (!spec) return null;
6628
6917
  return findNode(spec.root, id);
6629
6918
  }, [spec]);
@@ -6640,7 +6929,7 @@ function useInterfaceState(initialSpec) {
6640
6929
  }
6641
6930
 
6642
6931
  // src/hooks/useInterfaceAI.ts
6643
- import { useCallback as useCallback6, useRef as useRef11 } from "react";
6932
+ import { useCallback as useCallback7, useRef as useRef11 } from "react";
6644
6933
  function useInterfaceAI({
6645
6934
  agentId,
6646
6935
  apiUrl,
@@ -6660,7 +6949,7 @@ function useInterfaceAI({
6660
6949
  ...apiKey && { apiKey }
6661
6950
  });
6662
6951
  }
6663
- const sendMessage = useCallback6(async (message) => {
6952
+ const sendMessage = useCallback7(async (message) => {
6664
6953
  accumulatedTextRef.current = "";
6665
6954
  onStreamStart?.();
6666
6955
  const systemPrompt = [