polymorph-sdk 0.2.3 → 0.2.5

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.
@@ -1,41 +1,440 @@
1
- .widgetRoot { position: fixed; z-index: 9999; display: flex; flex-direction: column; align-items: flex-end; justify-content: flex-end; gap: 12px; pointer-events: none; }
2
- .bottomRight { top: 24px; bottom: 24px; right: 24px; }
3
- .bottomLeft { top: 24px; bottom: 24px; left: 24px; align-items: flex-start; }
4
- .fab { width: 56px; height: 56px; border-radius: 28px; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); transition: transform 150ms ease, box-shadow 150ms ease; color: white; pointer-events: auto; }
5
- .fab svg { transition: transform 200ms ease; }
6
- .fabOpen svg { transform: rotate(90deg); }
7
- .fab:hover { transform: scale(1.05); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2); }
8
- .panel { width: 380px; max-height: 600px; flex-shrink: 1; min-height: 0; display: flex; flex-direction: column; border-radius: 16px; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08), 0 12px 48px rgba(0, 0, 0, 0.12); overflow: hidden; background: var(--mantine-color-body); color: var(--mantine-color-text); pointer-events: auto; transition: opacity 200ms ease, transform 200ms ease; transform-origin: bottom right; }
9
- .panelHidden { opacity: 0; transform: scale(0.95) translateY(8px); pointer-events: none; }
10
- .header { padding: 16px 16px 12px; border-bottom: 1px solid var(--mantine-color-default-border); display: flex; justify-content: space-between; align-items: flex-start; }
11
- .chatThread { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 8px; min-height: 0; }
12
- .messageBubble { max-width: 80%; padding: 8px 12px; border-radius: 12px; font-size: 14px; line-height: 1.4; word-wrap: break-word; animation: messageAppear 200ms ease; }
13
- @keyframes messageAppear { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
14
- .agentMessage { align-self: flex-start; background: var(--mantine-color-gray-light); color: var(--mantine-color-text); }
15
- .userMessage { align-self: flex-end; color: white; }
16
- .voiceLabel { font-size: 10px; opacity: 0.6; margin-top: 2px; }
17
- .voiceOverlay { padding: 8px 16px; display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--mantine-color-dimmed); border-top: 1px solid var(--mantine-color-default-border); }
18
- .voiceBars { display: flex; align-items: center; gap: 2px; height: 16px; }
19
- .voiceBar { width: 3px; border-radius: 2px; background: #22c55e; animation: voiceBar 1.2s ease-in-out infinite; }
20
- .voiceBar:nth-child(1) { height: 6px; animation-delay: 0s; }
21
- .voiceBar:nth-child(2) { height: 12px; animation-delay: 0.2s; }
22
- .voiceBar:nth-child(3) { height: 8px; animation-delay: 0.4s; }
23
- @keyframes voiceBar { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(0.4); } }
24
- .voiceToggle { margin-left: auto; width: 28px; height: 28px; border-radius: 14px; border: 1px solid var(--mantine-color-default-border); cursor: pointer; display: flex; align-items: center; justify-content: center; background: var(--mantine-color-gray-light); color: var(--mantine-color-dimmed); transition: all 150ms ease; }
25
- .voiceToggle:hover { background: var(--mantine-color-gray-light-hover); }
26
- .voiceToggleActive { background: light-dark(#dcfce7, #1a3a2a); border-color: light-dark(#bbf7d0, #2a5a3a); color: light-dark(#16a34a, #4ade80); }
27
- .voiceToggleActive:hover { background: light-dark(#bbf7d0, #2a5a3a); }
28
- .inputBar { padding: 12px 16px; border-top: 1px solid var(--mantine-color-default-border); display: flex; gap: 8px; align-items: flex-end; }
29
- .inputField { flex: 1; border: 1px solid var(--mantine-color-default-border); border-radius: 8px; padding: 8px 12px; font-size: 14px; line-height: 1.4; outline: none; font-family: inherit; background: var(--mantine-color-body); color: var(--mantine-color-text); resize: none; overflow-y: auto; max-height: 120px; }
30
- .inputField:focus { border-color: var(--mantine-color-dimmed); }
31
- .iconButton { width: 36px; height: 36px; border-radius: 8px; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; background: transparent; color: var(--mantine-color-dimmed); transition: background 150ms ease, color 150ms ease; flex-shrink: 0; }
32
- .iconButton:hover { background: var(--mantine-color-gray-light); }
33
- .iconButton:disabled { opacity: 0.4; cursor: default; }
34
- .iconButtonActive { color: #22c55e; }
35
- .statusBadge { font-size: 11px; padding: 2px 8px; border-radius: 10px; font-weight: 500; }
36
- .errorText { color: #dc2626; font-size: 13px; padding: 0 16px; }
37
- .thinkingDots { display: flex; gap: 4px; align-items: center; height: 20px; }
38
- .thinkingDots span { width: 6px; height: 6px; border-radius: 50%; background: var(--mantine-color-dimmed); animation: thinkingDot 1.4s ease-in-out infinite; }
39
- .thinkingDots span:nth-child(2) { animation-delay: 0.2s; }
40
- .thinkingDots span:nth-child(3) { animation-delay: 0.4s; }
41
- @keyframes thinkingDot { 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); } 40% { opacity: 1; transform: scale(1); } }
1
+ .widgetRoot {
2
+ position: fixed;
3
+ z-index: 9999;
4
+ display: flex;
5
+ flex-direction: column;
6
+ align-items: flex-end;
7
+ justify-content: flex-end;
8
+ gap: 12px;
9
+ pointer-events: none;
10
+ }
11
+ .bottomRight {
12
+ top: 24px;
13
+ bottom: 24px;
14
+ right: 24px;
15
+ }
16
+ .bottomLeft {
17
+ top: 24px;
18
+ bottom: 24px;
19
+ left: 24px;
20
+ align-items: flex-start;
21
+ }
22
+ .fab {
23
+ position: relative;
24
+ width: 56px;
25
+ height: 56px;
26
+ border-radius: 28px;
27
+ border: none;
28
+ cursor: pointer;
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
33
+ transition:
34
+ transform 150ms ease,
35
+ box-shadow 150ms ease;
36
+ color: white;
37
+ pointer-events: auto;
38
+ }
39
+ .fab svg {
40
+ transition: transform 200ms ease;
41
+ }
42
+ .fabOpen svg {
43
+ transform: rotate(90deg);
44
+ }
45
+ .fab:hover {
46
+ transform: scale(1.05);
47
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
48
+ }
49
+ .fab:focus-visible {
50
+ outline: 2px solid white;
51
+ outline-offset: 2px;
52
+ }
53
+ .notificationDot {
54
+ position: absolute;
55
+ top: 2px;
56
+ right: 2px;
57
+ width: 12px;
58
+ height: 12px;
59
+ border-radius: 50%;
60
+ background: #ef4444;
61
+ border: 2px solid white;
62
+ animation: notificationPulse 2s ease-in-out infinite;
63
+ }
64
+ @keyframes notificationPulse {
65
+ 0%,
66
+ 100% {
67
+ transform: scale(1);
68
+ box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4);
69
+ }
70
+ 50% {
71
+ transform: scale(1.1);
72
+ box-shadow: 0 0 0 4px rgba(239, 68, 68, 0);
73
+ }
74
+ }
75
+ .fabWiggle {
76
+ animation: fabWiggle 0.6s ease-in-out;
77
+ }
78
+ @keyframes fabWiggle {
79
+ 0% {
80
+ transform: rotate(0deg);
81
+ }
82
+ 15% {
83
+ transform: rotate(-12deg);
84
+ }
85
+ 30% {
86
+ transform: rotate(10deg);
87
+ }
88
+ 45% {
89
+ transform: rotate(-8deg);
90
+ }
91
+ 60% {
92
+ transform: rotate(6deg);
93
+ }
94
+ 75% {
95
+ transform: rotate(-2deg);
96
+ }
97
+ 100% {
98
+ transform: rotate(0deg);
99
+ }
100
+ }
101
+ .panel {
102
+ width: 380px;
103
+ height: 400px;
104
+ flex-shrink: 0;
105
+ min-height: 0;
106
+ display: flex;
107
+ flex-direction: column;
108
+ border-radius: 16px;
109
+ box-shadow:
110
+ 0 4px 24px rgba(0, 0, 0, 0.08),
111
+ 0 12px 48px rgba(0, 0, 0, 0.12);
112
+ overflow: hidden;
113
+ background: var(--mantine-color-body);
114
+ color: var(--mantine-color-text);
115
+ pointer-events: auto;
116
+ transition:
117
+ opacity 200ms ease,
118
+ transform 200ms ease;
119
+ transform-origin: bottom right;
120
+ }
121
+ .panelHidden {
122
+ opacity: 0;
123
+ transform: scale(0.95) translateY(8px);
124
+ pointer-events: none;
125
+ }
126
+ .header {
127
+ padding: 16px 16px 12px;
128
+ border-bottom: 1px solid var(--mantine-color-default-border);
129
+ display: flex;
130
+ justify-content: space-between;
131
+ align-items: flex-start;
132
+ }
133
+ .chatThread {
134
+ flex: 1;
135
+ overflow-y: auto;
136
+ padding: 16px;
137
+ display: flex;
138
+ flex-direction: column;
139
+ gap: 8px;
140
+ min-height: 0;
141
+ }
142
+ .messageBubble {
143
+ max-width: 80%;
144
+ padding: 8px 12px;
145
+ border-radius: 12px;
146
+ font-size: 14px;
147
+ line-height: 1.4;
148
+ word-wrap: break-word;
149
+ animation: messageAppear 200ms ease;
150
+ }
151
+ @keyframes messageAppear {
152
+ from {
153
+ opacity: 0;
154
+ transform: translateY(4px);
155
+ }
156
+ to {
157
+ opacity: 1;
158
+ transform: translateY(0);
159
+ }
160
+ }
161
+ .agentMessage {
162
+ align-self: flex-start;
163
+ background: var(--mantine-color-gray-light);
164
+ color: var(--mantine-color-text);
165
+ }
166
+ .userMessage {
167
+ align-self: flex-end;
168
+ color: white;
169
+ }
170
+ .senderLabel {
171
+ font-size: 11px;
172
+ font-weight: 500;
173
+ opacity: 0.7;
174
+ margin-bottom: 2px;
175
+ }
176
+ .voiceLabel {
177
+ font-size: 10px;
178
+ opacity: 0.6;
179
+ margin-top: 2px;
180
+ }
181
+ .voiceOverlay {
182
+ padding: 8px 16px;
183
+ display: flex;
184
+ align-items: center;
185
+ gap: 8px;
186
+ font-size: 13px;
187
+ color: var(--mantine-color-dimmed);
188
+ border-top: 1px solid var(--mantine-color-default-border);
189
+ }
190
+ .voiceBars {
191
+ display: flex;
192
+ align-items: center;
193
+ gap: 2px;
194
+ height: 16px;
195
+ }
196
+ .voiceBar {
197
+ width: 3px;
198
+ border-radius: 2px;
199
+ background: light-dark(#16a34a, #4ade80);
200
+ animation: voiceBar 1.2s ease-in-out infinite;
201
+ }
202
+ .voiceBar:nth-child(1) {
203
+ height: 6px;
204
+ animation-delay: 0s;
205
+ }
206
+ .voiceBar:nth-child(2) {
207
+ height: 12px;
208
+ animation-delay: 0.2s;
209
+ }
210
+ .voiceBar:nth-child(3) {
211
+ height: 8px;
212
+ animation-delay: 0.4s;
213
+ }
214
+ @keyframes voiceBar {
215
+ 0%,
216
+ 100% {
217
+ transform: scaleY(1);
218
+ }
219
+ 50% {
220
+ transform: scaleY(0.4);
221
+ }
222
+ }
223
+ .toggleGroup {
224
+ margin-left: auto;
225
+ display: flex;
226
+ gap: 6px;
227
+ }
228
+ .voiceToggle {
229
+ width: 36px;
230
+ height: 36px;
231
+ border-radius: 18px;
232
+ border: 1px solid var(--mantine-color-default-border);
233
+ cursor: pointer;
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ background: var(--mantine-color-gray-light);
238
+ color: var(--mantine-color-dimmed);
239
+ transition: all 150ms ease;
240
+ }
241
+ .voiceToggle:hover {
242
+ background: var(--mantine-color-gray-light-hover);
243
+ }
244
+ .voiceToggle:focus-visible {
245
+ outline: 2px solid var(--mantine-primary-color-filled);
246
+ outline-offset: 1px;
247
+ }
248
+ .voiceToggleActive {
249
+ background: light-dark(#dcfce7, #1a3a2a);
250
+ border-color: light-dark(#bbf7d0, #2a5a3a);
251
+ color: light-dark(#16a34a, #4ade80);
252
+ }
253
+ .voiceToggleActive:hover {
254
+ background: light-dark(#bbf7d0, #2a5a3a);
255
+ }
256
+ .screenShareToggleActive {
257
+ background: light-dark(#dbeafe, #1e3a5f);
258
+ border-color: light-dark(#93c5fd, #3b82f6);
259
+ color: light-dark(#2563eb, #60a5fa);
260
+ }
261
+ .screenShareToggleActive:hover {
262
+ background: light-dark(#bfdbfe, #2a4a6f);
263
+ }
264
+ .inputBar {
265
+ padding: 12px 16px;
266
+ border-top: 1px solid var(--mantine-color-default-border);
267
+ display: flex;
268
+ gap: 8px;
269
+ align-items: flex-end;
270
+ }
271
+ .inputField {
272
+ flex: 1;
273
+ border: 1px solid var(--mantine-color-default-border);
274
+ border-radius: 8px;
275
+ padding: 8px 12px;
276
+ font-size: 16px;
277
+ line-height: 1.4;
278
+ outline: none;
279
+ font-family: inherit;
280
+ background: var(--mantine-color-body);
281
+ color: var(--mantine-color-text);
282
+ resize: none;
283
+ overflow-y: auto;
284
+ max-height: 120px;
285
+ }
286
+ .inputField:focus {
287
+ border-color: var(--mantine-color-dimmed);
288
+ }
289
+ .inputField:focus-visible {
290
+ outline: 2px solid var(--mantine-primary-color-filled);
291
+ outline-offset: -1px;
292
+ }
293
+ .iconButton {
294
+ width: 36px;
295
+ height: 36px;
296
+ border-radius: 8px;
297
+ border: none;
298
+ cursor: pointer;
299
+ display: flex;
300
+ align-items: center;
301
+ justify-content: center;
302
+ background: transparent;
303
+ color: var(--mantine-color-dimmed);
304
+ transition:
305
+ background 150ms ease,
306
+ color 150ms ease;
307
+ flex-shrink: 0;
308
+ }
309
+ .iconButton:hover {
310
+ background: var(--mantine-color-gray-light);
311
+ }
312
+ .iconButton:focus-visible {
313
+ outline: 2px solid var(--mantine-primary-color-filled);
314
+ outline-offset: 1px;
315
+ }
316
+ .iconButton:disabled {
317
+ opacity: 0.4;
318
+ cursor: default;
319
+ }
320
+ .iconButtonActive {
321
+ color: light-dark(#16a34a, #4ade80);
322
+ }
323
+ .statusBadge {
324
+ font-size: 11px;
325
+ padding: 2px 8px;
326
+ border-radius: 10px;
327
+ font-weight: 500;
328
+ }
329
+ .errorText {
330
+ color: light-dark(#dc2626, #f87171);
331
+ font-size: 13px;
332
+ padding: 0 16px;
333
+ }
334
+ .thinkingDots {
335
+ display: flex;
336
+ gap: 4px;
337
+ align-items: center;
338
+ height: 20px;
339
+ }
340
+ .thinkingDots span {
341
+ width: 6px;
342
+ height: 6px;
343
+ border-radius: 50%;
344
+ background: var(--mantine-color-dimmed);
345
+ animation: thinkingDot 1.4s ease-in-out infinite;
346
+ }
347
+ .thinkingDots span:nth-child(2) {
348
+ animation-delay: 0.2s;
349
+ }
350
+ .thinkingDots span:nth-child(3) {
351
+ animation-delay: 0.4s;
352
+ }
353
+ @keyframes thinkingDot {
354
+ 0%,
355
+ 80%,
356
+ 100% {
357
+ opacity: 0.3;
358
+ transform: scale(0.8);
359
+ }
360
+ 40% {
361
+ opacity: 1;
362
+ transform: scale(1);
363
+ }
364
+ }
365
+ .identityForm {
366
+ padding: 20px 16px;
367
+ display: flex;
368
+ flex-direction: column;
369
+ gap: 14px;
370
+ flex: 1;
371
+ overflow-y: auto;
372
+ }
373
+ .formField {
374
+ display: flex;
375
+ flex-direction: column;
376
+ gap: 4px;
377
+ }
378
+ .formLabel {
379
+ font-size: 13px;
380
+ font-weight: 500;
381
+ color: var(--mantine-color-text);
382
+ }
383
+ .formInput {
384
+ border: 1px solid var(--mantine-color-default-border);
385
+ border-radius: 8px;
386
+ padding: 8px 12px;
387
+ font-size: 16px;
388
+ line-height: 1.4;
389
+ outline: none;
390
+ font-family: inherit;
391
+ background: var(--mantine-color-body);
392
+ color: var(--mantine-color-text);
393
+ }
394
+ .formInput:focus {
395
+ border-color: var(--mantine-color-dimmed);
396
+ }
397
+ .formInput:focus-visible {
398
+ outline: 2px solid var(--mantine-primary-color-filled);
399
+ outline-offset: -1px;
400
+ }
401
+ .formError {
402
+ font-size: 12px;
403
+ color: light-dark(#dc2626, #f87171);
404
+ }
405
+ .formSubmitButton {
406
+ margin-top: 4px;
407
+ padding: 10px 16px;
408
+ border: none;
409
+ border-radius: 8px;
410
+ font-size: 14px;
411
+ font-weight: 500;
412
+ color: white;
413
+ cursor: pointer;
414
+ transition: opacity 150ms ease;
415
+ }
416
+ .formSubmitButton:hover {
417
+ opacity: 0.9;
418
+ }
419
+ .formSubmitButton:focus-visible {
420
+ outline: 2px solid white;
421
+ outline-offset: 2px;
422
+ }
423
+
424
+ /* Mobile: full-width panel on small screens */
425
+ @media (max-width: 420px) {
426
+ .bottomRight,
427
+ .bottomLeft {
428
+ top: 0;
429
+ bottom: 0;
430
+ left: 0;
431
+ right: 0;
432
+ padding: 12px;
433
+ }
434
+ .panel {
435
+ width: 100%;
436
+ height: auto;
437
+ flex: 1;
438
+ border-radius: 12px;
439
+ }
440
+ }
package/src/types.ts CHANGED
@@ -1,21 +1,19 @@
1
1
  export interface WidgetConfig {
2
- apiBaseUrl: string;
2
+ /** Backend API URL. Defaults to https://api.usepolymorph.com in production, http://localhost:8080 on localhost. */
3
+ apiBaseUrl?: string;
3
4
  apiKey?: string;
4
- /** Server-side widget config ID. When set, metadata is ignored and config is resolved server-side. */
5
+ /** Server-side widget config ID. When omitted, the org's default config is used. */
5
6
  configId?: string;
6
7
  /** LiveKit dispatched agent name (default: "custom-voice-agent"). */
7
8
  agentName?: string;
9
+ /** Inline metadata (only used when no server-side config is resolved). */
8
10
  metadata?: Record<string, string | string[]>;
9
- /** Pre-filled user identity. When provided, the agent skips asking for these fields. */
11
+ /** Pre-filled user identity. When provided, the identity form is skipped. */
10
12
  user?: WidgetUser;
11
- branding?: WidgetBranding;
12
- position?: "bottom-right" | "bottom-left";
13
- /** Enable voice call (default: true). When false, widget is chat-only. */
14
- enableVoice?: boolean;
15
13
  /** Extra options passed to fetch (e.g. { credentials: "include" }) */
16
14
  fetchOptions?: RequestInit;
17
- /** Render widget in dark mode (default: false) */
18
- darkMode?: boolean;
15
+ /** Called when a session starts with room info for constructing join URLs. */
16
+ onSessionStart?: (info: { roomId: string; joinToken: string }) => void;
19
17
  }
20
18
 
21
19
  export interface WidgetUser {
@@ -27,15 +25,25 @@ export interface WidgetUser {
27
25
  phone?: string;
28
26
  }
29
27
 
30
- export interface WidgetBranding {
31
- /** FAB and accent color (default: "#171717") */
32
- primaryColor?: string;
33
- /** Panel header title (default: "Hi there") */
34
- title?: string;
35
- /** Panel subheader */
36
- subtitle?: string;
37
- /** Initial message shown before agent connects */
38
- greeting?: string;
28
+ /** Resolved from the backend via /widget-configs/resolve. */
29
+ export interface ResolvedWidgetConfig {
30
+ id: string;
31
+ title: string;
32
+ subtitle: string;
33
+ primaryColor: string;
34
+ position: "bottom-right" | "bottom-left";
35
+ darkMode: boolean;
36
+ enableVoice: boolean;
37
+ greeting: string;
38
+ collectEmail: FieldRequirement;
39
+ collectPhone: FieldRequirement;
40
+ }
41
+
42
+ export type FieldRequirement = "required" | "optional" | "hidden";
43
+
44
+ export interface IdentityCollection {
45
+ collectEmail: FieldRequirement;
46
+ collectPhone: FieldRequirement;
39
47
  }
40
48
 
41
49
  export interface ChatMessage {
@@ -44,6 +52,8 @@ export interface ChatMessage {
44
52
  text: string;
45
53
  source: "chat" | "voice";
46
54
  timestamp: number;
55
+ senderName?: string;
56
+ senderType?: "human";
47
57
  }
48
58
 
49
59
  export type SessionStatus = "idle" | "connecting" | "connected" | "error";