@filigran/chatbot 3.13.0 → 3.14.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 CHANGED
@@ -8,7 +8,36 @@ 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.13.0 — unreleased
11
+ ## 3.14.0 — 2026-10-19
12
+
13
+ ### Added
14
+
15
+ - A deck, report or video the assistant made shows as a card where its
16
+ `[[PRESENTATION:…]]`, `[[REPORT:…]]` or `[[VIDEO:…]]` marker stands (#450):
17
+ its title, its slides, sections or scenes and length, a video's render
18
+ progress, and `Open in XTM One`. The marker never shows as text, mid-stream
19
+ or after a reload, and copy and read aloud skip it. The panel tells XTM One
20
+ it renders them (`supports_artifacts`); XTM One describes each one in the
21
+ answer's `artifacts`.
22
+ - The fullscreen history can be grouped by workspace (#276): one foldable group
23
+ per workspace the user can file into, then `Not in a workspace` and
24
+ `Other workspaces`. A workspace is created, renamed and deleted from the
25
+ list, and a conversation is started in one, or moved to one from its row menu
26
+ or by dragging it onto a group.
27
+
28
+ ### Host opt-in
29
+
30
+ - `apiEndpoints.artifacts` (no default; XTM One serves `/chat/artifacts`) lets
31
+ the deck, report and video cards download them (PDF and PowerPoint for a
32
+ deck, PDF for a report, MP4 for a rendered video) and follow a video while
33
+ it renders, through the host's proxy. Without it the cards show and link to
34
+ XTM One, with no download. A failed download goes to `onDownloadError`, with
35
+ the name of the file asked for.
36
+ - `apiEndpoints.workspaces` (no default, like `approve`) turns the workspace
37
+ groups on, once the host's proxy forwards the workspace routes. A backend
38
+ that refuses the list leaves the history flat, as before.
39
+
40
+ ## 3.13.0 — 2026-10-16
12
41
 
13
42
  ### Added
14
43
 
package/README.md CHANGED
@@ -9,6 +9,7 @@ Filigran chat panel — a standalone React + Tailwind chatbot component with SSE
9
9
  - ✋ **Tool Approval** — When the agent stops at a tool that needs a human's consent, the turn pauses mid-answer and the reviewer approves, declines with a reason, or approves always — opt-in per host, see [Tool approval](#post-apibaseurlapiendpointsapprove)
10
10
  - 🗂️ **Conversation History** — Switch between (and delete) past conversations from a header menu, or from a permanent sidebar in fullscreen mode (collapsible, searchable past 7 entries, rename in place)
11
11
  - 🤖 **Multi-Agent Support** — Switch between different AI agents
12
+ - 🔗 **Conversation References** — Type `@` in the composer to point the agent at another conversation; opt-in per host, see [Conversation references](#conversation-references)
12
13
  - 📎 **File Attachments** — Upload and paste files (PDF, TXT, images)
13
14
  - 📥 **Agent-Generated Files** — Renders downloadable file cards from agent output and strips the `[[FILE:id]]` markers from the prose
14
15
  - 📝 **Full Markdown** — Tables (mis-delimited ones repaired), code blocks with copy button, lists, blockquotes, soft line breaks, inline images with a lightbox
@@ -258,6 +259,78 @@ Renames a conversation. Body: `{ "title": "..." }`. Only reached from the
258
259
  fullscreen sidebar; a backend without the route simply fails the request and
259
260
  the row reverts to its previous title.
260
261
 
262
+ With [workspaces](#workspaces) on, the same route files a conversation:
263
+ `{ "workspace_id": "uuid" }`, or `{ "workspace_id": null }` to take it out of
264
+ its workspace. A refusal's `detail` is shown under the list.
265
+
266
+ ### Workspaces
267
+
268
+ The fullscreen conversation list can be grouped by workspace, the way a
269
+ desktop assistant groups its projects: one collapsible group per workspace
270
+ (the collapsed ones are remembered per browser), a conversation started
271
+ inside a workspace with its group's compose button, a workspace created,
272
+ renamed or deleted from the list, and a conversation moved between groups
273
+ from its row menu or by dragging it onto a group.
274
+
275
+ The feature is off until the host names the route:
276
+ `apiEndpoints.workspaces` has **no default**, for the same reason as
277
+ `apiEndpoints.approve` - a proxied host must expose every route below before
278
+ the panel offers them. A backend that refuses the list (not licensed, an
279
+ older proxy) also leaves the list flat.
280
+
281
+ | Request | Purpose |
282
+ | --- | --- |
283
+ | `GET {apiBaseUrl}{workspaces}` | The caller's workspaces: a bare array or `{ "workspaces": [...] }` of `{ id, name, is_default, is_own, can_manage, is_archived }`. Archived ones are left out. |
284
+ | `POST {apiBaseUrl}{workspaces}` | Create one: `{ "name": "..." }`. |
285
+ | `PATCH {apiBaseUrl}{workspaces}/{id}` | Rename one: `{ "name": "..." }`. |
286
+ | `DELETE {apiBaseUrl}{workspaces}/{id}` | Delete one (never offered for the default). Its conversations are expected back in no workspace (`workspace_id: null`), never moved to a default; the history is reloaded after the delete. |
287
+ | `PATCH {apiBaseUrl}{history}/{conversation_id}` | File a conversation: `{ "workspace_id": "uuid" \| null }`. |
288
+ | `POST {apiBaseUrl}{sessions}` | Carries `workspace_id` when the conversation is started inside a workspace. |
289
+
290
+ `GET {history}` entries carry `workspace_id` (`null` for none). A conversation
291
+ in no workspace is a normal state, listed under "Not in a workspace": a new
292
+ conversation starts there unless it is started inside a workspace. A workspace the
293
+ caller may only read (`can_manage: false`) gets a group only while it holds
294
+ one of their conversations, and conversations filed in a workspace the list
295
+ does not return are grouped under "Other workspaces". XTM One serves all of it
296
+ at `/chat/workspaces` and `/chat/sessions`.
297
+
298
+ ### Conversation references
299
+
300
+ Typing `@` in the composer - at the start of the text or after a space, never
301
+ inside a word or an email (`a@b`) - opens a menu of the user's other
302
+ conversations, searched by title or key as they type. A pick inserts `@<key> `, and the
303
+ message goes out with the conversations it still references, so the agent reads
304
+ them; a `@word` typed by hand stays plain text, and so does a pick whose `@key`
305
+ is edited out before sending. The arrows move in the menu, Enter or Tab insert,
306
+ Escape closes it.
307
+
308
+ The feature is off until the host names the route:
309
+ `apiEndpoints.conversationReferences` has **no default**, for the same reason
310
+ as `apiEndpoints.approve` - a proxied host must expose the route, and forward
311
+ the new body field, before the composer offers the menu. Unset, `@` is plain
312
+ text and the message body is unchanged.
313
+
314
+ | Request | Purpose |
315
+ | --- | --- |
316
+ | `GET {apiBaseUrl}{conversationReferences}?q=<text>&limit=8&exclude=<conversation_id>` | The menu: `{ "conversations": [{ id, title, key, updated_at, is_own }] }` (or a bare array), the conversations the user can open, most recent first, that `q` matches (case-insensitive): every word of `q` (split on spaces, `-` and `_`) is in the title, or the key starts with `q` - so a key typed back (`@hi-bob` for "Hi Bob"), or a key that is not made of the title's words (an accent folded, a title without Latin letters), still finds its conversation. `q` is omitted for a bare `@`, `exclude` while the conversation is not created yet. `key` is what the pick inserts after `@`, always the backend's own. `is_own: false` marks a conversation shared with the user ("Shared with you"). |
317
+ | `POST {apiBaseUrl}{messages}` | Carries `referenced_conversation_ids: string[]`: the picks the text still holds, in the order they appear, each once, at most 5. Omitted when there is none. |
318
+ | `POST {apiBaseUrl}{sessions}` | A restored user message may carry `conversation_refs: [{ conversation_id, title, key }]`. |
319
+
320
+ The references of a sent message show as chips above it, also after a reload.
321
+ A chip opens its conversation in the panel when the conversation list holds it
322
+ (the list is read as soon as the thread shows a reference), and is a plain label
323
+ otherwise - a conversation shared with the user, say. While an answer streams,
324
+ a message with a reference waits for it instead of steering the running turn:
325
+ a steer reaches the agent as text alone, so it would never read what the
326
+ message references. Once the text references five conversations the menu says
327
+ so instead of offering more. The picks of an unsent draft are kept with its text, so a
328
+ draft restored when the panel reopens still references what it did.
329
+
330
+ XTM One serves it at `/chat/conversation-references`; a host behind its own
331
+ proxy names its route, relative to its `apiBaseUrl` (e.g.
332
+ `conversationReferences: '/conversation-references'`).
333
+
261
334
  ### `DELETE {apiBaseUrl}/chat/sessions/{conversation_id}`
262
335
 
263
336
  Deletes a conversation from the history menu. Any 2xx response counts as
@@ -279,6 +352,9 @@ Sends a message and streams the response via SSE.
279
352
  }
280
353
  ```
281
354
 
355
+ With [conversation references](#conversation-references) on, a message that
356
+ references another conversation also carries `referenced_conversation_ids`.
357
+
282
358
  The optional `context` object is forwarded verbatim from the `pageContext`
283
359
  prop (REST backend only) and is omitted entirely when empty. Use it to make
284
360
  the agent aware of the user's current location/page; the shape is up to the
@@ -601,6 +677,38 @@ abandonment, never the person deciding.
601
677
 
602
678
  REST backend only: `legacy` and `ag-ui` never meet this gate.
603
679
 
680
+ ### Decks, reports and videos
681
+
682
+ An answer that made a deck, a report or a video carries a marker where it shows
683
+ it: `[[PRESENTATION:<id>]]` (`[[PRESENTATION:<id>#/<slide id>]]` for one
684
+ slide), `[[REPORT:<id>]]`, `[[VIDEO:<id>]]`. The panel sends
685
+ `supports_artifacts: true` with every message and session restore of the REST
686
+ backend, and XTM One then describes each one in the answer's `artifacts`; the
687
+ panel draws a card where its marker stands. A marker is never shown as text, a
688
+ descriptor whose marker is missing renders after the prose, and a marker with
689
+ no descriptor renders nothing.
690
+
691
+ | Field | Meaning |
692
+ | --- | --- |
693
+ | `kind`, `id`, `title` | `presentation`, `report` or `video`, and what it is called. |
694
+ | `url` | Its page in XTM One: the card's `Open in XTM One` (http(s) only, new tab). |
695
+ | `slide_count`, `slide_ref` | A deck's slides, and the slide its marker named. |
696
+ | `section_count` | A report's sections. |
697
+ | `scene_count`, `duration_ms`, `status`, `progress` | A video's scenes and length, and its newest render (`pending`, `running`, `ready`, `failed`, `cancelled`; progress 0-100). |
698
+ | `downloads` | The formats it downloads in: `pdf`, `pptx`, `pptx_native`, `mp4`. Empty until a video has a finished render. |
699
+
700
+ The downloads go through the host, like file downloads, once it names the
701
+ route: `apiEndpoints.artifacts` has **no default**, for the same reason as
702
+ `apiEndpoints.approve`. Unset, the cards show and link to XTM One, with no
703
+ download buttons.
704
+
705
+ | Request | Purpose |
706
+ | --- | --- |
707
+ | `POST {apiBaseUrl}{artifacts}/{kind}/{id}/download` | `{ "format": "pdf" }`: the file, saved under its `Content-Disposition` name. A failure goes to `onDownloadError` with the name of the file asked for. |
708
+ | `GET {apiBaseUrl}{artifacts}/{kind}/{id}` | The descriptor as of now: a card asks again 10 seconds after each answer, one request at a time, for 15 minutes at most, while its video renders. |
709
+
710
+ XTM One serves both at `/chat/artifacts`.
711
+
604
712
  ### Message feedback
605
713
 
606
714
  Every message ends with a footer. An assistant answer (once it has finished
@@ -788,6 +896,7 @@ Every key the package can ask for, grouped by where it appears:
788
896
 
789
897
  **Composer**
790
898
 
899
+ - `'A message references at most {count} conversations'`
791
900
  - `'Ask a question...'`
792
901
  - `'Attachments wait for the current response'`
793
902
  - `'Dictate a message'`
@@ -795,11 +904,16 @@ Every key the package can ask for, grouped by where it appears:
795
904
  - `'Files uploading...'`
796
905
  - `'Insert prompt template'`
797
906
  - `'Listening...'`
907
+ - `'No conversation matches'`
798
908
  - `'No prompt matches'`
909
+ - `'Reference a conversation'`
910
+ - `'Referenced conversations wait for the current response'`
799
911
  - `'Search prompts...'`
800
912
  - `'Send now'`
913
+ - `'Shared with you'`
801
914
  - `'Stop dictation'`
802
915
  - `'Stop generating'`
916
+ - `'Untitled conversation'`
803
917
  - `'Uses AI. Verify results.'`
804
918
 
805
919
  **Messages, markdown and files**
@@ -821,6 +935,7 @@ Every key the package can ask for, grouped by where it appears:
821
935
  - `'Image preview'`
822
936
  - `'Load earlier messages'`
823
937
  - `'Loading image…'`
938
+ - `'Open conversation'`
824
939
  - `'Read aloud'`
825
940
  - `'Reasoning details'`
826
941
  - `'Reasoning details — turn limit reached'`
@@ -830,6 +945,23 @@ Every key the package can ask for, grouped by where it appears:
830
945
  - `'Tell us what went wrong (optional)'`
831
946
  - `'What could be improved?'`
832
947
 
948
+ **Decks, reports and videos**
949
+
950
+ - `'Download {format}'`
951
+ - `'Not rendered yet'`
952
+ - `'Open in XTM One'`
953
+ - `'PowerPoint'`
954
+ - `'PowerPoint (editable)'`
955
+ - `'Presentation'`
956
+ - `'Rendering {progress}%'`
957
+ - `'Report'`
958
+ - `'The render did not finish'`
959
+ - `'Video'`
960
+ - `'Waiting to render'`
961
+ - `'{count} scenes'`
962
+ - `'{count} sections'`
963
+ - `'{count} slides'`
964
+
833
965
  **Agent status**
834
966
 
835
967
  - `'Analyzing results…'`
package/dist/index.d.ts CHANGED
@@ -140,6 +140,55 @@ interface ApiEndpoints {
140
140
  * REST backend only, and not in `singleEndpoint` mode.
141
141
  */
142
142
  feedback?: string | null;
143
+ /**
144
+ * Path of the caller's workspaces, which the conversation list is grouped
145
+ * by (XTM One serves it at `/chat/workspaces`). The panel lists them with
146
+ * `GET {workspaces}`, creates one with `POST`, renames one with
147
+ * `PATCH {workspaces}/{id}` and deletes one with `DELETE {workspaces}/{id}`;
148
+ * it files a conversation with `PATCH {history}/{conversation_id}` and
149
+ * `{ workspace_id }`, and starts one inside a workspace by sending
150
+ * `workspace_id` with the session `POST`.
151
+ *
152
+ * No default, for the same reason as {@link ApiEndpoints.approve}: a
153
+ * proxied host has to expose all of those routes before the panel offers
154
+ * the feature. Unset (or a backend that refuses the list), the history is
155
+ * the flat list it always was.
156
+ *
157
+ * REST backend only, and not in `singleEndpoint` mode.
158
+ */
159
+ workspaces?: string | null;
160
+ /**
161
+ * Path of the conversations a message can reference with `@` in the
162
+ * composer (XTM One serves it at `/chat/conversation-references`). Typing
163
+ * `@` opens a menu searched with
164
+ * `GET {conversationReferences}?q=<typed text>&limit=8&exclude=<current conversation id>`,
165
+ * answered with `{ conversations: [{ id, title, key, updated_at, is_own }] }`:
166
+ * the conversations the user can open, most recent first. A pick inserts
167
+ * `@<key>`, and the message is sent with the ids of the picks it still
168
+ * holds as `referenced_conversation_ids` (at most 5); a restored user
169
+ * message lists them under `conversation_refs`.
170
+ *
171
+ * No default, for the same reason as {@link ApiEndpoints.approve}: a proxied
172
+ * host has to expose the route, and forward the field, before the composer
173
+ * offers the menu. Unset, `@` is plain text and the message body is
174
+ * unchanged.
175
+ *
176
+ * REST backend only, and not in `singleEndpoint` mode.
177
+ */
178
+ conversationReferences?: string | null;
179
+ /**
180
+ * Base path of the decks, reports and videos an answer made (XTM One serves
181
+ * it at `/chat/artifacts`). Their cards download one through
182
+ * `POST {apiBaseUrl}{artifacts}/{kind}/{id}/download` with `{ format }`, and
183
+ * follow a video still rendering through `GET {apiBaseUrl}{artifacts}/{kind}/{id}`.
184
+ *
185
+ * No default, for the same reason as {@link ApiEndpoints.approve}: a proxied
186
+ * host has to forward both routes before the cards offer them. Unset, the
187
+ * cards still render, with `Open in XTM One` and no download.
188
+ *
189
+ * REST backend only.
190
+ */
191
+ artifacts?: string | null;
143
192
  }
144
193
  /** A reusable prompt the user can insert into the composer. */
145
194
  interface ChatPromptTemplate {
@@ -460,8 +509,15 @@ interface ChatMessage {
460
509
  */
461
510
  agentName?: string;
462
511
  files?: ChatFile[];
512
+ /**
513
+ * The conversations a user message references with `@` (see
514
+ * {@link ApiEndpoints.conversationReferences}), shown as chips.
515
+ */
516
+ conversationRefs?: ChatConversationRef[];
463
517
  /** Agent-generated downloadable files attached to an assistant message. */
464
518
  attachments?: ChatAttachment[];
519
+ /** The decks, reports and videos an assistant message made, in marker order. */
520
+ artifacts?: ChatArtifact[];
465
521
  toolNames?: string[];
466
522
  toolCallCount?: number;
467
523
  iterations?: number;
@@ -526,6 +582,33 @@ interface ChatAttachment {
526
582
  */
527
583
  fileTag?: 'download_file' | 'working_file';
528
584
  }
585
+ /** What an answer can make besides a file. */
586
+ type ChatArtifactKind = 'presentation' | 'report' | 'video';
587
+ /**
588
+ * A deck, report or video an answer made, as the backend describes it: the
589
+ * `artifacts` of a `done` event or of a restored answer, sent because the panel
590
+ * says it renders them (`supports_artifacts`). Rendered as a card where its
591
+ * `[[PRESENTATION:<id>]]`, `[[REPORT:<id>]]` or `[[VIDEO:<id>]]` marker stands.
592
+ */
593
+ interface ChatArtifact {
594
+ kind: ChatArtifactKind;
595
+ id: string;
596
+ title: string;
597
+ /** The page that shows it in XTM One; linked only when it is http(s). */
598
+ url?: string;
599
+ /** The slide a deck's marker points at. */
600
+ slideRef?: string;
601
+ slideCount?: number;
602
+ sectionCount?: number;
603
+ sceneCount?: number;
604
+ durationMs?: number;
605
+ /** A video's newest render: `pending`, `running`, `ready`, `failed` or `cancelled`. */
606
+ status?: string;
607
+ /** That render's progress, 0-100. */
608
+ progress?: number;
609
+ /** The formats it downloads in: `pdf`, `pptx`, `pptx_native`, `mp4`. Empty until a video is rendered. */
610
+ downloads: string[];
611
+ }
529
612
  interface ChatFile {
530
613
  name: string;
531
614
  type: string;
@@ -556,6 +639,42 @@ interface ChatConversationSummary {
556
639
  * genuinely have no agent.
557
640
  */
558
641
  agentName?: string;
642
+ /**
643
+ * The workspace the conversation is filed in (`null`: none), when the
644
+ * backend reports it. Undefined on backends that do not.
645
+ */
646
+ workspaceId?: string | null;
647
+ }
648
+ /**
649
+ * A conversation the composer's `@` menu offers (see
650
+ * {@link ApiEndpoints.conversationReferences}).
651
+ */
652
+ interface ChatConversationReferenceCandidate {
653
+ id: string;
654
+ title: string;
655
+ /** What the pick inserts after `@`: the backend's own, never one computed here. */
656
+ key: string;
657
+ updatedAt?: string;
658
+ /** False for a conversation shared with the user. */
659
+ isOwn: boolean;
660
+ }
661
+ /** A conversation a message references with `@<key>`. */
662
+ interface ChatConversationRef {
663
+ conversationId: string;
664
+ title: string;
665
+ key: string;
666
+ /** As the menu offered it: `false` keeps the "Shared with you" hint. Absent reads as the user's own. */
667
+ isOwn?: boolean;
668
+ }
669
+ /** A workspace the conversation list is grouped by. */
670
+ interface ChatWorkspace {
671
+ id: string;
672
+ name: string;
673
+ /** The caller owns it: a colleague's default shared with them is not theirs. */
674
+ isOwn: boolean;
675
+ isDefault: boolean;
676
+ /** Whether the caller may file conversations into it, rename or delete it. */
677
+ canManage: boolean;
559
678
  }
560
679
  interface XtmAgent {
561
680
  id: string;
@@ -575,4 +694,4 @@ interface TransferredAgent {
575
694
  }
576
695
 
577
696
  export { ChatPanel, ChatToggleButton };
578
- export type { ApiEndpoints, BackendType, ChatAttachment, ChatContextBreakdown, ChatContextUsage, ChatConversationSummary, ChatFile, ChatMessage, ChatMessagePersistedFeedback, ChatMode, ChatPanelProps, ChatPromptTemplate, ChatQuotaStatus, ChatToggleButtonProps, MessageFeedback, ToolApprovalDecision, ToolApprovalProposal, ToolApprovalVerdict, TransferredAgent, Translate, XtmAgent };
697
+ export type { ApiEndpoints, BackendType, ChatArtifact, ChatArtifactKind, ChatAttachment, ChatContextBreakdown, ChatContextUsage, ChatConversationRef, ChatConversationReferenceCandidate, ChatConversationSummary, ChatFile, ChatMessage, ChatMessagePersistedFeedback, ChatMode, ChatPanelProps, ChatPromptTemplate, ChatQuotaStatus, ChatToggleButtonProps, ChatWorkspace, MessageFeedback, ToolApprovalDecision, ToolApprovalProposal, ToolApprovalVerdict, TransferredAgent, Translate, XtmAgent };