@filigran/chatbot 3.19.0 → 3.20.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/CHANGELOG.md +147 -2
- package/README.md +55 -22
- package/dist/index.d.ts +68 -8
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/markdown.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,7 +8,152 @@ the versions follow [semantic versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
8
8
|
This file starts at 3.10.0; earlier releases are on the
|
|
9
9
|
[releases page](https://github.com/FiligranHQ/filigran-ui/releases).
|
|
10
10
|
|
|
11
|
-
## 3.
|
|
11
|
+
## 3.20.0 — unreleased
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `ChatMarkerRenderer.standalone`: an answer that is the renderer's marker and
|
|
16
|
+
nothing else shows the card alone, without the agent's name, the time or the
|
|
17
|
+
actions, as a note between messages (a change of agent, for instance). Each
|
|
18
|
+
answer showed its marker's card inside the answer's frame.
|
|
19
|
+
- The panel speaks German, Italian, Korean, Russian and Simplified Chinese on
|
|
20
|
+
its own: a catalog for each, picked from `locale` by its language whatever
|
|
21
|
+
the region (`de-de`, `it-it`, `ko-kr`, `ru-ru`, `zh-cn`). Those languages
|
|
22
|
+
showed in English unless the host translated every key through `t`.
|
|
23
|
+
Traditional Chinese tags (`zh-TW`, `zh-HK`, `zh-Hant`) keep falling back.
|
|
24
|
+
- An agent the backend marks `disable_chat: true` (`XtmAgent.disableChat`) is
|
|
25
|
+
said `Not recommended for chat` in the agent picker, and the composer warns
|
|
26
|
+
while it is the conversation's agent as the header names it. It stays
|
|
27
|
+
selectable.
|
|
28
|
+
- The header names the channel a conversation was started in under the
|
|
29
|
+
agent's name (`Agent · {channel}`), as its row does, when the host draws no
|
|
30
|
+
`headerSubtitle`; `channelLabel` names it there too. The restore's
|
|
31
|
+
`channel` names it before the list is read.
|
|
32
|
+
- The composer's warning also covers an agent the draft `@`-mentions that is
|
|
33
|
+
not meant for chat, until the mention is removed.
|
|
34
|
+
- With workspaces, the list's `Not in a workspace` group has a `New
|
|
35
|
+
conversation outside a workspace` button, beside each workspace's own.
|
|
36
|
+
- In a reply's "Working files" list, a file the same reply wrote again later,
|
|
37
|
+
with the same name and kind of file (MIME type without its parameters, else
|
|
38
|
+
the type label), carries a "Superseded" tag; its last version carries none.
|
|
39
|
+
A file listed again ranks where it first appeared.
|
|
40
|
+
- `ChatPanelHandle.attachFiles(files)` puts files in the composer's tray as if
|
|
41
|
+
the user picked them, uploaded and held to the same limits, for the next
|
|
42
|
+
message: files another app shared to the host. Nothing happens where the
|
|
43
|
+
panel takes no files.
|
|
44
|
+
- `initialConversationListCollapsed` starts the conversation list collapsed to
|
|
45
|
+
its strip, from which the user opens it: for a phone or a narrow window.
|
|
46
|
+
Read once, when the panel mounts.
|
|
47
|
+
- `replaceMessage(message, { streaming })` lets a host draw a message its own
|
|
48
|
+
way: `undefined` or a boolean leaves it to the panel, `null` leaves it out,
|
|
49
|
+
and anything else is drawn in its place without the message's frame. It is
|
|
50
|
+
asked of every message, the reply's empty placeholder included, and a throw
|
|
51
|
+
leaves that message to the panel. For messages a host folds into one of its
|
|
52
|
+
own, such as the replies of a turn cut and resumed shown as one card.
|
|
53
|
+
- `expandedWorkspaceGroups` and `onExpandedWorkspaceGroupsChange` let a host
|
|
54
|
+
keep, per user, which workspace groups of the conversation list are open.
|
|
55
|
+
Given, every other group starts collapsed until the user opens it, and a
|
|
56
|
+
workspace the user creates from the list opens. A host still reading them
|
|
57
|
+
passes `undefined` until it has them. Unset, groups start open and the ones
|
|
58
|
+
the user collapses are remembered per browser, as before.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- The agent picker follows the conversation on screen: opening a conversation
|
|
63
|
+
of another agent, or reading back one the host continued with another agent,
|
|
64
|
+
picks that agent, so a new chat starts with it, after a reload too, and
|
|
65
|
+
`onAgentChange` is called.
|
|
66
|
+
An agent picked in the menu meanwhile stays picked. The picker kept the agent
|
|
67
|
+
last picked in the menu, while the header named the conversation's.
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- `maxTotalSize: Infinity` lifts the cap on the total size of a message's
|
|
72
|
+
files, as a host with a per-file limit only passes it (XTM One's web chat and
|
|
73
|
+
Office add-in). The panel read a value that is not finite as invalid and kept
|
|
74
|
+
its 50 MB default total.
|
|
75
|
+
- A message sent from a new chat no longer comes back as the next new chat's
|
|
76
|
+
draft: the draft leaves the conversation it was typed in as it is sent. The
|
|
77
|
+
write of the emptied composer could land on the conversation the send
|
|
78
|
+
created, leaving the sent text under the new chat's draft.
|
|
79
|
+
- An image's lightbox is named after the image (its alt text, or the file's
|
|
80
|
+
name for an uploaded picture) and keeps `Image preview` for one with none; it
|
|
81
|
+
read `Image preview` for every picture.
|
|
82
|
+
- Tab and Shift+Tab stay on the lightbox's own controls (Download when offered,
|
|
83
|
+
Close) while it is open; they reached the page under it.
|
|
84
|
+
- An image's Expand button is visible while it has keyboard focus, so the focus
|
|
85
|
+
given back after the lightbox closes can be seen. It showed on hover only.
|
|
86
|
+
- A refusal whose body has no `detail` shows its `error` when that is a
|
|
87
|
+
sentence, as the products' proxies answer (`{ "error": "..." }`, beside a
|
|
88
|
+
`status` or not), never a code (`invalid_token`, `Unauthorized`) nor a
|
|
89
|
+
status's reason phrase (`Not Found`, `Too Many Requests`). It shows above
|
|
90
|
+
the composer, in place of the answer, on a failed upload's chip, under the
|
|
91
|
+
conversation list and on a conversation that could not be loaded. The panel
|
|
92
|
+
showed its generic message instead.
|
|
93
|
+
- The status line has its own wording when it has no agent's name (`The agent
|
|
94
|
+
is answering…`, `Consulting an agent…`, `Transferring to another agent…`),
|
|
95
|
+
where it filled the name with a lowercase `the agent`: `the agent is
|
|
96
|
+
answering…` started without a capital. A host translating through `t` in a
|
|
97
|
+
language the panel has no catalog for translates these three keys; it
|
|
98
|
+
translated `the agent` before, and shows them in English until it does.
|
|
99
|
+
- A host that can steer (a `steer` route, on by default) gets its conversation
|
|
100
|
+
created before the first message, so the first turn can be steered. Where
|
|
101
|
+
turns end with their stream (no `cancel`, `skipWait` or `events` route) the
|
|
102
|
+
panel learned the conversation's id only at the turn's end, and a message
|
|
103
|
+
typed while that first turn ran could not be sent.
|
|
104
|
+
- A steer the backend refuses because the turn just ended (409) goes out as
|
|
105
|
+
the next message, as a send during a turn winding down does, whatever
|
|
106
|
+
`holdSendsDuringTurn` says. Without it, the text went back to the composer
|
|
107
|
+
and the user had to send it again.
|
|
108
|
+
- Files added from outside the panel's own events (a host's call) upload too:
|
|
109
|
+
the upload launched on a timer that assumed the tray had been updated
|
|
110
|
+
already, which only a pick guaranteed.
|
|
111
|
+
- A wait a resumed turn continued (`wait_start`) ends on `wait_continued`
|
|
112
|
+
with `Analyzing results…`, as the turn moves on to read what it waited for.
|
|
113
|
+
The status line read `Thinking...` until the next status.
|
|
114
|
+
|
|
115
|
+
## 3.19.1 — 2026-11-02
|
|
116
|
+
|
|
117
|
+
### Added
|
|
118
|
+
|
|
119
|
+
- The panel speaks German, Italian, Korean, Russian and Simplified Chinese on
|
|
120
|
+
its own: a catalog for each, picked from `locale` by its language whatever
|
|
121
|
+
the region (`de-de`, `it-it`, `ko-kr`, `ru-ru`, `zh-cn`). Those languages
|
|
122
|
+
showed in English unless the host translated every key through `t`.
|
|
123
|
+
Traditional Chinese tags (`zh-TW`, `zh-HK`, `zh-Hant`) keep falling back.
|
|
124
|
+
- Inline, as a sidebar and fullscreen, the composer's footer has a line of
|
|
125
|
+
its own for what the keys do (`filigran-chat-composer-hint`), above the
|
|
126
|
+
disclaimer: an idle composer reads
|
|
127
|
+
`Enter to send · Shift+Enter for new line`, a busy one the status it read
|
|
128
|
+
before, and the line keeps its height when empty. The floating window keeps
|
|
129
|
+
one line, where a status takes the disclaimer's place.
|
|
130
|
+
|
|
131
|
+
### Fixed
|
|
132
|
+
|
|
133
|
+
- `filigran-chat-disclaimer` holds the disclaimer alone. It held the
|
|
134
|
+
composer's status in its place while a turn ran or a file failed to
|
|
135
|
+
upload; the status is now `filigran-chat-composer-hint`.
|
|
136
|
+
- A refusal whose body has no `detail` shows its `error` when that is a
|
|
137
|
+
sentence, as the products' proxies answer (`{ "error": "..." }`, beside a
|
|
138
|
+
`status` or not), never a code (`invalid_token`, `Unauthorized`) nor a
|
|
139
|
+
status's reason phrase (`Not Found`, `Too Many Requests`). It shows above
|
|
140
|
+
the composer, in place of the answer, on a failed upload's chip, under the
|
|
141
|
+
conversation list and on a conversation that could not be loaded. The panel
|
|
142
|
+
showed its generic message instead.
|
|
143
|
+
- The status line has its own wording when it has no agent's name (`The agent
|
|
144
|
+
is answering…`, `Consulting an agent…`, `Transferring to another agent…`),
|
|
145
|
+
where it filled the name with a lowercase `the agent`: `the agent is
|
|
146
|
+
answering…` started without a capital. A host translating through `t` in a
|
|
147
|
+
language the panel has no catalog for translates these three keys; it
|
|
148
|
+
translated `the agent` before, and shows them in English until it does.
|
|
149
|
+
- A conversation list that could not be read (a refusal, a server or network
|
|
150
|
+
error) says "Could not load your conversations." with a Retry, in the
|
|
151
|
+
fullscreen list and the header's history menu, where it said "No
|
|
152
|
+
conversations yet". A read that fails once the list was read keeps the
|
|
153
|
+
conversations it showed, unless it was refused (401/403), which drops them;
|
|
154
|
+
a backend without the route (404/405) still shows an empty list.
|
|
155
|
+
|
|
156
|
+
## 3.19.0 — 2026-11-02
|
|
12
157
|
|
|
13
158
|
### Added
|
|
14
159
|
|
|
@@ -164,7 +309,7 @@ This file starts at 3.10.0; earlier releases are on the
|
|
|
164
309
|
query: the list stayed filtered by a search the user could neither see nor
|
|
165
310
|
clear.
|
|
166
311
|
|
|
167
|
-
## 3.18.1 —
|
|
312
|
+
## 3.18.1 — 2026-11-02
|
|
168
313
|
|
|
169
314
|
### Fixed
|
|
170
315
|
|
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ import { ChatPanel } from '@filigran/chatbot';
|
|
|
107
107
|
| `conversationId` | `string \| null` | — | The conversation on screen when the host owns it (a `?c=` URL); `null` is a new chat. Left `undefined`, the panel remembers its own, and a host-owned one is never written to storage |
|
|
108
108
|
| `onConversationChange` | `(id: string \| null) => void` | — | Called whenever the conversation on screen changes, whoever changed it (a first answer, New chat, the history) |
|
|
109
109
|
| `initialAgent` | `string` | — | Agent selected once the list loads, by id or slug, over the remembered one |
|
|
110
|
-
| `onAgentChange` | `(agent: XtmAgent) => void` | — | Called when another agent is picked |
|
|
110
|
+
| `onAgentChange` | `(agent: XtmAgent) => void` | — | Called when another agent is picked, in the menu or by opening a conversation of another agent: the picker follows the conversation on screen |
|
|
111
111
|
| `readOnly` | `boolean` | `false` | Hides the composer: a conversation the user may read but not write to |
|
|
112
112
|
| `readOnlyNotice` | `React.ReactNode` | — | Shown in place of the composer while `readOnly` |
|
|
113
113
|
| `getRequestExtras` | `() => Record<string, unknown>` | — | Fields merged into every message body, read at send time (the host's own per-message options). They never override the panel's fields |
|
|
@@ -119,6 +119,7 @@ import { ChatPanel } from '@filigran/chatbot';
|
|
|
119
119
|
| `onArtifactOpen` | `(artifact: ChatArtifact) => void` | — | Opens a deck, report or video in the host, in place of the `Open in XTM One` link |
|
|
120
120
|
| `headerActions` / `banner` / `composerHeader` / `transcriptFooter` / `welcome` | `React.ReactNode` | — | The host's content in the header, above the thread, above the composer, after the last message, or in place of the welcome screen |
|
|
121
121
|
| `renderMessageExtras` / `messageFooterActions` | `(message, { streaming }) => React.ReactNode` | — | The host's content under a message, and controls in a finished answer's footer; `streaming` while the message is the answer still being written. `message.raw` holds the backend's own fields — see [Host extensions](#host-extensions) |
|
|
122
|
+
| `replaceMessage` | `(message, { streaming }) => React.ReactNode` | — | The host's own drawing of a message: `undefined` or a boolean leaves it to the panel, `null` leaves it out, anything else is drawn in its place without the message's frame (name, time, actions). Asked of every message, the reply's empty placeholder included. A throw leaves that message to the panel. For messages a host folds into one of its own, such as a turn cut and resumed shown as one card |
|
|
122
123
|
| `sidebarRowActions` | `(conversation) => React.ReactNode` | — | The host's controls on a row of the conversation list |
|
|
123
124
|
| `sidebarHeaderActions` | `React.ReactNode` | — | The host's controls beside New conversation |
|
|
124
125
|
| `sidebarSections` | `React.ReactNode \| ((search: ChatSidebarSearch) => React.ReactNode)` | — | Sections under the list; a function draws them from the list's search (`query`, trimmed), again when the search or the function changes (pass a stable one, and put hooks in a component it returns), and offers the search box whatever the number of conversations |
|
|
@@ -137,12 +138,14 @@ import { ChatPanel } from '@filigran/chatbot';
|
|
|
137
138
|
| `onTurnRunningChange` | `(running: boolean, info?: ChatTurnEnd) => void` | — | A turn started or stopped running on screen, own or relayed: the state of the Stop button and of `draft.turnRunning`; with `false` comes how it ended, `info.ended`: `'answered'`, `'stopped'` or `'failed'` — see [Host extensions](#host-extensions). After a provisional end (see [Turns that outlive their stream](#turns-that-outlive-their-stream)) `false` comes at once, while sends are still held as the turn winds down: until its stored end or its stream's close, at most 5 s |
|
|
138
139
|
| `onBeforeSend` | `(draft) => false \| { text?, displayText?, extras? } \| void` | — | Sees every non-empty send first and takes it over (`false`), rewords it, shows the user's bubble other words or adds body fields (a message with fields of its own is held with `holdSendsDuringTurn`) — see [Host extensions](#host-extensions) |
|
|
139
140
|
| `hideConversationList` | `boolean` | `false` | Hides the conversation list of fullscreen and inline modes, for a rail or a task pane (usable from 320 px wide); the header's history menu stands in for it — see [Host extensions](#host-extensions) |
|
|
141
|
+
| `expandedWorkspaceGroups` | `string[]` | — | The workspace groups the user has opened (workspace ids), for a host that keeps them per user: every other group starts collapsed, and one the user creates from the list opens. Read once, the first time it is given (`undefined` while the host reads them). Unset, groups start open and the collapsed ones are remembered per browser |
|
|
142
|
+
| `onExpandedWorkspaceGroupsChange` | `(keys: string[]) => void` | — | The groups the user has opened, each time one is opened or collapsed (see `expandedWorkspaceGroups`) |
|
|
140
143
|
| `holdSendsDuringTurn` | `boolean` | `false` | A message that cannot steer is held and sent when the turn ends, instead of waiting in the composer. A turn winding down after a provisional end (until its stored end or its stream's close, at most 5 s) holds sends with or without it |
|
|
141
144
|
| `onBeforeMove` | `(conversation, workspace \| null) => boolean \| Promise<boolean>` | — | Asked before a drag or a row's Move menu files a conversation into a workspace (`null`: out of one); `false` leaves it where it was |
|
|
142
145
|
| `onConversationActionFailed` | `(action, conversation \| null, error \| null) => boolean \| void` | — | A failed `'delete'`, `'rename'`, `'move'` or `'clearAll'`, with the backend's reason; return `true` when the host told the user itself, and the panel says nothing |
|
|
143
146
|
| `sidebarSearchThreshold` | `number` | `7` | The conversation list's search box shows above this many conversations; `0` shows it from the first |
|
|
144
|
-
| `channelLabel` | `(channel: string) => string \| null \| undefined` | — | Names the channel a
|
|
145
|
-
| `headerTitle` / `headerSubtitle` | `React.ReactNode \| ((context: ChatHeaderContext) => React.ReactNode)` | — | A title beside the header's agent name and a line under it, or functions of the conversation on screen (as the list holds it) and the agent |
|
|
147
|
+
| `channelLabel` | `(channel: string) => string \| null \| undefined` | — | Names the channel a conversation was started in, on its row (`Started in {channel}`) and in the header (`Agent · {channel}`); `null` shows nothing, `undefined` keeps the panel's names (Slack, Teams, WhatsApp, Telegram, Discord, Google Chat) |
|
|
148
|
+
| `headerTitle` / `headerSubtitle` | `React.ReactNode \| ((context: ChatHeaderContext) => React.ReactNode)` | — | A title beside the header's agent name and a line under it, or functions of the conversation on screen (as the list holds it) and the agent. Without `headerSubtitle`, the line reads `Agent · {channel}` for a conversation started in a channel its row names |
|
|
146
149
|
| `agentHref` | `(agent: XtmAgent) => string \| undefined` | — | Links the header's agent name to this address (http(s) or a path of the host's origin), in place; the chevron beside it opens the picker |
|
|
147
150
|
| `onAgentNameClick` | `(agent: XtmAgent) => void` | — | Takes a plain click on the header's agent name (over `agentHref`); the chevron beside it opens the picker |
|
|
148
151
|
| `clearAllAction` | `'delete' \| 'archive'` | `'delete'` | What Clear all does, for its wording (`Archive all conversations`) |
|
|
@@ -194,13 +197,16 @@ A host adds what is its own without the package learning about it. An answer
|
|
|
194
197
|
marker `[[KIND:args]]` renders the host's card where it stands, once the answer
|
|
195
198
|
is done (`renderWhileStreaming` to show it earlier); a renderer named `FILE`,
|
|
196
199
|
`PRESENTATION`, `REPORT` or `VIDEO` replaces the built-in card, and a marker no
|
|
197
|
-
renderer names stays text
|
|
200
|
+
renderer names stays text. A `standalone` renderer draws an answer that is its
|
|
201
|
+
marker and nothing else as the card alone, without the agent's name, the time
|
|
202
|
+
or the actions: a note between messages, such as a change of agent:
|
|
198
203
|
|
|
199
204
|
```tsx
|
|
200
205
|
const markers = useMemo(
|
|
201
206
|
() => ({
|
|
202
207
|
WORK_ITEM: { render: (id) => <WorkItemCard id={id} /> },
|
|
203
208
|
INVESTIGATION: { render: (id) => <InvestigationBlock id={id} />, fullWidth: true },
|
|
209
|
+
AGENT_SWITCH: { render: (id) => <AgentSwitchDivider id={id} />, fullWidth: true, standalone: true },
|
|
204
210
|
}),
|
|
205
211
|
[],
|
|
206
212
|
);
|
|
@@ -465,6 +471,11 @@ Returns available AI agents.
|
|
|
465
471
|
`badges` is optional: short labels, already translated, shown after the
|
|
466
472
|
agent's name in the agent picker. Anything but strings is ignored.
|
|
467
473
|
|
|
474
|
+
`disable_chat` is optional: `true` marks an agent meant for assignments rather
|
|
475
|
+
than chat. The picker says `Not recommended for chat` after its name, and the
|
|
476
|
+
composer warns while it is the agent written to; it stays selectable. The
|
|
477
|
+
panel reads it as `XtmAgent.disableChat`.
|
|
478
|
+
|
|
468
479
|
### `POST {apiBaseUrl}/chat/sessions`
|
|
469
480
|
|
|
470
481
|
Restores conversation history (and, implicitly, resolves the session).
|
|
@@ -508,6 +519,11 @@ without one shows no time. `feedback` is the caller's own rating of an
|
|
|
508
519
|
assistant answer: the thumbs are then locked on it ("Feedback already
|
|
509
520
|
submitted").
|
|
510
521
|
|
|
522
|
+
The response may also carry the conversation's `channel` (`slack`, `teams`,
|
|
523
|
+
…), as the list does: the header names it (`Agent · {channel}`) without
|
|
524
|
+
reading the list, which floating and sidebar modes read only once the history
|
|
525
|
+
menu opens.
|
|
526
|
+
|
|
511
527
|
The response **should echo a `conversation_id`**. When the stored
|
|
512
528
|
`conversation_id` no longer exists (e.g. the platform was reset but the
|
|
513
529
|
browser kept an old id in `localStorage`), the backend is expected to
|
|
@@ -520,8 +536,8 @@ conversation. If the endpoint responds with a non-2xx status, the component
|
|
|
520
536
|
silently resets the stored id and starts fresh on the next message, except
|
|
521
537
|
when it says nothing of the conversation: a `502`, `503` or `504` (or no
|
|
522
538
|
answer at all) keeps the id and reads it back once the backend answers, and a
|
|
523
|
-
`401` or `403` (expired credentials) keeps it, shows the response's
|
|
524
|
-
(or "This conversation could not be loaded.") with a Retry button, and reads
|
|
539
|
+
`401` or `403` (expired credentials) keeps it, shows the response's
|
|
540
|
+
[reason](#a-refusals-reason) (or "This conversation could not be loaded.") with a Retry button, and reads
|
|
525
541
|
it back as soon as `requestHeaders` change.
|
|
526
542
|
|
|
527
543
|
Assistant history messages **should echo the same `attachments[]` array** that
|
|
@@ -564,14 +580,15 @@ Lists the user's past conversations for the history menu in the chat header.
|
|
|
564
580
|
```
|
|
565
581
|
|
|
566
582
|
`last_message_preview` and `channel` are optional: a row shows the preview
|
|
567
|
-
under its title, and
|
|
568
|
-
in (see `channelLabel`).
|
|
583
|
+
under its title, and the search matches it; the row and the header name a
|
|
584
|
+
messaging channel the conversation was started in (see `channelLabel`).
|
|
569
585
|
|
|
570
586
|
Selecting a conversation restores it through the existing
|
|
571
587
|
`POST /chat/sessions` contract above. The endpoint is fetched lazily when the
|
|
572
588
|
history menu opens; a backend that doesn't implement it yet (404/405) simply
|
|
573
589
|
yields an empty list, so the menu shows its empty state instead of breaking
|
|
574
|
-
the chat.
|
|
590
|
+
the chat. Any other failed read shows "Could not load your conversations."
|
|
591
|
+
with a Retry, and keeps the conversations a previous read gave. Set `apiEndpoints.history` to `null` to hide the history menu
|
|
575
592
|
entirely, or point it at a dedicated path if your proxy can't route `GET` on
|
|
576
593
|
the sessions path.
|
|
577
594
|
|
|
@@ -583,7 +600,7 @@ the row reverts to its previous title.
|
|
|
583
600
|
|
|
584
601
|
With [workspaces](#workspaces) on, the same route files a conversation:
|
|
585
602
|
`{ "workspace_id": "uuid" }`, or `{ "workspace_id": null }` to take it out of
|
|
586
|
-
its workspace. A refusal's
|
|
603
|
+
its workspace. A refusal's [reason](#a-refusals-reason) is shown under the list.
|
|
587
604
|
|
|
588
605
|
### Workspaces
|
|
589
606
|
|
|
@@ -659,6 +676,17 @@ Deletes a conversation from the history menu. Any 2xx response counts as
|
|
|
659
676
|
success; deleting the active conversation resets the panel to a fresh chat so
|
|
660
677
|
the next message never targets a dead conversation id.
|
|
661
678
|
|
|
679
|
+
### A refusal's reason
|
|
680
|
+
|
|
681
|
+
Where the panel shows why the backend refused a request, it reads the JSON body
|
|
682
|
+
of the non-2xx answer: its `detail` when a string, or the `message` (or
|
|
683
|
+
`detail`) of a `detail` object, as XTM One answers; else its `error` when that
|
|
684
|
+
is a sentence, as the products' proxies answer (`{ "error": "..." }`, beside a
|
|
685
|
+
`status` or not). A code (`invalid_token`, `Unauthorized`), a status's reason
|
|
686
|
+
phrase (`Not Found`, `Too Many Requests`), anything else in the body (a
|
|
687
|
+
top-level `message` included), a string over 500 characters or a body that is
|
|
688
|
+
not JSON shows the panel's own, translated message.
|
|
689
|
+
|
|
662
690
|
### `POST {apiBaseUrl}/chat/messages`
|
|
663
691
|
|
|
664
692
|
Sends a message and streams the response via SSE.
|
|
@@ -686,7 +714,7 @@ It must be JSON-serializable — a non-serializable value (circular reference,
|
|
|
686
714
|
|
|
687
715
|
A refusal before the stream (a non-2xx answer: a rate limit, a refused
|
|
688
716
|
option, an unknown conversation) shows the response's `detail` in place of the
|
|
689
|
-
answer
|
|
717
|
+
answer ([its reason](#a-refusals-reason)), and
|
|
690
718
|
"Unable to connect. Please check the configuration." when it has none; the
|
|
691
719
|
handle's `send` resolves `'failed'`.
|
|
692
720
|
|
|
@@ -820,23 +848,25 @@ until the next send. On a non-2xx
|
|
|
820
848
|
response or a network error nothing was stored: the optimistic user bubble is
|
|
821
849
|
rolled back, the text is restored into the composer (prepended on its own line
|
|
822
850
|
if the user already typed something new), the send resolves `'failed'` and the
|
|
823
|
-
refusal's
|
|
851
|
+
refusal's [reason](#a-refusals-reason), when there is one, shows above the composer — so a backend
|
|
824
852
|
without steering support degrades gracefully and the message is never silently
|
|
825
853
|
lost. A `409` means no turn is running any more, an ordinary race: no notice
|
|
826
|
-
shows, and
|
|
827
|
-
|
|
828
|
-
|
|
854
|
+
shows, and the message is held and sent as the next turn, with or without
|
|
855
|
+
`holdSendsDuringTurn`. Set `apiEndpoints.steer` to `null` to disable the
|
|
856
|
+
steering affordances entirely.
|
|
829
857
|
|
|
830
858
|
Steering only applies to sends on the `rest` backend with a known
|
|
831
|
-
`conversation_id
|
|
832
|
-
|
|
859
|
+
`conversation_id`. The stream names a fresh conversation only on `done`, so
|
|
860
|
+
while steering is on a new chat is created (`POST {sessions}`) before its first
|
|
861
|
+
message, and that first turn can be steered too; with `steer: null` it is not.
|
|
862
|
+
A message with attachments steers only with `steerAttachments`
|
|
833
863
|
(once every file is uploaded); one carrying conversation references, agent
|
|
834
864
|
mentions or body fields of its own (`send`'s `extras`, `onBeforeSend`'s
|
|
835
865
|
`extras`) never steers, since a steer reaches the agent as text alone: it waits
|
|
836
866
|
for the turn to end (`holdSendsDuringTurn`), and `onBeforeSend` is asked again,
|
|
837
867
|
with `turnRunning: false`, before it goes out. A file that failed to upload
|
|
838
868
|
neither steers nor waits, nor goes without that file: its chip says why (the
|
|
839
|
-
upload's
|
|
869
|
+
upload's [reason](#a-refusals-reason)), and the composer offers no send, and says why, until the
|
|
840
870
|
user removes it. `send` and a voice
|
|
841
871
|
call attach no files, so this never holds them. Esc stops generating.
|
|
842
872
|
|
|
@@ -1044,7 +1074,7 @@ chat) opts into six more routes, none with a default:
|
|
|
1044
1074
|
|
|
1045
1075
|
| `apiEndpoints` key | Request | What it does |
|
|
1046
1076
|
| --- | --- | --- |
|
|
1047
|
-
| `cancel` | `POST {cancel}` with `{ conversation_id }` | Stop asks the backend to end the turn; the panel keeps reading until the stream's `cancelled` frame (or lets go after 10 s, or at once on a network error or an answer of `{ "turn": "none" }`, reading the conversation back for an end it had not read yet). A Stop pressed before the panel's own send started its turn (its stream delivered nothing yet) carries `"awaiting_send": true`, and its `none` says nothing of that turn: the Stop stays pending and is sent again, without the flag, as the turn starts (or as the answer comes, the turn started meanwhile); any other answer reached a turn, and nothing goes again. A backend may keep a Stop with that flag for the send's turn, and must then take a second cancel of one turn; a Stop without it says nothing of a later send. A refusal (non-2xx) keeps the turn running and shows its
|
|
1077
|
+
| `cancel` | `POST {cancel}` with `{ conversation_id }` | Stop asks the backend to end the turn; the panel keeps reading until the stream's `cancelled` frame (or lets go after 10 s, or at once on a network error or an answer of `{ "turn": "none" }`, reading the conversation back for an end it had not read yet). A Stop pressed before the panel's own send started its turn (its stream delivered nothing yet) carries `"awaiting_send": true`, and its `none` says nothing of that turn: the Stop stays pending and is sent again, without the flag, as the turn starts (or as the answer comes, the turn started meanwhile); any other answer reached a turn, and nothing goes again. A backend may keep a Stop with that flag for the send's turn, and must then take a second cancel of one turn; a Stop without it says nothing of a later send. A refusal (non-2xx) keeps the turn running and shows its [reason](#a-refusals-reason) above the composer |
|
|
1048
1078
|
| `skipWait` | `POST {skipWait}` with `{ conversation_id }` | `Continue now` on `status: wait_start` (`{ seconds, reason }`), for as long as the pause lasts (see the status values) |
|
|
1049
1079
|
| `events` | `GET {events}/{conversation_id}/events?client_id=<id>` → SSE | Turns this panel did not start (another tab, a reload mid-turn), `user_message`, `conversation_title`, `approval_resolved`; every frame of a turn names it (`turn_id`, below) |
|
|
1050
1080
|
| `messagesPage` | `GET {messagesPage}/{conversation_id}/messages?before=<message id>&limit=50&supports_artifacts=true` → `{ messages, has_more }` | "Load earlier messages" past the restored ones, offered when the restore answers `has_more: true` |
|
|
@@ -1290,9 +1320,12 @@ both. The same variables may be set in CSS on an ancestor instead.
|
|
|
1290
1320
|
|
|
1291
1321
|
### Internationalization
|
|
1292
1322
|
|
|
1293
|
-
The panel speaks English, French, Spanish
|
|
1294
|
-
|
|
1295
|
-
|
|
1323
|
+
The panel speaks English, French, German, Italian, Spanish, Japanese, Korean,
|
|
1324
|
+
Russian and Simplified Chinese on its own: it ships a catalog for each and
|
|
1325
|
+
picks one from `locale` by its language, whatever the region (`fr-FR` reads
|
|
1326
|
+
French, `zh-cn` Chinese; English, or a language it lacks, is its own keys).
|
|
1327
|
+
Traditional Chinese (`zh-TW`, `zh-HK`, `zh-Hant`) has no catalog: it reads
|
|
1328
|
+
English unless the host translates it through `t`. A host in another language translates
|
|
1296
1329
|
through `t`. A host with nothing to add only passes `locale`:
|
|
1297
1330
|
|
|
1298
1331
|
```tsx
|
package/dist/index.d.ts
CHANGED
|
@@ -36,8 +36,11 @@ interface ApiEndpoints {
|
|
|
36
36
|
* message was persisted and will be injected into the running agentic loop
|
|
37
37
|
* at the next iteration boundary. On a non-2xx response the optimistic
|
|
38
38
|
* bubble is rolled back and the text is restored into the composer, so a
|
|
39
|
-
* backend without steering support degrades gracefully.
|
|
40
|
-
*
|
|
39
|
+
* backend without steering support degrades gracefully. A new chat is
|
|
40
|
+
* created (`POST {sessions}`) before its first message, so a steer can name
|
|
41
|
+
* the first turn's conversation: the stream names it only when the turn
|
|
42
|
+
* ends. Set to null to disable mid-run steering entirely, and that create
|
|
43
|
+
* with it.
|
|
41
44
|
*/
|
|
42
45
|
steer?: string | null;
|
|
43
46
|
/**
|
|
@@ -471,7 +474,7 @@ interface ChatPanelProps {
|
|
|
471
474
|
onDownloadError?: (error: unknown, attachment: ChatAttachment) => void;
|
|
472
475
|
/** Maximum number of files attachable in one chat context. Default: 10. */
|
|
473
476
|
maxFileCount?: number;
|
|
474
|
-
/** Maximum total size in bytes for attached files. Default: 50 * 1024 * 1024 (50 MB). */
|
|
477
|
+
/** Maximum total size in bytes for attached files; `Infinity` for none. Default: 50 * 1024 * 1024 (50 MB). */
|
|
475
478
|
maxTotalSize?: number;
|
|
476
479
|
/** Maximum size in bytes of one attached file. Default: none (only `maxTotalSize`). */
|
|
477
480
|
maxFileSize?: number;
|
|
@@ -570,7 +573,7 @@ interface ChatPanelProps {
|
|
|
570
573
|
onMessageFeedback?: (messageId: string, feedback: MessageFeedback | null, message: ChatMessage) => void;
|
|
571
574
|
/**
|
|
572
575
|
* The user's language as a BCP 47 tag (`'fr'`, `'en-US'`): the language the
|
|
573
|
-
* panel speaks (its own catalog: en, es, fr, ja) where `t` does not
|
|
576
|
+
* panel speaks (its own catalog: en, de, es, fr, it, ja, ko, ru, zh-Hans) where `t` does not
|
|
574
577
|
* translate a key, formats the time under each message and is the language
|
|
575
578
|
* answers are read aloud in. Default: the browser's own, except that a host
|
|
576
579
|
* passing `t` and no `locale` gets no catalog.
|
|
@@ -619,7 +622,11 @@ interface ChatPanelProps {
|
|
|
619
622
|
onConversationChange?: (conversationId: string | null) => void;
|
|
620
623
|
/** Agent selected once the list loads, by id or slug, over the remembered one. */
|
|
621
624
|
initialAgent?: string;
|
|
622
|
-
/**
|
|
625
|
+
/**
|
|
626
|
+
* Called when another agent is picked, in the menu or by opening a
|
|
627
|
+
* conversation of another agent (the picker follows the conversation on
|
|
628
|
+
* screen, so a new chat starts with its agent).
|
|
629
|
+
*/
|
|
623
630
|
onAgentChange?: (agent: XtmAgent) => void;
|
|
624
631
|
/** Hides the composer: a conversation the user may read but not write to. */
|
|
625
632
|
readOnly?: boolean;
|
|
@@ -682,6 +689,18 @@ interface ChatPanelProps {
|
|
|
682
689
|
welcome?: React.ReactNode;
|
|
683
690
|
/** Rendered under a message's content, before its footer; `context.streaming` while the answer is still coming. */
|
|
684
691
|
renderMessageExtras?: (message: ChatMessage, context: ChatMessageSlotContext) => React.ReactNode;
|
|
692
|
+
/**
|
|
693
|
+
* Draws a message in its own way: `undefined` (or a boolean, as
|
|
694
|
+
* `isCut(m) && <Card />` gives) leaves it to the panel, `null` leaves it
|
|
695
|
+
* out, and anything else is drawn in its place, without the message's frame
|
|
696
|
+
* (its name, time and actions). For a host that folds several messages into
|
|
697
|
+
* one of its own (a turn cut and resumed, drawn as one card on the first of
|
|
698
|
+
* them, the others left out). Asked of every message, streaming ones
|
|
699
|
+
* included, the reply's empty placeholder too (`null` leaves out its status
|
|
700
|
+
* bubble): return `undefined` fast for the others. A throw, while asked or
|
|
701
|
+
* while the replacement is drawn, leaves that message to the panel.
|
|
702
|
+
*/
|
|
703
|
+
replaceMessage?: (message: ChatMessage, context: ChatMessageSlotContext) => React.ReactNode;
|
|
685
704
|
/** Controls added to a finished message's footer. */
|
|
686
705
|
messageFooterActions?: (message: ChatMessage, context: ChatMessageSlotContext) => React.ReactNode;
|
|
687
706
|
/** Controls added to a conversation's row in the conversation list. */
|
|
@@ -805,6 +824,26 @@ interface ChatPanelProps {
|
|
|
805
824
|
* `sidebar*` slots render nothing meanwhile. Default: false.
|
|
806
825
|
*/
|
|
807
826
|
hideConversationList?: boolean;
|
|
827
|
+
/**
|
|
828
|
+
* The conversation list starts collapsed to its strip, from which the user
|
|
829
|
+
* opens it: where it would squeeze the thread, a phone or a narrow window.
|
|
830
|
+
* Read once, when the panel mounts. Default: false.
|
|
831
|
+
*/
|
|
832
|
+
initialConversationListCollapsed?: boolean;
|
|
833
|
+
/**
|
|
834
|
+
* The workspace groups of the conversation list the user has opened (their
|
|
835
|
+
* keys: a workspace's id), for a host that keeps them per user. Given, every
|
|
836
|
+
* other group starts collapsed until the user opens it, so many workspaces
|
|
837
|
+
* do not push the conversations in none below the fold; one the user creates
|
|
838
|
+
* from the list opens. Read once, the first time it is given: a host still
|
|
839
|
+
* reading them passes `undefined` until it has them. The panel keeps what
|
|
840
|
+
* the user opens from then on, while the list is hidden and shown again too.
|
|
841
|
+
* Unset, groups start open and the ones the user collapses are remembered
|
|
842
|
+
* per browser.
|
|
843
|
+
*/
|
|
844
|
+
expandedWorkspaceGroups?: string[];
|
|
845
|
+
/** The workspace groups the user has opened, each time they open or collapse one (see `expandedWorkspaceGroups`). */
|
|
846
|
+
onExpandedWorkspaceGroupsChange?: (keys: string[]) => void;
|
|
808
847
|
/**
|
|
809
848
|
* Asked before a conversation is filed into a workspace (`null`: taken out
|
|
810
849
|
* of its workspace), by a drag or the row's Move menu: `false` (or a promise
|
|
@@ -826,14 +865,18 @@ interface ChatPanelProps {
|
|
|
826
865
|
sidebarSearchThreshold?: number;
|
|
827
866
|
/**
|
|
828
867
|
* Names the channel a conversation was started in, on its row (`Started in
|
|
829
|
-
* {channel}`): a string names it, `null` shows nothing, `undefined` keeps
|
|
868
|
+
* {channel}`) and in the header (`Agent · {channel}`): a string names it, `null` shows nothing, `undefined` keeps
|
|
830
869
|
* the panel's own name (Slack, Teams, WhatsApp, Telegram, Discord, Google
|
|
831
870
|
* Chat; no other channel is shown).
|
|
832
871
|
*/
|
|
833
872
|
channelLabel?: (channel: string) => string | null | undefined;
|
|
834
873
|
/** A title beside the agent's name in the header (the conversation's, say), or a function of what is on screen. */
|
|
835
874
|
headerTitle?: React.ReactNode | ((context: ChatHeaderContext) => React.ReactNode);
|
|
836
|
-
/**
|
|
875
|
+
/**
|
|
876
|
+
* A line under the agent's name in the header, or a function of what is on
|
|
877
|
+
* screen. Undefined: `Agent · {channel}` for a conversation started in a
|
|
878
|
+
* channel its row names (see `channelLabel`), nothing otherwise.
|
|
879
|
+
*/
|
|
837
880
|
headerSubtitle?: React.ReactNode | ((context: ChatHeaderContext) => React.ReactNode);
|
|
838
881
|
/**
|
|
839
882
|
* Links the header's agent name to this address (an http(s) URL or a path
|
|
@@ -877,6 +920,11 @@ interface ChatMarkerRenderer {
|
|
|
877
920
|
renderWhileStreaming?: boolean;
|
|
878
921
|
/** Give it the thread's full width rather than the prose's. */
|
|
879
922
|
fullWidth?: boolean;
|
|
923
|
+
/**
|
|
924
|
+
* An answer that is this marker and nothing else shows the card alone, as a
|
|
925
|
+
* note between messages: no agent name, time or actions around it.
|
|
926
|
+
*/
|
|
927
|
+
standalone?: boolean;
|
|
880
928
|
}
|
|
881
929
|
/** What a code block renderer is drawn from. */
|
|
882
930
|
interface ChatCodeBlockContext {
|
|
@@ -925,6 +973,12 @@ interface ChatPanelHandle {
|
|
|
925
973
|
*/
|
|
926
974
|
getDraft: () => string;
|
|
927
975
|
focusComposer: () => void;
|
|
976
|
+
/**
|
|
977
|
+
* Put *files* in the composer's tray as if the user picked them (uploaded and
|
|
978
|
+
* checked the same way), to go with the next message: files another app
|
|
979
|
+
* shared to the host. Nothing where the panel takes no files.
|
|
980
|
+
*/
|
|
981
|
+
attachFiles: (files: readonly File[]) => void;
|
|
928
982
|
/** Same as the Stop button; also cancels the question a voice call awaits, and stops it speaking. */
|
|
929
983
|
stop: () => void;
|
|
930
984
|
/**
|
|
@@ -1264,7 +1318,7 @@ interface ChatFile {
|
|
|
1264
1318
|
fileId?: string;
|
|
1265
1319
|
/** Upload status: 'pending' while uploading, 'done' when uploaded, 'error' on failure. */
|
|
1266
1320
|
uploadStatus?: 'pending' | 'done' | 'error';
|
|
1267
|
-
/** Why the upload failed, as the backend said
|
|
1321
|
+
/** Why the upload failed, as the backend said, when it said: its reason (README, "A refusal's reason"). */
|
|
1268
1322
|
uploadError?: string;
|
|
1269
1323
|
}
|
|
1270
1324
|
/** The conversation list's search, as a host's sections read it (`sidebarSections`). */
|
|
@@ -1349,6 +1403,12 @@ interface XtmAgent {
|
|
|
1349
1403
|
group?: string;
|
|
1350
1404
|
/** Short labels shown after its name in the agent picker (`Company`), already translated. */
|
|
1351
1405
|
badges?: string[];
|
|
1406
|
+
/**
|
|
1407
|
+
* The agent is meant for assignments rather than chat (`disable_chat`): the
|
|
1408
|
+
* picker says "Not recommended for chat" and the composer warns while it is
|
|
1409
|
+
* the one written to. It stays selectable.
|
|
1410
|
+
*/
|
|
1411
|
+
disableChat?: boolean;
|
|
1352
1412
|
}
|
|
1353
1413
|
|
|
1354
1414
|
/**
|