@signalcore/ui 1.0.0-beta.2 → 1.0.0-beta.3
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 +47 -21
- package/dist/AgentAbout.js +6 -2
- package/dist/AgentAbout.js.map +1 -1
- package/dist/AgentChat.d.ts +7 -1
- package/dist/AgentChat.js +154 -2
- package/dist/AgentChat.js.map +1 -1
- package/dist/AgentSpace.d.ts +12 -3
- package/dist/AgentSpace.js +53 -11
- package/dist/AgentSpace.js.map +1 -1
- package/dist/Threads.d.ts +73 -0
- package/dist/Threads.js +140 -0
- package/dist/Threads.js.map +1 -0
- package/dist/automation/marks.js +7 -3
- package/dist/automation/marks.js.map +1 -1
- package/dist/automation/messages.d.ts +4 -0
- package/dist/automation/messages.js +5 -1
- package/dist/automation/messages.js.map +1 -1
- package/dist/automation/when.js +8 -0
- package/dist/automation/when.js.map +1 -1
- package/dist/client.d.ts +14 -4
- package/dist/client.js +7 -2
- package/dist/client.js.map +1 -1
- package/dist/contract.d.ts +9 -4
- package/dist/es.d.ts +2 -0
- package/dist/es.js +22 -3
- package/dist/es.js.map +1 -1
- package/dist/frames.d.ts +7 -1
- package/dist/frames.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +5 -4
- package/dist/surface/Form.js +1 -1
- package/dist/types.d.ts +11 -0
- package/dist/types.js.map +1 -1
- package/dist/useConversation.d.ts +2 -0
- package/dist/useConversation.js +16 -0
- package/dist/useConversation.js.map +1 -1
- package/dist/widget.js +17 -16
- package/package.json +1 -1
- package/src/AgentAbout.tsx +4 -0
- package/src/AgentChat.tsx +203 -1
- package/src/AgentSpace.tsx +89 -13
- package/src/Threads.tsx +218 -0
- package/src/automation/marks.tsx +7 -0
- package/src/automation/messages.ts +8 -0
- package/src/automation/when.ts +4 -0
- package/src/client.ts +26 -10
- package/src/contract.ts +240 -26
- package/src/es.ts +21 -0
- package/src/frames.ts +3 -1
- package/src/index.ts +9 -1
- package/src/styles.css +191 -0
- package/src/types.ts +11 -0
- package/src/useConversation.ts +10 -0
- package/src/widget/element.tsx +13 -4
- package/src/widget/widget.css +13 -0
package/src/es.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { TaskMessages } from "./AgentTasks";
|
|
|
10
10
|
import type { AutomationMessages, DayRef } from "./automation/messages";
|
|
11
11
|
import type { BrowserMessages } from "./BrowserView";
|
|
12
12
|
import type { KitMessages } from "./kit/messages";
|
|
13
|
+
import type { ThreadMessages } from "./Threads";
|
|
13
14
|
import type { VoiceCallMessages } from "./VoiceCall";
|
|
14
15
|
|
|
15
16
|
/** The article before a time: "la 1:00", "las 8:00". */
|
|
@@ -76,6 +77,8 @@ export const AUTOMATION_MESSAGES_ES: AutomationMessages = {
|
|
|
76
77
|
on: (often, when) => `${often}, ${when.charAt(0).toLowerCase()}${when.slice(1)}`,
|
|
77
78
|
custom: "Con un horario propio",
|
|
78
79
|
webhook: "Cuando otro sistema la llama",
|
|
80
|
+
manual: "Cuando alguien la inicia",
|
|
81
|
+
after: (automation) => (automation ? `Tras «${automation}»` : "Tras otra automatización"),
|
|
79
82
|
platform: "Cuando llega un mensaje",
|
|
80
83
|
event: (app, event) =>
|
|
81
84
|
event
|
|
@@ -118,6 +121,8 @@ export const AUTOMATION_MESSAGES_ES: AutomationMessages = {
|
|
|
118
121
|
platform: "un mensaje",
|
|
119
122
|
agent: "se lo pidieron en el chat",
|
|
120
123
|
test: "prueba",
|
|
124
|
+
api: "tu app",
|
|
125
|
+
after: "tras otra",
|
|
121
126
|
})[by],
|
|
122
127
|
statuses: {
|
|
123
128
|
running: "En curso",
|
|
@@ -310,6 +315,10 @@ export const CHAT_MESSAGES_ES: ChatMessages = {
|
|
|
310
315
|
inputs: {},
|
|
311
316
|
allow: "Aprobar",
|
|
312
317
|
decline: "Rechazar",
|
|
318
|
+
approvals: (count) => `${count} acciones esperan tu aprobación`,
|
|
319
|
+
approveAll: "Aprobar todo",
|
|
320
|
+
approveSome: (count) => `Aprobar ${count}`,
|
|
321
|
+
declineAll: "Rechazar todo",
|
|
313
322
|
confirming: "Esperando la confirmación del negocio",
|
|
314
323
|
handedOff: "Pasada a una persona",
|
|
315
324
|
handover: (holder, by, name) => {
|
|
@@ -417,6 +426,17 @@ export const TASK_MESSAGES_ES: TaskMessages = {
|
|
|
417
426
|
failed: "No se ha podido cambiar. Inténtalo de nuevo.",
|
|
418
427
|
};
|
|
419
428
|
|
|
429
|
+
export const THREAD_MESSAGES_ES: ThreadMessages = {
|
|
430
|
+
open: "Conversaciones",
|
|
431
|
+
title: "Tus conversaciones",
|
|
432
|
+
back: "Volver a la conversación",
|
|
433
|
+
new: "Nueva conversación",
|
|
434
|
+
untitled: "Conversación nueva",
|
|
435
|
+
unread: (count) => (count === 1 ? "1 mensaje sin leer" : `${count} mensajes sin leer`),
|
|
436
|
+
waiting: "Te espera",
|
|
437
|
+
empty: "Aún no hay nada aquí.",
|
|
438
|
+
};
|
|
439
|
+
|
|
420
440
|
export const SPACE_MESSAGES_ES: SpaceMessages = {
|
|
421
441
|
modes: { label: "Modo", text: "Texto", voice: "Voz" },
|
|
422
442
|
again: "Volver a llamar",
|
|
@@ -428,4 +448,5 @@ export const SPACE_MESSAGES_ES: SpaceMessages = {
|
|
|
428
448
|
chat: CHAT_MESSAGES_ES,
|
|
429
449
|
voice: VOICE_CALL_MESSAGES_ES,
|
|
430
450
|
tasks: TASK_MESSAGES_ES,
|
|
451
|
+
threads: THREAD_MESSAGES_ES,
|
|
431
452
|
};
|
package/src/frames.ts
CHANGED
|
@@ -52,13 +52,15 @@ export type Frame =
|
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* Up: the person says something (words, files they uploaded to the conversation, or both), acts
|
|
55
|
-
* on a surface
|
|
55
|
+
* on a surface (several things at once: a yes or no on each call a reply waits on), or stops
|
|
56
|
+
* the reply on its way; takes the browser or gives it back, and acts on it
|
|
56
57
|
* while they hold it. And what a tool of the host's answered, run in the page: its output, or
|
|
57
58
|
* why it failed.
|
|
58
59
|
*/
|
|
59
60
|
export type UpFrame =
|
|
60
61
|
| { type: "say"; text: string; files?: string[] }
|
|
61
62
|
| ({ type: "action" } & Action)
|
|
63
|
+
| { type: "actions"; actions: ({ type: "action" } & Action)[] }
|
|
62
64
|
| { type: "stop" }
|
|
63
65
|
| { type: "browser.control"; to: BrowserControl }
|
|
64
66
|
| { type: "browser.close" }
|
package/src/index.ts
CHANGED
|
@@ -97,6 +97,14 @@ export type {
|
|
|
97
97
|
export { taskCard } from "./automation/types";
|
|
98
98
|
export { AgentTasks, TASK_MESSAGES, TASKS_POLL_MS, useTasks } from "./AgentTasks";
|
|
99
99
|
export type { AgentTasksProps, TaskMessages, TaskPart } from "./AgentTasks";
|
|
100
|
+
export { Conversation, THREAD_MESSAGES, THREADS_POLL_MS, Threads, useUnread } from "./Threads";
|
|
101
|
+
export type {
|
|
102
|
+
ConversationProps,
|
|
103
|
+
ThreadMessages,
|
|
104
|
+
ThreadPart,
|
|
105
|
+
ThreadsProps,
|
|
106
|
+
Unread,
|
|
107
|
+
} from "./Threads";
|
|
100
108
|
export { AgentCard } from "./AgentCard";
|
|
101
109
|
export type { AgentCardProps, CardField, CardPart } from "./AgentCard";
|
|
102
110
|
export { AgentChat, CHAT_MESSAGES } from "./AgentChat";
|
|
@@ -110,6 +118,7 @@ export {
|
|
|
110
118
|
KIT_MESSAGES_ES,
|
|
111
119
|
SPACE_MESSAGES_ES,
|
|
112
120
|
TASK_MESSAGES_ES,
|
|
121
|
+
THREAD_MESSAGES_ES,
|
|
113
122
|
VOICE_CALL_MESSAGES_ES,
|
|
114
123
|
} from "./es";
|
|
115
124
|
export { VOICE_CALL_MESSAGES, VoiceCall } from "./VoiceCall";
|
|
@@ -235,7 +244,6 @@ export type {
|
|
|
235
244
|
ChannelWeb,
|
|
236
245
|
ContactGlance,
|
|
237
246
|
ConversationPerson,
|
|
238
|
-
Conversation,
|
|
239
247
|
ConversationDetail,
|
|
240
248
|
ConversationFile,
|
|
241
249
|
Decides,
|
package/src/styles.css
CHANGED
|
@@ -633,6 +633,45 @@
|
|
|
633
633
|
line-height: 1.375;
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
+
/* Several actions waiting for the person's leave, in one card: a row for each, its box ticked
|
|
637
|
+
and what it would take under it, editable; one button at the foot. */
|
|
638
|
+
.sc-chat__requests > ul {
|
|
639
|
+
display: flex;
|
|
640
|
+
flex-direction: column;
|
|
641
|
+
margin: 0;
|
|
642
|
+
padding: 0;
|
|
643
|
+
list-style: none;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.sc-chat__ask {
|
|
647
|
+
display: flex;
|
|
648
|
+
flex-direction: column;
|
|
649
|
+
gap: calc(var(--_space) * 2.5);
|
|
650
|
+
padding: calc(var(--_space) * 3) calc(var(--_space) * 4);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.sc-chat__ask + .sc-chat__ask {
|
|
654
|
+
border-top: 1px solid var(--_line);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.sc-chat__ask > .sc-check > span {
|
|
658
|
+
font-weight: var(--_font-weight-medium);
|
|
659
|
+
line-height: 1.375;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
.sc-chat__ask-fields {
|
|
663
|
+
display: grid;
|
|
664
|
+
gap: calc(var(--_space) * 2.5);
|
|
665
|
+
padding-left: calc(var(--_space) * 6.5);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.sc-chat__requests > footer {
|
|
669
|
+
display: flex;
|
|
670
|
+
justify-content: flex-end;
|
|
671
|
+
padding: calc(var(--_space) * 3) calc(var(--_space) * 4);
|
|
672
|
+
border-top: 1px solid var(--_line);
|
|
673
|
+
}
|
|
674
|
+
|
|
636
675
|
/* On a person's page, in place of the approval card: what waits for the business's yes, a quiet
|
|
637
676
|
line under the steps; the person has nothing to press. */
|
|
638
677
|
.sc-chat__confirming {
|
|
@@ -6427,6 +6466,158 @@ button.sc-attachment__main:focus-visible {
|
|
|
6427
6466
|
outline-offset: -2px;
|
|
6428
6467
|
}
|
|
6429
6468
|
|
|
6469
|
+
/* Over the chat, one line: the way to the person's threads (a dot when another has something
|
|
6470
|
+
for them) and, at its end, their tasks. */
|
|
6471
|
+
.sc-space__bar {
|
|
6472
|
+
display: flex;
|
|
6473
|
+
flex-shrink: 0;
|
|
6474
|
+
align-items: stretch;
|
|
6475
|
+
border-bottom: 1px solid var(--_line);
|
|
6476
|
+
}
|
|
6477
|
+
|
|
6478
|
+
.sc-space__bar > .sc-space__threads,
|
|
6479
|
+
.sc-space__bar > .sc-space__tasks {
|
|
6480
|
+
width: auto;
|
|
6481
|
+
border-bottom: 0;
|
|
6482
|
+
}
|
|
6483
|
+
|
|
6484
|
+
.sc-space__bar > .sc-space__tasks:not(:only-child) {
|
|
6485
|
+
margin-left: auto;
|
|
6486
|
+
text-align: right;
|
|
6487
|
+
}
|
|
6488
|
+
|
|
6489
|
+
/* The way to the person's threads; a dot when another thread has something for them. */
|
|
6490
|
+
.sc-space__threads {
|
|
6491
|
+
display: flex;
|
|
6492
|
+
flex-shrink: 0;
|
|
6493
|
+
align-items: center;
|
|
6494
|
+
gap: calc(var(--_space) * 1.5);
|
|
6495
|
+
width: 100%;
|
|
6496
|
+
padding: calc(var(--_space) * 2) calc(var(--_space) * 3.5);
|
|
6497
|
+
border: 0;
|
|
6498
|
+
border-bottom: 1px solid var(--_line);
|
|
6499
|
+
background: transparent;
|
|
6500
|
+
color: var(--_muted);
|
|
6501
|
+
font: inherit;
|
|
6502
|
+
font-size: var(--_font-size-xs);
|
|
6503
|
+
font-weight: var(--_font-weight-medium);
|
|
6504
|
+
text-align: left;
|
|
6505
|
+
cursor: pointer;
|
|
6506
|
+
transition:
|
|
6507
|
+
background-color 0.12s,
|
|
6508
|
+
color 0.12s;
|
|
6509
|
+
}
|
|
6510
|
+
|
|
6511
|
+
.sc-space__threads:hover {
|
|
6512
|
+
background: var(--_wash);
|
|
6513
|
+
color: var(--_fg);
|
|
6514
|
+
}
|
|
6515
|
+
|
|
6516
|
+
.sc-space__threads:focus-visible {
|
|
6517
|
+
outline: 2px solid var(--_accent);
|
|
6518
|
+
outline-offset: -2px;
|
|
6519
|
+
}
|
|
6520
|
+
|
|
6521
|
+
.sc-space__dot,
|
|
6522
|
+
.sc-threads__dot {
|
|
6523
|
+
flex-shrink: 0;
|
|
6524
|
+
width: 7px;
|
|
6525
|
+
height: 7px;
|
|
6526
|
+
border-radius: var(--_radius-full);
|
|
6527
|
+
background: var(--_accent);
|
|
6528
|
+
}
|
|
6529
|
+
|
|
6530
|
+
/* The person's threads in the chat's place: the way back, their title and another to start, then
|
|
6531
|
+
each by its title with what is unread. */
|
|
6532
|
+
.sc-threads {
|
|
6533
|
+
display: flex;
|
|
6534
|
+
flex: 1;
|
|
6535
|
+
flex-direction: column;
|
|
6536
|
+
min-height: 0;
|
|
6537
|
+
overflow-y: auto;
|
|
6538
|
+
}
|
|
6539
|
+
|
|
6540
|
+
.sc-threads__head {
|
|
6541
|
+
display: flex;
|
|
6542
|
+
flex-shrink: 0;
|
|
6543
|
+
align-items: center;
|
|
6544
|
+
gap: calc(var(--_space) * 1.5);
|
|
6545
|
+
padding: calc(var(--_space) * 2) calc(var(--_space) * 3);
|
|
6546
|
+
border-bottom: 1px solid var(--_line);
|
|
6547
|
+
}
|
|
6548
|
+
|
|
6549
|
+
.sc-threads__title {
|
|
6550
|
+
flex: 1;
|
|
6551
|
+
margin: 0;
|
|
6552
|
+
font-size: var(--_font-size);
|
|
6553
|
+
font-weight: var(--_font-weight-bold);
|
|
6554
|
+
}
|
|
6555
|
+
|
|
6556
|
+
.sc-threads__list {
|
|
6557
|
+
display: flex;
|
|
6558
|
+
flex-direction: column;
|
|
6559
|
+
margin: 0;
|
|
6560
|
+
padding: calc(var(--_space) * 1.5);
|
|
6561
|
+
list-style: none;
|
|
6562
|
+
}
|
|
6563
|
+
|
|
6564
|
+
.sc-threads__item {
|
|
6565
|
+
display: flex;
|
|
6566
|
+
align-items: center;
|
|
6567
|
+
gap: calc(var(--_space) * 2);
|
|
6568
|
+
width: 100%;
|
|
6569
|
+
padding: calc(var(--_space) * 2.5) calc(var(--_space) * 2.5);
|
|
6570
|
+
border: 0;
|
|
6571
|
+
border-radius: var(--_radius);
|
|
6572
|
+
background: transparent;
|
|
6573
|
+
color: var(--_fg);
|
|
6574
|
+
font: inherit;
|
|
6575
|
+
text-align: left;
|
|
6576
|
+
cursor: pointer;
|
|
6577
|
+
transition: background-color 0.12s;
|
|
6578
|
+
}
|
|
6579
|
+
|
|
6580
|
+
.sc-threads__item:hover {
|
|
6581
|
+
background: var(--_wash);
|
|
6582
|
+
}
|
|
6583
|
+
|
|
6584
|
+
.sc-threads__item:focus-visible {
|
|
6585
|
+
outline: 2px solid var(--_accent);
|
|
6586
|
+
outline-offset: -2px;
|
|
6587
|
+
}
|
|
6588
|
+
|
|
6589
|
+
.sc-threads__item--current {
|
|
6590
|
+
background: var(--_wash);
|
|
6591
|
+
font-weight: var(--_font-weight-medium);
|
|
6592
|
+
}
|
|
6593
|
+
|
|
6594
|
+
.sc-threads__name {
|
|
6595
|
+
flex: 1;
|
|
6596
|
+
min-width: 0;
|
|
6597
|
+
overflow: hidden;
|
|
6598
|
+
text-overflow: ellipsis;
|
|
6599
|
+
white-space: nowrap;
|
|
6600
|
+
}
|
|
6601
|
+
|
|
6602
|
+
.sc-threads__badge {
|
|
6603
|
+
flex-shrink: 0;
|
|
6604
|
+
min-width: 20px;
|
|
6605
|
+
padding: 1px calc(var(--_space) * 1.5);
|
|
6606
|
+
border-radius: var(--_radius-full);
|
|
6607
|
+
background: var(--_accent);
|
|
6608
|
+
color: var(--_accent-fg);
|
|
6609
|
+
font-size: var(--_font-size-xs);
|
|
6610
|
+
font-variant-numeric: tabular-nums;
|
|
6611
|
+
font-weight: var(--_font-weight-medium);
|
|
6612
|
+
text-align: center;
|
|
6613
|
+
}
|
|
6614
|
+
|
|
6615
|
+
.sc-threads__empty {
|
|
6616
|
+
margin: 0;
|
|
6617
|
+
padding: calc(var(--_space) * 4) calc(var(--_space) * 3.5);
|
|
6618
|
+
color: var(--_muted);
|
|
6619
|
+
}
|
|
6620
|
+
|
|
6430
6621
|
/* The tasks in the chat's place: the way back and their title, then each as an automation
|
|
6431
6622
|
reads, a hairline between them. */
|
|
6432
6623
|
.sc-tasks {
|
package/src/types.ts
CHANGED
|
@@ -90,6 +90,12 @@ export interface Conversation {
|
|
|
90
90
|
* host's app (`host_person`, its `contact` then) or the widget's visitor.
|
|
91
91
|
*/
|
|
92
92
|
person?: ConversationPerson | null | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* For the person it is with: how many of the business's messages came since they last read it,
|
|
95
|
+
* and whether it waits on them (a question, their yes); left out by an older API.
|
|
96
|
+
*/
|
|
97
|
+
unread?: number | undefined;
|
|
98
|
+
waiting_on_person?: boolean | undefined;
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
/** Who a person's token or a publishable key started a conversation for. */
|
|
@@ -237,6 +243,8 @@ export interface Request {
|
|
|
237
243
|
input: Record<string, unknown>;
|
|
238
244
|
/** The person's when left out (an API from before it). */
|
|
239
245
|
decides?: Decides | undefined;
|
|
246
|
+
/** The JSON Schema of its input, which an edit given with the yes must fit. */
|
|
247
|
+
schema?: Record<string, unknown> | null | undefined;
|
|
240
248
|
}
|
|
241
249
|
|
|
242
250
|
/** An action a reply called: what, how it went, and what it drew. */
|
|
@@ -306,6 +314,9 @@ export interface Message {
|
|
|
306
314
|
heard?: string | null | undefined;
|
|
307
315
|
/** On a channel, a person's message: what was decided of it; null otherwise. */
|
|
308
316
|
decision?: Decision | null | undefined;
|
|
317
|
+
/** The agent's words first, without a turn of the person's, and the run whose code sent them. */
|
|
318
|
+
proactive?: boolean | undefined;
|
|
319
|
+
run_id?: string | null | undefined;
|
|
309
320
|
created_at: string;
|
|
310
321
|
}
|
|
311
322
|
|
package/src/useConversation.ts
CHANGED
|
@@ -444,6 +444,8 @@ export interface LiveConversation {
|
|
|
444
444
|
say: (text: string, files?: readonly string[]) => boolean;
|
|
445
445
|
/** What the person did on a surface; false when the socket is closed. */
|
|
446
446
|
act: (action: Action) => boolean;
|
|
447
|
+
/** Several things done at once (a yes or no on each call a reply waits on), sent together. */
|
|
448
|
+
actAll: (actions: readonly Action[]) => boolean;
|
|
447
449
|
/** Stops the reply on its way; false when the socket is closed. */
|
|
448
450
|
stop: () => boolean;
|
|
449
451
|
/** The conversation's browser while it is open: where it is, who holds it. */
|
|
@@ -605,6 +607,13 @@ export function useConversation(
|
|
|
605
607
|
return true;
|
|
606
608
|
};
|
|
607
609
|
|
|
610
|
+
const actAll = (actions: readonly Action[]): boolean => {
|
|
611
|
+
const sent = actions.map((one) => ({ type: "action" as const, ...one }));
|
|
612
|
+
if (!live.send({ type: "actions", actions: sent })) return false;
|
|
613
|
+
for (const action of actions) dispatch({ type: "acted", action });
|
|
614
|
+
return true;
|
|
615
|
+
};
|
|
616
|
+
|
|
608
617
|
return {
|
|
609
618
|
messages: state.messages,
|
|
610
619
|
draft: state.draft,
|
|
@@ -615,6 +624,7 @@ export function useConversation(
|
|
|
615
624
|
error: state.error,
|
|
616
625
|
say,
|
|
617
626
|
act,
|
|
627
|
+
actAll,
|
|
618
628
|
stop: () => live.send({ type: "stop" }),
|
|
619
629
|
browser: state.browser,
|
|
620
630
|
watchScreen: screen.watch,
|
package/src/widget/element.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { createRoot, type Root } from "react-dom/client";
|
|
|
5
5
|
|
|
6
6
|
import type { ChatSteps } from "../AgentChat";
|
|
7
7
|
import { AgentSpace, SPACE_MESSAGES } from "../AgentSpace";
|
|
8
|
+
import { useUnread } from "../Threads";
|
|
8
9
|
import { createClient } from "../client";
|
|
9
10
|
import type { Credentials } from "../credentials";
|
|
10
11
|
import { SPACE_MESSAGES_ES } from "../es";
|
|
@@ -61,9 +62,11 @@ export interface WidgetSettings {
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
/** The launcher's words, by language. */
|
|
64
|
-
const LAUNCHER: Readonly<
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
const LAUNCHER: Readonly<
|
|
66
|
+
Record<Language, { open: string; close: string; chat: string; news: string }>
|
|
67
|
+
> = {
|
|
68
|
+
en: { open: "Open the chat", close: "Close the chat", chat: "Chat", news: "something new" },
|
|
69
|
+
es: { open: "Abrir el chat", close: "Cerrar el chat", chat: "Chat", news: "hay novedades" },
|
|
67
70
|
};
|
|
68
71
|
|
|
69
72
|
/**
|
|
@@ -254,6 +257,11 @@ export function Widget({ settings, look = null }: WidgetProps) {
|
|
|
254
257
|
const [open, setOpen] = useState(false);
|
|
255
258
|
const words = LAUNCHER[language];
|
|
256
259
|
const floating = mode === "floating";
|
|
260
|
+
// While closed, a dot on the launcher says something waits (an unread message, a question), for
|
|
261
|
+
// someone who talked before: nobody new is asked after until they open it.
|
|
262
|
+
const returning = keptConversation(agent) !== undefined;
|
|
263
|
+
const news = useUnread(client, agent, floating && !open && returning && client.who === "person");
|
|
264
|
+
const waits = news.unread > 0 || news.waiting;
|
|
257
265
|
const header = settings.header || look?.header === true;
|
|
258
266
|
|
|
259
267
|
const space = (
|
|
@@ -295,7 +303,7 @@ export function Widget({ settings, look = null }: WidgetProps) {
|
|
|
295
303
|
<button
|
|
296
304
|
type="button"
|
|
297
305
|
part="launcher"
|
|
298
|
-
aria-label={open ? words.close : words.open}
|
|
306
|
+
aria-label={open ? words.close : waits ? `${words.open}: ${words.news}` : words.open}
|
|
299
307
|
aria-expanded={open}
|
|
300
308
|
onClick={() => {
|
|
301
309
|
setOpen(!open);
|
|
@@ -307,6 +315,7 @@ export function Widget({ settings, look = null }: WidgetProps) {
|
|
|
307
315
|
) : (
|
|
308
316
|
<ChatCircleDotsIcon aria-hidden weight="fill" />
|
|
309
317
|
)}
|
|
318
|
+
{!open && waits && <span aria-hidden className="sc-widget__dot" />}
|
|
310
319
|
</button>
|
|
311
320
|
</div>
|
|
312
321
|
);
|
package/src/widget/widget.css
CHANGED
|
@@ -104,6 +104,19 @@ signalcore-chat[shadow="none"][mode="inline"] {
|
|
|
104
104
|
height: 46%;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/* While the chat is closed and something waits in it (an unread message, a question): a dot at
|
|
108
|
+
the launcher's corner, ringed in the page's surface so it reads on the accent. */
|
|
109
|
+
.sc-widget__dot {
|
|
110
|
+
position: absolute;
|
|
111
|
+
top: 6%;
|
|
112
|
+
right: 6%;
|
|
113
|
+
width: 22%;
|
|
114
|
+
height: 22%;
|
|
115
|
+
border: 2px solid var(--_surface);
|
|
116
|
+
border-radius: var(--_radius-full);
|
|
117
|
+
background: var(--_danger);
|
|
118
|
+
}
|
|
119
|
+
|
|
107
120
|
.sc-widget--floating > .sc-widget__panel {
|
|
108
121
|
position: fixed;
|
|
109
122
|
right: var(--_offset);
|