@lokutor/sdk 1.1.19 → 1.1.21

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
@@ -903,6 +903,7 @@ var VoiceAgentClient = class {
903
903
  }
904
904
  case "tool_call":
905
905
  console.log(`\u{1F6E0}\uFE0F Tool Call: ${msg.name}(${msg.arguments})`);
906
+ this.emit("tool_call", msg);
906
907
  break;
907
908
  }
908
909
  } catch (e) {
@@ -1206,9 +1207,585 @@ async function simpleTTS(options) {
1206
1207
  const client = new TTSClient({ apiKey: options.apiKey });
1207
1208
  return client.synthesize(options);
1208
1209
  }
1210
+
1211
+ // src/conversational-panel.ts
1212
+ var PANEL_CSS = (
1213
+ /*css*/
1214
+ `
1215
+ .cv-panel {
1216
+ position: relative;
1217
+ width: 100%;
1218
+ aspect-ratio: 16 / 9;
1219
+ max-height: 800px;
1220
+ display: flex;
1221
+ flex-direction: column;
1222
+ align-items: center;
1223
+ justify-content: center;
1224
+ padding: 2rem 0;
1225
+ margin: 0;
1226
+ background: var(--cv-bg, #0a0a0a);
1227
+ box-shadow: inset 0 10px 40px rgba(0, 0, 0, 0.1), inset 0 0 100px rgba(0, 0, 0, 0.05);
1228
+ border-radius: 40px;
1229
+ overflow: hidden;
1230
+ container-type: inline-size;
1231
+ }
1232
+ .cv-curtain {
1233
+ position: absolute;
1234
+ inset: 0;
1235
+ display: flex;
1236
+ flex-direction: column;
1237
+ align-items: center;
1238
+ justify-content: center;
1239
+ background: var(--cv-bg, #0a0a0a);
1240
+ z-index: 100;
1241
+ transition: transform 1s cubic-bezier(0.16, 1, 0.3, 1);
1242
+ }
1243
+ .cv-curtain .cv-curtain-bg {
1244
+ position: absolute;
1245
+ inset: 0;
1246
+ background: url('/background_gradient.jpeg') center / cover no-repeat;
1247
+ z-index: -1;
1248
+ }
1249
+ .cv-curtain .cv-curtain-overlay {
1250
+ position: absolute;
1251
+ inset: 0;
1252
+ background: var(--cv-accent);
1253
+ opacity: 0.35;
1254
+ z-index: -1;
1255
+ }
1256
+ .cv-curtain.is-up { transform: translateY(-100%); }
1257
+ .cv-curtain-content {
1258
+ display: flex;
1259
+ flex-direction: column;
1260
+ align-items: center;
1261
+ gap: clamp(0.75rem, 1.5cqi, 1.5rem);
1262
+ color: #fff;
1263
+ text-align: center;
1264
+ z-index: 2;
1265
+ padding: clamp(1rem, 2cqi, 2rem);
1266
+ }
1267
+ .cv-curtain-title {
1268
+ font-size: clamp(1.2rem, 4cqi, 2.5rem);
1269
+ font-weight: 800;
1270
+ letter-spacing: -0.03em;
1271
+ margin: 0;
1272
+ text-shadow: 0 4px 20px rgba(0,0,0,0.3);
1273
+ }
1274
+ .cv-curtain-desc {
1275
+ font-size: clamp(0.75rem, 2cqi, 1.1rem);
1276
+ opacity: 0.8;
1277
+ max-width: 400px;
1278
+ margin: 0;
1279
+ line-height: 1.5;
1280
+ }
1281
+ .cv-curtain-btn {
1282
+ margin-top: 1rem;
1283
+ padding: clamp(0.6rem, 1.5cqi, 1rem) clamp(1.5rem, 3cqi, 2.5rem);
1284
+ border-radius: 100px;
1285
+ background: #fff;
1286
+ color: #000;
1287
+ border: none;
1288
+ font-weight: 700;
1289
+ font-size: clamp(0.8rem, 1.5cqi, 1rem);
1290
+ display: flex;
1291
+ align-items: center;
1292
+ gap: 0.75rem;
1293
+ cursor: pointer;
1294
+ transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
1295
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
1296
+ }
1297
+ .cv-curtain-btn:hover {
1298
+ transform: scale(1.05);
1299
+ background: #f0f0f0;
1300
+ box-shadow: 0 15px 40px rgba(0,0,0,0.3);
1301
+ }
1302
+ .cv-curtain-btn svg { transition: transform 0.3s ease; }
1303
+ .cv-curtain-btn:hover svg { transform: translateX(4px); }
1304
+ .cv-header {
1305
+ width: 100%;
1306
+ display: flex;
1307
+ flex-direction: column;
1308
+ align-items: center;
1309
+ gap: 0.25rem;
1310
+ z-index: 20;
1311
+ margin-bottom: auto;
1312
+ }
1313
+ .cv-title {
1314
+ font-size: clamp(1rem, 2.5cqi, 1.75rem);
1315
+ font-weight: 700;
1316
+ color: #e0e0e0;
1317
+ display: flex;
1318
+ align-items: center;
1319
+ gap: 1rem;
1320
+ letter-spacing: -0.02em;
1321
+ }
1322
+ .cv-title .cv-timer {
1323
+ font-variant-numeric: tabular-nums;
1324
+ color: var(--cv-accent);
1325
+ font-weight: 400;
1326
+ opacity: 0.8;
1327
+ }
1328
+ .cv-visualizer-wrap {
1329
+ position: absolute;
1330
+ top: 50%;
1331
+ left: 50%;
1332
+ transform: translate(-50%, -50%);
1333
+ width: clamp(140px, 40cqi, 280px);
1334
+ height: clamp(140px, 40cqi, 280px);
1335
+ display: flex;
1336
+ align-items: center;
1337
+ justify-content: center;
1338
+ z-index: 10;
1339
+ transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1);
1340
+ }
1341
+ .cv-is-speaking .cv-visualizer-wrap {
1342
+ animation: cv-pulse 2.5s infinite ease-in-out;
1343
+ }
1344
+ .cv-is-thinking .cv-visualizer-wrap {
1345
+ opacity: 0.5;
1346
+ transform: translate(-50%, -50%) scale(0.9);
1347
+ }
1348
+ @keyframes cv-pulse {
1349
+ 0%, 100% { transform: translate(-50%, -50%) scale(1); }
1350
+ 50% { transform: translate(-50%, -50%) scale(1.05); }
1351
+ }
1352
+ .cv-canvas {
1353
+ width: 100% !important;
1354
+ height: 100% !important;
1355
+ position: relative;
1356
+ z-index: 0;
1357
+ }
1358
+ .cv-canvas {
1359
+ width: 100% !important;
1360
+ height: 100% !important;
1361
+ position: relative;
1362
+ z-index: 0;
1363
+ }
1364
+ .cv-controls {
1365
+ width: 100%;
1366
+ display: flex;
1367
+ flex-direction: column;
1368
+ align-items: center;
1369
+ gap: 1.5rem;
1370
+ padding-top: 1rem;
1371
+ margin-top: auto;
1372
+ z-index: 20;
1373
+ }
1374
+ .cv-pill {
1375
+ display: flex;
1376
+ align-items: center;
1377
+ gap: 0.75rem;
1378
+ background: rgba(255, 255, 255, 0.03);
1379
+ backdrop-filter: blur(30px);
1380
+ -webkit-backdrop-filter: blur(30px);
1381
+ border: 1px solid rgba(255, 255, 255, 0.08);
1382
+ padding: 0.3rem 0.75rem;
1383
+ border-radius: 100px;
1384
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
1385
+ }
1386
+ .cv-btn {
1387
+ display: flex;
1388
+ align-items: center;
1389
+ gap: 0.5rem;
1390
+ background: transparent;
1391
+ border: none;
1392
+ color: rgba(255,255,255,0.55);
1393
+ font-weight: 600;
1394
+ font-size: 0.75rem;
1395
+ cursor: pointer;
1396
+ transition: all 0.2s ease;
1397
+ padding: 0.35rem 0.75rem;
1398
+ border-radius: 50px;
1399
+ }
1400
+ .cv-btn:hover { color: #fff; background: rgba(255,255,255,0.05); }
1401
+ .cv-btn--end { color: #ff4444; }
1402
+ .cv-btn--end .cv-btn-box {
1403
+ background: #ff4444;
1404
+ width: 8px;
1405
+ height: 8px;
1406
+ border-radius: 2px;
1407
+ }
1408
+ .cv-btn.is-muted { color: var(--cv-accent); }
1409
+ .cv-error {
1410
+ position: absolute;
1411
+ bottom: 1.25rem;
1412
+ left: 50%;
1413
+ transform: translateX(-50%);
1414
+ background: #0a0a0a;
1415
+ color: #e0e0e0;
1416
+ padding: 0.75rem 1.5rem;
1417
+ border-radius: 12px;
1418
+ font-size: 0.875rem;
1419
+ font-weight: 500;
1420
+ display: none;
1421
+ align-items: center;
1422
+ gap: 0.75rem;
1423
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
1424
+ z-index: 1000;
1425
+ border: 1px solid rgba(255, 255, 255, 0.06);
1426
+ backdrop-filter: blur(20px);
1427
+ }
1428
+ .cv-error.is-visible { display: flex; }
1429
+ .cv-error-icon { color: var(--cv-accent); flex-shrink: 0; }
1430
+ `
1431
+ );
1432
+ var styleInjected = false;
1433
+ function injectStyles() {
1434
+ if (styleInjected) return;
1435
+ const style = document.createElement("style");
1436
+ style.textContent = PANEL_CSS;
1437
+ document.head.appendChild(style);
1438
+ styleInjected = true;
1439
+ }
1440
+ var ConversationalPanel = class {
1441
+ cfg;
1442
+ container;
1443
+ agent = null;
1444
+ visualizer = null;
1445
+ timerTicker = null;
1446
+ connectingSound = null;
1447
+ connectingFadeTimer = null;
1448
+ isRunning = false;
1449
+ // Cached DOM refs
1450
+ el;
1451
+ curtain;
1452
+ curtainTitle;
1453
+ curtainDesc;
1454
+ startBtn;
1455
+ errorEl;
1456
+ errorText;
1457
+ timerEl;
1458
+ canvas;
1459
+ muteBtn;
1460
+ stopBtn;
1461
+ visualizerWrap;
1462
+ // Callbacks
1463
+ onTranscription;
1464
+ onResponse;
1465
+ onStart;
1466
+ onStop;
1467
+ onError;
1468
+ constructor(cfg) {
1469
+ this.cfg = cfg;
1470
+ this.container = cfg.container;
1471
+ injectStyles();
1472
+ this.buildDOM();
1473
+ }
1474
+ buildDOM() {
1475
+ const accent = this.cfg.accentColor || "#a25a6b";
1476
+ const bg = this.cfg.backgroundColor || "#0a0a0a";
1477
+ this.container.style.setProperty("--cv-accent", accent);
1478
+ this.container.style.setProperty("--cv-bg", bg);
1479
+ this.el = document.createElement("div");
1480
+ this.el.className = "cv-panel";
1481
+ this.el.innerHTML = `
1482
+ <div class="cv-curtain">
1483
+ <div class="cv-curtain-bg"></div>
1484
+ <div class="cv-curtain-overlay"></div>
1485
+ <div class="cv-curtain-content">
1486
+ <h3 class="cv-curtain-title">${this.esc(this.cfg.title)}</h3>
1487
+ <p class="cv-curtain-desc">${this.esc(this.cfg.description)}</p>
1488
+ <button class="cv-curtain-btn">
1489
+ <span>Start Conversation</span>
1490
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" width="20" height="20">
1491
+ <path d="M5 12h14M12 5l7 7-7 7"/>
1492
+ </svg>
1493
+ </button>
1494
+ </div>
1495
+ <div class="cv-error">
1496
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
1497
+ width="20" height="20" class="cv-error-icon">
1498
+ <circle cx="12" cy="12" r="10"></circle>
1499
+ <path d="M12 8v4"></path>
1500
+ <path d="M12 16h.01"></path>
1501
+ </svg>
1502
+ <span class="cv-error-text"></span>
1503
+ </div>
1504
+ </div>
1505
+ <div class="cv-header">
1506
+ <h2 class="cv-title">${this.esc(this.cfg.title)} <span class="cv-timer">00:00</span></h2>
1507
+ </div>
1508
+ <div class="cv-visualizer-wrap">
1509
+ <canvas class="cv-canvas"></canvas>
1510
+ </div>
1511
+ <div class="cv-controls">
1512
+ <div class="cv-pill">
1513
+ <button class="cv-btn cv-btn--mute">
1514
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" width="18" height="18">
1515
+ <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
1516
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
1517
+ </svg>
1518
+ <span>Mute</span>
1519
+ </button>
1520
+ <button class="cv-btn cv-btn--end">
1521
+ <div class="cv-btn-box"></div>
1522
+ <span>End call</span>
1523
+ </button>
1524
+ </div>
1525
+ </div>
1526
+ `;
1527
+ this.container.appendChild(this.el);
1528
+ this.curtain = this.el.querySelector(".cv-curtain");
1529
+ if (this.cfg.curtainBgSrc) {
1530
+ this.curtain.style.backgroundImage = `url('${this.cfg.curtainBgSrc}')`;
1531
+ }
1532
+ this.curtainTitle = this.el.querySelector(".cv-curtain-title");
1533
+ this.curtainDesc = this.el.querySelector(".cv-curtain-desc");
1534
+ this.startBtn = this.el.querySelector(".cv-curtain-btn");
1535
+ this.errorEl = this.el.querySelector(".cv-error");
1536
+ this.errorText = this.el.querySelector(".cv-error-text");
1537
+ this.timerEl = this.el.querySelector(".cv-timer");
1538
+ this.canvas = this.el.querySelector(".cv-canvas");
1539
+ this.muteBtn = this.el.querySelector(".cv-btn--mute");
1540
+ this.stopBtn = this.el.querySelector(".cv-btn--end");
1541
+ this.visualizerWrap = this.el.querySelector(".cv-visualizer-wrap");
1542
+ this.startBtn.addEventListener("click", () => this.start());
1543
+ this.stopBtn.addEventListener("click", () => this.stop());
1544
+ this.muteBtn.addEventListener("click", () => this.toggleMute());
1545
+ }
1546
+ esc(s) {
1547
+ const d = document.createElement("div");
1548
+ d.textContent = s;
1549
+ return d.innerHTML;
1550
+ }
1551
+ /** Start the conversation (called when user clicks "Start Conversation" or externally) */
1552
+ async start() {
1553
+ if (this.isRunning) return;
1554
+ this.isRunning = true;
1555
+ this.startBtn.disabled = true;
1556
+ this.errorEl.classList.remove("is-visible");
1557
+ this.playConnectingSound();
1558
+ try {
1559
+ const ConvoAgent = this.cfg.ConvoAgent;
1560
+ const SphereVisualizer = this.cfg.SphereVisualizer;
1561
+ const visualizer = new SphereVisualizer(this.canvas);
1562
+ visualizer.resize(280, 280);
1563
+ this.visualizer = visualizer;
1564
+ if (this.cfg.accentColor && typeof visualizer.setAccentColor === "function") {
1565
+ const hex = this.cfg.accentColor;
1566
+ visualizer.setAccentColor(
1567
+ parseInt(hex.slice(1, 3), 16) / 255,
1568
+ parseInt(hex.slice(3, 5), 16) / 255,
1569
+ parseInt(hex.slice(5, 7), 16) / 255
1570
+ );
1571
+ }
1572
+ const agentHandle = new ConvoAgent({
1573
+ apiKey: this.cfg.apiKey,
1574
+ tools: this.cfg.tools,
1575
+ prompt: this.cfg.prompt,
1576
+ voice: this.cfg.voice || "M1",
1577
+ language: this.cfg.language || "en",
1578
+ onStatusChange: (status) => {
1579
+ this.el.classList.remove("cv-is-speaking", "cv-is-thinking");
1580
+ if (status === "speaking") this.el.classList.add("cv-is-speaking");
1581
+ else if (status === "thinking") this.el.classList.add("cv-is-thinking");
1582
+ },
1583
+ onTranscription: (text) => {
1584
+ this.onTranscription?.(text);
1585
+ },
1586
+ onResponse: (text) => {
1587
+ this.onResponse?.(text);
1588
+ },
1589
+ onError: (err) => {
1590
+ if (!this.isRunning) return;
1591
+ this.onError?.(err);
1592
+ this.stop();
1593
+ this.showError("Connection issue. Try again in a moment.");
1594
+ }
1595
+ });
1596
+ this.agent = agentHandle;
1597
+ visualizer.setAudioClient(agentHandle);
1598
+ this.curtain.classList.add("is-up");
1599
+ try {
1600
+ agentHandle.unlockAudioForMobile?.();
1601
+ } catch (_) {
1602
+ }
1603
+ const ok = await agentHandle.connect();
1604
+ if (ok) {
1605
+ this.fadeOutConnectingSound();
1606
+ const wrap = this.visualizerWrap;
1607
+ if (wrap) {
1608
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
1609
+ const w = Math.floor(wrap.clientWidth * dpr);
1610
+ const h = Math.floor(wrap.clientHeight * dpr);
1611
+ visualizer.resize(w, h);
1612
+ }
1613
+ visualizer.start();
1614
+ this.startTimer();
1615
+ this.onStart?.();
1616
+ if (window.lucide) window.lucide.createIcons();
1617
+ } else {
1618
+ this.fadeOutConnectingSound();
1619
+ this.playErrorTone();
1620
+ this.curtain.classList.remove("is-up");
1621
+ this.showError("Could not connect. Please try again.");
1622
+ this.isRunning = false;
1623
+ }
1624
+ } catch (err) {
1625
+ this.fadeOutConnectingSound();
1626
+ this.playErrorTone();
1627
+ console.error("ConversationalPanel start error:", err);
1628
+ this.stop();
1629
+ this.showError("Something went wrong. Please try again.");
1630
+ } finally {
1631
+ this.startBtn.disabled = false;
1632
+ }
1633
+ }
1634
+ /** Stop / disconnect the conversation */
1635
+ stop() {
1636
+ if (!this.isRunning && !this.agent && !this.visualizer) return;
1637
+ this.isRunning = false;
1638
+ this.fadeOutConnectingSound();
1639
+ this.errorEl.classList.remove("is-visible");
1640
+ if (this.agent) {
1641
+ try {
1642
+ this.agent.disconnect();
1643
+ } catch (_) {
1644
+ }
1645
+ this.agent = null;
1646
+ }
1647
+ if (this.timerTicker) {
1648
+ clearInterval(this.timerTicker);
1649
+ this.timerTicker = null;
1650
+ }
1651
+ if (this.visualizer) {
1652
+ this.visualizer.stop();
1653
+ this.visualizer = null;
1654
+ }
1655
+ this.curtain.classList.remove("is-up");
1656
+ this.el.classList.remove("cv-is-speaking", "cv-is-thinking");
1657
+ this.muteBtn.classList.remove("is-muted");
1658
+ const span = this.muteBtn.querySelector("span");
1659
+ if (span) span.textContent = "Mute";
1660
+ this.onStop?.();
1661
+ this.startBtn.disabled = false;
1662
+ }
1663
+ /** Update accent color dynamically */
1664
+ setColor(color) {
1665
+ this.cfg.accentColor = color;
1666
+ this.container.style.setProperty("--cv-accent", color);
1667
+ if (this.visualizer && typeof this.visualizer.setAccentColor === "function") {
1668
+ const r = parseInt(color.slice(1, 3), 16) / 255;
1669
+ const g = parseInt(color.slice(3, 5), 16) / 255;
1670
+ const b = parseInt(color.slice(5, 7), 16) / 255;
1671
+ this.visualizer.setAccentColor(r, g, b);
1672
+ }
1673
+ }
1674
+ /** Update title text */
1675
+ setTitle(title) {
1676
+ this.cfg.title = title;
1677
+ this.curtainTitle.textContent = title;
1678
+ const h2 = this.el.querySelector(".cv-title");
1679
+ if (h2) h2.innerHTML = `${this.esc(title)} <span class="cv-timer">${this.timerEl?.textContent || "00:00"}</span>`;
1680
+ }
1681
+ /** Update description text */
1682
+ setDescription(desc) {
1683
+ this.cfg.description = desc;
1684
+ this.curtainDesc.textContent = desc;
1685
+ }
1686
+ /** Update prompt (only takes effect on next start()) */
1687
+ setPrompt(prompt) {
1688
+ this.cfg.prompt = prompt;
1689
+ }
1690
+ /** Show an error message */
1691
+ showError(text) {
1692
+ this.errorText.textContent = text;
1693
+ this.errorEl.classList.add("is-visible");
1694
+ setTimeout(() => {
1695
+ this.errorEl.classList.remove("is-visible");
1696
+ }, 5e3);
1697
+ }
1698
+ /** Destroy the component, removing all DOM and stopping any active session */
1699
+ destroy() {
1700
+ this.stop();
1701
+ this.el.remove();
1702
+ }
1703
+ // ─── internal helpers ────────────────────────────────────
1704
+ startTimer() {
1705
+ let elapsed = 0;
1706
+ this.timerEl.textContent = "00:00";
1707
+ this.timerTicker = window.setInterval(() => {
1708
+ elapsed++;
1709
+ const m = Math.floor(elapsed / 60).toString().padStart(2, "0");
1710
+ const s = (elapsed % 60).toString().padStart(2, "0");
1711
+ this.timerEl.textContent = `${m}:${s}`;
1712
+ }, 1e3);
1713
+ }
1714
+ toggleMute() {
1715
+ if (!this.agent) return;
1716
+ try {
1717
+ const muted = this.agent.toggleMute();
1718
+ this.muteBtn.classList.toggle("is-muted", muted);
1719
+ const span = this.muteBtn.querySelector("span");
1720
+ if (span) span.textContent = muted ? "Unmute" : "Mute";
1721
+ if (muted) {
1722
+ this.agent.pauseAudio?.();
1723
+ this.visualizer?.setDeactivated?.(true);
1724
+ } else {
1725
+ this.agent.resumeAudio?.();
1726
+ this.visualizer?.setDeactivated?.(false);
1727
+ }
1728
+ } catch (_) {
1729
+ }
1730
+ }
1731
+ playConnectingSound() {
1732
+ this.fadeOutConnectingSound();
1733
+ const src = this.cfg.connectingSoundSrc || "/connecting.mp3";
1734
+ this.connectingSound = new Audio(src);
1735
+ this.connectingSound.volume = 0.6;
1736
+ this.connectingSound.play().catch(() => {
1737
+ });
1738
+ }
1739
+ fadeOutConnectingSound() {
1740
+ if (this.connectingFadeTimer) {
1741
+ clearInterval(this.connectingFadeTimer);
1742
+ this.connectingFadeTimer = null;
1743
+ }
1744
+ if (!this.connectingSound) return;
1745
+ const startVol = this.connectingSound.volume;
1746
+ const steps = 15;
1747
+ let step = 0;
1748
+ this.connectingFadeTimer = window.setInterval(() => {
1749
+ step++;
1750
+ const progress = step / steps;
1751
+ if (this.connectingSound) this.connectingSound.volume = startVol * Math.max(0, 1 - progress);
1752
+ if (step >= steps) {
1753
+ if (this.connectingFadeTimer) {
1754
+ clearInterval(this.connectingFadeTimer);
1755
+ this.connectingFadeTimer = null;
1756
+ }
1757
+ if (this.connectingSound) {
1758
+ this.connectingSound.pause();
1759
+ this.connectingSound.currentTime = 0;
1760
+ this.connectingSound = null;
1761
+ }
1762
+ }
1763
+ }, 100);
1764
+ }
1765
+ playErrorTone() {
1766
+ try {
1767
+ const ctx = new (window.AudioContext || window.webkitAudioContext)();
1768
+ const now = ctx.currentTime;
1769
+ [0, 0.3].forEach((offset, i) => {
1770
+ const osc = ctx.createOscillator();
1771
+ const gain = ctx.createGain();
1772
+ osc.connect(gain);
1773
+ gain.connect(ctx.destination);
1774
+ osc.type = "sine";
1775
+ osc.frequency.setValueAtTime(600 - i * 200, now + offset);
1776
+ gain.gain.setValueAtTime(0.25, now + offset);
1777
+ gain.gain.exponentialRampToValueAtTime(1e-3, now + offset + 0.35);
1778
+ osc.start(now + offset);
1779
+ osc.stop(now + offset + 0.4);
1780
+ });
1781
+ } catch (_) {
1782
+ }
1783
+ }
1784
+ };
1209
1785
  export {
1210
1786
  AUDIO_CONFIG,
1211
1787
  BrowserAudioManager,
1788
+ ConversationalPanel,
1212
1789
  DEFAULT_URLS,
1213
1790
  Language,
1214
1791
  LokutorError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lokutor/sdk",
3
- "version": "1.1.19",
3
+ "version": "1.1.21",
4
4
  "description": "JavaScript/TypeScript SDK for Lokutor Real-time Voice AI",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/src/client.ts CHANGED
@@ -517,6 +517,7 @@ export class VoiceAgentClient {
517
517
  }
518
518
  case 'tool_call':
519
519
  console.log(`🛠️ Tool Call: ${msg.name}(${msg.arguments})`);
520
+ this.emit('tool_call', msg);
520
521
  break;
521
522
  }
522
523
  } catch (e) {