@sippet-ai/operator-widget 0.0.12 → 0.0.13

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/README.md CHANGED
@@ -121,16 +121,15 @@ Known event names:
121
121
  - Speaker selection uses `setSinkId` when supported by the browser; some browsers require a user gesture.
122
122
  - Queue entries are refreshed on realtime socket events and when the queue tab is opened (no polling loop).
123
123
 
124
- ## Using `@sippet-ai/sdk-js` directly
124
+ ## Using `@sippet-ai/sdk-js/client` directly
125
125
 
126
126
  If you need RPC calls outside the widget, use the SDK client directly:
127
127
 
128
128
  ```ts
129
- import { createClient } from '@sippet-ai/sdk-js';
129
+ import { listCalls } from '@sippet-ai/sdk-js/client';
130
130
 
131
- const client = createClient({ apiKey: 'YOUR_PUBLISHABLE_API_KEY' });
132
-
133
- const calls = await client.listCalls({
131
+ const calls = await listCalls({
132
+ headers: { 'x-api-key': 'YOUR_PUBLISHABLE_API_KEY' },
134
133
  fields: ['id', 'callUuid', 'status'],
135
134
  });
136
135
  ```
@@ -614,6 +614,28 @@
614
614
  "default": "false",
615
615
  "attribute": "isMuted"
616
616
  },
617
+ {
618
+ "kind": "method",
619
+ "name": "participantRoleLabel",
620
+ "privacy": "private",
621
+ "parameters": [
622
+ {
623
+ "name": "role",
624
+ "type": {
625
+ "text": "ActiveCallParticipant['role']"
626
+ }
627
+ }
628
+ ]
629
+ },
630
+ {
631
+ "kind": "field",
632
+ "name": "participants",
633
+ "type": {
634
+ "text": "ActiveCallParticipant[]"
635
+ },
636
+ "default": "[]",
637
+ "attribute": "participants"
638
+ },
617
639
  {
618
640
  "kind": "method",
619
641
  "name": "playDtmfTone",
@@ -697,6 +719,15 @@
697
719
  "fieldName": "isMuted",
698
720
  "propName": "ismuted"
699
721
  },
722
+ {
723
+ "name": "participants",
724
+ "type": {
725
+ "text": "ActiveCallParticipant[]"
726
+ },
727
+ "default": "[]",
728
+ "fieldName": "participants",
729
+ "propName": "participants"
730
+ },
700
731
  {
701
732
  "name": "sipStatus",
702
733
  "type": {
@@ -1033,8 +1064,7 @@
1033
1064
  "declarations": [
1034
1065
  {
1035
1066
  "kind": "variable",
1036
- "name": "aor",
1037
- "default": "`sip:${this.sipUser}@${this.sipUrl}`"
1067
+ "name": "memberUuid"
1038
1068
  },
1039
1069
  {
1040
1070
  "kind": "class",
@@ -1054,6 +1084,15 @@
1054
1084
  }
1055
1085
  ]
1056
1086
  },
1087
+ {
1088
+ "kind": "field",
1089
+ "name": "activeCallUuid",
1090
+ "type": {
1091
+ "text": "string"
1092
+ },
1093
+ "privacy": "private",
1094
+ "default": "''"
1095
+ },
1057
1096
  {
1058
1097
  "kind": "field",
1059
1098
  "name": "activeTab",
@@ -1092,6 +1131,11 @@
1092
1131
  "default": "''",
1093
1132
  "attribute": "api-origin"
1094
1133
  },
1134
+ {
1135
+ "kind": "field",
1136
+ "name": "applySipSessionState",
1137
+ "privacy": "private"
1138
+ },
1095
1139
  {
1096
1140
  "kind": "method",
1097
1141
  "name": "applySpeakerSelection",
@@ -1140,6 +1184,15 @@
1140
1184
  "text": "callNumber(phoneNumber: string) => void"
1141
1185
  }
1142
1186
  },
1187
+ {
1188
+ "kind": "field",
1189
+ "name": "callParticipants",
1190
+ "type": {
1191
+ "text": "RealtimeParticipantRow[]"
1192
+ },
1193
+ "privacy": "private",
1194
+ "default": "[]"
1195
+ },
1143
1196
  {
1144
1197
  "kind": "field",
1145
1198
  "name": "callSeconds",
@@ -1285,6 +1338,21 @@
1285
1338
  }
1286
1339
  ]
1287
1340
  },
