polymorph-sdk 0.2.2 → 0.2.4

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.
@@ -8,21 +8,19 @@
8
8
  gap: 12px;
9
9
  pointer-events: none;
10
10
  }
11
-
12
11
  .bottomRight {
13
12
  top: 24px;
14
13
  bottom: 24px;
15
14
  right: 24px;
16
15
  }
17
-
18
16
  .bottomLeft {
19
17
  top: 24px;
20
18
  bottom: 24px;
21
19
  left: 24px;
22
20
  align-items: flex-start;
23
21
  }
24
-
25
22
  .fab {
23
+ position: relative;
26
24
  width: 56px;
27
25
  height: 56px;
28
26
  border-radius: 28px;
@@ -38,15 +36,73 @@
38
36
  color: white;
39
37
  pointer-events: auto;
40
38
  }
41
-
39
+ .fab svg {
40
+ transition: transform 200ms ease;
41
+ }
42
+ .fabOpen svg {
43
+ transform: rotate(90deg);
44
+ }
42
45
  .fab:hover {
43
46
  transform: scale(1.05);
44
47
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
45
48
  }
46
-
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
+ }
47
101
  .panel {
48
102
  width: 380px;
49
- max-height: 600px;
103
+ height: 400px;
104
+ flex-shrink: 0;
105
+ min-height: 0;
50
106
  display: flex;
51
107
  flex-direction: column;
52
108
  border-radius: 16px;
@@ -57,8 +113,16 @@
57
113
  background: var(--mantine-color-body);
58
114
  color: var(--mantine-color-text);
59
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;
60
125
  }
61
-
62
126
  .header {
63
127
  padding: 16px 16px 12px;
64
128
  border-bottom: 1px solid var(--mantine-color-default-border);
@@ -66,7 +130,6 @@
66
130
  justify-content: space-between;
67
131
  align-items: flex-start;
68
132
  }
69
-
70
133
  .chatThread {
71
134
  flex: 1;
72
135
  overflow-y: auto;
@@ -74,10 +137,8 @@
74
137
  display: flex;
75
138
  flex-direction: column;
76
139
  gap: 8px;
77
- min-height: 200px;
78
- max-height: 400px;
140
+ min-height: 0;
79
141
  }
80
-
81
142
  .messageBubble {
82
143
  max-width: 80%;
83
144
  padding: 8px 12px;
@@ -85,25 +146,38 @@
85
146
  font-size: 14px;
86
147
  line-height: 1.4;
87
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
+ }
88
160
  }
89
-
90
161
  .agentMessage {
91
162
  align-self: flex-start;
92
163
  background: var(--mantine-color-gray-light);
93
164
  color: var(--mantine-color-text);
94
165
  }
95
-
96
166
  .userMessage {
97
167
  align-self: flex-end;
98
168
  color: white;
99
169
  }
100
-
170
+ .senderLabel {
171
+ font-size: 11px;
172
+ font-weight: 500;
173
+ opacity: 0.7;
174
+ margin-bottom: 2px;
175
+ }
101
176
  .voiceLabel {
102
177
  font-size: 10px;
103
178
  opacity: 0.6;
104
179
  margin-top: 2px;
105
180
  }
106
-
107
181
  .voiceOverlay {
108
182
  padding: 8px 16px;
109
183
  display: flex;
@@ -113,21 +187,18 @@
113
187
  color: var(--mantine-color-dimmed);
114
188
  border-top: 1px solid var(--mantine-color-default-border);
115
189
  }
116
-
117
190
  .voiceBars {
118
191
  display: flex;
119
192
  align-items: center;
120
193
  gap: 2px;
121
194
  height: 16px;
122
195
  }
