@extrachill/chat 0.3.0 → 0.5.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.
@@ -1,31 +1,38 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useMemo } from 'react';
2
+ import { lazy, Suspense } from 'react';
3
3
  import { markdownToHtml } from "../markdown.js";
4
+ const ReactMarkdown = lazy(() => import('react-markdown'));
4
5
  /**
5
6
  * Renders a single chat message bubble.
6
7
  *
7
8
  * User messages align right, assistant messages align left.
8
- * Content rendering is pluggable via `renderContent` or `contentFormat`.
9
+ * Markdown content is rendered via react-markdown (lazy-loaded).
10
+ * Media attachments render inline below the text content.
9
11
  */
10
12
  export function ChatMessage({ message, contentFormat = 'markdown', renderContent, className, }) {
11
13
  const isUser = message.role === 'user';
12
14
  const baseClass = 'ec-chat-message';
13
15
  const roleClass = isUser ? `${baseClass}--user` : `${baseClass}--assistant`;
14
16
  const classes = [baseClass, roleClass, className].filter(Boolean).join(' ');
15
- return (_jsxs("div", { className: classes, "data-message-id": message.id, children: [_jsx("div", { className: `${baseClass}__bubble`, children: renderContent
16
- ? renderContent(message.content, message.role)
17
- : _jsx(DefaultContent, { content: message.content, format: contentFormat }) }), message.timestamp && (_jsx("time", { className: `${baseClass}__timestamp`, dateTime: message.timestamp, title: new Date(message.timestamp).toLocaleString(), children: formatTime(message.timestamp) }))] }));
17
+ const hasText = message.content.trim().length > 0;
18
+ const hasAttachments = message.attachments && message.attachments.length > 0;
19
+ return (_jsxs("div", { className: classes, "data-message-id": message.id, children: [_jsxs("div", { className: `${baseClass}__bubble`, children: [hasText && (renderContent
20
+ ? renderContent(message.content, message.role)
21
+ : _jsx(DefaultContent, { content: message.content, format: contentFormat })), hasAttachments && (_jsx(MessageAttachments, { attachments: message.attachments }))] }), message.timestamp && (_jsx("time", { className: `${baseClass}__timestamp`, dateTime: message.timestamp, title: new Date(message.timestamp).toLocaleString(), children: formatTime(message.timestamp) }))] }));
22
+ }
23
+ /**
24
+ * Markdown rendered via lazy-loaded react-markdown.
25
+ * Falls back to the built-in lightweight parser while loading.
26
+ */
27
+ function MarkdownContent({ content }) {
28
+ return (_jsx(Suspense, { fallback: _jsx("div", { dangerouslySetInnerHTML: { __html: markdownToHtml(content) } }), children: _jsx(ReactMarkdown, { children: content }) }));
18
29
  }