1341
+ {
1342
+ "kind": "method",
1343
+ "name": "generateCallUuid",
1344
+ "privacy": "private"
1345
+ },
1346
+ {
1347
+ "kind": "method",
1348
+ "name": "getActiveParticipantCards",
1349
+ "privacy": "private",
1350
+ "return": {
1351
+ "type": {
1352
+ "text": "ActiveCallParticipant[]"
1353
+ }
1354
+ }
1355
+ },
1288
1356
  {
1289
1357
  "kind": "method",
1290
1358
  "name": "getRpcConfig",
@@ -1356,6 +1424,16 @@
1356
1424
  "name": "handlePageHide",
1357
1425
  "privacy": "private"
1358
1426
  },
1427
+ {
1428
+ "kind": "field",
1429
+ "name": "handleParticipantJoinedEvent",
1430
+ "privacy": "private"
1431
+ },
1432
+ {
1433
+ "kind": "field",
1434
+ "name": "handleParticipantLeftEvent",
1435
+ "privacy": "private"
1436
+ },
1359
1437
  {
1360
1438
  "kind": "field",
1361
1439
  "name": "handleQueueEntryEvent",
@@ -1490,6 +1568,19 @@
1490
1568
  "name": "isSipCredentialExpiringSoon",
1491
1569
  "privacy": "private"
1492
1570
  },
1571
+ {
1572
+ "kind": "method",
1573
+ "name": "loadActiveCallParticipants",
1574
+ "privacy": "private",
1575
+ "parameters": [
1576
+ {
1577
+ "name": "callUuid",
1578
+ "type": {
1579
+ "text": "string"
1580
+ }
1581
+ }
1582
+ ]
1583
+ },
1493
1584
  {
1494
1585
  "kind": "method",
1495
1586
  "name": "loadContacts",
@@ -1532,6 +1623,24 @@
1532
1623
  "text": "openPanel(tab: VoipTab = 'phone') => void"
1533
1624
  }
1534
1625
  },
1626
+ {
1627
+ "kind": "method",
1628
+ "name": "parseParticipantPayload",
1629
+ "privacy": "private",
1630
+ "return": {
1631
+ "type": {
1632
+ "text": "RealtimeParticipantPayload | null"
1633
+ }
1634
+ },
1635
+ "parameters": [
1636
+ {
1637
+ "name": "rawPayload",
1638
+ "type": {
1639
+ "text": "unknown"
1640
+ }
1641
+ }
1642
+ ]
1643
+ },
1535
1644
  {
1536
1645
  "kind": "method",
1537
1646
  "name": "parseSipExpiry",
@@ -1545,6 +1654,45 @@
1545
1654
  }
1546
1655
  ]
1547
1656
  },
1657
+ {
1658
+ "kind": "method",
1659
+ "name": "payloadCallId",
1660
+ "privacy": "private",
1661
+ "parameters": [
1662
+ {
1663
+ "name": "payload",
1664
+ "type": {
1665
+ "text": "RealtimeParticipantPayload"
1666
+ }
1667
+ }
1668
+ ]
1669
+ },
1670
+ {
1671
+ "kind": "method",
1672
+ "name": "payloadCallUuid",
1673
+ "privacy": "private",
1674
+ "parameters": [
1675
+ {
1676
+ "name": "payload",
1677
+ "type": {
1678
+ "text": "RealtimeParticipantPayload"
1679
+ }
1680
+ }
1681
+ ]
1682
+ },
1683
+ {
1684
+ "kind": "method",
1685
+ "name": "payloadMemberUuid",
1686
+ "privacy": "private",
1687
+ "parameters": [
1688
+ {
1689
+ "name": "payload",
1690
+ "type": {
1691
+ "text": "RealtimeParticipantPayload"
1692
+ }
1693
+ }
1694
+ ]
1695
+ },
1548
1696
  {
1549
1697
  "kind": "field",
1550
1698
  "name": "permissionStream",
@@ -1595,6 +1743,22 @@
1595
1743
  "default": "[]",
1596
1744
  "attribute": "queuedCalls"
1597
1745
  },
1746
+ {
1747
+ "kind": "field",
1748
+ "name": "queueParticipantJoinedRef",
1749
+ "type": {
1750
+ "text": "number | undefined"
1751
+ },
1752
+ "privacy": "private"
1753
+ },
1754
+ {
1755
+ "kind": "field",
1756
+ "name": "queueParticipantLeftRef",
1757
+ "type": {
1758
+ "text": "number | undefined"
1759
+ },
1760
+ "privacy": "private"
1761
+ },
1598
1762
  {
1599
1763
  "kind": "field",
1600
1764
  "name": "queueSocket",
@@ -1637,6 +1801,19 @@
1637
1801
  },
1638
1802
  "privacy": "private"
1639
1803
  },