123
-
124
196
  .voiceBar {
125
197
  width: 3px;
126
198
  border-radius: 2px;
127
- background: #22c55e;
199
+ background: light-dark(#16a34a, #4ade80);
128
200
  animation: voiceBar 1.2s ease-in-out infinite;
129
201
  }
130
-
131
202
  .voiceBar:nth-child(1) {
132
203
  height: 6px;
133
204
  animation-delay: 0s;
@@ -140,7 +211,6 @@
140
211
  height: 8px;
141
212
  animation-delay: 0.4s;
142
213
  }
143
-
144
214
  @keyframes voiceBar {
145
215
  0%,
146
216
  100% {
@@ -150,12 +220,15 @@
150
220
  transform: scaleY(0.4);
151
221
  }
152
222
  }
153
-
154
- .voiceToggle {
223
+ .toggleGroup {
155
224
  margin-left: auto;
156
- width: 28px;
157
- height: 28px;
158
- border-radius: 14px;
225
+ display: flex;
226
+ gap: 6px;
227
+ }
228
+ .voiceToggle {
229
+ width: 36px;
230
+ height: 36px;
231
+ border-radius: 18px;
159
232
  border: 1px solid var(--mantine-color-default-border);
160
233
  cursor: pointer;
161
234
  display: flex;
@@ -165,45 +238,58 @@
165
238
  color: var(--mantine-color-dimmed);
166
239
  transition: all 150ms ease;
167
240
  }
168
-
169
241
  .voiceToggle:hover {
170
242
  background: var(--mantine-color-gray-light-hover);
171
243
  }
172
-
244
+ .voiceToggle:focus-visible {
245
+ outline: 2px solid var(--mantine-primary-color-filled);
246
+ outline-offset: 1px;
247
+ }
173
248
  .voiceToggleActive {
174
249
  background: light-dark(#dcfce7, #1a3a2a);
175
250
  border-color: light-dark(#bbf7d0, #2a5a3a);
176
251
  color: light-dark(#16a34a, #4ade80);
177
252
  }
178
-
179
253
  .voiceToggleActive:hover {
180
254
  background: light-dark(#bbf7d0, #2a5a3a);
181
255
  }
182
-
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
+ }
183
264
  .inputBar {
184
265
  padding: 12px 16px;
185
266
  border-top: 1px solid var(--mantine-color-default-border);
186
267
  display: flex;
187
268
  gap: 8px;
188
- align-items: center;
269
+ align-items: flex-end;
189
270
  }
190
-
191
271
  .inputField {
192
272
  flex: 1;
193
273
  border: 1px solid var(--mantine-color-default-border);
194
274
  border-radius: 8px;
195
275
  padding: 8px 12px;
196
- font-size: 14px;
276
+ font-size: 16px;
277
+ line-height: 1.4;
197
278
  outline: none;
198
279
  font-family: inherit;
199
280
  background: var(--mantine-color-body);
200
281
  color: var(--mantine-color-text);
282
+ resize: none;
283
+ overflow-y: auto;
284
+ max-height: 120px;
201
285
  }
202
-
203
286
  .inputField:focus {
204
287
  border-color: var(--mantine-color-dimmed);
205
288
  }
206
-
289
+ .inputField:focus-visible {
290
+ outline: 2px solid var(--mantine-primary-color-filled);
291
+ outline-offset: -1px;
292
+ }
207
293
  .iconButton {
208
294
  width: 36px;
209
295
  height: 36px;
@@ -218,62 +304,39 @@
218
304
  transition:
219
305
  background 150ms ease,
220
306
  color 150ms ease;
307
+ flex-shrink: 0;
221
308
  }
222
-
223
309
  .iconButton:hover {
224
310
  background: var(--mantine-color-gray-light);
225
311
  }
226
-
312
+ .iconButton:focus-visible {
313
+ outline: 2px solid var(--mantine-primary-color-filled);
314
+ outline-offset: 1px;
315
+ }
227
316
  .iconButton:disabled {
228
317
  opacity: 0.4;
229
318
  cursor: default;
230
319
  }
231
-
232
320
  .iconButtonActive {
233
- color: #22c55e;
234
- }
235
-
236
- .connectButton {
237
- margin: 16px;
238
- padding: 10px 20px;
239
- border-radius: 8px;
240
- border: none;
241
- cursor: pointer;
242
- font-size: 14px;
243
- font-weight: 500;
244
- color: white;
245
- transition: opacity 150ms ease;
246
- }
247
-
248
- .connectButton:hover {
249
- opacity: 0.9;
250
- }
251
-
252
- .connectButton:disabled {
253
- opacity: 0.5;
254
- cursor: default;
321
+ color: light-dark(#16a34a, #4ade80);
255
322
  }
256
-
257
323
  .statusBadge {
258
324
  font-size: 11px;
259
325
  padding: 2px 8px;
260
326
  border-radius: 10px;
261
327
  font-weight: 500;
262
328
  }
263
-
264
329
  .errorText {
265
- color: #dc2626;
330
+ color: light-dark(#dc2626, #f87171);
266
331
  font-size: 13px;
267
332
  padding: 0 16px;
268
333
  }
269
-
270
334
  .thinkingDots {
271
335
  display: flex;
272
336
  gap: 4px;
273
337
  align-items: center;
274
338
  height: 20px;
275
339
  }
276
-
277
340
  .thinkingDots span {
278
341
  width: 6px;
279
342
  height: 6px;
@@ -281,15 +344,12 @@
281
344
  background: var(--mantine-color-dimmed);
282
345
  animation: thinkingDot 1.4s ease-in-out infinite;
283
346
  }
284
-
285
347
  .thinkingDots span:nth-child(2) {
286
348
  animation-delay: 0.2s;
287
349
  }
288
-
289
350
  .thinkingDots span:nth-child(3) {
290
351
  animation-delay: 0.4s;
291
352
  }
292
-
293
353
  @keyframes thinkingDot {
294
354
  0%,
295
355
  80%,
@@ -302,3 +362,79 @@
302
362
  transform: scale(1);
303
363
  }
304
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,28 +1,49 @@
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;
5
+ /** Server-side widget config ID. When omitted, the org's default config is used. */
6
+ configId?: string;
4
7
  /** LiveKit dispatched agent name (default: "custom-voice-agent"). */
5
8
  agentName?: string;
9
+ /** Inline metadata (only used when no server-side config is resolved). */
6
10
  metadata?: Record<string, string | string[]>;
7
- branding?: WidgetBranding;
8
- position?: "bottom-right" | "bottom-left";
9
- /** Enable voice call (default: true). When false, widget is chat-only. */
10
- enableVoice?: boolean;
11
+ /** Pre-filled user identity. When provided, the identity form is skipped. */
12
+ user?: WidgetUser;
11
13
  /** Extra options passed to fetch (e.g. { credentials: "include" }) */
12
14
  fetchOptions?: RequestInit;
13
- /** Render widget in dark mode (default: false) */
14
- darkMode?: boolean;
15
+ /** Called when a session starts with room info for constructing join URLs. */
16
+ onSessionStart?: (info: { roomId: string; joinToken: string }) => void;
15
17
  }
16
18
 
17
- export interface WidgetBranding {
18
- /** FAB and accent color (default: "#171717") */
19
- primaryColor?: string;
20
- /** Panel header title (default: "Hi there") */
21
- title?: string;
22
- /** Panel subheader */
23
- subtitle?: string;
24
- /** Initial message shown before agent connects */
25
- greeting?: string;
19
+ export interface WidgetUser {
20
+ /** Display name (e.g. "Jane Smith") */
21
+ name?: string;
22
+ /** Email address */
23
+ email?: string;
24
+ /** Phone number */
25
+ phone?: string;
26
+ }
27
+
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;
26
47
  }
27
48
 
28
49
  export interface ChatMessage {
@@ -31,6 +52,8 @@ export interface ChatMessage {
31
52
  text: string;
32
53
  source: "chat" | "voice";
33
54
  timestamp: number;
55
+ senderName?: string;
56
+ senderType?: "human";
34
57
  }
35
58
 
36
59
  export type SessionStatus = "idle" | "connecting" | "connected" | "error";