19
30
  function DefaultContent({ content, format }) {
20
- const html = useMemo(() => {
21
- if (format === 'html')
22
- return content;
23
- if (format === 'markdown')
24
- return markdownToHtml(content);
25
- return null;
26
- }, [content, format]);
27
- if (html !== null) {
28
- return _jsx("div", { dangerouslySetInnerHTML: { __html: html } });
31
+ if (format === 'html') {
32
+ return _jsx("div", { dangerouslySetInnerHTML: { __html: content } });
33
+ }
34
+ if (format === 'markdown') {
35
+ return _jsx(MarkdownContent, { content: content });
29
36
  }
30
37
  // Plain text — split on double newlines for paragraphs.
31
38
  return (_jsx(_Fragment, { children: content.split('\n\n').map((paragraph, i) => (_jsx("p", { children: paragraph }, i))) }));
@@ -39,3 +46,20 @@ function formatTime(iso) {
39
46
  return '';
40
47
  }
41
48
  }
49
+ /* ---- Media Attachments ---- */
50
+ function MessageAttachments({ attachments }) {
51
+ const images = attachments.filter((a) => a.type === 'image');
52
+ const videos = attachments.filter((a) => a.type === 'video');
53
+ const files = attachments.filter((a) => a.type === 'file');
54
+ return (_jsxs("div", { className: "ec-chat-message__attachments", children: [images.length > 0 && (_jsx("div", { className: "ec-chat-message__images", children: images.map((img, i) => (_jsx("a", { href: img.url, target: "_blank", rel: "noopener noreferrer", className: "ec-chat-message__image-link", children: _jsx("img", { src: img.thumbnailUrl ?? img.url, alt: img.alt ?? img.filename ?? 'Image attachment', className: "ec-chat-message__image", loading: "lazy" }) }, i))) })), videos.map((vid, i) => (_jsx("video", { src: vid.url, controls: true, className: "ec-chat-message__video", preload: "metadata", children: _jsx("track", { kind: "captions" }) }, i))), files.map((file, i) => (_jsxs("a", { href: file.url, download: file.filename, className: "ec-chat-message__file", target: "_blank", rel: "noopener noreferrer", children: [_jsx(FileIcon, {}), _jsx("span", { className: "ec-chat-message__file-name", children: file.filename ?? 'Download file' }), file.size != null && (_jsx("span", { className: "ec-chat-message__file-size", children: formatFileSize(file.size) }))] }, i)))] }));
55
+ }
56
+ function FileIcon() {
57
+ return (_jsxs("svg", { className: "ec-chat-message__file-icon", viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), _jsx("polyline", { points: "14 2 14 8 20 8" })] }));
58
+ }
59
+ function formatFileSize(bytes) {
60
+ if (bytes < 1024)
61
+ return `${bytes} B`;
62
+ if (bytes < 1024 * 1024)
63
+ return `${(bytes / 1024).toFixed(1)} KB`;
64
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
65
+ }
@@ -1,4 +1,4 @@
1
- import type { ChatMessage } from '../types/message.ts';
1
+ import type { ChatMessage, ToolCall } from '../types/message.ts';
2
2
  import type { ChatSession } from '../types/session.ts';
3
3
  import type { ChatAvailability } from '../types/session.ts';
4
4
  import type { FetchFn } from '../api.ts';
@@ -41,6 +41,23 @@ export interface UseChatOptions {
41
41
  * Called when an error occurs.
42
42
  */
43
43
  onError?: (error: Error) => void;
44
+ /**
45
+ * Called after each turn when tool calls are present in the response.
46
+ * Use this to react to tool executions (e.g. invalidate caches,
47
+ * apply diffs to the editor, update external state).
48
+ */
49
+ onToolCalls?: (toolCalls: ToolCall[]) => void;
50
+ /**
51
+ * Arbitrary metadata forwarded to the backend with each message.
52
+ * Use for context scoping (e.g. `{ selected_pipeline_id: 42 }`,
53
+ * `{ post_id: 100, context: 'editor' }`).
54
+ */
55
+ metadata?: Record<string, unknown>;
56
+ /**
57
+ * Optional context filter for session listing.
58
+ * Only sessions created in the matching context are shown.
59
+ */
60
+ sessionContext?: string;
44
61
  }
45
62
  /**
46
63
  * Return value of the useChat hook.
@@ -56,12 +73,19 @@ export interface UseChatReturn {
56
73
  availability: ChatAvailability;
57
74
  /** Active session ID. */
58
75
  sessionId: string | null;
76
+ /**
77
+ * The session ID that initiated the current request.
78
+ * Use to avoid stale loading indicators when the user switches
79
+ * sessions while a request is in flight.
80
+ * Null when idle.
81
+ */
82
+ processingSessionId: string | null;
59
83
  /** List of sessions. */
60
84
  sessions: ChatSession[];
61
85
  /** Whether sessions are loading. */
62
86
  sessionsLoading: boolean;
63
- /** Send a user message. */
64
- sendMessage: (content: string) => void;
87
+ /** Send a user message (with optional file attachments). */
88
+ sendMessage: (content: string, files?: File[]) => void;
65
89
  /** Switch to a different session. */
66
90
  switchSession: (sessionId: string) => void;
67
91
  /** Create a new session. */
@@ -98,5 +122,5 @@ export interface UseChatReturn {
98
122
  * );
