@clivly/chat-widget 0.14.1 → 0.15.0
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 +22 -1
- package/dist/browser-mount.cjs +192 -19
- package/dist/browser-mount.d.cts +42 -1
- package/dist/browser-mount.d.mts +42 -1
- package/dist/browser-mount.mjs +192 -19
- package/dist/browser.cjs +192 -19
- package/dist/browser.d.cts +42 -1
- package/dist/browser.d.mts +42 -1
- package/dist/browser.mjs +192 -19
- package/dist/index.cjs +192 -19
- package/dist/index.d.cts +44 -3
- package/dist/index.d.mts +44 -3
- package/dist/index.mjs +192 -19
- package/dist/widget.global.js +58 -298
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -163,6 +163,13 @@ const WIDGET_STYLES = `
|
|
|
163
163
|
.clivly-chat-widget textarea {
|
|
164
164
|
font: inherit;
|
|
165
165
|
}
|
|
166
|
+
/* The standalone embed does not load the app's shared stylesheet. */
|
|
167
|
+
@media (width < 48rem), (any-pointer: coarse) {
|
|
168
|
+
.clivly-chat-widget input,
|
|
169
|
+
.clivly-chat-widget textarea {
|
|
170
|
+
font-size: max(16px, 1em);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
166
173
|
.clivly-chat-widget__launcher {
|
|
167
174
|
align-items: center;
|
|
168
175
|
background: var(--clivly-chat-accent);
|
|
@@ -372,6 +379,47 @@ const WIDGET_STYLES = `
|
|
|
372
379
|
margin: 0;
|
|
373
380
|
padding: 0.625rem 0.75rem;
|
|
374
381
|
}
|
|
382
|
+
/* Automated and AI messages are marked as such, never styled as a teammate. */
|
|
383
|
+
.clivly-chat-widget__message[data-automation] {
|
|
384
|
+
background: transparent;
|
|
385
|
+
border: 1px dashed var(--clivly-chat-border);
|
|
386
|
+
}
|
|
387
|
+
.clivly-chat-widget__badge {
|
|
388
|
+
border: 1px solid var(--clivly-chat-border);
|
|
389
|
+
border-radius: 999px;
|
|
390
|
+
font-size: 0.6875rem;
|
|
391
|
+
padding: 0 0.4rem;
|
|
392
|
+
}
|
|
393
|
+
.clivly-chat-widget__topics {
|
|
394
|
+
display: flex;
|
|
395
|
+
flex-wrap: wrap;
|
|
396
|
+
gap: 0.5rem;
|
|
397
|
+
margin: 0;
|
|
398
|
+
padding: 0;
|
|
399
|
+
list-style: none;
|
|
400
|
+
}
|
|
401
|
+
.clivly-chat-widget__topic {
|
|
402
|
+
background: var(--clivly-chat-panel);
|
|
403
|
+
border: 1px solid var(--clivly-chat-accent);
|
|
404
|
+
border-radius: 999px;
|
|
405
|
+
color: var(--clivly-chat-text);
|
|
406
|
+
cursor: pointer;
|
|
407
|
+
font-size: 0.875rem;
|
|
408
|
+
min-height: 2.25rem;
|
|
409
|
+
padding: 0.4rem 0.85rem;
|
|
410
|
+
}
|
|
411
|
+
.clivly-chat-widget__topic:hover,
|
|
412
|
+
.clivly-chat-widget__topic:focus-visible {
|
|
413
|
+
background: var(--clivly-chat-visitor);
|
|
414
|
+
}
|
|
415
|
+
.clivly-chat-widget__topic:focus-visible {
|
|
416
|
+
outline: 2px solid var(--clivly-chat-accent);
|
|
417
|
+
outline-offset: 2px;
|
|
418
|
+
}
|
|
419
|
+
.clivly-chat-widget__topic[disabled] {
|
|
420
|
+
cursor: not-allowed;
|
|
421
|
+
opacity: 0.55;
|
|
422
|
+
}
|
|
375
423
|
.clivly-chat-widget__empty {
|
|
376
424
|
color: var(--clivly-chat-muted);
|
|
377
425
|
font-size: 0.875rem;
|
|
@@ -468,8 +516,15 @@ function useResolvedTheme(theme) {
|
|
|
468
516
|
* protocol reads as a single unit, and so `ChatWidget` stays inside the
|
|
469
517
|
* cognitive-complexity budget.
|
|
470
518
|
*/
|
|
471
|
-
function applyServerEvent(event,
|
|
519
|
+
function applyServerEvent(event, actions) {
|
|
520
|
+
const { setError, setIsReady, setMessages } = actions;
|
|
472
521
|
switch (event.type) {
|
|
522
|
+
case "assistant.status":
|
|
523
|
+
actions.setAssistantThinking(event.status === "thinking");
|
|
524
|
+
return;
|
|
525
|
+
case "support.state":
|
|
526
|
+
actions.setSupportState(event.state);
|
|
527
|
+
return;
|
|
473
528
|
case "conversation.snapshot":
|
|
474
529
|
setMessages(sortMessages(event.messages.map((message) => createLocalMessage(message))));
|
|
475
530
|
return;
|
|
@@ -490,14 +545,41 @@ function applyServerEvent(event, { setError, setIsReady, setMessages }) {
|
|
|
490
545
|
default: return;
|
|
491
546
|
}
|
|
492
547
|
}
|
|
548
|
+
/**
|
|
549
|
+
* What a message from the support assistant is called. Automated
|
|
550
|
+
* acknowledgements, AI answers and people are never presented alike, so a
|
|
551
|
+
* visitor is never led to think a person has read their message.
|
|
552
|
+
*/
|
|
553
|
+
const AUTOMATION_BADGES = {
|
|
554
|
+
ai: "AI assistant",
|
|
555
|
+
ai_clarify: "AI assistant",
|
|
556
|
+
greeting: "Automated",
|
|
557
|
+
handoff: "Automated",
|
|
558
|
+
topic: "Automated"
|
|
559
|
+
};
|
|
560
|
+
const AUTHOR_LABEL_SUFFIX = / · (?:Automated|AI assistant)$/;
|
|
561
|
+
function automationBadge(message) {
|
|
562
|
+
if (message.senderType !== "system" || !message.automation) return null;
|
|
563
|
+
return AUTOMATION_BADGES[message.automation] ?? "Automated";
|
|
564
|
+
}
|
|
493
565
|
function MessageBubble({ message }) {
|
|
566
|
+
const badge = automationBadge(message);
|
|
567
|
+
const author = badge ? (message.author ?? "").replace(AUTHOR_LABEL_SUFFIX, "") : message.author;
|
|
494
568
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
495
569
|
className: "clivly-chat-widget__message",
|
|
570
|
+
"data-automation": badge ? message.automation : void 0,
|
|
496
571
|
"data-sender": message.senderType,
|
|
497
572
|
children: [
|
|
498
573
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
499
574
|
className: "clivly-chat-widget__message-meta",
|
|
500
|
-
children: [
|
|
575
|
+
children: [
|
|
576
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: author || "Clivly" }),
|
|
577
|
+
badge ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
578
|
+
className: "clivly-chat-widget__badge",
|
|
579
|
+
children: badge
|
|
580
|
+
}) : null,
|
|
581
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatTimestamp(message.createdAt) })
|
|
582
|
+
]
|
|
501
583
|
}),
|
|
502
584
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: message.body }),
|
|
503
585
|
message.delivery === "sent" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -540,6 +622,7 @@ function awaitingReplySince(messages) {
|
|
|
540
622
|
for (let index = messages.length - 1; index >= 0; index--) {
|
|
541
623
|
const message = messages[index];
|
|
542
624
|
if (!message || message.delivery === "failed") continue;
|
|
625
|
+
if (message.senderType === "system" || message.automation === "topic_choice") continue;
|
|
543
626
|
if (message.senderType !== "visitor") return null;
|
|
544
627
|
return new Date(message.createdAt).getTime();
|
|
545
628
|
}
|
|
@@ -593,6 +676,70 @@ function composerStatusText(hasTransport, isReady) {
|
|
|
593
676
|
if (!hasTransport) return "Chat is unavailable";
|
|
594
677
|
return isReady ? "Conversation ready" : "Connecting…";
|
|
595
678
|
}
|
|
679
|
+
/**
|
|
680
|
+
* Guided options under the greeting. Native buttons in a labelled group, so
|
|
681
|
+
* they are reachable with Tab and operable with Enter/Space; free text stays
|
|
682
|
+
* available beside them the whole time.
|
|
683
|
+
*/
|
|
684
|
+
function SupportTopics({ disabled, onSelect, topics }) {
|
|
685
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
686
|
+
"aria-label": "Suggested topics",
|
|
687
|
+
className: "clivly-chat-widget__topics",
|
|
688
|
+
children: topics.map((topic) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
689
|
+
className: "clivly-chat-widget__topic",
|
|
690
|
+
disabled,
|
|
691
|
+
onClick: () => {
|
|
692
|
+
onSelect(topic);
|
|
693
|
+
},
|
|
694
|
+
type: "button",
|
|
695
|
+
children: topic.label
|
|
696
|
+
}) }, topic.id))
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
/** Topics are offered only while automation is answering this conversation. */
|
|
700
|
+
function visibleTopics(session, state) {
|
|
701
|
+
return state === "automated" ? session?.support?.topics ?? [] : [];
|
|
702
|
+
}
|
|
703
|
+
function composerPlaceholder(hasTopics) {
|
|
704
|
+
return hasTopics ? "Or type your question" : "Write your message";
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Hand one visitor message to the transport: a topic click as `topic.select`
|
|
708
|
+
* when the transport supports it (the server resolves the answer), otherwise
|
|
709
|
+
* as the label, typed. Throws what the transport throws.
|
|
710
|
+
*/
|
|
711
|
+
function dispatchVisitorMessage(transport, clientMessageId, body, topic) {
|
|
712
|
+
if (topic && transport.selectTopic) {
|
|
713
|
+
transport.selectTopic({
|
|
714
|
+
id: clientMessageId,
|
|
715
|
+
topicId: topic.id,
|
|
716
|
+
type: "topic.select"
|
|
717
|
+
});
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
transport.sendMessage({
|
|
721
|
+
body,
|
|
722
|
+
id: clientMessageId,
|
|
723
|
+
type: "message.create"
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* One honest status line: the AI is working on an answer, or — once the
|
|
728
|
+
* visitor has waited — that the team has been told. Never both.
|
|
729
|
+
*/
|
|
730
|
+
function StatusNotice({ assistantThinking, email, showWaitingNotice }) {
|
|
731
|
+
if (assistantThinking) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
732
|
+
className: "clivly-chat-widget__notice",
|
|
733
|
+
role: "status",
|
|
734
|
+
children: "The AI assistant is looking for an answer…"
|
|
735
|
+
});
|
|
736
|
+
if (!showWaitingNotice) return null;
|
|
737
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
738
|
+
className: "clivly-chat-widget__notice",
|
|
739
|
+
role: "status",
|
|
740
|
+
children: waitingNoticeText(email)
|
|
741
|
+
});
|
|
742
|
+
}
|
|
596
743
|
function ChatWidget({ className, createTransport, defaultVisitor, getSession, initiallyOpen = false, launcherLabel = "Chat with us", storage = DEFAULT_STORAGE, style, theme = "auto", widgetId }) {
|
|
597
744
|
const [isOpen, setIsOpen] = (0, react.useState)(initiallyOpen);
|
|
598
745
|
const [error, setError] = (0, react.useState)(null);
|
|
@@ -601,6 +748,8 @@ function ChatWidget({ className, createTransport, defaultVisitor, getSession, in
|
|
|
601
748
|
const [messageDraft, setMessageDraft] = (0, react.useState)("");
|
|
602
749
|
const [messages, setMessages] = (0, react.useState)([]);
|
|
603
750
|
const [session, setSession] = (0, react.useState)(null);
|
|
751
|
+
const [supportState, setSupportState] = (0, react.useState)(null);
|
|
752
|
+
const [assistantThinking, setAssistantThinking] = (0, react.useState)(false);
|
|
604
753
|
const [visitor, setVisitor] = (0, react.useState)({
|
|
605
754
|
email: defaultVisitor?.email ?? "",
|
|
606
755
|
name: defaultVisitor?.name ?? ""
|
|
@@ -631,15 +780,18 @@ function ChatWidget({ className, createTransport, defaultVisitor, getSession, in
|
|
|
631
780
|
handlers: {
|
|
632
781
|
onClose: () => {
|
|
633
782
|
setIsReady(false);
|
|
783
|
+
setAssistantThinking(false);
|
|
634
784
|
},
|
|
635
785
|
onError: (transportError) => {
|
|
636
786
|
setError(transportError.message);
|
|
637
787
|
},
|
|
638
788
|
onEvent: (event) => {
|
|
639
789
|
applyServerEvent(event, {
|
|
790
|
+
setAssistantThinking,
|
|
640
791
|
setError,
|
|
641
792
|
setIsReady,
|
|
642
|
-
setMessages
|
|
793
|
+
setMessages,
|
|
794
|
+
setSupportState
|
|
643
795
|
});
|
|
644
796
|
},
|
|
645
797
|
onOpen: () => {
|
|
@@ -681,6 +833,7 @@ function ChatWidget({ className, createTransport, defaultVisitor, getSession, in
|
|
|
681
833
|
widgetId
|
|
682
834
|
});
|
|
683
835
|
storage?.setItem(createStorageKey(widgetId), nextSession.visitorToken);
|
|
836
|
+
setSupportState(nextSession.support?.state ?? null);
|
|
684
837
|
setSession(nextSession);
|
|
685
838
|
setMessages(sortMessages(nextSession.messages.map((message) => createLocalMessage(message))));
|
|
686
839
|
} catch (sessionError) {
|
|
@@ -713,41 +866,48 @@ function ChatWidget({ className, createTransport, defaultVisitor, getSession, in
|
|
|
713
866
|
session,
|
|
714
867
|
startSession
|
|
715
868
|
]);
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
869
|
+
/**
|
|
870
|
+
* Show the visitor's message at once and hand it to the transport. A topic
|
|
871
|
+
* click goes as `topic.select` when the transport supports it, so the
|
|
872
|
+
* server resolves the answer; otherwise as the label, typed.
|
|
873
|
+
*/
|
|
874
|
+
const sendVisitorMessage = (body, topic) => {
|
|
875
|
+
const transport = transportRef.current;
|
|
876
|
+
if (!(session && transport)) {
|
|
719
877
|
setError("Chat is not connected yet.");
|
|
720
878
|
return;
|
|
721
879
|
}
|
|
722
|
-
const body = messageDraft.trim();
|
|
723
|
-
if (!body) return;
|
|
724
880
|
const clientMessageId = generateId();
|
|
725
881
|
const optimisticMessage = {
|
|
726
882
|
author: sessionDisplayName(session),
|
|
883
|
+
automation: topic && transport.selectTopic ? "topic_choice" : null,
|
|
727
884
|
body,
|
|
728
885
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
729
886
|
id: clientMessageId,
|
|
730
887
|
senderType: "visitor"
|
|
731
888
|
};
|
|
732
889
|
setMessages((currentMessages) => sortMessages([...currentMessages, createLocalMessage(optimisticMessage, "pending")]));
|
|
733
|
-
setMessageDraft("");
|
|
734
890
|
setError(null);
|
|
735
891
|
try {
|
|
736
|
-
|
|
737
|
-
body,
|
|
738
|
-
id: clientMessageId,
|
|
739
|
-
type: "message.create"
|
|
740
|
-
});
|
|
892
|
+
dispatchVisitorMessage(transport, clientMessageId, body, topic);
|
|
741
893
|
} catch (transportError) {
|
|
742
894
|
const resolved = transportError instanceof Error ? transportError : /* @__PURE__ */ new Error("Failed to send your message.");
|
|
743
895
|
setMessages((currentMessages) => markMessageFailed(currentMessages, clientMessageId));
|
|
744
896
|
setError(resolved.message);
|
|
745
897
|
}
|
|
746
898
|
};
|
|
899
|
+
const submitMessage = (event) => {
|
|
900
|
+
event?.preventDefault();
|
|
901
|
+
const body = messageDraft.trim();
|
|
902
|
+
if (!body) return;
|
|
903
|
+
setMessageDraft("");
|
|
904
|
+
sendVisitorMessage(body);
|
|
905
|
+
};
|
|
747
906
|
const canSend = Boolean(createTransport) && isReady;
|
|
748
907
|
const greeting = session?.widget.config.greeting ?? "Tell us a little about yourself and we’ll pick up the conversation from there.";
|
|
749
908
|
const panelTitle = session?.widget.name ?? "Start a conversation";
|
|
750
909
|
const sessionSummary = sessionSummaryText(session, greeting);
|
|
910
|
+
const topics = visibleTopics(session, supportState);
|
|
751
911
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
752
912
|
className: `clivly-chat-widget ${className ?? ""}`.trim(),
|
|
753
913
|
"data-clivly-theme": resolvedTheme,
|
|
@@ -785,8 +945,10 @@ function ChatWidget({ className, createTransport, defaultVisitor, getSession, in
|
|
|
785
945
|
})]
|
|
786
946
|
}),
|
|
787
947
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
948
|
+
"aria-label": "Conversation",
|
|
788
949
|
className: "clivly-chat-widget__body",
|
|
789
950
|
ref: listRef,
|
|
951
|
+
role: "log",
|
|
790
952
|
children: [
|
|
791
953
|
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
792
954
|
className: "clivly-chat-widget__error",
|
|
@@ -797,10 +959,17 @@ function ChatWidget({ className, createTransport, defaultVisitor, getSession, in
|
|
|
797
959
|
hasSession: Boolean(session),
|
|
798
960
|
messages
|
|
799
961
|
}),
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
962
|
+
topics.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SupportTopics, {
|
|
963
|
+
disabled: !canSend,
|
|
964
|
+
onSelect: (topic) => {
|
|
965
|
+
sendVisitorMessage(topic.label, topic);
|
|
966
|
+
},
|
|
967
|
+
topics
|
|
968
|
+
}) : null,
|
|
969
|
+
session ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusNotice, {
|
|
970
|
+
assistantThinking,
|
|
971
|
+
email: session.contact?.email,
|
|
972
|
+
showWaitingNotice
|
|
804
973
|
}) : null
|
|
805
974
|
]
|
|
806
975
|
}),
|
|
@@ -822,7 +991,7 @@ function ChatWidget({ className, createTransport, defaultVisitor, getSession, in
|
|
|
822
991
|
event.preventDefault();
|
|
823
992
|
if (messageDraft.trim() && canSend) submitMessage();
|
|
824
993
|
},
|
|
825
|
-
placeholder:
|
|
994
|
+
placeholder: composerPlaceholder(topics.length > 0),
|
|
826
995
|
value: messageDraft
|
|
827
996
|
})]
|
|
828
997
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -963,6 +1132,10 @@ function createWebSocketTransport({ createSocket = (url, protocols) => new WebSo
|
|
|
963
1132
|
markRead: () => {
|
|
964
1133
|
if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ type: "conversation.read" }));
|
|
965
1134
|
},
|
|
1135
|
+
selectTopic: (event) => {
|
|
1136
|
+
if (socket.readyState !== WebSocket.OPEN) throw new Error("Chat socket is not connected.");
|
|
1137
|
+
socket.send(JSON.stringify(event));
|
|
1138
|
+
},
|
|
966
1139
|
sendMessage: (message) => {
|
|
967
1140
|
if (socket.readyState !== WebSocket.OPEN) throw new Error("Chat socket is not connected.");
|
|
968
1141
|
socket.send(JSON.stringify(message));
|
package/dist/index.d.cts
CHANGED
|
@@ -48,11 +48,30 @@ interface WidgetSummary {
|
|
|
48
48
|
}
|
|
49
49
|
interface WidgetMessage {
|
|
50
50
|
author: string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Set on messages the support assistant wrote (`greeting`, `topic`,
|
|
53
|
+
* `handoff`, `ai`, `ai_clarify`) and on a visitor's topic click
|
|
54
|
+
* (`topic_choice`). Absent or null on everything a person typed, and on
|
|
55
|
+
* every message from a server that predates the assistant.
|
|
56
|
+
*/
|
|
57
|
+
automation?: string | null;
|
|
51
58
|
body: string;
|
|
52
59
|
createdAt: string;
|
|
53
60
|
id: string;
|
|
54
61
|
senderType: WidgetSenderType;
|
|
55
62
|
}
|
|
63
|
+
type WidgetSupportState = "automated" | "handoff" | "human";
|
|
64
|
+
interface WidgetSupportTopic {
|
|
65
|
+
action: "answer" | "ask" | "link" | "human";
|
|
66
|
+
id: string;
|
|
67
|
+
label: string;
|
|
68
|
+
}
|
|
69
|
+
/** Guided support for one conversation. Answers stay on the server. */
|
|
70
|
+
interface WidgetSupport {
|
|
71
|
+
mode: "human" | "guided" | "ai";
|
|
72
|
+
state: WidgetSupportState | null;
|
|
73
|
+
topics: WidgetSupportTopic[];
|
|
74
|
+
}
|
|
56
75
|
interface WidgetSessionPayload {
|
|
57
76
|
contact: WidgetContact | null;
|
|
58
77
|
conversation: WidgetConversation;
|
|
@@ -60,6 +79,8 @@ interface WidgetSessionPayload {
|
|
|
60
79
|
messages: WidgetMessage[];
|
|
61
80
|
/** Absolute socket URL returned by Clivly's bootstrap. */
|
|
62
81
|
socketUrl?: string;
|
|
82
|
+
/** Absent or null when the widget has no guided support configured. */
|
|
83
|
+
support?: WidgetSupport | null;
|
|
63
84
|
token: string;
|
|
64
85
|
visitorToken: string;
|
|
65
86
|
widget: WidgetSummary;
|
|
@@ -83,7 +104,13 @@ interface WidgetClientCreateMessageEvent {
|
|
|
83
104
|
interface WidgetClientReadEvent {
|
|
84
105
|
type: "conversation.read";
|
|
85
106
|
}
|
|
86
|
-
|
|
107
|
+
/** The visitor picked a guided topic (see `ChatTransportConnection.selectTopic`). */
|
|
108
|
+
interface WidgetClientTopicEvent {
|
|
109
|
+
id: string;
|
|
110
|
+
topicId: string;
|
|
111
|
+
type: "topic.select";
|
|
112
|
+
}
|
|
113
|
+
type WidgetClientEvent = WidgetClientCreateMessageEvent | WidgetClientReadEvent | WidgetClientTopicEvent;
|
|
87
114
|
interface WidgetSessionReadyEvent {
|
|
88
115
|
conversationId: string;
|
|
89
116
|
type: "session.ready";
|
|
@@ -102,11 +129,20 @@ interface WidgetMessageAckEvent {
|
|
|
102
129
|
message: WidgetMessage;
|
|
103
130
|
type: "message.ack";
|
|
104
131
|
}
|
|
132
|
+
interface WidgetSupportStateEvent {
|
|
133
|
+
state: WidgetSupportState;
|
|
134
|
+
type: "support.state";
|
|
135
|
+
}
|
|
136
|
+
/** The assistant is preparing an answer (`thinking`) or has finished (`idle`). */
|
|
137
|
+
interface WidgetAssistantStatusEvent {
|
|
138
|
+
status: "thinking" | "idle";
|
|
139
|
+
type: "assistant.status";
|
|
140
|
+
}
|
|
105
141
|
interface WidgetErrorEvent {
|
|
106
142
|
message: string;
|
|
107
143
|
type: "error";
|
|
108
144
|
}
|
|
109
|
-
type WidgetServerEvent = WidgetConversationSnapshotEvent | WidgetErrorEvent | WidgetMessageAckEvent | WidgetMessageCreatedEvent | WidgetSessionReadyEvent;
|
|
145
|
+
type WidgetServerEvent = WidgetAssistantStatusEvent | WidgetConversationSnapshotEvent | WidgetErrorEvent | WidgetMessageAckEvent | WidgetMessageCreatedEvent | WidgetSessionReadyEvent | WidgetSupportStateEvent;
|
|
110
146
|
//#endregion
|
|
111
147
|
//#region src/transport.d.ts
|
|
112
148
|
interface ChatTransportHandlers {
|
|
@@ -123,6 +159,11 @@ interface ChatTransportConnection {
|
|
|
123
159
|
* without it still works, its visitors are just emailed replies they saw.
|
|
124
160
|
*/
|
|
125
161
|
markRead?: () => void;
|
|
162
|
+
/**
|
|
163
|
+
* Send a guided-topic click. Optional: without it the widget sends the
|
|
164
|
+
* topic's label as an ordinary message, so a custom transport still works.
|
|
165
|
+
*/
|
|
166
|
+
selectTopic?: (event: WidgetClientTopicEvent) => void;
|
|
126
167
|
sendMessage: (event: WidgetClientCreateMessageEvent) => void;
|
|
127
168
|
}
|
|
128
169
|
interface CreateChatTransportOptions {
|
|
@@ -246,4 +287,4 @@ export declare const createClivlyTransport: typeof createClivlyTransport$1;
|
|
|
246
287
|
export declare const mountChatWidget: typeof mountChatWidget$1;
|
|
247
288
|
export declare const mountChatWidgetScript: typeof mountChatWidgetScript$1;
|
|
248
289
|
//#endregion
|
|
249
|
-
export type { ChatTransportConnection, ChatTransportHandlers, ChatWidgetProps, ChatWidgetTheme, ChatWidgetVisitor, CreateChatTransport, CreateChatTransportOptions, CreateLoopbackTransportOptions, CreateWebSocketTransportOptions, GetSession, GetSessionOptions, ResolvedTheme, WidgetClientCreateMessageEvent, WidgetClientEvent, WidgetClientReadEvent, WidgetConfig, WidgetContact, WidgetConversation, WidgetConversationSnapshotEvent, WidgetErrorEvent, WidgetMessage, WidgetMessageAckEvent, WidgetMessageCreatedEvent, WidgetSenderType, WidgetServerEvent, WidgetSessionPayload, WidgetSessionReadyEvent, WidgetSummary };
|
|
290
|
+
export type { ChatTransportConnection, ChatTransportHandlers, ChatWidgetProps, ChatWidgetTheme, ChatWidgetVisitor, CreateChatTransport, CreateChatTransportOptions, CreateLoopbackTransportOptions, CreateWebSocketTransportOptions, GetSession, GetSessionOptions, ResolvedTheme, WidgetAssistantStatusEvent, WidgetClientCreateMessageEvent, WidgetClientEvent, WidgetClientReadEvent, WidgetClientTopicEvent, WidgetConfig, WidgetContact, WidgetConversation, WidgetConversationSnapshotEvent, WidgetErrorEvent, WidgetMessage, WidgetMessageAckEvent, WidgetMessageCreatedEvent, WidgetSenderType, WidgetServerEvent, WidgetSessionPayload, WidgetSessionReadyEvent, WidgetSummary, WidgetSupport, WidgetSupportState, WidgetSupportStateEvent, WidgetSupportTopic };
|
package/dist/index.d.mts
CHANGED
|
@@ -48,11 +48,30 @@ interface WidgetSummary {
|
|
|
48
48
|
}
|
|
49
49
|
interface WidgetMessage {
|
|
50
50
|
author: string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Set on messages the support assistant wrote (`greeting`, `topic`,
|
|
53
|
+
* `handoff`, `ai`, `ai_clarify`) and on a visitor's topic click
|
|
54
|
+
* (`topic_choice`). Absent or null on everything a person typed, and on
|
|
55
|
+
* every message from a server that predates the assistant.
|
|
56
|
+
*/
|
|
57
|
+
automation?: string | null;
|
|
51
58
|
body: string;
|
|
52
59
|
createdAt: string;
|
|
53
60
|
id: string;
|
|
54
61
|
senderType: WidgetSenderType;
|
|
55
62
|
}
|
|
63
|
+
type WidgetSupportState = "automated" | "handoff" | "human";
|
|
64
|
+
interface WidgetSupportTopic {
|
|
65
|
+
action: "answer" | "ask" | "link" | "human";
|
|
66
|
+
id: string;
|
|
67
|
+
label: string;
|
|
68
|
+
}
|
|
69
|
+
/** Guided support for one conversation. Answers stay on the server. */
|
|
70
|
+
interface WidgetSupport {
|
|
71
|
+
mode: "human" | "guided" | "ai";
|
|
72
|
+
state: WidgetSupportState | null;
|
|
73
|
+
topics: WidgetSupportTopic[];
|
|
74
|
+
}
|
|
56
75
|
interface WidgetSessionPayload {
|
|
57
76
|
contact: WidgetContact | null;
|
|
58
77
|
conversation: WidgetConversation;
|
|
@@ -60,6 +79,8 @@ interface WidgetSessionPayload {
|
|
|
60
79
|
messages: WidgetMessage[];
|
|
61
80
|
/** Absolute socket URL returned by Clivly's bootstrap. */
|
|
62
81
|
socketUrl?: string;
|
|
82
|
+
/** Absent or null when the widget has no guided support configured. */
|
|
83
|
+
support?: WidgetSupport | null;
|
|
63
84
|
token: string;
|
|
64
85
|
visitorToken: string;
|
|
65
86
|
widget: WidgetSummary;
|
|
@@ -83,7 +104,13 @@ interface WidgetClientCreateMessageEvent {
|
|
|
83
104
|
interface WidgetClientReadEvent {
|
|
84
105
|
type: "conversation.read";
|
|
85
106
|
}
|
|
86
|
-
|
|
107
|
+
/** The visitor picked a guided topic (see `ChatTransportConnection.selectTopic`). */
|
|
108
|
+
interface WidgetClientTopicEvent {
|
|
109
|
+
id: string;
|
|
110
|
+
topicId: string;
|
|
111
|
+
type: "topic.select";
|
|
112
|
+
}
|
|
113
|
+
type WidgetClientEvent = WidgetClientCreateMessageEvent | WidgetClientReadEvent | WidgetClientTopicEvent;
|
|
87
114
|
interface WidgetSessionReadyEvent {
|
|
88
115
|
conversationId: string;
|
|
89
116
|
type: "session.ready";
|
|
@@ -102,11 +129,20 @@ interface WidgetMessageAckEvent {
|
|
|
102
129
|
message: WidgetMessage;
|
|
103
130
|
type: "message.ack";
|
|
104
131
|
}
|
|
132
|
+
interface WidgetSupportStateEvent {
|
|
133
|
+
state: WidgetSupportState;
|
|
134
|
+
type: "support.state";
|
|
135
|
+
}
|
|
136
|
+
/** The assistant is preparing an answer (`thinking`) or has finished (`idle`). */
|
|
137
|
+
interface WidgetAssistantStatusEvent {
|
|
138
|
+
status: "thinking" | "idle";
|
|
139
|
+
type: "assistant.status";
|
|
140
|
+
}
|
|
105
141
|
interface WidgetErrorEvent {
|
|
106
142
|
message: string;
|
|
107
143
|
type: "error";
|
|
108
144
|
}
|
|
109
|
-
type WidgetServerEvent = WidgetConversationSnapshotEvent | WidgetErrorEvent | WidgetMessageAckEvent | WidgetMessageCreatedEvent | WidgetSessionReadyEvent;
|
|
145
|
+
type WidgetServerEvent = WidgetAssistantStatusEvent | WidgetConversationSnapshotEvent | WidgetErrorEvent | WidgetMessageAckEvent | WidgetMessageCreatedEvent | WidgetSessionReadyEvent | WidgetSupportStateEvent;
|
|
110
146
|
//#endregion
|
|
111
147
|
//#region src/transport.d.ts
|
|
112
148
|
interface ChatTransportHandlers {
|
|
@@ -123,6 +159,11 @@ interface ChatTransportConnection {
|
|
|
123
159
|
* without it still works, its visitors are just emailed replies they saw.
|
|
124
160
|
*/
|
|
125
161
|
markRead?: () => void;
|
|
162
|
+
/**
|
|
163
|
+
* Send a guided-topic click. Optional: without it the widget sends the
|
|
164
|
+
* topic's label as an ordinary message, so a custom transport still works.
|
|
165
|
+
*/
|
|
166
|
+
selectTopic?: (event: WidgetClientTopicEvent) => void;
|
|
126
167
|
sendMessage: (event: WidgetClientCreateMessageEvent) => void;
|
|
127
168
|
}
|
|
128
169
|
interface CreateChatTransportOptions {
|
|
@@ -246,4 +287,4 @@ export declare const createClivlyTransport: typeof createClivlyTransport$1;
|
|
|
246
287
|
export declare const mountChatWidget: typeof mountChatWidget$1;
|
|
247
288
|
export declare const mountChatWidgetScript: typeof mountChatWidgetScript$1;
|
|
248
289
|
//#endregion
|
|
249
|
-
export type { ChatTransportConnection, ChatTransportHandlers, ChatWidgetProps, ChatWidgetTheme, ChatWidgetVisitor, CreateChatTransport, CreateChatTransportOptions, CreateLoopbackTransportOptions, CreateWebSocketTransportOptions, GetSession, GetSessionOptions, ResolvedTheme, WidgetClientCreateMessageEvent, WidgetClientEvent, WidgetClientReadEvent, WidgetConfig, WidgetContact, WidgetConversation, WidgetConversationSnapshotEvent, WidgetErrorEvent, WidgetMessage, WidgetMessageAckEvent, WidgetMessageCreatedEvent, WidgetSenderType, WidgetServerEvent, WidgetSessionPayload, WidgetSessionReadyEvent, WidgetSummary };
|
|
290
|
+
export type { ChatTransportConnection, ChatTransportHandlers, ChatWidgetProps, ChatWidgetTheme, ChatWidgetVisitor, CreateChatTransport, CreateChatTransportOptions, CreateLoopbackTransportOptions, CreateWebSocketTransportOptions, GetSession, GetSessionOptions, ResolvedTheme, WidgetAssistantStatusEvent, WidgetClientCreateMessageEvent, WidgetClientEvent, WidgetClientReadEvent, WidgetClientTopicEvent, WidgetConfig, WidgetContact, WidgetConversation, WidgetConversationSnapshotEvent, WidgetErrorEvent, WidgetMessage, WidgetMessageAckEvent, WidgetMessageCreatedEvent, WidgetSenderType, WidgetServerEvent, WidgetSessionPayload, WidgetSessionReadyEvent, WidgetSummary, WidgetSupport, WidgetSupportState, WidgetSupportStateEvent, WidgetSupportTopic };
|