@webority/chat-react 0.0.0-stage → 0.21.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.
@@ -0,0 +1,208 @@
1
+ /**
2
+ * A failed chat request. `message` is written for the person and is safe to show as-is.
3
+ */
4
+ export declare class ChatApiError extends Error {
5
+ code: string;
6
+ status: number;
7
+ resetsAtUtc: string;
8
+ /**
9
+ * @param {string} code Machine-readable reason: the server's code (`daily-limit`,
10
+ * `assistant-unavailable`, ...), "network" when no response arrived, "aborted" when the
11
+ * request was cancelled, or "unknown".
12
+ * @param {string} message The sentence to show the person.
13
+ * @param {number} [status] HTTP status, when a response arrived.
14
+ * @param {string|null} [resetsAtUtc] For `daily-limit`: when the allowance comes back, in UTC.
15
+ */
16
+ constructor(code: string, message: string, status?: number, resetsAtUtc?: string | null);
17
+ }
18
+ export type ChatHttp = {
19
+ get: (url: string, config?: object) => Promise<{
20
+ data: any;
21
+ }>;
22
+ post: (url: string, data?: any, config?: object) => Promise<{
23
+ data: any;
24
+ }>;
25
+ put: (url: string, data?: any, config?: object) => Promise<{
26
+ data: any;
27
+ }>;
28
+ delete: (url: string, config?: object) => Promise<{
29
+ data: any;
30
+ }>;
31
+ };
32
+ export type SupportAction = {
33
+ /**
34
+ * Sent back by the confirm button; nothing happens without it.
35
+ */
36
+ token: string;
37
+ kind: "RaiseTicket" | "Reply" | "Reopen";
38
+ ticketId: string | null;
39
+ ticketNumber: string | null;
40
+ category: string | null;
41
+ subject: string | null;
42
+ body: string | null;
43
+ expiresDateTimeUtc: string;
44
+ };
45
+ export type ChatTurnInput = {
46
+ /**
47
+ * The person's question.
48
+ */
49
+ message: string;
50
+ /**
51
+ * The conversation to continue; left out to start one.
52
+ */
53
+ conversationId?: string | null;
54
+ /**
55
+ * Product-defined context about the screen, passed through as-is.
56
+ */
57
+ pageContext?: any;
58
+ };
59
+ export type ChatStreamOptions = {
60
+ /**
61
+ * Cancels the request.
62
+ */
63
+ signal?: AbortSignal;
64
+ /**
65
+ * Called for each event
66
+ * before the end of the turn: `delta { text }` to append, `reset {}` to discard what was shown,
67
+ * `actions { actions }` for support cards.
68
+ */
69
+ onEvent?: (name: "delta" | "reset" | "actions", data: any) => void;
70
+ };
71
+ export type ChatTurnResult = {
72
+ conversationId: string;
73
+ /**
74
+ * The stored reply's id, for rating; null when storing failed.
75
+ */
76
+ messageId: string | null;
77
+ /**
78
+ * The reply of record. It replaces whatever was streamed.
79
+ */
80
+ reply: string;
81
+ /**
82
+ * Null when the person has no daily limit.
83
+ */
84
+ messagesLeftToday: number | null;
85
+ };
86
+ export type ConversationSummary = {
87
+ publicId: string;
88
+ title: string | null;
89
+ createdDateTimeUtc: string;
90
+ lastMessageDateTimeUtc: string;
91
+ };
92
+ export type ConversationMessage = {
93
+ publicId: string;
94
+ role: "User" | "Assistant";
95
+ content: string;
96
+ /**
97
+ * The support actions offered with an assistant reply, as a JSON array.
98
+ */
99
+ actionsJson: string | null;
100
+ rating: "Up" | "Down" | null;
101
+ ratingNote: string | null;
102
+ createdDateTimeUtc: string;
103
+ };
104
+ export type Conversation = {
105
+ publicId: string;
106
+ title: string | null;
107
+ messages: ConversationMessage[];
108
+ };
109
+ export type ChatClient = {
110
+ sendMessage: (input: ChatTurnInput, options?: ChatStreamOptions) => Promise<ChatTurnResult>;
111
+ getAllowance: () => Promise<{
112
+ messagesLeftToday: number | null;
113
+ }>;
114
+ listConversations: (query?: {
115
+ skip?: number;
116
+ take?: number;
117
+ }) => Promise<ConversationSummary[]>;
118
+ getConversation: (id: string) => Promise<Conversation>;
119
+ deleteConversation: (id: string) => Promise<void>;
120
+ rateMessage: (conversationId: string, messageId: string, input: {
121
+ rating: "Up" | "Down";
122
+ note?: string | null;
123
+ }) => Promise<void>;
124
+ };
125
+ /**
126
+ * @typedef {object} ChatHttp An axios instance (or axios-compatible) that already carries the
127
+ * bearer token, tenant headers and refresh. The streamed turn needs axios's XHR adapter (the
128
+ * browser default) to read the reply as it arrives.
129
+ * @property {(url: string, config?: object) => Promise<{ data: any }>} get
130
+ * @property {(url: string, data?: any, config?: object) => Promise<{ data: any }>} post
131
+ * @property {(url: string, data?: any, config?: object) => Promise<{ data: any }>} put
132
+ * @property {(url: string, config?: object) => Promise<{ data: any }>} delete
133
+ */
134
+ /**
135
+ * @typedef {object} SupportAction An action the assistant prepared for the person to confirm.
136
+ * @property {string} token Sent back by the confirm button; nothing happens without it.
137
+ * @property {"RaiseTicket"|"Reply"|"Reopen"} kind
138
+ * @property {string|null} ticketId
139
+ * @property {string|null} ticketNumber
140
+ * @property {string|null} category
141
+ * @property {string|null} subject
142
+ * @property {string|null} body
143
+ * @property {string} expiresDateTimeUtc
144
+ */
145
+ /**
146
+ * @typedef {object} ChatTurnInput
147
+ * @property {string} message The person's question.
148
+ * @property {string|null} [conversationId] The conversation to continue; left out to start one.
149
+ * @property {any} [pageContext] Product-defined context about the screen, passed through as-is.
150
+ */
151
+ /**
152
+ * @typedef {object} ChatStreamOptions
153
+ * @property {AbortSignal} [signal] Cancels the request.
154
+ * @property {(name: "delta"|"reset"|"actions", data: any) => void} [onEvent] Called for each event
155
+ * before the end of the turn: `delta { text }` to append, `reset {}` to discard what was shown,
156
+ * `actions { actions }` for support cards.
157
+ */
158
+ /**
159
+ * @typedef {object} ChatTurnResult The `done` event: the turn's outcome of record.
160
+ * @property {string} conversationId
161
+ * @property {string|null} messageId The stored reply's id, for rating; null when storing failed.
162
+ * @property {string} reply The reply of record. It replaces whatever was streamed.
163
+ * @property {number|null} messagesLeftToday Null when the person has no daily limit.
164
+ */
165
+ /**
166
+ * @typedef {object} ConversationSummary
167
+ * @property {string} publicId
168
+ * @property {string|null} title
169
+ * @property {string} createdDateTimeUtc
170
+ * @property {string} lastMessageDateTimeUtc
171
+ */
172
+ /**
173
+ * @typedef {object} ConversationMessage
174
+ * @property {string} publicId
175
+ * @property {"User"|"Assistant"} role
176
+ * @property {string} content
177
+ * @property {string|null} actionsJson The support actions offered with an assistant reply, as a JSON array.
178
+ * @property {"Up"|"Down"|null} rating
179
+ * @property {string|null} ratingNote
180
+ * @property {string} createdDateTimeUtc
181
+ */
182
+ /**
183
+ * @typedef {object} Conversation
184
+ * @property {string} publicId
185
+ * @property {string|null} title
186
+ * @property {ConversationMessage[]} messages
187
+ */
188
+ /**
189
+ * @typedef {object} ChatClient
190
+ * @property {(input: ChatTurnInput, options?: ChatStreamOptions) => Promise<ChatTurnResult>} sendMessage
191
+ * @property {() => Promise<{ messagesLeftToday: number|null }>} getAllowance
192
+ * @property {(query?: { skip?: number, take?: number }) => Promise<ConversationSummary[]>} listConversations
193
+ * @property {(id: string) => Promise<Conversation>} getConversation
194
+ * @property {(id: string) => Promise<void>} deleteConversation
195
+ * @property {(conversationId: string, messageId: string, input: { rating: "Up"|"Down", note?: string|null }) => Promise<void>} rateMessage
196
+ */
197
+ /**
198
+ * Binds the chat HTTP contract to the product's own authenticated axios instance. The package
199
+ * never handles auth: tokens, tenant headers and refresh stay in the instance.
200
+ *
201
+ * @param {ChatHttp} http
202
+ * @param {{ base: string }} options `base` is the prefix the API maps the chat endpoints under,
203
+ * e.g. "/api/assistant" for `POST /api/assistant/chat`.
204
+ * @returns {ChatClient}
205
+ */
206
+ export declare function createChatClient(http: ChatHttp, { base }: {
207
+ base: string;
208
+ }): ChatClient;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * One support action the assistant prepared, shown under its reply. Nothing reaches support until
3
+ * the person confirms. The card moves from ready to sending to done (with View ticket), or to
4
+ * dismissed when the person says not now. A card whose token has expired (a reopened
5
+ * conversation) still shows what was offered, without a button that could only fail.
6
+ *
7
+ * @param {object} props
8
+ * @param {import('../client.js').SupportAction} props.action
9
+ * @param {(token: string) => Promise<{ id: string, number: string }>} props.confirmAction Sends the
10
+ * person's yes, e.g. `supportClient.confirmAction`; resolves to the ticket raised, replied on or
11
+ * reopened. A rejection's `message` is shown when present.
12
+ * @param {(ticket: { id: string, number: string }) => void} [props.onViewTicket] Shows a View
13
+ * ticket button once confirmed.
14
+ * @param {Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>} [props.labels]
15
+ */
16
+ export default function ActionCard({ action, confirmAction, onViewTicket, labels: labelOverrides }: {
17
+ action: import('../client.js').SupportAction;
18
+ confirmAction: (token: string) => Promise<{
19
+ id: string;
20
+ number: string;
21
+ }>;
22
+ onViewTicket?: (ticket: {
23
+ id: string;
24
+ number: string;
25
+ }) => void;
26
+ labels?: Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>;
27
+ }): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The question box. Enter sends; Shift and Enter starts a new line. The box clears once a
3
+ * question is handed on, and stays usable while an answer streams, though sending waits for it.
4
+ *
5
+ * @param {object} props
6
+ * @param {(message: string) => unknown} props.onSubmit Called with the trimmed question.
7
+ * @param {boolean} [props.busy=false] An answer is still arriving; Send is held.
8
+ * @param {boolean} [props.disabled=false] Nothing can be asked (no questions left today).
9
+ * @param {number} [props.maxLength=4000] The server's message limit.
10
+ * @param {import('react').Ref<HTMLTextAreaElement>} [props.inputRef] The question box, for focusing it.
11
+ * @param {Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>} [props.labels]
12
+ */
13
+ export default function ChatComposer({ onSubmit, busy, disabled, maxLength, inputRef, labels: labelOverrides }: {
14
+ onSubmit: (message: string) => unknown;
15
+ busy?: boolean;
16
+ disabled?: boolean;
17
+ maxLength?: number;
18
+ inputRef?: import('react').Ref<HTMLTextAreaElement>;
19
+ labels?: Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>;
20
+ }): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The person's past chats, newest first, for a past-chats page or a panel. It pages with Show
3
+ * more, opens one through `onOpen`, and deletes one after a confirm.
4
+ *
5
+ * @param {object} props
6
+ * @param {import('../client.js').ChatClient} props.client
7
+ * @param {(conversation: import('../client.js').ConversationSummary) => void} props.onOpen
8
+ * E.g. open the panel and call `chat.openConversation(conversation.publicId)`.
9
+ * @param {(id: string) => void} [props.onDeleted] Called after a chat is deleted, e.g. to start a
10
+ * new chat when the open one was deleted.
11
+ * @param {number} [props.pageSize=20]
12
+ * @param {Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>} [props.labels]
13
+ */
14
+ export default function ChatConversationList({ client, onOpen, onDeleted, pageSize, labels: labelOverrides }: {
15
+ client: import('../client.js').ChatClient;
16
+ onOpen: (conversation: import('../client.js').ConversationSummary) => void;
17
+ onDeleted?: (id: string) => void;
18
+ pageSize?: number;
19
+ labels?: Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>;
20
+ }): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The floating pill that opens the assistant, pinned to a bottom corner. Move it clear of other
3
+ * fixed controls (a report-an-issue button, a cookie banner) with `style`, e.g.
4
+ * `{ bottom: "5rem" }`, or with `className`.
5
+ *
6
+ * @param {object} props
7
+ * @param {() => void} props.onClick Opens the panel.
8
+ * @param {boolean} [props.open=false] Whether the panel is open; announced on the button.
9
+ * @param {"end"|"start"} [props.position="end"] The bottom corner it sits in.
10
+ * @param {string} [props.icon="sparkles"] A library icon name.
11
+ * @param {string} [props.className] Added to the floating wrapper.
12
+ * @param {import('react').CSSProperties} [props.style] Merged over the wrapper's own style.
13
+ * @param {Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>} [props.labels] `launcher` is the button text.
14
+ */
15
+ export default function ChatLauncher({ onClick, open, position, icon, className, style, labels: labelOverrides, }: {
16
+ onClick: () => void;
17
+ open?: boolean;
18
+ position?: "end" | "start";
19
+ icon?: string;
20
+ className?: string;
21
+ style?: import('react').CSSProperties;
22
+ labels?: Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>;
23
+ }): import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * One turn: the person's question on the right, the assistant's answer on the left. The answer
3
+ * is a polite live region, so a screen reader reads it once it settles; while it streams it is
4
+ * marked busy. Support actions show under the answer, and the rating once the reply is stored.
5
+ *
6
+ * @param {object} props
7
+ * @param {import('../useChat.js').ChatTurn} props.turn
8
+ * @param {(turnId: string, rating: "Up"|"Down", note?: string) => Promise<unknown>} [props.onRate]
9
+ * Shows the rating under a stored reply.
10
+ * @param {(href: string) => string|null|undefined} [props.resolveLink] See `ChatMarkdown`.
11
+ * @param {import('react').ElementType} [props.linkComponent] A router link (it receives `to`).
12
+ * @param {(token: string) => Promise<{ id: string, number: string }>} [props.confirmAction] Shows
13
+ * support action cards; see `ActionCard`.
14
+ * @param {(ticket: { id: string, number: string }) => void} [props.onViewTicket]
15
+ * @param {Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>} [props.labels]
16
+ */
17
+ export default function ChatMessage({ turn, onRate, resolveLink, linkComponent, confirmAction, onViewTicket, labels: labelOverrides }: {
18
+ turn: import('../useChat.js').ChatTurn;
19
+ onRate?: (turnId: string, rating: "Up" | "Down", note?: string) => Promise<unknown>;
20
+ resolveLink?: (href: string) => string | null | undefined;
21
+ linkComponent?: import('react').ElementType;
22
+ confirmAction?: (token: string) => Promise<{
23
+ id: string;
24
+ number: string;
25
+ }>;
26
+ onViewTicket?: (ticket: {
27
+ id: string;
28
+ number: string;
29
+ }) => void;
30
+ labels?: Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>;
31
+ }): import("react").JSX.Element;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The assistant's drawer: about 440px from the right, the full screen on a phone. The header
3
+ * carries the title, New chat and Close; the body the conversation; the footer the question box,
4
+ * the questions left today (once 10 or fewer remain) and the AI footnote. Focus stays inside
5
+ * while it is open, Escape closes it, and the question box takes focus when it opens.
6
+ *
7
+ * @param {object} props
8
+ * @param {ReturnType<typeof import('../useChat.js').useChat>} props.chat The hook's result.
9
+ * @param {boolean} props.open
10
+ * @param {() => void} props.onClose
11
+ * @param {import('react').ReactNode} [props.title] Defaults to `labels.panelTitle`.
12
+ * @param {string} [props.titleIcon="sparkles"] A library icon name.
13
+ * @param {import('react').ReactNode} [props.headerActions] Extra header controls before New chat,
14
+ * e.g. an `AppActionIcon` that opens past chats.
15
+ * @param {import('react').ReactNode} [props.empty] Shown before the first question.
16
+ * @param {import('react').ReactNode} [props.disclaimer] The footnote; defaults to `labels.disclaimer`.
17
+ * @param {number} [props.maxLength=4000] The server's message limit.
18
+ * @param {number} [props.allowanceThreshold=10] Show the questions left once this many or fewer remain.
19
+ * @param {string} [props.width="440px"]
20
+ * @param {(href: string) => string|null|undefined} [props.resolveLink] See `ChatMarkdown`.
21
+ * @param {import('react').ElementType} [props.linkComponent] A router link (it receives `to`).
22
+ * @param {(token: string) => Promise<{ id: string, number: string }>} [props.confirmAction] Shows
23
+ * support action cards, e.g. `supportClient.confirmAction`.
24
+ * @param {(ticket: { id: string, number: string }) => void} [props.onViewTicket]
25
+ * @param {boolean} [props.rating=true] Offer thumbs under stored replies.
26
+ * @param {Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>} [props.labels]
27
+ */
28
+ export default function ChatPanel({ chat, open, onClose, title, titleIcon, headerActions, empty, disclaimer, maxLength, allowanceThreshold, width, resolveLink, linkComponent, confirmAction, onViewTicket, rating, labels: labelOverrides, }: {
29
+ chat: ReturnType<typeof import('../useChat.js').useChat>;
30
+ open: boolean;
31
+ onClose: () => void;
32
+ title?: import('react').ReactNode;
33
+ titleIcon?: string;
34
+ headerActions?: import('react').ReactNode;
35
+ empty?: import('react').ReactNode;
36
+ disclaimer?: import('react').ReactNode;
37
+ maxLength?: number;
38
+ allowanceThreshold?: number;
39
+ width?: string;
40
+ resolveLink?: (href: string) => string | null | undefined;
41
+ linkComponent?: import('react').ElementType;
42
+ confirmAction?: (token: string) => Promise<{
43
+ id: string;
44
+ number: string;
45
+ }>;
46
+ onViewTicket?: (ticket: {
47
+ id: string;
48
+ number: string;
49
+ }) => void;
50
+ rating?: boolean;
51
+ labels?: Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>;
52
+ }): import("react").JSX.Element;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The conversation, oldest turn first. It keeps the newest text in view as an answer streams.
3
+ * With no turns it shows `empty`, or a default "How can I help?" state.
4
+ *
5
+ * @param {object} props
6
+ * @param {import('../useChat.js').ChatTurn[]} props.turns
7
+ * @param {import('react').ReactNode} [props.empty] Shown when there are no turns.
8
+ * @param {(turnId: string, rating: "Up"|"Down", note?: string) => Promise<unknown>} [props.onRate]
9
+ * @param {(href: string) => string|null|undefined} [props.resolveLink]
10
+ * @param {import('react').ElementType} [props.linkComponent]
11
+ * @param {(token: string) => Promise<{ id: string, number: string }>} [props.confirmAction]
12
+ * @param {(ticket: { id: string, number: string }) => void} [props.onViewTicket]
13
+ * @param {Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>} [props.labels]
14
+ */
15
+ export default function ChatThread({ turns, empty, onRate, resolveLink, linkComponent, confirmAction, onViewTicket, labels: labelOverrides, }: {
16
+ turns: import('../useChat.js').ChatTurn[];
17
+ empty?: import('react').ReactNode;
18
+ onRate?: (turnId: string, rating: "Up" | "Down", note?: string) => Promise<unknown>;
19
+ resolveLink?: (href: string) => string | null | undefined;
20
+ linkComponent?: import('react').ElementType;
21
+ confirmAction?: (token: string) => Promise<{
22
+ id: string;
23
+ number: string;
24
+ }>;
25
+ onViewTicket?: (ticket: {
26
+ id: string;
27
+ number: string;
28
+ }) => void;
29
+ labels?: Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>;
30
+ }): 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>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Thumbs for one reply. Up saves at once. Down also saves at once, then offers an optional note
3
+ * that saves the same rating again with the note. Either choice can be changed.
4
+ *
5
+ * @param {object} props
6
+ * @param {"Up"|"Down"|null} [props.rating] The rating already saved.
7
+ * @param {(rating: "Up"|"Down", note?: string) => Promise<unknown>} props.onRate Rejects on failure.
8
+ * @param {number} [props.noteMaxLength=1000]
9
+ * @param {Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>} [props.labels]
10
+ */
11
+ export default function ReplyRating({ rating, onRate, noteMaxLength, labels: labelOverrides }: {
12
+ rating?: "Up" | "Down" | null;
13
+ onRate: (rating: "Up" | "Down", note?: string) => Promise<unknown>;
14
+ noteMaxLength?: number;
15
+ labels?: Partial<typeof import('../labels.js').DEFAULT_CHAT_LABELS>;
16
+ }): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ export { ChatApiError, createChatClient } from "./client.js";
2
+ export { default as ActionCard } from "./components/ActionCard.jsx";
3
+ export { default as ChatComposer } from "./components/ChatComposer.jsx";
4
+ export { default as ChatConversationList } from "./components/ChatConversationList.jsx";
5
+ export { default as ChatLauncher } from "./components/ChatLauncher.jsx";
6
+ export { default as ChatMessage } from "./components/ChatMessage.jsx";
7
+ export { default as ChatPanel } from "./components/ChatPanel.jsx";
8
+ export { default as ChatThread } from "./components/ChatThread.jsx";
9
+ export { default as ReplyRating } from "./components/ReplyRating.jsx";
10
+ export { DEFAULT_CHAT_LABELS } from "./labels.js";
11
+ export { createSseParser } from "./lib/sse.js";
12
+ export { ChatMarkdown } from "./markdown.jsx";
13
+ export { useChat } from "./useChat.js";
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Every word the chat components show, in English. Pass a `labels` object with any subset to a
3
+ * component to change its wording; the rest keep these defaults. The few that take a value are
4
+ * functions.
5
+ */
6
+ export declare const DEFAULT_CHAT_LABELS: Readonly<{
7
+ launcher: "Ask the assistant";
8
+ panelTitle: "Assistant";
9
+ newChat: "New chat";
10
+ close: "Close";
11
+ thread: "Conversation";
12
+ you: "You";
13
+ assistant: "Assistant";
14
+ thinking: "Thinking";
15
+ emptyTitle: "How can I help?";
16
+ emptyText: "Ask a question about what you see on this page.";
17
+ composer: "Your question";
18
+ placeholder: "Ask a question";
19
+ send: "Send";
20
+ composerHint: "Press Enter to send, Shift and Enter for a new line.";
21
+ /** @type {(count: number) => string} */
22
+ allowance: (count: number) => string;
23
+ disclaimer: "Answers are written by AI and can be wrong. Check anything important.";
24
+ loadingConversation: "Loading the conversation";
25
+ rateUp: "Helpful";
26
+ rateDown: "Not helpful";
27
+ rated: "Thanks for the feedback.";
28
+ ratingNote: "What was wrong with this answer?";
29
+ ratingNoteHint: "Optional.";
30
+ ratingNoteSend: "Send";
31
+ ratingNoteSkip: "Skip";
32
+ ratingFailed: "Your rating did not save. Please try again.";
33
+ actionRaiseTicket: "New support ticket";
34
+ /** @type {(ticketNumber: string) => string} */
35
+ actionReply: (ticketNumber: string) => string;
36
+ /** @type {(ticketNumber: string) => string} */
37
+ actionReopen: (ticketNumber: string) => string;
38
+ actionNote: "Nothing is sent until you confirm.";
39
+ actionConfirm: "Confirm";
40
+ actionDismiss: "Not now";
41
+ actionDismissed: "Not sent.";
42
+ actionExpired: "This request expired and was not sent. Ask again if you still need it.";
43
+ actionFailed: "We could not send this. Please try again.";
44
+ /** @type {(ticketNumber: string) => string} */
45
+ actionDoneRaiseTicket: (ticketNumber: string) => string;
46
+ /** @type {(ticketNumber: string) => string} */
47
+ actionDoneReply: (ticketNumber: string) => string;
48
+ /** @type {(ticketNumber: string) => string} */
49
+ actionDoneReopen: (ticketNumber: string) => string;
50
+ viewTicket: "View ticket";
51
+ conversations: "Past chats";
52
+ conversationsEmptyTitle: "No past chats";
53
+ conversationsEmptyText: "Your conversations with the assistant show here.";
54
+ untitled: "Untitled chat";
55
+ deleteConversation: "Delete chat";
56
+ deleteConfirmTitle: "Delete this chat?";
57
+ deleteConfirmMessage: "The conversation is removed for good.";
58
+ deleteConfirm: "Delete";
59
+ deleteFailed: "We could not delete this chat. Please try again.";
60
+ cancel: "Cancel";
61
+ loadMore: "Show more";
62
+ loadFailed: "We could not load your chats. Please try again.";
63
+ retry: "Try again";
64
+ }>;
65
+ /**
66
+ * @param {Partial<typeof DEFAULT_CHAT_LABELS>} [labels]
67
+ * @returns {typeof DEFAULT_CHAT_LABELS}
68
+ */
69
+ export declare function mergeLabels(labels?: Partial<typeof DEFAULT_CHAT_LABELS>): typeof DEFAULT_CHAT_LABELS;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * A timestamp in the viewer's own time zone as "21 Sep 2026, 3:30 pm IST", with the zone named
3
+ * so the reader knows which clock it is. Returns a plain hyphen for a missing or unreadable value.
4
+ *
5
+ * @param {string|null|undefined} iso
6
+ * @returns {string}
7
+ */
8
+ export declare function formatDateTime(iso: string | null | undefined): string;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @typedef {object} SseEvent
3
+ * @property {string} name The `event:` field, or "message" when the block names none.
4
+ * @property {any} data The `data:` lines joined by newlines and parsed as JSON.
5
+ */
6
+ export type SseEvent = {
7
+ /**
8
+ * The `event:` field, or "message" when the block names none.
9
+ */
10
+ name: string;
11
+ /**
12
+ * The `data:` lines joined by newlines and parsed as JSON.
13
+ */
14
+ data: any;
15
+ };
16
+ /**
17
+ * An incremental reader for a `text/event-stream` body that arrives in pieces. Each `push` takes
18
+ * the next piece of text; only complete events (ended by a blank line) are handed on, and the
19
+ * incomplete tail waits for the next piece. `end` flushes a final event the stream closed
20
+ * without a blank line after.
21
+ *
22
+ * Line endings may be `\n`, `\r\n` or `\r`, and a `\r\n` pair may be split across two pieces.
23
+ * Comment lines (`:`), `id` and `retry` are ignored. A block whose data is not JSON is reported
24
+ * as an `error` event with the code `invalid-response`, so a caller ends the turn instead of
25
+ * hanging on it.
26
+ *
27
+ * @param {(event: SseEvent) => void} onEvent
28
+ * @returns {{ push: (text: string) => void, end: () => void }}
29
+ */
30
+ export declare function createSseParser(onEvent: (event: SseEvent) => void): {
31
+ push: (text: string) => void;
32
+ end: () => void;
33
+ };
@@ -0,0 +1,97 @@
1
+ export type MarkdownBlockShape = {
2
+ type: "paragraph";
3
+ lines: string[];
4
+ } | {
5
+ type: "heading";
6
+ text: string;
7
+ } | {
8
+ type: "list";
9
+ ordered: boolean;
10
+ start: number;
11
+ items: string[];
12
+ } | {
13
+ type: "table";
14
+ header: string[];
15
+ rows: string[][];
16
+ } | {
17
+ type: "code";
18
+ text: string;
19
+ } | {
20
+ type: "rule";
21
+ };
22
+ export type MarkdownBlock = MarkdownBlockShape & {
23
+ key: number;
24
+ };
25
+ /**
26
+ * @typedef {{ type: "paragraph", lines: string[] }
27
+ * | { type: "heading", text: string }
28
+ * | { type: "list", ordered: boolean, start: number, items: string[] }
29
+ * | { type: "table", header: string[], rows: string[][] }
30
+ * | { type: "code", text: string }
31
+ * | { type: "rule" }} MarkdownBlockShape
32
+ * @typedef {MarkdownBlockShape & { key: number }} MarkdownBlock
33
+ */
34
+ /**
35
+ * Splits reply text into blocks. Each block's `key` is its position, so text arriving at the end
36
+ * of a streaming reply updates the last block in place and never remounts the ones before it.
37
+ *
38
+ * @param {string} text
39
+ * @returns {MarkdownBlock[]}
40
+ */
41
+ export declare function parseBlocks(text: string): MarkdownBlock[];
42
+ export type InlineToken = {
43
+ type: "text";
44
+ text: string;
45
+ key: number;
46
+ } | {
47
+ type: "code";
48
+ text: string;
49
+ key: number;
50
+ } | {
51
+ type: "strong" | "em";
52
+ children: InlineToken[];
53
+ key: number;
54
+ } | {
55
+ type: "link";
56
+ href: string;
57
+ children: InlineToken[];
58
+ key: number;
59
+ };
60
+ /**
61
+ * Reads bold (`**` or `__`), italics (`*` or `_`), inline code and `[label](href)` links. A marker
62
+ * with no partner yet (a reply still streaming) shows as the characters typed.
63
+ *
64
+ * @param {string} text
65
+ * @returns {InlineToken[]}
66
+ */
67
+ export declare function parseInline(text: string): InlineToken[];
68
+ /**
69
+ * The in-app route a reply link may open, or null when it may not open one. Only
70
+ * `scheme:kind/id` hrefs reach `resolveLink`, and only a route it returns that starts with a
71
+ * single "/" is used.
72
+ *
73
+ * @param {string} href
74
+ * @param {((href: string) => string|null|undefined)|undefined} resolveLink
75
+ * @returns {string|null}
76
+ */
77
+ export declare function linkRoute(href: string, resolveLink: ((href: string) => string | null | undefined) | undefined): string | null;
78
+ /**
79
+ * A reply rendered from a small, safe subset of Markdown: paragraphs, bold, italics, inline code,
80
+ * bullet and numbered lists, simple tables, headings (shown as bold lines) and code blocks. It
81
+ * never renders HTML: every character reaches the page as text. A link shows only when its href
82
+ * is `scheme:kind/id` and `resolveLink` maps it to an in-app route; any other link shows its label
83
+ * as plain text.
84
+ *
85
+ * @param {object} props
86
+ * @param {string} props.text
87
+ * @param {(href: string) => string|null|undefined} [props.resolveLink] Maps `scheme:kind/id` to a
88
+ * route such as "/support?ticket=3f2a", or null to leave it as text.
89
+ * @param {import('react').ElementType} [props.linkComponent] A router link (it receives `to`).
90
+ * @param {string} [props.className]
91
+ */
92
+ export declare function ChatMarkdown({ text, resolveLink, linkComponent, className }: {
93
+ text: string;
94
+ resolveLink?: (href: string) => string | null | undefined;
95
+ linkComponent?: import('react').ElementType;
96
+ className?: string;
97
+ }): import("react").JSX.Element;