@webority/chat-react 0.22.0 → 0.23.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
CHANGED
|
@@ -194,11 +194,13 @@ Escape closes it, and the question box takes focus when it opens.
|
|
|
194
194
|
| `resolveLink`, `linkComponent` | Reply links; see Replies below. |
|
|
195
195
|
| `confirmAction`, `onViewTicket` | Support action cards; see `ActionCard`. |
|
|
196
196
|
| `rating` | Offer ratings under stored replies. Default true. |
|
|
197
|
+
| `renderReply` | Draws each assistant reply your way; see Custom reply rendering below. |
|
|
197
198
|
|
|
198
199
|
### `<ChatThread />`, `<ChatMessage />`, `<ChatComposer />`
|
|
199
200
|
|
|
200
201
|
The panel's parts, for a product that lays out its own assistant page. `ChatThread` takes
|
|
201
|
-
`turns`, `empty`, `onRate`, `resolveLink`, `linkComponent`, `confirmAction` and
|
|
202
|
+
`turns`, `empty`, `onRate`, `resolveLink`, `linkComponent`, `confirmAction`, `onViewTicket` and
|
|
203
|
+
`renderReply`.
|
|
202
204
|
Each answer is a polite live region, marked busy while it streams. `ChatComposer` takes
|
|
203
205
|
`onSubmit`, `busy`, `disabled`, `maxLength` and `inputRef`; Enter sends, Shift and Enter starts a
|
|
204
206
|
new line.
|
|
@@ -231,3 +233,21 @@ A link shows only when its href has the form `scheme:kind/id` (for example `tick
|
|
|
231
233
|
and `resolveLink(href)` returns an in-app route starting with `/`. Any other link, including every
|
|
232
234
|
web address, shows its label as plain text. Pass the router's `Link` as `linkComponent` so a link
|
|
233
235
|
navigates inside the portal.
|
|
236
|
+
|
|
237
|
+
### Custom reply rendering
|
|
238
|
+
|
|
239
|
+
A product that draws replies its own way (charts, maths) passes `renderReply` to `ChatPanel`,
|
|
240
|
+
`ChatThread` or `ChatMessage`. It is called as `renderReply(turn, { streaming })` for each
|
|
241
|
+
assistant reply that has text, and what it returns replaces `ChatMarkdown`. `turn.answer` is the
|
|
242
|
+
text so far, and `streaming` is true while it is still arriving. The person's question, the
|
|
243
|
+
"thinking" state, errors, action cards, the rating and the layout stay the library's. With no
|
|
244
|
+
`renderReply`, replies render as above.
|
|
245
|
+
|
|
246
|
+
```jsx
|
|
247
|
+
<ChatPanel
|
|
248
|
+
chat={chat}
|
|
249
|
+
open={open}
|
|
250
|
+
onClose={() => setOpen(false)}
|
|
251
|
+
renderReply={(turn, { streaming }) => <RichReply text={turn.answer} live={streaming} />}
|
|
252
|
+
/>
|
|
253
|
+
```
|
package/dist/chat-react.js
CHANGED
|
@@ -471,21 +471,21 @@ function re({ client: a, onOpen: s, onDeleted: c, pageSize: l = 20, labels: d })
|
|
|
471
471
|
}
|
|
472
472
|
//#endregion
|
|
473
473
|
//#region src/components/ChatLauncher.jsx
|
|
474
|
-
var ie = {
|
|
475
|
-
|
|
476
|
-
|
|
474
|
+
var ie = {
|
|
475
|
+
zIndex: "var(--wui-z-fixed)",
|
|
476
|
+
bottom: 0
|
|
477
|
+
}, ae = {
|
|
478
|
+
end: { insetInlineEnd: 0 },
|
|
479
|
+
start: { insetInlineStart: 0 }
|
|
477
480
|
};
|
|
478
481
|
function oe({ onClick: e, open: t = !1, position: r = "end", icon: i = "sparkles", className: a, style: o, labels: s }) {
|
|
479
482
|
let c = M(s);
|
|
480
483
|
return /* @__PURE__ */ b("span", {
|
|
481
484
|
"data-support-capture": "exclude",
|
|
482
|
-
className: [
|
|
483
|
-
"position-fixed bottom-0 m-3",
|
|
484
|
-
ae[r],
|
|
485
|
-
a
|
|
486
|
-
].filter(Boolean).join(" "),
|
|
485
|
+
className: ["position-fixed m-3", a].filter(Boolean).join(" "),
|
|
487
486
|
style: {
|
|
488
487
|
...ie,
|
|
488
|
+
...ae[r],
|
|
489
489
|
...o
|
|
490
490
|
},
|
|
491
491
|
children: /* @__PURE__ */ b(n, {
|
|
@@ -842,8 +842,8 @@ var pe = {
|
|
|
842
842
|
whiteSpace: "pre-wrap",
|
|
843
843
|
overflowWrap: "anywhere"
|
|
844
844
|
};
|
|
845
|
-
function Q({ turn: e, onRate: n, resolveLink: i, linkComponent: a, confirmAction: o, onViewTicket: s,
|
|
846
|
-
let
|
|
845
|
+
function Q({ turn: e, onRate: n, resolveLink: i, linkComponent: a, confirmAction: o, onViewTicket: s, renderReply: c, labels: l }) {
|
|
846
|
+
let u = M(l), f = e.streaming && !e.answer;
|
|
847
847
|
return /* @__PURE__ */ x("li", {
|
|
848
848
|
className: "d-flex flex-column gap-2",
|
|
849
849
|
children: [e.question && /* @__PURE__ */ x("div", {
|
|
@@ -851,7 +851,7 @@ function Q({ turn: e, onRate: n, resolveLink: i, linkComponent: a, confirmAction
|
|
|
851
851
|
style: pe,
|
|
852
852
|
children: [/* @__PURE__ */ x("span", {
|
|
853
853
|
className: "visually-hidden",
|
|
854
|
-
children: [
|
|
854
|
+
children: [u.you, ": "]
|
|
855
855
|
}), /* @__PURE__ */ b(r, {
|
|
856
856
|
padding: "sm",
|
|
857
857
|
variant: "muted",
|
|
@@ -864,25 +864,25 @@ function Q({ turn: e, onRate: n, resolveLink: i, linkComponent: a, confirmAction
|
|
|
864
864
|
children: [
|
|
865
865
|
/* @__PURE__ */ x("span", {
|
|
866
866
|
className: "visually-hidden",
|
|
867
|
-
children: [
|
|
867
|
+
children: [u.assistant, ": "]
|
|
868
868
|
}),
|
|
869
869
|
/* @__PURE__ */ b("div", {
|
|
870
870
|
"aria-live": "polite",
|
|
871
871
|
"aria-busy": e.streaming,
|
|
872
|
-
children:
|
|
872
|
+
children: f ? /* @__PURE__ */ x("span", {
|
|
873
873
|
className: "d-inline-flex align-items-center gap-2 wui-text-sm text-body-secondary",
|
|
874
874
|
children: [/* @__PURE__ */ b(d, {
|
|
875
875
|
size: "sm",
|
|
876
|
-
label:
|
|
876
|
+
label: u.thinking
|
|
877
877
|
}), /* @__PURE__ */ b("span", {
|
|
878
878
|
"aria-hidden": "true",
|
|
879
|
-
children:
|
|
879
|
+
children: u.thinking
|
|
880
880
|
})]
|
|
881
|
-
}) : e.answer && /* @__PURE__ */ b(X, {
|
|
881
|
+
}) : e.answer && (c ? c(e, { streaming: !!e.streaming }) : /* @__PURE__ */ b(X, {
|
|
882
882
|
text: e.answer,
|
|
883
883
|
resolveLink: i,
|
|
884
884
|
linkComponent: a
|
|
885
|
-
})
|
|
885
|
+
}))
|
|
886
886
|
}),
|
|
887
887
|
e.error && /* @__PURE__ */ b(t, {
|
|
888
888
|
size: "sm",
|
|
@@ -893,12 +893,12 @@ function Q({ turn: e, onRate: n, resolveLink: i, linkComponent: a, confirmAction
|
|
|
893
893
|
action: e,
|
|
894
894
|
confirmAction: o,
|
|
895
895
|
onViewTicket: s,
|
|
896
|
-
labels:
|
|
896
|
+
labels: l
|
|
897
897
|
}, e.token)),
|
|
898
898
|
n && e.messageId && !e.streaming && /* @__PURE__ */ b(Z, {
|
|
899
899
|
rating: e.rating,
|
|
900
900
|
onRate: (t, r) => n(e.id, t, r),
|
|
901
|
-
labels:
|
|
901
|
+
labels: l
|
|
902
902
|
})
|
|
903
903
|
]
|
|
904
904
|
})]
|
|
@@ -906,23 +906,23 @@ function Q({ turn: e, onRate: n, resolveLink: i, linkComponent: a, confirmAction
|
|
|
906
906
|
}
|
|
907
907
|
//#endregion
|
|
908
908
|
//#region src/components/ChatThread.jsx
|
|
909
|
-
function $({ turns: e, empty: t, onRate: n, resolveLink: r, linkComponent: i, confirmAction: a, onViewTicket: s,
|
|
910
|
-
let
|
|
909
|
+
function $({ turns: e, empty: t, onRate: n, resolveLink: r, linkComponent: i, confirmAction: a, onViewTicket: s, renderReply: c, labels: l }) {
|
|
910
|
+
let u = M(l), d = _(null), f = e[e.length - 1];
|
|
911
911
|
return h(() => {
|
|
912
|
-
|
|
912
|
+
d.current?.scrollIntoView({ block: "end" });
|
|
913
913
|
}, [
|
|
914
914
|
e.length,
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
915
|
+
f?.answer,
|
|
916
|
+
f?.error,
|
|
917
|
+
f?.actions
|
|
918
918
|
]), e.length === 0 ? t ?? /* @__PURE__ */ b(o, {
|
|
919
919
|
icon: "sparkles",
|
|
920
920
|
size: "sm",
|
|
921
|
-
title:
|
|
922
|
-
text:
|
|
921
|
+
title: u.emptyTitle,
|
|
922
|
+
text: u.emptyText
|
|
923
923
|
}) : /* @__PURE__ */ x(y, { children: [/* @__PURE__ */ b("ol", {
|
|
924
924
|
className: "list-unstyled d-flex flex-column gap-3 mb-0",
|
|
925
|
-
"aria-label":
|
|
925
|
+
"aria-label": u.thread,
|
|
926
926
|
children: e.map((e) => /* @__PURE__ */ b(Q, {
|
|
927
927
|
turn: e,
|
|
928
928
|
onRate: n,
|
|
@@ -930,18 +930,19 @@ function $({ turns: e, empty: t, onRate: n, resolveLink: r, linkComponent: i, co
|
|
|
930
930
|
linkComponent: i,
|
|
931
931
|
confirmAction: a,
|
|
932
932
|
onViewTicket: s,
|
|
933
|
-
|
|
933
|
+
renderReply: c,
|
|
934
|
+
labels: l
|
|
934
935
|
}, e.id))
|
|
935
|
-
}), /* @__PURE__ */ b("div", { ref:
|
|
936
|
+
}), /* @__PURE__ */ b("div", { ref: d })] });
|
|
936
937
|
}
|
|
937
938
|
//#endregion
|
|
938
939
|
//#region src/components/ChatPanel.jsx
|
|
939
|
-
function me({ chat: n, open: r, onClose: i, title: a, titleIcon: o = "sparkles", headerActions: c, empty: u, disclaimer: f, maxLength: p = 4e3, allowanceThreshold: m = 10, width: g = "440px", resolveLink: v, linkComponent: y, confirmAction: S, onViewTicket: C, rating: w = !0,
|
|
940
|
-
let
|
|
940
|
+
function me({ chat: n, open: r, onClose: i, title: a, titleIcon: o = "sparkles", headerActions: c, empty: u, disclaimer: f, maxLength: p = 4e3, allowanceThreshold: m = 10, width: g = "440px", resolveLink: v, linkComponent: y, confirmAction: S, onViewTicket: C, rating: w = !0, renderReply: T, labels: E }) {
|
|
941
|
+
let D = M(E), O = _(null), { refreshAllowance: k } = n;
|
|
941
942
|
h(() => {
|
|
942
|
-
r &&
|
|
943
|
-
}, [r,
|
|
944
|
-
let
|
|
943
|
+
r && k().catch(() => {});
|
|
944
|
+
}, [r, k]);
|
|
945
|
+
let A = n.messagesLeftToday, j = a ?? D.panelTitle;
|
|
945
946
|
return /* @__PURE__ */ b(l, {
|
|
946
947
|
show: r,
|
|
947
948
|
onHide: i,
|
|
@@ -957,20 +958,20 @@ function me({ chat: n, open: r, onClose: i, title: a, titleIcon: o = "sparkles",
|
|
|
957
958
|
name: o,
|
|
958
959
|
size: 18
|
|
959
960
|
})
|
|
960
|
-
}),
|
|
961
|
+
}), j]
|
|
961
962
|
}), /* @__PURE__ */ x("div", {
|
|
962
963
|
className: "d-flex align-items-center gap-1",
|
|
963
964
|
children: [
|
|
964
965
|
c,
|
|
965
966
|
/* @__PURE__ */ b(e, {
|
|
966
967
|
icon: "plus",
|
|
967
|
-
label:
|
|
968
|
+
label: D.newChat,
|
|
968
969
|
onClick: n.newConversation,
|
|
969
970
|
disabled: n.turns.length === 0 && !n.loading
|
|
970
971
|
}),
|
|
971
972
|
/* @__PURE__ */ b(e, {
|
|
972
973
|
icon: "x",
|
|
973
|
-
label:
|
|
974
|
+
label: D.close,
|
|
974
975
|
onClick: i
|
|
975
976
|
})
|
|
976
977
|
]
|
|
@@ -979,30 +980,30 @@ function me({ chat: n, open: r, onClose: i, title: a, titleIcon: o = "sparkles",
|
|
|
979
980
|
footer: /* @__PURE__ */ x("div", {
|
|
980
981
|
className: "d-flex flex-column gap-2",
|
|
981
982
|
children: [
|
|
982
|
-
|
|
983
|
+
A !== null && A <= m && /* @__PURE__ */ b("p", {
|
|
983
984
|
className: "wui-text-xs text-body-secondary mb-0",
|
|
984
985
|
role: "status",
|
|
985
|
-
children:
|
|
986
|
+
children: D.allowance(A)
|
|
986
987
|
}),
|
|
987
988
|
/* @__PURE__ */ b(I, {
|
|
988
989
|
onSubmit: n.submit,
|
|
989
990
|
busy: n.streaming || n.loading,
|
|
990
|
-
disabled:
|
|
991
|
+
disabled: A === 0,
|
|
991
992
|
maxLength: p,
|
|
992
|
-
inputRef:
|
|
993
|
-
labels:
|
|
993
|
+
inputRef: O,
|
|
994
|
+
labels: E
|
|
994
995
|
}),
|
|
995
996
|
/* @__PURE__ */ b("p", {
|
|
996
997
|
className: "wui-text-xs text-body-secondary mb-0",
|
|
997
|
-
children: f ??
|
|
998
|
+
children: f ?? D.disclaimer
|
|
998
999
|
})
|
|
999
1000
|
]
|
|
1000
1001
|
}),
|
|
1001
|
-
ariaLabel: typeof
|
|
1002
|
-
onEntered: () =>
|
|
1002
|
+
ariaLabel: typeof j == "string" ? j : D.panelTitle,
|
|
1003
|
+
onEntered: () => O.current?.focus(),
|
|
1003
1004
|
children: n.loading ? /* @__PURE__ */ b("div", {
|
|
1004
1005
|
className: "d-flex justify-content-center py-4",
|
|
1005
|
-
children: /* @__PURE__ */ b(d, { label:
|
|
1006
|
+
children: /* @__PURE__ */ b(d, { label: D.loadingConversation })
|
|
1006
1007
|
}) : n.loadError ? /* @__PURE__ */ b(t, {
|
|
1007
1008
|
variant: "danger",
|
|
1008
1009
|
children: n.loadError.message
|
|
@@ -1014,7 +1015,8 @@ function me({ chat: n, open: r, onClose: i, title: a, titleIcon: o = "sparkles",
|
|
|
1014
1015
|
linkComponent: y,
|
|
1015
1016
|
confirmAction: S,
|
|
1016
1017
|
onViewTicket: C,
|
|
1017
|
-
|
|
1018
|
+
renderReply: T,
|
|
1019
|
+
labels: E
|
|
1018
1020
|
})
|
|
1019
1021
|
});
|
|
1020
1022
|
}
|
|
@@ -12,9 +12,12 @@
|
|
|
12
12
|
* @param {(token: string) => Promise<{ id: string, number: string }>} [props.confirmAction] Shows
|
|
13
13
|
* support action cards; see `ActionCard`.
|
|
14
14
|
* @param {(ticket: { id: string, number: string }) => void} [props.onViewTicket]
|
|
15
|
+
* @param {(turn: import('../useChat.js').ChatTurn, state: { streaming: boolean }) => import('react').ReactNode} [props.renderReply]
|
|
16
|
+
* Draws the assistant's answer in place of `ChatMarkdown` (charts, maths). The question, action
|
|
17
|
+
* cards, rating and layout stay the library's.
|
|
15
18
|
* @param {Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>} [props.labels]
|
|
16
19
|
*/
|
|
17
|
-
export default function ChatMessage({ turn, onRate, resolveLink, linkComponent, confirmAction, onViewTicket, labels: labelOverrides }: {
|
|
20
|
+
export default function ChatMessage({ turn, onRate, resolveLink, linkComponent, confirmAction, onViewTicket, renderReply, labels: labelOverrides, }: {
|
|
18
21
|
turn: import('../useChat.js').ChatTurn;
|
|
19
22
|
onRate?: (turnId: string, rating: "Up" | "Down", note?: string) => Promise<unknown>;
|
|
20
23
|
resolveLink?: (href: string) => string | null | undefined;
|
|
@@ -27,5 +30,8 @@ export default function ChatMessage({ turn, onRate, resolveLink, linkComponent,
|
|
|
27
30
|
id: string;
|
|
28
31
|
number: string;
|
|
29
32
|
}) => void;
|
|
33
|
+
renderReply?: (turn: import('../useChat.js').ChatTurn, state: {
|
|
34
|
+
streaming: boolean;
|
|
35
|
+
}) => import('react').ReactNode;
|
|
30
36
|
labels?: Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>;
|
|
31
37
|
}): import("react").JSX.Element;
|
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
* support action cards, e.g. `supportClient.confirmAction`.
|
|
24
24
|
* @param {(ticket: { id: string, number: string }) => void} [props.onViewTicket]
|
|
25
25
|
* @param {boolean} [props.rating=true] Offer thumbs under stored replies.
|
|
26
|
+
* @param {(turn: import('../useChat.js').ChatTurn, state: { streaming: boolean }) => import('react').ReactNode} [props.renderReply]
|
|
27
|
+
* Draws each assistant answer instead of `ChatMarkdown`; see `ChatMessage`.
|
|
26
28
|
* @param {Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>} [props.labels]
|
|
27
29
|
*/
|
|
28
|
-
export default function ChatPanel({ chat, open, onClose, title, titleIcon, headerActions, empty, disclaimer, maxLength, allowanceThreshold, width, resolveLink, linkComponent, confirmAction, onViewTicket, rating, labels: labelOverrides, }: {
|
|
30
|
+
export default function ChatPanel({ chat, open, onClose, title, titleIcon, headerActions, empty, disclaimer, maxLength, allowanceThreshold, width, resolveLink, linkComponent, confirmAction, onViewTicket, rating, renderReply, labels: labelOverrides, }: {
|
|
29
31
|
chat: ReturnType<typeof import('../useChat.js').useChat>;
|
|
30
32
|
open: boolean;
|
|
31
33
|
onClose: () => void;
|
|
@@ -48,5 +50,8 @@ export default function ChatPanel({ chat, open, onClose, title, titleIcon, heade
|
|
|
48
50
|
number: string;
|
|
49
51
|
}) => void;
|
|
50
52
|
rating?: boolean;
|
|
53
|
+
renderReply?: (turn: import('../useChat.js').ChatTurn, state: {
|
|
54
|
+
streaming: boolean;
|
|
55
|
+
}) => import('react').ReactNode;
|
|
51
56
|
labels?: Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>;
|
|
52
57
|
}): import("react").JSX.Element;
|
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
* @param {import('react').ElementType} [props.linkComponent]
|
|
11
11
|
* @param {(token: string) => Promise<{ id: string, number: string }>} [props.confirmAction]
|
|
12
12
|
* @param {(ticket: { id: string, number: string }) => void} [props.onViewTicket]
|
|
13
|
+
* @param {(turn: import('../useChat.js').ChatTurn, state: { streaming: boolean }) => import('react').ReactNode} [props.renderReply]
|
|
14
|
+
* Draws each assistant answer instead of `ChatMarkdown`; see `ChatMessage`.
|
|
13
15
|
* @param {Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>} [props.labels]
|
|
14
16
|
*/
|
|
15
|
-
export default function ChatThread({ turns, empty, onRate, resolveLink, linkComponent, confirmAction, onViewTicket, labels: labelOverrides, }: {
|
|
17
|
+
export default function ChatThread({ turns, empty, onRate, resolveLink, linkComponent, confirmAction, onViewTicket, renderReply, labels: labelOverrides, }: {
|
|
16
18
|
turns: import('../useChat.js').ChatTurn[];
|
|
17
19
|
empty?: import('react').ReactNode;
|
|
18
20
|
onRate?: (turnId: string, rating: "Up" | "Down", note?: string) => Promise<unknown>;
|
|
@@ -26,5 +28,8 @@ export default function ChatThread({ turns, empty, onRate, resolveLink, linkComp
|
|
|
26
28
|
id: string;
|
|
27
29
|
number: string;
|
|
28
30
|
}) => void;
|
|
31
|
+
renderReply?: (turn: import('../useChat.js').ChatTurn, state: {
|
|
32
|
+
streaming: boolean;
|
|
33
|
+
}) => import('react').ReactNode;
|
|
29
34
|
labels?: Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>;
|
|
30
35
|
}): string | number | bigint | boolean | import("react").JSX.Element | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | Iterable<import("react").ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/chat-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Webority AI assistant for React portals: a streaming chat client, a store-agnostic chat hook, and the launcher, panel, thread, rating and support action card, built on @webority/ui-react.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"react": "^19.0.0",
|
|
33
33
|
"react-bootstrap": "^2.10.0",
|
|
34
34
|
"react-dom": "^19.0.0",
|
|
35
|
-
"@webority/ui-react": "0.
|
|
35
|
+
"@webority/ui-react": "0.23.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@testing-library/dom": "10.4.2",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"typescript": "^7.0.2",
|
|
51
51
|
"vite": "^8.0.12",
|
|
52
52
|
"vitest": "5.0.1",
|
|
53
|
-
"@webority/ui-react": "0.
|
|
53
|
+
"@webority/ui-react": "0.23.0"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "vite build && tsc -p tsconfig.json",
|