99
123
  * ```
100
124
  */
101
- export declare function useChat({ basePath, fetchFn, agentId, initialMessages, initialSessionId, maxContinueTurns, onMessage, onError, }: UseChatOptions): UseChatReturn;
125
+ export declare function useChat({ basePath, fetchFn, agentId, initialMessages, initialSessionId, maxContinueTurns, onMessage, onError, onToolCalls, metadata, sessionContext, }: UseChatOptions): UseChatReturn;
102
126
  //# sourceMappingURL=useChat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../src/hooks/useChat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAiB,MAAM,WAAW,CAAC;AASxD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,eAAe,CAAC,EAAE,WAAW,EAAE,CAAC;IAChC;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC3C;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,gDAAgD;IAChD,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,iDAAiD;IACjD,SAAS,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,yBAAyB;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,wBAAwB;IACxB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,oCAAoC;IACpC,eAAe,EAAE,OAAO,CAAC;IACzB,2BAA2B;IAC3B,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,qCAAqC;IACrC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,4BAA4B;IAC5B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,wBAAwB;IACxB,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,oDAAoD;IACpD,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,gCAAgC;IAChC,eAAe,EAAE,MAAM,IAAI,CAAC;CAC5B;AAyBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,OAAO,CAAC,EACvB,QAAQ,EACR,OAAO,EACP,OAAO,EACP,eAAe,EACf,gBAAgB,EAChB,gBAAqB,EACrB,SAAS,EACT,OAAO,GACP,EAAE,cAAc,GAAG,aAAa,CAwLhC"}
1
+ {"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../src/hooks/useChat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,KAAK,EAAE,OAAO,EAAiC,MAAM,WAAW,CAAC;AASxE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,eAAe,CAAC,EAAE,WAAW,EAAE,CAAC;IAChC;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC3C;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,gDAAgD;IAChD,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,iDAAiD;IACjD,SAAS,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,yBAAyB;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;;OAKG;IACH,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,wBAAwB;IACxB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,oCAAoC;IACpC,eAAe,EAAE,OAAO,CAAC;IACzB,4DAA4D;IAC5D,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACvD,qCAAqC;IACrC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,4BAA4B;IAC5B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,wBAAwB;IACxB,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,oDAAoD;IACpD,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,gCAAgC;IAChC,eAAe,EAAE,MAAM,IAAI,CAAC;CAC5B;AAyBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,OAAO,CAAC,EACvB,QAAQ,EACR,OAAO,EACP,OAAO,EACP,eAAe,EACf,gBAAgB,EAChB,gBAAqB,EACrB,SAAS,EACT,OAAO,EACP,WAAW,EACX,QAAQ,EACR,cAAc,GACd,EAAE,cAAc,GAAG,aAAa,CA0QhC"}
@@ -53,12 +53,13 @@ function toError(err) {
53
53
  * );
54
54
  * ```
55
55
  */
