@filigran/chatbot 3.2.2 β†’ 3.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,7 @@ Filigran chat panel β€” a standalone React + Tailwind chatbot component with SSE
7
7
  - πŸ”„ **SSE Message Streaming** β€” Real-time response streaming with status indicators
8
8
  - πŸ€– **Multi-Agent Support** β€” Switch between different AI agents
9
9
  - πŸ“Ž **File Attachments** β€” Upload and paste files (PDF, TXT, images)
10
+ - πŸ“₯ **Agent-Generated Files** β€” Renders downloadable file cards from agent output and strips the `[[FILE:id]]` markers from the prose
10
11
  - πŸ“ **Full Markdown** β€” Tables, code blocks with copy button, lists, blockquotes
11
12
  - 🎨 **Customizable Theme** β€” Accent color and logo customization
12
13
  - πŸ“± **3 Display Modes** β€” Floating, sidebar (resizable), and fullscreen
@@ -32,20 +33,9 @@ function App() {
32
33
 
33
34
  return (
34
35
  <>
35
- <ChatToggleButton
36
- isOpen={isOpen}
37
- onToggle={() => setIsOpen(!isOpen)}
38
- label="Ask Assistant"
39
- accentColor="#7b5cff"
40
- />
36
+ <ChatToggleButton isOpen={isOpen} onToggle={() => setIsOpen(!isOpen)} label="Ask Assistant" accentColor="#7b5cff" />
41
37
  {isOpen && (
42
- <ChatPanel
43
- mode={mode}
44
- onClose={() => setIsOpen(false)}
45
- onModeChange={setMode}
46
- apiBaseUrl="/api/assistant"
47
- user={{ firstName: 'John' }}
48
- />
38
+ <ChatPanel mode={mode} onClose={() => setIsOpen(false)} onModeChange={setMode} apiBaseUrl="/api/assistant" user={{ firstName: 'John' }} />
49
39
  )}
50
40
  </>
51
41
  );
@@ -64,23 +54,24 @@ import { ChatPanel } from '@filigran/chatbot';
64
54
 
65
55
  #### Props
66
56
 
67
- | Prop | Type | Default | Description |
68
- |------|------|---------|-------------|
69
- | `mode` | `'floating' \| 'sidebar' \| 'fullscreen'` | **required** | Display mode |
70
- | `onClose` | `() => void` | **required** | Called when close button is clicked |
71
- | `onModeChange` | `(mode: ChatMode) => void` | **required** | Called when user switches display mode |
72
- | `apiBaseUrl` | `string` | **required** | Base URL for chat API endpoints |
73
- | `user` | `{ firstName: string }` | **required** | Current user info |
74
- | `topOffset` | `number` | `0` | Top offset in pixels (for sidebar/fullscreen with fixed headers) |
75
- | `agentDashboardUrl` | `string` | β€” | URL for "Browse agents" / "Create agent" links |
76
- | `t` | `(key: string) => string` | identity | Translation function for i18n |
77
- | `accentColor` | `string` | `'#7b5cff'` | Primary accent color (hex) |
78
- | `logoIcon` | `React.ReactNode` | default icon | Custom logo/icon for the assistant |
79
- | `promptSuggestions` | `string[]` | default list | Prompt suggestions shown on welcome screen |
80
- | `resizable` | `boolean` | `false` | Enable drag-to-resize for sidebar mode |
81
- | `onWidthChange` | `(width: number) => void` | β€” | Called when sidebar width changes during resize |
82
- | `onResizeStart` | `() => void` | β€” | Called when resize drag starts |
83
- | `onResizeEnd` | `() => void` | β€” | Called when resize drag ends |
57
+ | Prop | Type | Default | Description |
58
+ | ------------------- | ----------------------------------------- | ------------ | ---------------------------------------------------------------- |
59
+ | `mode` | `'floating' \| 'sidebar' \| 'fullscreen'` | **required** | Display mode |
60
+ | `onClose` | `() => void` | **required** | Called when close button is clicked |
61
+ | `onModeChange` | `(mode: ChatMode) => void` | **required** | Called when user switches display mode |
62
+ | `apiBaseUrl` | `string` | **required** | Base URL for chat API endpoints |
63
+ | `user` | `{ firstName: string }` | **required** | Current user info |
64
+ | `topOffset` | `number` | `0` | Top offset in pixels (for sidebar/fullscreen with fixed headers) |
65
+ | `agentDashboardUrl` | `string` | β€” | URL for "Browse agents" / "Create agent" links |
66
+ | `t` | `(key: string) => string` | identity | Translation function for i18n |
67
+ | `accentColor` | `string` | `'#7b5cff'` | Primary accent color (hex) |
68
+ | `logoIcon` | `React.ReactNode` | default icon | Custom logo/icon for the assistant |
69
+ | `promptSuggestions` | `string[]` | default list | Prompt suggestions shown on welcome screen |
70
+ | `pageContext` | `Record<string, unknown>` | β€” | Arbitrary host page context (e.g. `{ url: '/dashboard/...' }`) sent as `context` on each `rest` message so the agent knows where the user is. Must be JSON-serializable (skipped if not). Read fresh at send time; omitted when empty. |
71
+ | `resizable` | `boolean` | `false` | Enable drag-to-resize for sidebar mode |
72
+ | `onWidthChange` | `(width: number) => void` | β€” | Called when sidebar width changes during resize |
73
+ | `onResizeStart` | `() => void` | β€” | Called when resize drag starts |
74
+ | `onResizeEnd` | `() => void` | β€” | Called when resize drag ends |
84
75
 
85
76
  #### Resizable Sidebar Example
86
77
 
@@ -117,13 +108,13 @@ import { ChatToggleButton } from '@filigran/chatbot';
117
108
 
118
109
  #### Props
119
110
 
120
- | Prop | Type | Default | Description |
121
- |------|------|---------|-------------|
122
- | `isOpen` | `boolean` | **required** | Whether the chat panel is open |
123
- | `onToggle` | `() => void` | **required** | Called when button is clicked |
124
- | `label` | `string` | `'Chat'` | Tooltip/aria label |
125
- | `accentColor` | `string` | `'#7b5cff'` | Button background color |
126
- | `icon` | `React.ReactNode` | default icon | Custom icon |
111
+ | Prop | Type | Default | Description |
112
+ | ------------- | ----------------- | ------------ | ------------------------------ |
113
+ | `isOpen` | `boolean` | **required** | Whether the chat panel is open |
114
+ | `onToggle` | `() => void` | **required** | Called when button is clicked |
115
+ | `label` | `string` | `'Chat'` | Tooltip/aria label |
116
+ | `accentColor` | `string` | `'#7b5cff'` | Button background color |
117
+ | `icon` | `React.ReactNode` | default icon | Custom icon |
127
118
 
128
119
  ## API Contract
129
120
 
@@ -150,6 +141,7 @@ Returns available AI agents.
150
141
  Restores conversation history.
151
142
 
152
143
  **Request:**
144
+
153
145
  ```json
154
146
  {
155
147
  "conversation_id": "uuid-here",
@@ -158,6 +150,7 @@ Restores conversation history.
158
150
  ```
159
151
 
160
152
  **Response:**
153
+
161
154
  ```json
162
155
  {
163
156
  "messages": [
@@ -167,19 +160,48 @@ Restores conversation history.
167
160
  }
168
161
  ```
169
162
 
163
+ Assistant history messages **should echo the same `attachments[]` array** that
164
+ was sent on the original `done` event (see [Agent-generated file attachments](#agent-generated-file-attachments)),
165
+ keyed by `file_id`. The component re-surfaces the download cards on restore,
166
+ so omitting them means download cards silently disappear after a page reload
167
+ even though streaming downloads work:
168
+
169
+ ```json
170
+ {
171
+ "messages": [
172
+ {
173
+ "role": "assistant",
174
+ "content": "Here is your export. [[FILE:0f3a...]]",
175
+ "attachments": [
176
+ { "file_id": "0f3a...", "filename": "iocs.csv", "type": "csv", "size": 2048, "content_type": "text/csv", "file_tag": "download_file" }
177
+ ]
178
+ }
179
+ ]
180
+ }
181
+ ```
182
+
170
183
  ### `POST {apiBaseUrl}/chat/messages`
171
184
 
172
185
  Sends a message and streams the response via SSE.
173
186
 
174
187
  **Request:**
188
+
175
189
  ```json
176
190
  {
177
191
  "content": "What is the weather?",
178
192
  "conversation_id": "uuid-or-null",
179
- "agent_slug": "general"
193
+ "agent_slug": "general",
194
+ "context": { "url": "/dashboard/analyses/reports/<id>/overview" }
180
195
  }
181
196
  ```
182
197
 
198
+ The optional `context` object is forwarded verbatim from the `pageContext`
199
+ prop (REST backend only) and is omitted entirely when empty. Use it to make
200
+ the agent aware of the user's current location/page; the shape is up to the
201
+ host and can be extended later (page title, selected entity, user role, etc.).
202
+ It must be JSON-serializable β€” a non-serializable value (circular reference,
203
+ `BigInt`, …) is skipped rather than breaking the request.
204
+
183
205
  **Response:** Server-Sent Events stream with these event types:
184
206
 
185
207
  ```
@@ -192,7 +214,35 @@ data: {"type": "stream", "content": "today is sunny."}
192
214
  data: {"type": "done", "content": "The weather today is sunny.", "conversation_id": "new-uuid", "tool_names": ["search_web"], "tool_call_count": 1, "iterations": 1}
193
215
  ```
194
216
 
217
+ #### Agent-generated file attachments
218
+
219
+ When an agent produces a downloadable file, the `done` event carries an `attachments` array and the streamed prose embeds `[[FILE:<file_id>]]` markers. The component strips those markers and renders a download card per attachment:
220
+
221
+ ```json
222
+ {
223
+ "type": "done",
224
+ "content": "Here is your export. [[FILE:0f3a...]]",
225
+ "conversation_id": "uuid",
226
+ "attachments": [
227
+ { "file_id": "0f3a...", "filename": "iocs.csv", "type": "csv", "size": 2048, "content_type": "text/csv", "file_tag": "download_file" }
228
+ ]
229
+ }
230
+ ```
231
+
232
+ Each attachment carries only `file_id` + display metadata β€” **never an absolute download URL**. Clicking a card issues:
233
+
234
+ ```
235
+ GET {apiBaseUrl}{apiEndpoints.download ?? '/chat/files'}/{file_id}/download
236
+ ```
237
+
238
+ with `credentials: 'include'` and your `requestHeaders`. Point `apiEndpoints.download` at your **own backend proxy** so the download is authenticated by your platform (the proxy mints any upstream token server-side) β€” the user never authenticates to the upstream chat service directly. Set `apiEndpoints.download` to `null` to disable download cards.
239
+
240
+ The `/chat/files` default applies to REST-style endpoints. In `singleEndpoint` mode there is no per-path routing, so the default is **not** applied β€” download cards stay disabled unless you set `apiEndpoints.download` explicitly to a proxy route.
241
+
242
+ Download failures (403/404/5xx/network) are reported through the optional `onDownloadError(error, attachment)` callback so the host can surface them via its own notification system (the chatbot has no toast surface of its own).
243
+
195
244
  **Status values:**
245
+
196
246
  - `thinking` β€” Agent is processing
197
247
  - `tool_start` β€” Agent is using tools (with `tools` array)
198
248
  - `analyzing` β€” Agent is analyzing tool results
@@ -200,6 +250,7 @@ data: {"type": "done", "content": "The weather today is sunny.", "conversation_i
200
250
  - `streaming` β€” Content is being streamed
201
251
 
202
252
  **Error event:**
253
+
203
254
  ```
204
255
  data: {"type": "error", "content": "Something went wrong"}
205
256
  ```
@@ -214,7 +265,7 @@ import { MyLogo } from './icons';
214
265
  <ChatPanel
215
266
  logoIcon={<MyLogo size={24} />}
216
267
  // ...
217
- />
268
+ />;
218
269
  ```
219
270
 
220
271
  ### Custom Accent Color
@@ -230,11 +281,7 @@ import { MyLogo } from './icons';
230
281
 
231
282
  ```tsx
232
283
  <ChatPanel
233
- promptSuggestions={[
234
- 'Help me write a report',
235
- 'Analyze this data',
236
- 'Summarize recent activity',
237
- ]}
284
+ promptSuggestions={['Help me write a report', 'Analyze this data', 'Summarize recent activity']}
238
285
  // ...
239
286
  />
240
287
  ```
@@ -246,7 +293,7 @@ import { useTranslation } from 'react-i18next';
246
293
 
247
294
  function App() {
248
295
  const { t } = useTranslation();
249
-
296
+
250
297
  return (
251
298
  <ChatPanel
252
299
  t={t}
@@ -257,6 +304,7 @@ function App() {
257
304
  ```
258
305
 
259
306
  **Translation keys used:**
307
+
260
308
  - `'Thinking...'`
261
309
  - `'Using tools…'`
262
310
  - `'Analyzing results…'`
@@ -270,6 +318,7 @@ function App() {
270
318
  - `'Browse agents'`
271
319
  - `'Create agent'`
272
320
  - `'Reasoning details'`
321
+ - `'Download'`
273
322
  - `'tool call'` / `'tool calls'`
274
323
  - `'Uses AI. Verify results.'`
275
324
  - `'How can I help you, '`
package/dist/index.d.ts CHANGED
@@ -18,6 +18,20 @@ interface ApiEndpoints {
18
18
  sessions?: string | null;
19
19
  /** Path for uploading files. Default: '/chat/upload'. Set to null to disable file uploads. */
20
20
  upload?: string | null;
21
+ /**
22
+ * Base path for downloading agent-generated files. Default: '/chat/files'.
23
+ * The download URL is built as
24
+ * `${apiBaseUrl}${download}/${fileId}/download`, resolved against the
25
+ * host app's own backend proxy. This keeps the download authenticated by
26
+ * the host platform (e.g. OpenCTI / OpenAEV session) β€” the proxy mints
27
+ * any upstream token server-side, so the user never authenticates to the
28
+ * upstream chat service directly. Set to null to disable download chips.
29
+ *
30
+ * Exception: in `singleEndpoint` mode the `/chat/files` default is NOT
31
+ * applied (there is no per-path routing), so download cards stay disabled
32
+ * unless this path is set explicitly to a proxy route.
33
+ */
34
+ download?: string | null;
21
35
  }
22
36
  interface ChatPanelProps {
23
37
  mode: ChatMode;
@@ -51,12 +65,37 @@ interface ChatPanelProps {
51
65
  disableFileManagement?: boolean;
52
66
  /** Called when a relative markdown link is clicked in assistant messages. */
53
67
  onRelativeLinkClick?: (href: string) => void;
68
+ /**
69
+ * Called when an agent-generated file download fails (non-2xx response or
70
+ * network error). Lets the host surface the failure through its own
71
+ * notification system (the chatbot has no toast surface of its own).
72
+ */
73
+ onDownloadError?: (error: unknown, attachment: ChatAttachment) => void;
54
74
  /** Maximum number of files attachable in one chat context. Default: 10. */
55
75
  maxFileCount?: number;
56
76
  /** Maximum total size in bytes for attached files. Default: 50 * 1024 * 1024 (50 MB). */
57
77
  maxTotalSize?: number;
58
78
  /** Additional HTTP headers added to chatbot API requests (messages, sessions, agents, uploads). */
59
79
  requestHeaders?: Record<string, string>;
80
+ /**
81
+ * Arbitrary contextual metadata about the host page/application, forwarded
82
+ * to the backend alongside every message as a `context` JSON object so the
83
+ * agent is aware of where the user is.
84
+ *
85
+ * The shape is up to the host. Today it typically carries the current
86
+ * relative URL, e.g.
87
+ * `{ url: '/dashboard/analyses/reports/<id>/overview' }`, and can be
88
+ * extended later (page title, selected entity, user role, etc.).
89
+ *
90
+ * Must be JSON-serializable β€” it is sent via `JSON.stringify`. A
91
+ * non-serializable value (circular reference, `BigInt`, etc.) is skipped
92
+ * rather than thrown, so it can never break message sending.
93
+ *
94
+ * Read fresh at send time, so it always reflects the page the user is on
95
+ * when the message is sent (not when the panel was opened). Only emitted
96
+ * for the `rest` backend, and omitted entirely when empty.
97
+ */
98
+ pageContext?: Record<string, unknown>;
60
99
  /**
61
100
  * CSS selector for the main content element that should be pushed when sidebar is open.
62
101
  * When set, the component will automatically apply margin-right to push the content.
@@ -84,10 +123,34 @@ interface ChatMessage {
84
123
  content: string;
85
124
  timestamp: Date;
86
125
  files?: ChatFile[];
126
+ /** Agent-generated downloadable files attached to an assistant message. */
127
+ attachments?: ChatAttachment[];
87
128
  toolNames?: string[];
88
129
  toolCallCount?: number;
89
130
  iterations?: number;
90
131
  }
132
+ /**
133
+ * An agent-generated file produced during a chat turn (via the backend
134
+ * `generate_file` tool / custom-tool `$output_files`). Rendered as a
135
+ * download card in assistant messages.
136
+ *
137
+ * Intentionally carries no absolute URL: the download is resolved against
138
+ * the host app's backend proxy from `fileId` (see `ApiEndpoints.download`)
139
+ * so the user stays authenticated to the host platform only.
140
+ */
141
+ interface ChatAttachment {
142
+ fileId: string;
143
+ filename: string;
144
+ /** Short extension-style label surfaced under the filename (e.g. "PDF"). */
145
+ type?: string;
146
+ size?: number;
147
+ contentType?: string;
148
+ /**
149
+ * `download_file` β†’ prominent download card (user deliverable).
150
+ * `working_file` β†’ de-emphasized scratch/working artifact chip.
151
+ */
152
+ fileTag?: 'download_file' | 'working_file';
153
+ }
91
154
  interface ChatFile {
92
155
  name: string;
93
156
  type: string;
@@ -117,4 +180,4 @@ interface TransferredAgent {
117
180
  }
118
181
 
119
182
  export { ChatPanel, ChatToggleButton };
120
- export type { ApiEndpoints, BackendType, ChatFile, ChatMessage, ChatMode, ChatPanelProps, ChatToggleButtonProps, TransferredAgent, XtmAgent };
183
+ export type { ApiEndpoints, BackendType, ChatAttachment, ChatFile, ChatMessage, ChatMode, ChatPanelProps, ChatToggleButtonProps, TransferredAgent, XtmAgent };