1804
+ {
1805
+ "kind": "method",
1806
+ "name": "removeParticipantFromPayload",
1807
+ "privacy": "private",
1808
+ "parameters": [
1809
+ {
1810
+ "name": "payload",
1811
+ "type": {
1812
+ "text": "RealtimeParticipantPayload"
1813
+ }
1814
+ }
1815
+ ]
1816
+ },
1640
1817
  {
1641
1818
  "kind": "method",
1642
1819
  "name": "requestMediaPermission",
@@ -1670,6 +1847,19 @@
1670
1847
  "name": "resolveSipUserId",
1671
1848
  "privacy": "private"
1672
1849
  },
1850
+ {
1851
+ "kind": "method",
1852
+ "name": "roleLabel",
1853
+ "privacy": "private",
1854
+ "parameters": [
1855
+ {
1856
+ "name": "role",
1857
+ "type": {
1858
+ "text": "CallParticipantRole"
1859
+ }
1860
+ }
1861
+ ]
1862
+ },
1673
1863
  {
1674
1864
  "kind": "method",
1675
1865
  "name": "scheduleSipCredentialRefresh",
@@ -1751,23 +1941,6 @@
1751
1941
  }
1752
1942
  ]
1753
1943
  },
1754
- {
1755
- "kind": "field",
1756
- "name": "simpleUser",
1757
- "type": {
1758
- "text": "Web.SimpleUser | undefined"
1759
- },
1760
- "privacy": "private"
1761
- },
1762
- {
1763
- "kind": "field",
1764
- "name": "sipConfigKey",
1765
- "type": {
1766
- "text": "string"
1767
- },
1768
- "privacy": "private",
1769
- "default": "''"
1770
- },
1771
1944
  {
1772
1945
  "kind": "field",
1773
1946
  "name": "sipCredentialExpiresAt",
@@ -1801,12 +1974,29 @@
1801
1974
  "default": "'supersecurepass'",
1802
1975
  "attribute": "sipPassword"
1803
1976
  },
1977
+ {
1978
+ "kind": "field",
1979
+ "name": "sipPhase",
1980
+ "type": {
1981
+ "text": "SipSessionState['phase']"
1982
+ },
1983
+ "privacy": "private",
1984
+ "default": "'idle'"
1985
+ },
1804
1986
  {
1805
1987
  "kind": "field",
1806
1988
  "name": "sipServer",
1807
1989
  "privacy": "private",
1808
1990
  "readonly": true
1809
1991
  },
1992
+ {
1993
+ "kind": "field",
1994
+ "name": "sipSessionUnsubscribe",
1995
+ "type": {
1996
+ "text": "() => void | undefined"
1997
+ },
1998
+ "privacy": "private"
1999
+ },
1810
2000
  {
1811
2001
  "kind": "field",
1812
2002
  "name": "sipStatus",
@@ -1880,6 +2070,19 @@
1880
2070
  "name": "stopWidgetLockHeartbeat",
1881
2071
  "privacy": "private"
1882
2072
  },
2073
+ {
2074
+ "kind": "method",
2075
+ "name": "syncActiveCall",
2076
+ "privacy": "private",
2077
+ "parameters": [
2078
+ {
2079
+ "name": "roomId",
2080
+ "type": {
2081
+ "text": "string | null"
2082
+ }
2083
+ }
2084
+ ]
2085
+ },
1883
2086
  {
1884
2087
  "kind": "method",
1885
2088
  "name": "syncWidgetLock",
@@ -1937,6 +2140,19 @@
1937
2140
  }
1938
2141
  ]
1939
2142
  },
