@lokutor/sdk 1.1.21 → 1.1.23
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.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +40 -6
- package/dist/index.mjs +40 -6
- package/package.json +1 -1
- package/src/conversational-panel.ts +51 -6
package/dist/index.d.mts
CHANGED
|
@@ -613,6 +613,10 @@ interface ConversationalPanelConfig {
|
|
|
613
613
|
curtainBgSrc?: string;
|
|
614
614
|
/** Tool definitions for LLM function calling */
|
|
615
615
|
tools?: any[];
|
|
616
|
+
/** Maximum conversation duration in seconds (default 300) */
|
|
617
|
+
maxDuration?: number;
|
|
618
|
+
/** Seconds of silence before auto-close (default 30) */
|
|
619
|
+
silenceTimeout?: number;
|
|
616
620
|
}
|
|
617
621
|
declare class ConversationalPanel {
|
|
618
622
|
private cfg;
|
|
@@ -623,6 +627,8 @@ declare class ConversationalPanel {
|
|
|
623
627
|
private connectingSound;
|
|
624
628
|
private connectingFadeTimer;
|
|
625
629
|
private isRunning;
|
|
630
|
+
private _locked;
|
|
631
|
+
private _lastSpeechTime;
|
|
626
632
|
private el;
|
|
627
633
|
private curtain;
|
|
628
634
|
private curtainTitle;
|
|
@@ -633,6 +639,7 @@ declare class ConversationalPanel {
|
|
|
633
639
|
private timerEl;
|
|
634
640
|
private canvas;
|
|
635
641
|
private muteBtn;
|
|
642
|
+
private muteSvg;
|
|
636
643
|
private stopBtn;
|
|
637
644
|
private visualizerWrap;
|
|
638
645
|
onTranscription?: (text: string) => void;
|
|
@@ -640,6 +647,7 @@ declare class ConversationalPanel {
|
|
|
640
647
|
onStart?: () => void;
|
|
641
648
|
onStop?: () => void;
|
|
642
649
|
onError?: (err: any) => void;
|
|
650
|
+
onToolCall?: (tool: any) => void;
|
|
643
651
|
constructor(cfg: ConversationalPanelConfig);
|
|
644
652
|
private buildDOM;
|
|
645
653
|
private esc;
|
|
@@ -655,6 +663,8 @@ declare class ConversationalPanel {
|
|
|
655
663
|
setDescription(desc: string): void;
|
|
656
664
|
/** Update prompt (only takes effect on next start()) */
|
|
657
665
|
setPrompt(prompt: string): void;
|
|
666
|
+
/** Update tool definitions (only takes effect on next start()) */
|
|
667
|
+
setTools(tools: any[]): void;
|
|
658
668
|
/** Show an error message */
|
|
659
669
|
showError(text: string): void;
|
|
660
670
|
/** Destroy the component, removing all DOM and stopping any active session */
|
package/dist/index.d.ts
CHANGED
|
@@ -613,6 +613,10 @@ interface ConversationalPanelConfig {
|
|
|
613
613
|
curtainBgSrc?: string;
|
|
614
614
|
/** Tool definitions for LLM function calling */
|
|
615
615
|
tools?: any[];
|
|
616
|
+
/** Maximum conversation duration in seconds (default 300) */
|
|
617
|
+
maxDuration?: number;
|
|
618
|
+
/** Seconds of silence before auto-close (default 30) */
|
|
619
|
+
silenceTimeout?: number;
|
|
616
620
|
}
|
|
617
621
|
declare class ConversationalPanel {
|
|
618
622
|
private cfg;
|
|
@@ -623,6 +627,8 @@ declare class ConversationalPanel {
|
|
|
623
627
|
private connectingSound;
|
|
624
628
|
private connectingFadeTimer;
|
|
625
629
|
private isRunning;
|
|
630
|
+
private _locked;
|
|
631
|
+
private _lastSpeechTime;
|
|
626
632
|
private el;
|
|
627
633
|
private curtain;
|
|
628
634
|
private curtainTitle;
|
|
@@ -633,6 +639,7 @@ declare class ConversationalPanel {
|
|
|
633
639
|
private timerEl;
|
|
634
640
|
private canvas;
|
|
635
641
|
private muteBtn;
|
|
642
|
+
private muteSvg;
|
|
636
643
|
private stopBtn;
|
|
637
644
|
private visualizerWrap;
|
|
638
645
|
onTranscription?: (text: string) => void;
|
|
@@ -640,6 +647,7 @@ declare class ConversationalPanel {
|
|
|
640
647
|
onStart?: () => void;
|
|
641
648
|
onStop?: () => void;
|
|
642
649
|
onError?: (err: any) => void;
|
|
650
|
+
onToolCall?: (tool: any) => void;
|
|
643
651
|
constructor(cfg: ConversationalPanelConfig);
|
|
644
652
|
private buildDOM;
|
|
645
653
|
private esc;
|
|
@@ -655,6 +663,8 @@ declare class ConversationalPanel {
|
|
|
655
663
|
setDescription(desc: string): void;
|
|
656
664
|
/** Update prompt (only takes effect on next start()) */
|
|
657
665
|
setPrompt(prompt: string): void;
|
|
666
|
+
/** Update tool definitions (only takes effect on next start()) */
|
|
667
|
+
setTools(tools: any[]): void;
|
|
658
668
|
/** Show an error message */
|
|
659
669
|
showError(text: string): void;
|
|
660
670
|
/** Destroy the component, removing all DOM and stopping any active session */
|
package/dist/index.js
CHANGED
|
@@ -1474,6 +1474,13 @@ var PANEL_CSS = (
|
|
|
1474
1474
|
}
|
|
1475
1475
|
.cv-error.is-visible { display: flex; }
|
|
1476
1476
|
.cv-error-icon { color: var(--cv-accent); flex-shrink: 0; }
|
|
1477
|
+
@media (max-width: 640px) {
|
|
1478
|
+
.cv-panel { aspect-ratio: 4 / 3; border-radius: 20px; padding: 1rem 0; }
|
|
1479
|
+
.cv-curtain-content { padding: 0.75rem; gap: 0.5rem; }
|
|
1480
|
+
.cv-curtain-title { font-size: clamp(1rem, 5cqi, 1.6rem); }
|
|
1481
|
+
.cv-curtain-btn { padding: 0.5rem 1.25rem; font-size: clamp(0.7rem, 2cqi, 0.85rem); }
|
|
1482
|
+
.cv-visualizer-wrap { width: clamp(100px, 50cqi, 200px); height: clamp(100px, 50cqi, 200px); }
|
|
1483
|
+
}
|
|
1477
1484
|
`
|
|
1478
1485
|
);
|
|
1479
1486
|
var styleInjected = false;
|
|
@@ -1493,6 +1500,8 @@ var ConversationalPanel = class {
|
|
|
1493
1500
|
connectingSound = null;
|
|
1494
1501
|
connectingFadeTimer = null;
|
|
1495
1502
|
isRunning = false;
|
|
1503
|
+
_locked = false;
|
|
1504
|
+
_lastSpeechTime = 0;
|
|
1496
1505
|
// Cached DOM refs
|
|
1497
1506
|
el;
|
|
1498
1507
|
curtain;
|
|
@@ -1504,6 +1513,7 @@ var ConversationalPanel = class {
|
|
|
1504
1513
|
timerEl;
|
|
1505
1514
|
canvas;
|
|
1506
1515
|
muteBtn;
|
|
1516
|
+
muteSvg;
|
|
1507
1517
|
stopBtn;
|
|
1508
1518
|
visualizerWrap;
|
|
1509
1519
|
// Callbacks
|
|
@@ -1512,6 +1522,7 @@ var ConversationalPanel = class {
|
|
|
1512
1522
|
onStart;
|
|
1513
1523
|
onStop;
|
|
1514
1524
|
onError;
|
|
1525
|
+
onToolCall;
|
|
1515
1526
|
constructor(cfg) {
|
|
1516
1527
|
this.cfg = cfg;
|
|
1517
1528
|
this.container = cfg.container;
|
|
@@ -1584,6 +1595,7 @@ var ConversationalPanel = class {
|
|
|
1584
1595
|
this.timerEl = this.el.querySelector(".cv-timer");
|
|
1585
1596
|
this.canvas = this.el.querySelector(".cv-canvas");
|
|
1586
1597
|
this.muteBtn = this.el.querySelector(".cv-btn--mute");
|
|
1598
|
+
this.muteSvg = this.muteBtn.querySelector("svg");
|
|
1587
1599
|
this.stopBtn = this.el.querySelector(".cv-btn--end");
|
|
1588
1600
|
this.visualizerWrap = this.el.querySelector(".cv-visualizer-wrap");
|
|
1589
1601
|
this.startBtn.addEventListener("click", () => this.start());
|
|
@@ -1598,6 +1610,10 @@ var ConversationalPanel = class {
|
|
|
1598
1610
|
/** Start the conversation (called when user clicks "Start Conversation" or externally) */
|
|
1599
1611
|
async start() {
|
|
1600
1612
|
if (this.isRunning) return;
|
|
1613
|
+
if (this._locked) {
|
|
1614
|
+
this.showError("This session has ended. Refresh the page to start a new one.");
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1601
1617
|
this.isRunning = true;
|
|
1602
1618
|
this.startBtn.disabled = true;
|
|
1603
1619
|
this.errorEl.classList.remove("is-visible");
|
|
@@ -1631,8 +1647,12 @@ var ConversationalPanel = class {
|
|
|
1631
1647
|
this.onTranscription?.(text);
|
|
1632
1648
|
},
|
|
1633
1649
|
onResponse: (text) => {
|
|
1650
|
+
this._lastSpeechTime = Date.now();
|
|
1634
1651
|
this.onResponse?.(text);
|
|
1635
1652
|
},
|
|
1653
|
+
onToolCall: (tool) => {
|
|
1654
|
+
this.onToolCall?.(tool);
|
|
1655
|
+
},
|
|
1636
1656
|
onError: (err) => {
|
|
1637
1657
|
if (!this.isRunning) return;
|
|
1638
1658
|
this.onError?.(err);
|
|
@@ -1734,6 +1754,10 @@ var ConversationalPanel = class {
|
|
|
1734
1754
|
setPrompt(prompt) {
|
|
1735
1755
|
this.cfg.prompt = prompt;
|
|
1736
1756
|
}
|
|
1757
|
+
/** Update tool definitions (only takes effect on next start()) */
|
|
1758
|
+
setTools(tools) {
|
|
1759
|
+
this.cfg.tools = tools;
|
|
1760
|
+
}
|
|
1737
1761
|
/** Show an error message */
|
|
1738
1762
|
showError(text) {
|
|
1739
1763
|
this.errorText.textContent = text;
|
|
@@ -1750,12 +1774,26 @@ var ConversationalPanel = class {
|
|
|
1750
1774
|
// ─── internal helpers ────────────────────────────────────
|
|
1751
1775
|
startTimer() {
|
|
1752
1776
|
let elapsed = 0;
|
|
1777
|
+
const maxDur = this.cfg.maxDuration || 300;
|
|
1778
|
+
const silentMax = this.cfg.silenceTimeout || 30;
|
|
1779
|
+
this._lastSpeechTime = Date.now();
|
|
1753
1780
|
this.timerEl.textContent = "00:00";
|
|
1754
1781
|
this.timerTicker = window.setInterval(() => {
|
|
1755
1782
|
elapsed++;
|
|
1756
1783
|
const m = Math.floor(elapsed / 60).toString().padStart(2, "0");
|
|
1757
1784
|
const s = (elapsed % 60).toString().padStart(2, "0");
|
|
1758
1785
|
this.timerEl.textContent = `${m}:${s}`;
|
|
1786
|
+
if (elapsed >= maxDur) {
|
|
1787
|
+
this._locked = true;
|
|
1788
|
+
this.stop();
|
|
1789
|
+
this.showError("Conversation time limit reached. Refresh to start a new one.");
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
if (Date.now() - this._lastSpeechTime > silentMax * 1e3) {
|
|
1793
|
+
this._locked = true;
|
|
1794
|
+
this.stop();
|
|
1795
|
+
this.showError("Session ended due to inactivity. Refresh to start a new one.");
|
|
1796
|
+
}
|
|
1759
1797
|
}, 1e3);
|
|
1760
1798
|
}
|
|
1761
1799
|
toggleMute() {
|
|
@@ -1765,12 +1803,8 @@ var ConversationalPanel = class {
|
|
|
1765
1803
|
this.muteBtn.classList.toggle("is-muted", muted);
|
|
1766
1804
|
const span = this.muteBtn.querySelector("span");
|
|
1767
1805
|
if (span) span.textContent = muted ? "Unmute" : "Mute";
|
|
1768
|
-
if (
|
|
1769
|
-
this.
|
|
1770
|
-
this.visualizer?.setDeactivated?.(true);
|
|
1771
|
-
} else {
|
|
1772
|
-
this.agent.resumeAudio?.();
|
|
1773
|
-
this.visualizer?.setDeactivated?.(false);
|
|
1806
|
+
if (this.muteSvg) {
|
|
1807
|
+
this.muteSvg.innerHTML = muted ? '<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="23" y1="1" x2="1" y2="23"/>' : '<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/>';
|
|
1774
1808
|
}
|
|
1775
1809
|
} catch (_) {
|
|
1776
1810
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1427,6 +1427,13 @@ var PANEL_CSS = (
|
|
|
1427
1427
|
}
|
|
1428
1428
|
.cv-error.is-visible { display: flex; }
|
|
1429
1429
|
.cv-error-icon { color: var(--cv-accent); flex-shrink: 0; }
|
|
1430
|
+
@media (max-width: 640px) {
|
|
1431
|
+
.cv-panel { aspect-ratio: 4 / 3; border-radius: 20px; padding: 1rem 0; }
|
|
1432
|
+
.cv-curtain-content { padding: 0.75rem; gap: 0.5rem; }
|
|
1433
|
+
.cv-curtain-title { font-size: clamp(1rem, 5cqi, 1.6rem); }
|
|
1434
|
+
.cv-curtain-btn { padding: 0.5rem 1.25rem; font-size: clamp(0.7rem, 2cqi, 0.85rem); }
|
|
1435
|
+
.cv-visualizer-wrap { width: clamp(100px, 50cqi, 200px); height: clamp(100px, 50cqi, 200px); }
|
|
1436
|
+
}
|
|
1430
1437
|
`
|
|
1431
1438
|
);
|
|
1432
1439
|
var styleInjected = false;
|
|
@@ -1446,6 +1453,8 @@ var ConversationalPanel = class {
|
|
|
1446
1453
|
connectingSound = null;
|
|
1447
1454
|
connectingFadeTimer = null;
|
|
1448
1455
|
isRunning = false;
|
|
1456
|
+
_locked = false;
|
|
1457
|
+
_lastSpeechTime = 0;
|
|
1449
1458
|
// Cached DOM refs
|
|
1450
1459
|
el;
|
|
1451
1460
|
curtain;
|
|
@@ -1457,6 +1466,7 @@ var ConversationalPanel = class {
|
|
|
1457
1466
|
timerEl;
|
|
1458
1467
|
canvas;
|
|
1459
1468
|
muteBtn;
|
|
1469
|
+
muteSvg;
|
|
1460
1470
|
stopBtn;
|
|
1461
1471
|
visualizerWrap;
|
|
1462
1472
|
// Callbacks
|
|
@@ -1465,6 +1475,7 @@ var ConversationalPanel = class {
|
|
|
1465
1475
|
onStart;
|
|
1466
1476
|
onStop;
|
|
1467
1477
|
onError;
|
|
1478
|
+
onToolCall;
|
|
1468
1479
|
constructor(cfg) {
|
|
1469
1480
|
this.cfg = cfg;
|
|
1470
1481
|
this.container = cfg.container;
|
|
@@ -1537,6 +1548,7 @@ var ConversationalPanel = class {
|
|
|
1537
1548
|
this.timerEl = this.el.querySelector(".cv-timer");
|
|
1538
1549
|
this.canvas = this.el.querySelector(".cv-canvas");
|
|
1539
1550
|
this.muteBtn = this.el.querySelector(".cv-btn--mute");
|
|
1551
|
+
this.muteSvg = this.muteBtn.querySelector("svg");
|
|
1540
1552
|
this.stopBtn = this.el.querySelector(".cv-btn--end");
|
|
1541
1553
|
this.visualizerWrap = this.el.querySelector(".cv-visualizer-wrap");
|
|
1542
1554
|
this.startBtn.addEventListener("click", () => this.start());
|
|
@@ -1551,6 +1563,10 @@ var ConversationalPanel = class {
|
|
|
1551
1563
|
/** Start the conversation (called when user clicks "Start Conversation" or externally) */
|
|
1552
1564
|
async start() {
|
|
1553
1565
|
if (this.isRunning) return;
|
|
1566
|
+
if (this._locked) {
|
|
1567
|
+
this.showError("This session has ended. Refresh the page to start a new one.");
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1554
1570
|
this.isRunning = true;
|
|
1555
1571
|
this.startBtn.disabled = true;
|
|
1556
1572
|
this.errorEl.classList.remove("is-visible");
|
|
@@ -1584,8 +1600,12 @@ var ConversationalPanel = class {
|
|
|
1584
1600
|
this.onTranscription?.(text);
|
|
1585
1601
|
},
|
|
1586
1602
|
onResponse: (text) => {
|
|
1603
|
+
this._lastSpeechTime = Date.now();
|
|
1587
1604
|
this.onResponse?.(text);
|
|
1588
1605
|
},
|
|
1606
|
+
onToolCall: (tool) => {
|
|
1607
|
+
this.onToolCall?.(tool);
|
|
1608
|
+
},
|
|
1589
1609
|
onError: (err) => {
|
|
1590
1610
|
if (!this.isRunning) return;
|
|
1591
1611
|
this.onError?.(err);
|
|
@@ -1687,6 +1707,10 @@ var ConversationalPanel = class {
|
|
|
1687
1707
|
setPrompt(prompt) {
|
|
1688
1708
|
this.cfg.prompt = prompt;
|
|
1689
1709
|
}
|
|
1710
|
+
/** Update tool definitions (only takes effect on next start()) */
|
|
1711
|
+
setTools(tools) {
|
|
1712
|
+
this.cfg.tools = tools;
|
|
1713
|
+
}
|
|
1690
1714
|
/** Show an error message */
|
|
1691
1715
|
showError(text) {
|
|
1692
1716
|
this.errorText.textContent = text;
|
|
@@ -1703,12 +1727,26 @@ var ConversationalPanel = class {
|
|
|
1703
1727
|
// ─── internal helpers ────────────────────────────────────
|
|
1704
1728
|
startTimer() {
|
|
1705
1729
|
let elapsed = 0;
|
|
1730
|
+
const maxDur = this.cfg.maxDuration || 300;
|
|
1731
|
+
const silentMax = this.cfg.silenceTimeout || 30;
|
|
1732
|
+
this._lastSpeechTime = Date.now();
|
|
1706
1733
|
this.timerEl.textContent = "00:00";
|
|
1707
1734
|
this.timerTicker = window.setInterval(() => {
|
|
1708
1735
|
elapsed++;
|
|
1709
1736
|
const m = Math.floor(elapsed / 60).toString().padStart(2, "0");
|
|
1710
1737
|
const s = (elapsed % 60).toString().padStart(2, "0");
|
|
1711
1738
|
this.timerEl.textContent = `${m}:${s}`;
|
|
1739
|
+
if (elapsed >= maxDur) {
|
|
1740
|
+
this._locked = true;
|
|
1741
|
+
this.stop();
|
|
1742
|
+
this.showError("Conversation time limit reached. Refresh to start a new one.");
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
if (Date.now() - this._lastSpeechTime > silentMax * 1e3) {
|
|
1746
|
+
this._locked = true;
|
|
1747
|
+
this.stop();
|
|
1748
|
+
this.showError("Session ended due to inactivity. Refresh to start a new one.");
|
|
1749
|
+
}
|
|
1712
1750
|
}, 1e3);
|
|
1713
1751
|
}
|
|
1714
1752
|
toggleMute() {
|
|
@@ -1718,12 +1756,8 @@ var ConversationalPanel = class {
|
|
|
1718
1756
|
this.muteBtn.classList.toggle("is-muted", muted);
|
|
1719
1757
|
const span = this.muteBtn.querySelector("span");
|
|
1720
1758
|
if (span) span.textContent = muted ? "Unmute" : "Mute";
|
|
1721
|
-
if (
|
|
1722
|
-
this.
|
|
1723
|
-
this.visualizer?.setDeactivated?.(true);
|
|
1724
|
-
} else {
|
|
1725
|
-
this.agent.resumeAudio?.();
|
|
1726
|
-
this.visualizer?.setDeactivated?.(false);
|
|
1759
|
+
if (this.muteSvg) {
|
|
1760
|
+
this.muteSvg.innerHTML = muted ? '<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="23" y1="1" x2="1" y2="23"/>' : '<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/>';
|
|
1727
1761
|
}
|
|
1728
1762
|
} catch (_) {
|
|
1729
1763
|
}
|
package/package.json
CHANGED
|
@@ -214,6 +214,13 @@ const PANEL_CSS = /*css*/ `
|
|
|
214
214
|
}
|
|
215
215
|
.cv-error.is-visible { display: flex; }
|
|
216
216
|
.cv-error-icon { color: var(--cv-accent); flex-shrink: 0; }
|
|
217
|
+
@media (max-width: 640px) {
|
|
218
|
+
.cv-panel { aspect-ratio: 4 / 3; border-radius: 20px; padding: 1rem 0; }
|
|
219
|
+
.cv-curtain-content { padding: 0.75rem; gap: 0.5rem; }
|
|
220
|
+
.cv-curtain-title { font-size: clamp(1rem, 5cqi, 1.6rem); }
|
|
221
|
+
.cv-curtain-btn { padding: 0.5rem 1.25rem; font-size: clamp(0.7rem, 2cqi, 0.85rem); }
|
|
222
|
+
.cv-visualizer-wrap { width: clamp(100px, 50cqi, 200px); height: clamp(100px, 50cqi, 200px); }
|
|
223
|
+
}
|
|
217
224
|
`;
|
|
218
225
|
|
|
219
226
|
let styleInjected = false;
|
|
@@ -249,6 +256,10 @@ export interface ConversationalPanelConfig {
|
|
|
249
256
|
curtainBgSrc?: string;
|
|
250
257
|
/** Tool definitions for LLM function calling */
|
|
251
258
|
tools?: any[];
|
|
259
|
+
/** Maximum conversation duration in seconds (default 300) */
|
|
260
|
+
maxDuration?: number;
|
|
261
|
+
/** Seconds of silence before auto-close (default 30) */
|
|
262
|
+
silenceTimeout?: number;
|
|
252
263
|
}
|
|
253
264
|
|
|
254
265
|
export class ConversationalPanel {
|
|
@@ -260,6 +271,8 @@ export class ConversationalPanel {
|
|
|
260
271
|
private connectingSound: HTMLAudioElement | null = null;
|
|
261
272
|
private connectingFadeTimer: number | null = null;
|
|
262
273
|
private isRunning = false;
|
|
274
|
+
private _locked = false;
|
|
275
|
+
private _lastSpeechTime = 0;
|
|
263
276
|
|
|
264
277
|
// Cached DOM refs
|
|
265
278
|
private el!: HTMLElement;
|
|
@@ -272,6 +285,7 @@ export class ConversationalPanel {
|
|
|
272
285
|
private timerEl!: HTMLElement;
|
|
273
286
|
private canvas!: HTMLCanvasElement;
|
|
274
287
|
private muteBtn!: HTMLElement;
|
|
288
|
+
private muteSvg!: SVGElement;
|
|
275
289
|
private stopBtn!: HTMLElement;
|
|
276
290
|
private visualizerWrap!: HTMLElement;
|
|
277
291
|
|
|
@@ -281,6 +295,7 @@ export class ConversationalPanel {
|
|
|
281
295
|
onStart?: () => void;
|
|
282
296
|
onStop?: () => void;
|
|
283
297
|
onError?: (err: any) => void;
|
|
298
|
+
onToolCall?: (tool: any) => void;
|
|
284
299
|
|
|
285
300
|
constructor(cfg: ConversationalPanelConfig) {
|
|
286
301
|
this.cfg = cfg;
|
|
@@ -360,6 +375,7 @@ export class ConversationalPanel {
|
|
|
360
375
|
this.timerEl = this.el.querySelector('.cv-timer')!;
|
|
361
376
|
this.canvas = this.el.querySelector('.cv-canvas')!;
|
|
362
377
|
this.muteBtn = this.el.querySelector('.cv-btn--mute')!;
|
|
378
|
+
this.muteSvg = this.muteBtn.querySelector('svg')!;
|
|
363
379
|
this.stopBtn = this.el.querySelector('.cv-btn--end')!;
|
|
364
380
|
this.visualizerWrap = this.el.querySelector('.cv-visualizer-wrap')!;
|
|
365
381
|
|
|
@@ -378,6 +394,10 @@ export class ConversationalPanel {
|
|
|
378
394
|
/** Start the conversation (called when user clicks "Start Conversation" or externally) */
|
|
379
395
|
async start() {
|
|
380
396
|
if (this.isRunning) return;
|
|
397
|
+
if (this._locked) {
|
|
398
|
+
this.showError('This session has ended. Refresh the page to start a new one.');
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
381
401
|
this.isRunning = true;
|
|
382
402
|
this.startBtn.disabled = true;
|
|
383
403
|
this.errorEl.classList.remove('is-visible');
|
|
@@ -416,8 +436,12 @@ export class ConversationalPanel {
|
|
|
416
436
|
this.onTranscription?.(text);
|
|
417
437
|
},
|
|
418
438
|
onResponse: (text: string) => {
|
|
439
|
+
this._lastSpeechTime = Date.now();
|
|
419
440
|
this.onResponse?.(text);
|
|
420
441
|
},
|
|
442
|
+
onToolCall: (tool: any) => {
|
|
443
|
+
this.onToolCall?.(tool);
|
|
444
|
+
},
|
|
421
445
|
onError: (err: any) => {
|
|
422
446
|
if (!this.isRunning) return;
|
|
423
447
|
this.onError?.(err);
|
|
@@ -520,6 +544,11 @@ export class ConversationalPanel {
|
|
|
520
544
|
this.cfg.prompt = prompt;
|
|
521
545
|
}
|
|
522
546
|
|
|
547
|
+
/** Update tool definitions (only takes effect on next start()) */
|
|
548
|
+
setTools(tools: any[]) {
|
|
549
|
+
this.cfg.tools = tools;
|
|
550
|
+
}
|
|
551
|
+
|
|
523
552
|
/** Show an error message */
|
|
524
553
|
showError(text: string) {
|
|
525
554
|
this.errorText.textContent = text;
|
|
@@ -539,12 +568,30 @@ export class ConversationalPanel {
|
|
|
539
568
|
|
|
540
569
|
private startTimer() {
|
|
541
570
|
let elapsed = 0;
|
|
571
|
+
const maxDur = this.cfg.maxDuration || 300;
|
|
572
|
+
const silentMax = this.cfg.silenceTimeout || 30;
|
|
573
|
+
this._lastSpeechTime = Date.now();
|
|
542
574
|
this.timerEl.textContent = '00:00';
|
|
543
575
|
this.timerTicker = window.setInterval(() => {
|
|
544
576
|
elapsed++;
|
|
545
577
|
const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
|
|
546
578
|
const s = (elapsed % 60).toString().padStart(2, '0');
|
|
547
579
|
this.timerEl.textContent = `${m}:${s}`;
|
|
580
|
+
|
|
581
|
+
// Check max duration
|
|
582
|
+
if (elapsed >= maxDur) {
|
|
583
|
+
this._locked = true;
|
|
584
|
+
this.stop();
|
|
585
|
+
this.showError('Conversation time limit reached. Refresh to start a new one.');
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Check silence timeout
|
|
590
|
+
if (Date.now() - this._lastSpeechTime > silentMax * 1000) {
|
|
591
|
+
this._locked = true;
|
|
592
|
+
this.stop();
|
|
593
|
+
this.showError('Session ended due to inactivity. Refresh to start a new one.');
|
|
594
|
+
}
|
|
548
595
|
}, 1000);
|
|
549
596
|
}
|
|
550
597
|
|
|
@@ -555,12 +602,10 @@ export class ConversationalPanel {
|
|
|
555
602
|
this.muteBtn.classList.toggle('is-muted', muted);
|
|
556
603
|
const span = this.muteBtn.querySelector('span');
|
|
557
604
|
if (span) span.textContent = muted ? 'Unmute' : 'Mute';
|
|
558
|
-
if (
|
|
559
|
-
this.
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
this.agent.resumeAudio?.();
|
|
563
|
-
this.visualizer?.setDeactivated?.(false);
|
|
605
|
+
if (this.muteSvg) {
|
|
606
|
+
this.muteSvg.innerHTML = muted
|
|
607
|
+
? '<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="23" y1="1" x2="1" y2="23"/>'
|
|
608
|
+
: '<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/>';
|
|
564
609
|
}
|
|
565
610
|
} catch (_) {}
|
|
566
611
|
}
|