56
- export function useChat({ basePath, fetchFn, agentId, initialMessages, initialSessionId, maxContinueTurns = 20, onMessage, onError, }) {
56
+ export function useChat({ basePath, fetchFn, agentId, initialMessages, initialSessionId, maxContinueTurns = 20, onMessage, onError, onToolCalls, metadata, sessionContext, }) {
57
57
  const [messages, setMessages] = useState(initialMessages ?? []);
58
58
  const [isLoading, setIsLoading] = useState(false);
59
59
  const [turnCount, setTurnCount] = useState(0);
60
60
  const [availability, setAvailability] = useState({ status: 'ready' });
61
61
  const [sessionId, setSessionId] = useState(initialSessionId ?? null);
62
+ const [processingSessionId, setProcessingSessionId] = useState(null);
62
63
  const [sessions, setSessions] = useState([]);
63
64
  const [sessionsLoading, setSessionsLoading] = useState(false);
64
65
  // Build API config from props
@@ -66,12 +67,21 @@ export function useChat({ basePath, fetchFn, agentId, initialMessages, initialSe
66
67
  configRef.current = { basePath, fetchFn, agentId };
67
68
  const sessionIdRef = useRef(sessionId);
68
69
  sessionIdRef.current = sessionId;
70
+ // Refs for latest callback/metadata values (avoid stale closures).
71
+ const onToolCallsRef = useRef(onToolCalls);
72
+ onToolCallsRef.current = onToolCalls;
73
+ const metadataRef = useRef(metadata);
74
+ metadataRef.current = metadata;
75
+ const sessionContextRef = useRef(sessionContext);
76
+ sessionContextRef.current = sessionContext;
77
+ // Guard against concurrent session creation.
78
+ const isCreatingRef = useRef(false);
69
79
  // Load sessions on mount
70
80
  useEffect(() => {
71
81
  const loadSessions = async () => {
72
82
  setSessionsLoading(true);
73
83
  try {
74
- const list = await apiListSessions(configRef.current);
84
+ const list = await apiListSessions(configRef.current, 20, sessionContextRef.current);
75
85
  setSessions(list);
76
86
  }
77
87
  catch (err) {
@@ -85,27 +95,74 @@ export function useChat({ basePath, fetchFn, agentId, initialMessages, initialSe
85
95
  loadSessions();
86
96
  // eslint-disable-next-line react-hooks/exhaustive-deps
87
97
  }, []);
88
- const sendMessage = useCallback(async (content) => {
89
- if (isLoading)
98
+ /**
99
+ * Collect tool calls from a list of messages and fire the onToolCalls callback.
100
+ */
101
+ const fireToolCalls = useCallback((msgs) => {
102
+ const cb = onToolCallsRef.current;
103
+ if (!cb)
90
104
  return;
105
+ const allToolCalls = [];
106
+ for (const msg of msgs) {
107
+ if (msg.toolCalls?.length) {
108
+ allToolCalls.push(...msg.toolCalls);
109
+ }
110
+ }
111
+ if (allToolCalls.length > 0) {
112
+ cb(allToolCalls);
113
+ }
114
+ }, []);
115
+ const sendMessage = useCallback(async (content, files) => {
116
+ if (isLoading || isCreatingRef.current)
117
+ return;
118
+ // Build optimistic attachment previews from local files.
119
+ let optimisticAttachments;
120
+ let sendAttachments;
121
+ if (files?.length) {
122
+ optimisticAttachments = files.map((file) => ({
123
+ type: file.type.startsWith('image/') ? 'image'
124
+ : file.type.startsWith('video/') ? 'video'
125
+ : 'file',
126
+ url: URL.createObjectURL(file),
127
+ filename: file.name,
128
+ mimeType: file.type,
129
+ size: file.size,
130
+ }));
131
+ sendAttachments = files.map((file) => ({
132
+ filename: file.name,
133
+ mime_type: file.type,
134
+ }));
135
+ }
91
136
  // Optimistically add user message
92
137
  const userMessage = {
93
138
  id: generateMessageId(),
94
139
  role: 'user',
95
140
  content,
96
141
  timestamp: new Date().toISOString(),
142
+ attachments: optimisticAttachments,
97
143
  };
144
+ // Guard against concurrent session creation.
145
+ if (!sessionIdRef.current) {
146
+ isCreatingRef.current = true;
147
+ }
98
148
  setMessages((prev) => [...prev, userMessage]);
99
149
  onMessage?.(userMessage);
100
150
  setIsLoading(true);
101
151
  setTurnCount(0);
152
+ // Track which session initiated the request.
153
+ const initiatingSessionId = sessionIdRef.current;
154
+ setProcessingSessionId(initiatingSessionId);
102
155
  try {
103
- const result = await apiSendMessage(configRef.current, content, sessionIdRef.current ?? undefined);
156
+ const result = await apiSendMessage(configRef.current, content, sessionIdRef.current ?? undefined, sendAttachments, metadataRef.current);
157
+ isCreatingRef.current = false;
104
158
  // Update session ID (may be newly created)
105
159
  setSessionId(result.sessionId);
106
160
  sessionIdRef.current = result.sessionId;
161
+ setProcessingSessionId(result.sessionId);
107
162
  // Replace all messages with the full normalized conversation
108
163
  setMessages(result.messages);
164
+ // Fire tool call callback for the initial response.
165
+ fireToolCalls(result.messages);
109
166
  // Handle multi-turn continuation
110
167
  if (!result.completed && !result.maxTurnsReached) {
111
168
  let completed = false;
@@ -118,15 +175,18 @@ export function useChat({ basePath, fetchFn, agentId, initialMessages, initialSe
118
175
  for (const msg of continuation.messages) {
119
176
  onMessage?.(msg);
120
177
  }
178
+ // Fire tool call callback for each continuation turn.
179
+ fireToolCalls(continuation.messages);
121
180
  completed = continuation.completed || continuation.maxTurnsReached;
122
181
  }
123
182
  }
124
183
  // Refresh sessions list after a message
125
- apiListSessions(configRef.current)
184
+ apiListSessions(configRef.current, 20, sessionContextRef.current)
126
185
  .then(setSessions)
127
186
  .catch(() => { });
128
187
  }
129
188
  catch (err) {
189
+ isCreatingRef.current = false;
130
190
  const error = toError(err);
131
191
  onError?.(error);
132
192
  // Check if it's an auth error
@@ -145,8 +205,9 @@ export function useChat({ basePath, fetchFn, agentId, initialMessages, initialSe
145
205
  finally {
146
206
  setIsLoading(false);
147
207
  setTurnCount(0);
208
+ setProcessingSessionId(null);
148
209
  }
149
- }, [isLoading, maxContinueTurns, onMessage, onError]);
210
+ }, [isLoading, maxContinueTurns, onMessage, onError, fireToolCalls]);
150
211
  const switchSession = useCallback(async (newSessionId) => {
151
212
  setSessionId(newSessionId);
152
213
  sessionIdRef.current = newSessionId;
@@ -188,7 +249,7 @@ export function useChat({ basePath, fetchFn, agentId, initialMessages, initialSe
188
249
  const refreshSessions = useCallback(async () => {
189
250
  setSessionsLoading(true);
190
251
  try {
191
- const list = await apiListSessions(configRef.current);
252
+ const list = await apiListSessions(configRef.current, 20, sessionContextRef.current);
192
253
  setSessions(list);
193
254
  }
194
255
  catch (err) {
@@ -204,6 +265,7 @@ export function useChat({ basePath, fetchFn, agentId, initialMessages, initialSe
204
265
  turnCount,
205
266
  availability,
206
267
  sessionId,
268
+ processingSessionId,
207
269
  sessions,
208
270
  sessionsLoading,
209
271
  sendMessage,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export type { MessageRole, ToolCall, ToolResultMeta, ChatMessage, ContentFormat, ChatSession, ChatAvailability, ChatInitialState, RawMessage, RawSession, SessionMetadata, } from './types/index.ts';
2
- export type { FetchFn, FetchOptions, ChatApiConfig, SendResult, ContinueResult } from './api.ts';
1
+ export type { MessageRole, ToolCall, ToolResultMeta, MediaAttachment, ChatMessage, ContentFormat, ChatSession, ChatAvailability, ChatInitialState, RawAttachment, RawMessage, RawSession, SessionMetadata, } from './types/index.ts';
2
+ export type { FetchFn, FetchOptions, ChatApiConfig, SendResult, ContinueResult, SendAttachment } from './api.ts';
3
3
  export { sendMessage, continueResponse, listSessions, loadSession, deleteSession, } from './api.ts';
4
4
  export { normalizeMessage, normalizeConversation, normalizeSession } from './normalizer.ts';
5
5
  export { markdownToHtml } from './markdown.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACX,WAAW,EACX,QAAQ,EACR,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,eAAe,GACf,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACjG,OAAO,EACN,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,aAAa,GACb,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAG5F,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EACN,WAAW,IAAI,oBAAoB,EACnC,KAAK,gBAAgB,GACrB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACN,YAAY,EACZ,KAAK,iBAAiB,GACtB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACN,SAAS,EACT,KAAK,cAAc,GACnB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACN,WAAW,EACX,KAAK,gBAAgB,EACrB,KAAK,SAAS,GACd,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACN,eAAe,EACf,KAAK,oBAAoB,GACzB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACN,eAAe,EACf,KAAK,oBAAoB,GACzB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACN,aAAa,EACb,KAAK,kBAAkB,GACvB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACN,gBAAgB,EAChB,KAAK,qBAAqB,GAC1B,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EACN,OAAO,EACP,KAAK,cAAc,EACnB,KAAK,aAAa,GAClB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACX,WAAW,EACX,QAAQ,EACR,cAAc,EACd,eAAe,EACf,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,UAAU,EACV,eAAe,GACf,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACjH,OAAO,EACN,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,aAAa,GACb,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAG5F,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EACN,WAAW,IAAI,oBAAoB,EACnC,KAAK,gBAAgB,GACrB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACN,YAAY,EACZ,KAAK,iBAAiB,GACtB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACN,SAAS,EACT,KAAK,cAAc,GACnB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACN,WAAW,EACX,KAAK,gBAAgB,EACrB,KAAK,SAAS,GACd,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACN,eAAe,EACf,KAAK,oBAAoB,GACzB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACN,eAAe,EACf,KAAK,oBAAoB,GACzB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACN,aAAa,EACb,KAAK,kBAAkB,GACvB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACN,gBAAgB,EAChB,KAAK,qBAAqB,GAC1B,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EACN,OAAO,EACP,KAAK,cAAc,EACnB,KAAK,aAAa,GAClB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../src/normalizer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAY,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAO7D;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAsE5E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,WAAW,EAAE,CAEtE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,WAAW,CAQ7D"}
1
+ {"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../src/normalizer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAA6B,MAAM,oBAAoB,CAAC;AACjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAiB,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAO5E;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CA4E5E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,WAAW,EAAE,CAEtE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,WAAW,CAQ7D"}
@@ -63,9 +63,14 @@ export function normalizeMessage(raw, index) {
63
63
  const message = {
64
64
  id: generateId(),
65
65
  role: raw.role === 'user' ? 'user' : 'assistant',
66
- content: raw.content,
66
+ content: extractTextContent(raw.content),
67
67
  timestamp,
68
68
  };
69
+ // Extract attachments from metadata (user uploads and tool-produced media).
70
+ const attachments = normalizeAttachments(raw);
71
+ if (attachments.length > 0) {
72
+ message.attachments = attachments;
73
+ }
69
74
  // Assistant messages may carry tool_calls at the top level
70
75
  if (raw.role === 'assistant' && raw.tool_calls?.length) {
71
76
  message.toolCalls = raw.tool_calls.map((tc, i) => ({
@@ -94,3 +99,81 @@ export function normalizeSession(raw) {
94
99
  messageCount: raw.message_count,
95
100
  };
96
101
  }
102
+ /**
103
+ * Extract text content from a message that may be multi-modal.
104
+ *
105
+ * When the backend sends multi-modal content, `content` is an array of
106
+ * content blocks. We extract the text portion for display.
107
+ */
108
+ function extractTextContent(content) {
109
+ if (typeof content === 'string')
110
+ return content;
111
+ if (Array.isArray(content)) {
112
+ const textParts = content
113
+ .filter((block) => block.type === 'text' && typeof block.text === 'string')
114
+ .map((block) => block.text);
115
+ return textParts.join('\n');
116
+ }
117
+ return '';
118
+ }
119
+ /**
120
+ * Normalize a single raw attachment into a MediaAttachment.
121
+ */
122
+ function normalizeRawAttachment(raw) {
123
+ if (!raw.url)
124
+ return null;
125
+ const mimeType = raw.mime_type ?? '';
126
+ let type = 'file';
127
+ if (raw.type === 'image' || raw.type === 'video' || raw.type === 'file') {
128
+ type = raw.type;
129
+ }
130
+ else if (mimeType.startsWith('image/')) {
131
+ type = 'image';
132
+ }
133
+ else if (mimeType.startsWith('video/')) {
134
+ type = 'video';
135
+ }
136
+ const attachment = {
137
+ type,
138
+ url: raw.url,
139
+ };
140
+ if (raw.alt)
141
+ attachment.alt = raw.alt;
142
+ if (raw.filename)
143
+ attachment.filename = raw.filename;
144
+ if (mimeType)
145
+ attachment.mimeType = mimeType;
146
+ if (raw.size)
147
+ attachment.size = raw.size;
148
+ if (raw.media_id)
149
+ attachment.mediaId = raw.media_id;
150
+ if (raw.thumbnail_url)
151
+ attachment.thumbnailUrl = raw.thumbnail_url;
152
+ return attachment;
153
+ }
154
+ /**
155
+ * Extract all attachments from a raw message.
156
+ *
157
+ * Checks metadata.attachments (user uploads) and metadata.media
158
+ * (tool-produced media) for renderable media.
159
+ */
160
+ function normalizeAttachments(raw) {
161
+ const attachments = [];
162
+ // User-uploaded attachments.
163
+ if (raw.metadata?.attachments) {
164
+ for (const rawAtt of raw.metadata.attachments) {
165
+ const att = normalizeRawAttachment(rawAtt);
166
+ if (att)
167
+ attachments.push(att);
168
+ }
169
+ }
170
+ // Tool-produced media.
171
+ if (raw.metadata?.media) {
172
+ for (const rawMedia of raw.metadata.media) {
173
+ const att = normalizeRawAttachment(rawMedia);
174
+ if (att)
175
+ attachments.push(att);
176
+ }
177
+ }
178
+ return attachments;
179
+ }
@@ -9,18 +9,35 @@
9
9
  * A raw message as stored/returned by the backend.
10
10
  * The package normalizes these into ChatMessage before rendering.
11
11
  */
12
+ /**
13
+ * A raw media attachment as returned by the backend.
14
+ */
15
+ export interface RawAttachment {
16
+ type?: string;
17
+ url?: string;
18
+ alt?: string;
19
+ filename?: string;
20
+ mime_type?: string;
21
+ size?: number;
22
+ media_id?: number;
23
+ thumbnail_url?: string;
24
+ }
12
25
  export interface RawMessage {
13
26
  role: 'user' | 'assistant';
14
27
  content: string;
15
28
  metadata?: {
16
29
  timestamp?: string;
17
- type?: 'text' | 'tool_call' | 'tool_result';
30
+ type?: 'text' | 'multimodal' | 'tool_call' | 'tool_result';
18
31
  tool_name?: string;
19
32
  parameters?: Record<string, unknown>;
20
33
  success?: boolean;
21
34
  error?: string;
22
35
  turn?: number;
23
36
  tool_data?: Record<string, unknown>;
37
+ /** Attachments sent with the message. */
38
+ attachments?: RawAttachment[];
39
+ /** Media produced by tool results. */
40
+ media?: RawAttachment[];
24
41
  };
25
42
  tool_calls?: Array<{
26
43
  tool_name: string;
@@ -34,6 +51,13 @@ export interface SendRequest {
34
51
  message: string;
35
52
  session_id?: string;
36
53
  agent_id?: number;
54
+ /** Media attachments to send with the message. */
55
+ attachments?: Array<{
56
+ url?: string;
57
+ media_id?: number;
58
+ mime_type?: string;
59
+ filename?: string;
60
+ }>;
37
61
  }
38
62
  export interface SendResponse {
39
63
  success: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/types/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/types/api.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- export type { MessageRole, ToolCall, ToolResultMeta, ChatMessage, ContentFormat, } from './message.ts';
1
+ export type { MessageRole, ToolCall, ToolResultMeta, MediaAttachment, ChatMessage, ContentFormat, } from './message.ts';
2
2
  export type { ChatSession, ChatAvailability, ChatInitialState, } from './session.ts';
3
- export type { RawMessage, RawSession, SendRequest, SendResponse, ContinueRequest, ContinueResponse, ListSessionsResponse, GetSessionResponse, DeleteSessionResponse, SessionMetadata, } from './api.ts';
3
+ export type { RawAttachment, RawMessage, RawSession, SendRequest, SendResponse, ContinueRequest, ContinueResponse, ListSessionsResponse, GetSessionResponse, DeleteSessionResponse, SessionMetadata, } from './api.ts';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACX,WAAW,EACX,QAAQ,EACR,cAAc,EACd,WAAW,EACX,aAAa,GACb,MAAM,cAAc,CAAC;AAEtB,YAAY,EACX,WAAW,EACX,gBAAgB,EAChB,gBAAgB,GAChB,MAAM,cAAc,CAAC;AAEtB,YAAY,EACX,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,GACf,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACX,WAAW,EACX,QAAQ,EACR,cAAc,EACd,eAAe,EACf,WAAW,EACX,aAAa,GACb,MAAM,cAAc,CAAC;AAEtB,YAAY,EACX,WAAW,EACX,gBAAgB,EAChB,gBAAgB,GAChB,MAAM,cAAc,CAAC;AAEtB,YAAY,EACX,aAAa,EACb,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,GACf,MAAM,UAAU,CAAC"}
@@ -34,6 +34,27 @@ export interface ToolResultMeta {
34
34
  /** Whether the tool call succeeded. */
35
35
  success: boolean;
36
36
  }
37
+ /**
38
+ * A media attachment on a chat message (image, video, or file).
39
+ */
40
+ export interface MediaAttachment {
41
+ /** Media type. */
42
+ type: 'image' | 'video' | 'file';
43
+ /** Public URL of the media. */
44
+ url: string;
45
+ /** Alt text or description. */
46
+ alt?: string;
47
+ /** Original filename. */
48
+ filename?: string;
49
+ /** MIME type (e.g. 'image/jpeg'). */
50
+ mimeType?: string;
51
+ /** File size in bytes. */
52
+ size?: number;
53
+ /** WordPress media library attachment ID. */
54
+ mediaId?: number;
55
+ /** Thumbnail URL for previews. */
56
+ thumbnailUrl?: string;
57
+ }
37
58
  /**
38
59
  * A single message in a chat conversation.
39
60
  */
@@ -50,6 +71,8 @@ export interface ChatMessage {
50
71
  toolCalls?: ToolCall[];
51
72
  /** Tool result metadata (only on tool_result messages). */
52
73
  toolResult?: ToolResultMeta;
74
+ /** Media attachments (images, videos, files) on this message. */
75
+ attachments?: MediaAttachment[];
53
76
  }
54
77
  /**
55
78
  * Content format hint for how message content should be rendered.
@@ -1 +1 @@
1
- {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../src/types/message.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,GAAG,aAAa,CAAC;AAExF;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,+DAA+D;IAC/D,EAAE,EAAE,MAAM,CAAC;IACX,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,yBAAyB;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,IAAI,EAAE,WAAW,CAAC;IAClB,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC"}
1
+ {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../src/types/message.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,GAAG,aAAa,CAAC;AAExF;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,+DAA+D;IAC/D,EAAE,EAAE,MAAM,CAAC;IACX,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,kBAAkB;IAClB,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;IACjC,+BAA+B;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,+BAA+B;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,yBAAyB;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,IAAI,EAAE,WAAW,CAAC;IAClB,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,iEAAiE;IACjE,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;CAChC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@extrachill/chat",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Chat UI components with built-in REST API client. Speaks the standard chat message format natively.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -52,10 +52,13 @@
52
52
  "react-dom": ">=18.0.0"
53
53
  },
54
54
  "devDependencies": {
55
- "typescript": "^5.7.0",
56
55
  "@types/react": "^18.0.0",
57
56
  "@types/react-dom": "^18.0.0",
58
57
  "react": "^18.0.0",
59
- "react-dom": "^18.0.0"
58
+ "react-dom": "^18.0.0",
59
+ "typescript": "^5.7.0"
60
+ },
61
+ "dependencies": {
62
+ "react-markdown": "^10.1.0"
60
63
  }
61
64
  }
package/src/Chat.tsx CHANGED
@@ -52,6 +52,10 @@ export interface ChatProps {
52
52
  showSessions?: boolean;
53
53
  /** Label shown during multi-turn processing. */
54
54
  processingLabel?: (turnCount: number) => string;
55
+ /** Whether to show the attachment button in the input. Defaults to true. */
56
+ allowAttachments?: boolean;
57
+ /** Accepted file types for attachments. Defaults to 'image/*,video/*'. */
58
+ acceptFileTypes?: string;
55
59
  }
56
60
 
57
61
  /**
@@ -95,6 +99,8 @@ export function Chat({
95
99
  className,
96
100
  showSessions = true,
97
101
  processingLabel,
102
+ allowAttachments = true,
103
+ acceptFileTypes,
98
104
  }: ChatProps) {
99
105
  const chat = useChat({
100
106
  basePath,
@@ -149,6 +155,8 @@ export function Chat({
149
155
  onSend={chat.sendMessage}
150
156
  disabled={chat.isLoading}
151
157
  placeholder={placeholder}
158
+ allowAttachments={allowAttachments}
159
+ accept={acceptFileTypes}
152
160
  />
153
161
  </AvailabilityGate>
154
162
  </div>