2143
+ {
2144
+ "kind": "method",
2145
+ "name": "upsertParticipantFromPayload",
2146
+ "privacy": "private",
2147
+ "parameters": [
2148
+ {
2149
+ "name": "payload",
2150
+ "type": {
2151
+ "text": "RealtimeParticipantPayload"
2152
+ }
2153
+ }
2154
+ ]
2155
+ },
1940
2156
  {
1941
2157
  "kind": "field",
1942
2158
  "name": "userEmail",
@@ -1,5 +1,5 @@
1
1
  import { LitElement } from 'lit';
2
- import type { CallState, SipStatus } from './voip-widget.types.js';
2
+ import type { ActiveCallParticipant, CallState, SipStatus } from './voip-widget.types.js';
3
3
  declare const TwLitElement: typeof LitElement;
4
4
  export declare class SippetAIVoipWidgetPhoneTab extends TwLitElement {
5
5
  dialNumber: string;
@@ -8,8 +8,10 @@ export declare class SippetAIVoipWidgetPhoneTab extends TwLitElement {
8
8
  callSeconds: number;
9
9
  isMuted: boolean;
10
10
  isHeld: boolean;
11
+ participants: ActiveCallParticipant[];
11
12
  updated(): void;
12
13
  private emit;
14
+ private participantRoleLabel;
13
15
  private playDtmfTone;
14
16
  render(): import("lit").TemplateResult<1>;
15
17
  }
@@ -18,6 +18,7 @@ export class SippetAIVoipWidgetPhoneTab extends TwLitElement {
18
18
  this.callSeconds = 0;
19
19
  this.isMuted = false;
20
20
  this.isHeld = false;
21
+ this.participants = [];
21
22
  }
22
23
  updated() {
23
24
  createIcons({ icons: { PhoneCall, PhoneOff, Mic, MicOff, Repeat, Pause }, root: this.renderRoot });
@@ -25,6 +26,13 @@ export class SippetAIVoipWidgetPhoneTab extends TwLitElement {
25
26
  emit(name, detail) {
26
27
  this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));
27
28
  }
29
+ participantRoleLabel(role) {
30
+ if (role === 'ai')
31
+ return 'AI Agent';
32
+ if (role === 'operator')
33
+ return 'Operator';
34
+ return 'Caller';
35
+ }
28
36
  playDtmfTone(digit) {
29
37
  const frequencies = {
30
38
  '1': [697, 1209],
@@ -68,14 +76,16 @@ export class SippetAIVoipWidgetPhoneTab extends TwLitElement {
68
76
  }, 140);
69
77
  }
70
78
  render() {
71
- const isActive = this.callState === 'active' || this.callState === 'held';
72
- const isOutgoing = this.callState === 'outgoing';
79
+ const isActive = this.callState === 'active' ||
80
+ this.callState === 'held' ||
81
+ this.callState === 'outgoing';
73
82
  const minutes = Math.floor(this.callSeconds / 60)
74
83
  .toString()
75
84
  .padStart(2, '0');
76
85
  const seconds = Math.floor(this.callSeconds % 60)
77
86
  .toString()
78
87
  .padStart(2, '0');
88
+ const participantCards = this.participants;
79
89
  return html `
80
90
  ${isActive
81
91
  ? html `
@@ -84,6 +94,47 @@ export class SippetAIVoipWidgetPhoneTab extends TwLitElement {
84
94
  Active call
85
95
  </div>
86
96
  <div class="text-lg font-semibold text-slate-900">${minutes}:${seconds}</div>
97
+ <div class="mt-3 rounded-xl border border-slate-200 bg-slate-50 p-2">
98
+ <div class="flex items-center justify-between text-[10px] font-semibold uppercase tracking-wide text-slate-700">
99
+ <span>Participants</span>
100
+ <span>${participantCards.length}</span>
101
+ </div>
102
+ ${participantCards.length === 0
103
+ ? html `
104
+ <div class="mt-2 rounded-lg border border-dashed border-slate-300 px-2 py-3 text-center text-xs text-slate-500">
105
+ No participants yet.
106
+ </div>
107
+ `
108
+ : html `
109
+ <div class="mt-2 grid grid-cols-2 gap-2">
110
+ ${participantCards.map(participant => html `
111
+ <div
112
+ class=${`rounded-lg border bg-white px-2 py-2 ${participant.isLocal
113
+ ? 'border-emerald-300 bg-emerald-50'
114
+ : 'border-slate-200'}`}
115
+ >
116
+ <div
117
+ class="flex items-center justify-between text-[10px] uppercase tracking-wide text-slate-600"
118
+ >
119
+ <span>${this.participantRoleLabel(participant.role)}</span>
120
+ <span
121
+ class=${`rounded-full px-1.5 py-0.5 text-[9px] font-semibold ${participant.status === 'joining'
122
+ ? 'bg-amber-100 text-amber-700'
123
+ : 'bg-emerald-100 text-emerald-700'}`}
124
+ >
125
+ ${participant.status === 'joining'
126
+ ? 'Joining'
127
+ : 'Live'}
128
+ </span>
129
+ </div>
130
+ <div class="mt-1 truncate text-xs font-semibold text-slate-900">
131
+ ${participant.name}
132
+ </div>
133
+ </div>
134
+ `)}
135
+ </div>
136
+ `}
137
+ </div>
87
138
  <div class="mt-3 grid grid-cols-2 gap-2">
88
139
  <button
89
140
  class="inline-flex items-center justify-center gap-2 rounded-xl voip-primary py-2 text-xs font-semibold transition-shadow"
@@ -141,25 +192,13 @@ export class SippetAIVoipWidgetPhoneTab extends TwLitElement {
141
192
  `)}
142
193
  </div>
143
194
 
144
- ${isOutgoing
145
- ? html `
146
- <button
147
- class="mt-4 inline-flex w-full items-center justify-center gap-2 rounded-2xl bg-red-700 py-2 text-sm font-semibold text-white"
148
- @click=${() => this.emit('voip-hangup')}
149
- >
150
- <i data-lucide="phone-off" class="h-4 w-4"></i>
151
- Cancel
152
- </button>
153
- `
154
- : html `
155
- <button
156
- class="mt-4 inline-flex w-full items-center justify-center gap-2 rounded-2xl voip-primary py-2 text-sm font-semibold transition-shadow"
157
- @click=${() => this.emit('voip-call')}
158
- >
159
- <i data-lucide="phone-call" class="h-4 w-4"></i>
160
- Call
161
- </button>
162
- `}
195
+ <button
196
+ class="mt-4 inline-flex w-full items-center justify-center gap-2 rounded-2xl voip-primary py-2 text-sm font-semibold transition-shadow"
197
+ @click=${() => this.emit('voip-call')}
198
+ >
199
+ <i data-lucide="phone-call" class="h-4 w-4"></i>
200
+ Call
201
+ </button>
163
202
  `}
164
203
  `;
165
204
  }
@@ -182,6 +221,9 @@ __decorate([
182
221
  __decorate([
183
222
  property({ type: Boolean })
184
223
  ], SippetAIVoipWidgetPhoneTab.prototype, "isHeld", void 0);
224
+ __decorate([
225
+ property({ type: Array })
226
+ ], SippetAIVoipWidgetPhoneTab.prototype, "participants", void 0);
185
227
  if (!customElements.get('sippetai-voip-widget-phone-tab')) {
186
228
  customElements.define('sippetai-voip-widget-phone-tab', SippetAIVoipWidgetPhoneTab);
187
229
  }
@@ -43,6 +43,7 @@ export declare class SippetAIVoipWidget extends TwLitElement {
43
43
  private callSeconds;
44
44
  private isMuted;
45
45
  private isHeld;
46
+ private sipPhase;
46
47
  private dragOffsetX;
47
48
  private dragOffsetY;
48
49
  private dragX;
@@ -53,15 +54,18 @@ export declare class SippetAIVoipWidget extends TwLitElement {
53
54
  private selectedSpeakerId;
54
55
  private hasMediaPermission;
55
56
  private widgetLocked;
57
+ private activeCallUuid;
58
+ private callParticipants;
56
59
  private remoteAudio?;
57
- private simpleUser?;
58
- private sipConfigKey;
60
+ private sipSessionUnsubscribe?;
59
61
  private callTimer?;
60
62
  private permissionStream?;
61
63
  private queueSocket?;
62
64
  private queueChannel?;
63
65
  private queueChannelUpdateRef?;
64
66
  private queueChannelDeleteRef?;
67
+ private queueParticipantJoinedRef?;
68
+ private queueParticipantLeftRef?;
65
69
  private sessionSipCredentialsIssued;
66
70
  private sipCredentialExpiresAt?;
67
71
  private sipCredentialRefreshTimer?;
@@ -83,6 +87,8 @@ export declare class SippetAIVoipWidget extends TwLitElement {
83
87
  private handleWidgetStorage;
84
88
  private handleVisibilityChange;
85
89
  private handlePageHide;
90
+ private applySipSessionState;
91
+ private syncActiveCall;
86
92
  private configureSdkEndpoints;
87
93
  private getRpcConfig;
88
94
  private clearSipCredentialRefresh;
@@ -112,6 +118,7 @@ export declare class SippetAIVoipWidget extends TwLitElement {
112
118
  private handleDrag;
113
119
  private handleEndDrag;
114
120
  private resolveDialTarget;
121
+ private generateCallUuid;
115
122
  private handleCall;
116
123
  private handleHangup;
117
124
  private handleAccept;
@@ -121,6 +128,17 @@ export declare class SippetAIVoipWidget extends TwLitElement {
121
128
  private handleTransfer;
122
129
  private emitCallState;
123
130
  private setCallState;
131
+ private roleLabel;
132
+ private getActiveParticipantCards;
133
+ private parseParticipantPayload;
134
+ private payloadCallUuid;
135
+ private payloadCallId;
136
+ private payloadMemberUuid;
137
+ private upsertParticipantFromPayload;
138
+ private removeParticipantFromPayload;
139
+ private handleParticipantJoinedEvent;
140
+ private handleParticipantLeftEvent;
141
+ private loadActiveCallParticipants;
124
142
  private startCallTimer;
125
143
  private stopCallTimer;
126
144
  private stopPermissionStream;