@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 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 (muted) {
1769
- this.agent.pauseAudio?.();
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 (muted) {
1722
- this.agent.pauseAudio?.();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lokutor/sdk",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
4
4
  "description": "JavaScript/TypeScript SDK for Lokutor Real-time Voice AI",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -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 (muted) {
559
- this.agent.pauseAudio?.();
560
- this.visualizer?.setDeactivated?.(true);
561
- } else {
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
  }