@coral-ai/chat 0.3.1 → 0.4.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/README.md +7 -10
- package/dist/index.d.ts +489 -87
- package/dist/index.js +1697 -468
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
// src/Chat.tsx
|
|
2
|
+
import { useLayoutEffect as useLayoutEffect3, useRef as useRef7, useState as useState7 } from "react";
|
|
2
3
|
import {
|
|
3
|
-
Caption1,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
makeStyles as makeStyles8,
|
|
4
|
+
Caption1 as Caption14,
|
|
5
|
+
Title3,
|
|
6
|
+
makeStyles as makeStyles10,
|
|
7
7
|
motionTokens
|
|
8
8
|
} from "@fluentui/react-components";
|
|
9
|
+
import { useCoralConfig } from "@coral-ai/react";
|
|
9
10
|
|
|
10
11
|
// src/Chat.Canvas.tsx
|
|
11
12
|
import { useState, useEffect, useLayoutEffect, useRef, useCallback } from "react";
|
|
12
|
-
import { Button, makeStyles, tokens } from "@fluentui/react-components";
|
|
13
|
+
import { Button as Button2, makeStyles as makeStyles2, tokens as tokens2 } from "@fluentui/react-components";
|
|
13
14
|
import { ArrowDown } from "@coral-ai/bundleicons";
|
|
14
15
|
|
|
15
16
|
// src/Chat.Context.tsx
|
|
@@ -19,9 +20,154 @@ function useChatContext() {
|
|
|
19
20
|
return useContext(ChatContext);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
// src/Chat.
|
|
23
|
+
// src/Chat.Error.tsx
|
|
24
|
+
import { Body1, Button, makeStyles, tokens } from "@fluentui/react-components";
|
|
25
|
+
import {
|
|
26
|
+
ErrorCircle20Regular,
|
|
27
|
+
Clock20Regular,
|
|
28
|
+
LockClosed20Regular,
|
|
29
|
+
PlugDisconnected20Regular,
|
|
30
|
+
Warning20Regular
|
|
31
|
+
} from "@fluentui/react-icons";
|
|
23
32
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
33
|
+
var ERROR_CATEGORIES = [
|
|
34
|
+
"auth_billing",
|
|
35
|
+
"rate_limit",
|
|
36
|
+
"overloaded",
|
|
37
|
+
"bad_input",
|
|
38
|
+
"not_found",
|
|
39
|
+
"server_error",
|
|
40
|
+
"unknown"
|
|
41
|
+
];
|
|
42
|
+
function isNormalizedChatError(err) {
|
|
43
|
+
return !!err && typeof err === "object" && typeof err.message === "string" && ERROR_CATEGORIES.includes(err.category);
|
|
44
|
+
}
|
|
45
|
+
function normalizeChatError(err) {
|
|
46
|
+
if (isNormalizedChatError(err)) return err;
|
|
47
|
+
const anyErr = err;
|
|
48
|
+
const rawMessage = String(
|
|
49
|
+
anyErr?.message ?? anyErr?.error?.message ?? err ?? ""
|
|
50
|
+
).toLowerCase();
|
|
51
|
+
const status = Number(anyErr?.status ?? anyErr?.statusCode ?? 0);
|
|
52
|
+
const errorType = String(
|
|
53
|
+
anyErr?.error?.type ?? anyErr?.type ?? anyErr?.code ?? ""
|
|
54
|
+
);
|
|
55
|
+
const errorCode = String(
|
|
56
|
+
anyErr?.error?.code ?? anyErr?.code ?? ""
|
|
57
|
+
);
|
|
58
|
+
if (rawMessage.includes("credit") || rawMessage.includes("billing") || rawMessage.includes("insufficient_quota") || errorCode === "insufficient_quota" || errorType === "PERMISSION_DENIED") {
|
|
59
|
+
return {
|
|
60
|
+
category: "auth_billing",
|
|
61
|
+
message: "API credits exhausted. Add credits in your provider's billing dashboard."
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (errorType === "rate_limit_error" || rawMessage.includes("rate limit") || rawMessage.includes("too many requests")) {
|
|
65
|
+
return {
|
|
66
|
+
category: "rate_limit",
|
|
67
|
+
message: "You're being rate limited. Wait a moment and try again."
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (status === 529 || status === 503 || errorType === "overloaded_error" || errorType === "RESOURCE_EXHAUSTED" || rawMessage.includes("overload") || rawMessage.includes("capacity")) {
|
|
71
|
+
return {
|
|
72
|
+
category: "overloaded",
|
|
73
|
+
message: "The AI service is overloaded right now. Try again in a few seconds."
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (status === 404 || errorType === "NOT_FOUND" || rawMessage.includes("model_not_found") || rawMessage.includes("not found")) {
|
|
77
|
+
return {
|
|
78
|
+
category: "not_found",
|
|
79
|
+
message: "Model not found. Check the model name in your configuration."
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (status === 400 || errorType === "invalid_request_error" || errorType === "INVALID_ARGUMENT") {
|
|
83
|
+
return {
|
|
84
|
+
category: "bad_input",
|
|
85
|
+
message: "The request was invalid. Check your message or API configuration."
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (status >= 500 || errorType === "api_error" || errorType === "INTERNAL") {
|
|
89
|
+
return {
|
|
90
|
+
category: "server_error",
|
|
91
|
+
message: "The AI service hit an internal error. Try again shortly."
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
category: "unknown",
|
|
96
|
+
message: "Something went wrong. Check your connection and try again."
|
|
97
|
+
};
|
|
98
|
+
}
|
|
24
99
|
var useStyles = makeStyles({
|
|
100
|
+
root: {
|
|
101
|
+
display: "flex",
|
|
102
|
+
alignItems: "center",
|
|
103
|
+
justifyContent: "space-between",
|
|
104
|
+
gap: "8px",
|
|
105
|
+
padding: "12px",
|
|
106
|
+
borderRadius: tokens.borderRadiusXLarge,
|
|
107
|
+
backgroundColor: tokens.colorNeutralBackground3,
|
|
108
|
+
border: `1px solid ${tokens.colorNeutralStroke2}`,
|
|
109
|
+
color: tokens.colorNeutralForeground3,
|
|
110
|
+
width: "100%",
|
|
111
|
+
boxSizing: "border-box",
|
|
112
|
+
margin: "8px 0"
|
|
113
|
+
},
|
|
114
|
+
left: {
|
|
115
|
+
display: "flex",
|
|
116
|
+
alignItems: "center",
|
|
117
|
+
gap: "8px",
|
|
118
|
+
minWidth: 0
|
|
119
|
+
},
|
|
120
|
+
message: {
|
|
121
|
+
fontFamily: "var(--fontFamilyMonospace)"
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
var categoryIcon = {
|
|
125
|
+
auth_billing: /* @__PURE__ */ jsx(LockClosed20Regular, {}),
|
|
126
|
+
rate_limit: /* @__PURE__ */ jsx(Clock20Regular, {}),
|
|
127
|
+
overloaded: /* @__PURE__ */ jsx(PlugDisconnected20Regular, {}),
|
|
128
|
+
bad_input: /* @__PURE__ */ jsx(Warning20Regular, {}),
|
|
129
|
+
not_found: /* @__PURE__ */ jsx(ErrorCircle20Regular, {}),
|
|
130
|
+
server_error: /* @__PURE__ */ jsx(ErrorCircle20Regular, {}),
|
|
131
|
+
unknown: /* @__PURE__ */ jsx(Warning20Regular, {})
|
|
132
|
+
};
|
|
133
|
+
var ChatError = ({ error, onRetry }) => {
|
|
134
|
+
const styles = useStyles();
|
|
135
|
+
const { category, message } = normalizeChatError(error);
|
|
136
|
+
return /* @__PURE__ */ jsxs("div", { className: styles.root, children: [
|
|
137
|
+
/* @__PURE__ */ jsxs("span", { className: styles.left, children: [
|
|
138
|
+
categoryIcon[category],
|
|
139
|
+
/* @__PURE__ */ jsx(Body1, { className: styles.message, children: message })
|
|
140
|
+
] }),
|
|
141
|
+
onRetry && /* @__PURE__ */ jsx(
|
|
142
|
+
Button,
|
|
143
|
+
{
|
|
144
|
+
size: "small",
|
|
145
|
+
appearance: "primary",
|
|
146
|
+
onClick: onRetry,
|
|
147
|
+
children: "Retry"
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
] });
|
|
151
|
+
};
|
|
152
|
+
var Chat_Error_default = ChatError;
|
|
153
|
+
|
|
154
|
+
// src/fileToBase64.ts
|
|
155
|
+
function fileToBase64(file) {
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
const reader = new FileReader();
|
|
158
|
+
reader.onload = () => {
|
|
159
|
+
const dataUrl = reader.result;
|
|
160
|
+
const comma = dataUrl.indexOf(",");
|
|
161
|
+
resolve(comma === -1 ? dataUrl : dataUrl.slice(comma + 1));
|
|
162
|
+
};
|
|
163
|
+
reader.onerror = () => reject(reader.error ?? new Error("FileReader failed"));
|
|
164
|
+
reader.readAsDataURL(file);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/Chat.Canvas.tsx
|
|
169
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
170
|
+
var useStyles2 = makeStyles2({
|
|
25
171
|
root: {
|
|
26
172
|
width: "100%",
|
|
27
173
|
display: "flex",
|
|
@@ -36,7 +182,7 @@ var useStyles = makeStyles({
|
|
|
36
182
|
transform: "translateX(-50%)",
|
|
37
183
|
zIndex: 1e3,
|
|
38
184
|
cursor: "pointer",
|
|
39
|
-
boxShadow:
|
|
185
|
+
boxShadow: tokens2.shadow8,
|
|
40
186
|
marginBottom: "48px",
|
|
41
187
|
backgroundColor: "var(--colorNeutralBackgroundAlpha2)",
|
|
42
188
|
backdropFilter: "blur(40px) saturate(150%)"
|
|
@@ -51,9 +197,54 @@ function isRateLimitError(err) {
|
|
|
51
197
|
const msg = String(e?.message ?? "").toLowerCase();
|
|
52
198
|
return status === 429 || msg.includes("rate limit") || msg.includes("too many requests");
|
|
53
199
|
}
|
|
200
|
+
async function* wrapWithStatusChunk(taskType, reply) {
|
|
201
|
+
yield { type: "status", name: taskType };
|
|
202
|
+
if (reply) yield reply;
|
|
203
|
+
}
|
|
204
|
+
async function* parseStream(res) {
|
|
205
|
+
const reader = res.body?.getReader();
|
|
206
|
+
if (!reader) return;
|
|
207
|
+
const decoder = new TextDecoder();
|
|
208
|
+
let buffer = "";
|
|
209
|
+
while (true) {
|
|
210
|
+
const { value, done } = await reader.read();
|
|
211
|
+
if (done) break;
|
|
212
|
+
buffer += decoder.decode(value, { stream: true });
|
|
213
|
+
const lines = buffer.split("\n");
|
|
214
|
+
buffer = lines.pop() ?? "";
|
|
215
|
+
for (const raw of lines) {
|
|
216
|
+
const line = raw.startsWith("data:") ? raw.slice(5).trim() : raw.trim();
|
|
217
|
+
if (!line || line === "[DONE]") continue;
|
|
218
|
+
try {
|
|
219
|
+
const parsed = JSON.parse(line);
|
|
220
|
+
if (typeof parsed === "string") {
|
|
221
|
+
yield parsed;
|
|
222
|
+
} else if (parsed && typeof parsed === "object" && "type" in parsed) {
|
|
223
|
+
yield parsed;
|
|
224
|
+
} else if (parsed && typeof parsed === "object" && typeof parsed.reply === "string") {
|
|
225
|
+
if (typeof parsed.type === "string") {
|
|
226
|
+
yield { type: "status", name: parsed.type };
|
|
227
|
+
}
|
|
228
|
+
yield parsed.reply;
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
yield line;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (buffer.trim()) {
|
|
236
|
+
try {
|
|
237
|
+
const parsed = JSON.parse(buffer);
|
|
238
|
+
if (typeof parsed === "string") yield parsed;
|
|
239
|
+
else if (parsed && typeof parsed === "object" && "type" in parsed) yield parsed;
|
|
240
|
+
} catch {
|
|
241
|
+
yield buffer;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
54
245
|
var ChatCanvas = ({
|
|
55
|
-
|
|
56
|
-
|
|
246
|
+
api,
|
|
247
|
+
allowedAttachments,
|
|
57
248
|
conversationId,
|
|
58
249
|
initialMessages,
|
|
59
250
|
systemPrompt,
|
|
@@ -66,20 +257,67 @@ var ChatCanvas = ({
|
|
|
66
257
|
onStop,
|
|
67
258
|
children
|
|
68
259
|
}) => {
|
|
69
|
-
const styles =
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
260
|
+
const styles = useStyles2();
|
|
261
|
+
const apiHandler = api ? async function(input2, history, files) {
|
|
262
|
+
const attachments2 = await Promise.all(
|
|
263
|
+
files.map(async (file) => ({
|
|
264
|
+
name: file.name,
|
|
265
|
+
mediaType: file.type,
|
|
266
|
+
data: await fileToBase64(file)
|
|
267
|
+
}))
|
|
268
|
+
);
|
|
269
|
+
const res = await fetch(api, {
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers: { "Content-Type": "application/json" },
|
|
272
|
+
body: JSON.stringify({
|
|
273
|
+
text: input2,
|
|
274
|
+
history,
|
|
275
|
+
attachments: attachments2
|
|
276
|
+
})
|
|
277
|
+
});
|
|
278
|
+
if (!res.ok) {
|
|
279
|
+
const text = await res.text().catch(() => "");
|
|
280
|
+
throw Object.assign(
|
|
281
|
+
new Error(`Chat API request failed (${res.status}): ${text || res.statusText}`),
|
|
282
|
+
{ status: res.status }
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
286
|
+
if (contentType.includes("text/event-stream") || contentType.includes("application/x-ndjson")) {
|
|
287
|
+
return parseStream(res);
|
|
288
|
+
}
|
|
289
|
+
const data = await res.json();
|
|
290
|
+
if (data && typeof data === "object") {
|
|
291
|
+
if (typeof data.type === "string") {
|
|
292
|
+
return wrapWithStatusChunk(data.type, String(data.reply ?? ""));
|
|
293
|
+
}
|
|
294
|
+
if (typeof data.reply === "string") return data.reply;
|
|
295
|
+
}
|
|
296
|
+
return typeof data === "string" ? data : "";
|
|
297
|
+
} : void 0;
|
|
298
|
+
const resolvedHandler = onSendMessage ?? apiHandler;
|
|
299
|
+
const [messages, setMessages] = useState(
|
|
300
|
+
() => (initialMessages ?? []).map((m) => m.id ? m : { ...m, id: crypto.randomUUID() })
|
|
301
|
+
);
|
|
73
302
|
const [input, setInput] = useState("");
|
|
74
303
|
const [isTyping, setIsTyping] = useState(false);
|
|
75
304
|
const [isRetrying, setIsRetrying] = useState(false);
|
|
76
|
-
const [chatError, setChatError] = useState(null);
|
|
77
305
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
78
306
|
const [inputHeight, setInputHeightState] = useState(0);
|
|
79
307
|
const [isInterrupted, setIsInterrupted] = useState(false);
|
|
80
308
|
const [streamingReasoning, setStreamingReasoning] = useState("");
|
|
81
|
-
const [
|
|
309
|
+
const [streamingStatus, setStreamingStatus] = useState();
|
|
82
310
|
const [attachments, setAttachments] = useState([]);
|
|
311
|
+
const [awaitingFreeformApproval, setAwaitingFreeformApproval] = useState(null);
|
|
312
|
+
const pendingApproval = (() => {
|
|
313
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
314
|
+
const m = messages[i];
|
|
315
|
+
if (m.role !== "assistant") continue;
|
|
316
|
+
if (m.approval && !m.approval.answer) return m.approval;
|
|
317
|
+
if (m.approval && m.approval.answer) return null;
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
})();
|
|
83
321
|
const reasoningRef = useRef("");
|
|
84
322
|
const lastUserInputRef = useRef("");
|
|
85
323
|
const hasEverSentRef = useRef(false);
|
|
@@ -92,7 +330,9 @@ var ChatCanvas = ({
|
|
|
92
330
|
useEffect(() => {
|
|
93
331
|
setMessages((current) => {
|
|
94
332
|
if ((initialMessages?.length ?? 0) > 0 || current.length === 0) {
|
|
95
|
-
return initialMessages ?? []
|
|
333
|
+
return (initialMessages ?? []).map(
|
|
334
|
+
(m) => m.id ? m : { ...m, id: crypto.randomUUID() }
|
|
335
|
+
);
|
|
96
336
|
}
|
|
97
337
|
return current;
|
|
98
338
|
});
|
|
@@ -159,40 +399,18 @@ var ChatCanvas = ({
|
|
|
159
399
|
const removeAttachment = (id) => {
|
|
160
400
|
setAttachments((prev) => prev.filter((a) => a.id !== id));
|
|
161
401
|
};
|
|
162
|
-
const
|
|
163
|
-
const textToSend = overrideInput ?? input;
|
|
164
|
-
if (!textToSend.trim()) return;
|
|
165
|
-
hasEverSentRef.current = true;
|
|
402
|
+
const runHandler = async (textToSend, updatedMessages, snapshotAttachments) => {
|
|
166
403
|
const sessionAtSend = conversationId;
|
|
167
|
-
const baseMessages = overrideInput ? messages.filter((_, i) => i < messages.length - 2) : messages;
|
|
168
|
-
const snapshotAttachments = attachments;
|
|
169
|
-
const attachmentMeta = snapshotAttachments.map((a) => ({
|
|
170
|
-
name: a.file.name,
|
|
171
|
-
type: a.file.type,
|
|
172
|
-
preview: a.preview
|
|
173
|
-
}));
|
|
174
|
-
const updatedMessages = [
|
|
175
|
-
...baseMessages,
|
|
176
|
-
{
|
|
177
|
-
id: crypto.randomUUID(),
|
|
178
|
-
role: "user",
|
|
179
|
-
content: textToSend.replace(/^[\n\r]+|[\n\r]+$/g, ""),
|
|
180
|
-
...attachmentMeta.length > 0 ? { metadata: { attachments: attachmentMeta } } : {}
|
|
181
|
-
}
|
|
182
|
-
];
|
|
183
404
|
lastUserInputRef.current = textToSend;
|
|
184
405
|
userScrolledUpRef.current = false;
|
|
185
406
|
setMessages(updatedMessages);
|
|
186
407
|
onMessagesChange?.(updatedMessages, sessionAtSend);
|
|
187
|
-
if (!overrideInput) setInput("");
|
|
188
|
-
setAttachments([]);
|
|
189
408
|
setIsTyping(true);
|
|
190
409
|
setIsInterrupted(false);
|
|
191
|
-
setChatError(null);
|
|
192
410
|
shouldStopRef.current = false;
|
|
193
411
|
reasoningRef.current = "";
|
|
194
412
|
setStreamingReasoning("");
|
|
195
|
-
|
|
413
|
+
setStreamingStatus(void 0);
|
|
196
414
|
const historyForAI = systemPrompt ? [{ role: "system", content: systemPrompt }, ...updatedMessages] : updatedMessages;
|
|
197
415
|
const MAX_RETRIES = 3;
|
|
198
416
|
try {
|
|
@@ -208,6 +426,8 @@ var ChatCanvas = ({
|
|
|
208
426
|
let assistantContent = "";
|
|
209
427
|
let rafId = null;
|
|
210
428
|
let pendingContent = "";
|
|
429
|
+
let streamedApproval;
|
|
430
|
+
let streamedTodos;
|
|
211
431
|
for await (const chunk of response) {
|
|
212
432
|
if (shouldStopRef.current) break;
|
|
213
433
|
if (sessionAtSend !== void 0 && currentConversationIdRef.current !== sessionAtSend) break;
|
|
@@ -217,9 +437,13 @@ var ChatCanvas = ({
|
|
|
217
437
|
if (rafId === null) {
|
|
218
438
|
rafId = requestAnimationFrame(() => {
|
|
219
439
|
const content = pendingContent;
|
|
220
|
-
|
|
440
|
+
let nextMsg = { id: assistantId, role: "assistant", content };
|
|
221
441
|
setMessages((prev) => {
|
|
222
442
|
const updated = [...prev];
|
|
443
|
+
const last = updated[updated.length - 1];
|
|
444
|
+
if (last && last.id === assistantId) {
|
|
445
|
+
nextMsg = { ...last, content };
|
|
446
|
+
}
|
|
223
447
|
updated[updated.length - 1] = nextMsg;
|
|
224
448
|
return updated;
|
|
225
449
|
});
|
|
@@ -230,8 +454,28 @@ var ChatCanvas = ({
|
|
|
230
454
|
} else if (chunk.type === "reasoning") {
|
|
231
455
|
reasoningRef.current += chunk.content;
|
|
232
456
|
setStreamingReasoning(reasoningRef.current);
|
|
233
|
-
} else if (chunk.type === "
|
|
234
|
-
|
|
457
|
+
} else if (chunk.type === "status") {
|
|
458
|
+
setStreamingStatus(chunk.name);
|
|
459
|
+
} else if (chunk.type === "approval") {
|
|
460
|
+
streamedApproval = chunk.approval;
|
|
461
|
+
setMessages((prev) => {
|
|
462
|
+
const updated = [...prev];
|
|
463
|
+
const last = updated[updated.length - 1];
|
|
464
|
+
if (last && last.id === assistantId) {
|
|
465
|
+
updated[updated.length - 1] = { ...last, approval: chunk.approval };
|
|
466
|
+
}
|
|
467
|
+
return updated;
|
|
468
|
+
});
|
|
469
|
+
} else if (chunk.type === "todos") {
|
|
470
|
+
streamedTodos = chunk.todos;
|
|
471
|
+
setMessages((prev) => {
|
|
472
|
+
const updated = [...prev];
|
|
473
|
+
const last = updated[updated.length - 1];
|
|
474
|
+
if (last && last.id === assistantId) {
|
|
475
|
+
updated[updated.length - 1] = { ...last, todos: chunk.todos };
|
|
476
|
+
}
|
|
477
|
+
return updated;
|
|
478
|
+
});
|
|
235
479
|
}
|
|
236
480
|
}
|
|
237
481
|
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
@@ -243,7 +487,9 @@ var ChatCanvas = ({
|
|
|
243
487
|
id: assistantId,
|
|
244
488
|
role: "assistant",
|
|
245
489
|
content: assistantContent,
|
|
246
|
-
...reasoningRef.current ? { metadata: { reasoning: reasoningRef.current } } : {}
|
|
490
|
+
...reasoningRef.current ? { metadata: { reasoning: reasoningRef.current } } : {},
|
|
491
|
+
...streamedApproval ? { approval: streamedApproval } : {},
|
|
492
|
+
...streamedTodos ? { todos: streamedTodos } : {}
|
|
247
493
|
};
|
|
248
494
|
const finalMessages = [...updatedMessages, finalMsg];
|
|
249
495
|
setMessages(finalMessages);
|
|
@@ -262,7 +508,8 @@ var ChatCanvas = ({
|
|
|
262
508
|
role: "assistant",
|
|
263
509
|
content: response.content,
|
|
264
510
|
type: response.type,
|
|
265
|
-
metadata: response.metadata
|
|
511
|
+
metadata: response.metadata,
|
|
512
|
+
todos: response.todos
|
|
266
513
|
};
|
|
267
514
|
const finalMessages = [...updatedMessages, assistantMsg];
|
|
268
515
|
setMessages(finalMessages);
|
|
@@ -282,17 +529,148 @@ var ChatCanvas = ({
|
|
|
282
529
|
}
|
|
283
530
|
} catch (err) {
|
|
284
531
|
onError?.(err);
|
|
285
|
-
|
|
532
|
+
if (sessionAtSend === void 0 || currentConversationIdRef.current === sessionAtSend) {
|
|
533
|
+
const erroredMessages = [
|
|
534
|
+
...updatedMessages,
|
|
535
|
+
{
|
|
536
|
+
id: crypto.randomUUID(),
|
|
537
|
+
role: "assistant",
|
|
538
|
+
content: "",
|
|
539
|
+
error: normalizeChatError(err)
|
|
540
|
+
}
|
|
541
|
+
];
|
|
542
|
+
setMessages(erroredMessages);
|
|
543
|
+
onMessagesChange?.(erroredMessages, sessionAtSend);
|
|
544
|
+
}
|
|
286
545
|
} finally {
|
|
287
546
|
setIsRetrying(false);
|
|
288
547
|
setIsTyping(false);
|
|
289
548
|
}
|
|
290
549
|
};
|
|
291
|
-
|
|
550
|
+
const resolveApproval = (approvalId, optionId) => {
|
|
551
|
+
const target = messages.find(
|
|
552
|
+
(m) => m.role === "assistant" && m.approval?.id === approvalId
|
|
553
|
+
);
|
|
554
|
+
if (!target || !target.approval) return;
|
|
555
|
+
if (target.approval.answer) return;
|
|
556
|
+
const option = target.approval.options.find((o) => o.id === optionId);
|
|
557
|
+
if (!option) return;
|
|
558
|
+
if (option.freeform) {
|
|
559
|
+
setAwaitingFreeformApproval({ approvalId, optionId });
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
const updated = messages.map(
|
|
563
|
+
(m) => m.role === "assistant" && m.approval?.id === approvalId ? { ...m, approval: { ...m.approval, answer: { optionId } } } : m
|
|
564
|
+
);
|
|
565
|
+
const replyText = option.label;
|
|
566
|
+
const userMsg = {
|
|
567
|
+
id: crypto.randomUUID(),
|
|
568
|
+
role: "user",
|
|
569
|
+
type: "approval_response",
|
|
570
|
+
content: replyText,
|
|
571
|
+
metadata: { approvalId, optionId, label: option.label }
|
|
572
|
+
};
|
|
573
|
+
const next = [...updated, userMsg];
|
|
574
|
+
void runHandler(replyText, next, []);
|
|
575
|
+
};
|
|
576
|
+
const sendMessage = async (overrideInput) => {
|
|
577
|
+
const textToSend = overrideInput ?? input;
|
|
578
|
+
if (!textToSend.trim()) return;
|
|
579
|
+
if (awaitingFreeformApproval) {
|
|
580
|
+
const { approvalId, optionId } = awaitingFreeformApproval;
|
|
581
|
+
const target = messages.find(
|
|
582
|
+
(m) => m.role === "assistant" && m.approval?.id === approvalId
|
|
583
|
+
);
|
|
584
|
+
if (target && target.approval) {
|
|
585
|
+
const option = target.approval.options.find((o) => o.id === optionId);
|
|
586
|
+
const updated = messages.map(
|
|
587
|
+
(m) => m.role === "assistant" && m.approval?.id === approvalId ? { ...m, approval: { ...m.approval, answer: { optionId, text: textToSend } } } : m
|
|
588
|
+
);
|
|
589
|
+
const userMsg = {
|
|
590
|
+
id: crypto.randomUUID(),
|
|
591
|
+
role: "user",
|
|
592
|
+
type: "approval_response",
|
|
593
|
+
content: textToSend.replace(/^[\n\r]+|[\n\r]+$/g, ""),
|
|
594
|
+
metadata: { approvalId, optionId, label: option?.label, text: textToSend }
|
|
595
|
+
};
|
|
596
|
+
const next = [...updated, userMsg];
|
|
597
|
+
if (!overrideInput) setInput("");
|
|
598
|
+
setAwaitingFreeformApproval(null);
|
|
599
|
+
hasEverSentRef.current = true;
|
|
600
|
+
await runHandler(textToSend, next, []);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
setAwaitingFreeformApproval(null);
|
|
604
|
+
}
|
|
605
|
+
hasEverSentRef.current = true;
|
|
606
|
+
const lastMessage = messages[messages.length - 1];
|
|
607
|
+
const baseMessages = overrideInput ? messages.filter((_, i) => i < messages.length - 2) : lastMessage?.role === "assistant" && lastMessage.error ? messages.slice(0, -1) : messages;
|
|
608
|
+
const snapshotAttachments = attachments;
|
|
609
|
+
const attachmentMeta = snapshotAttachments.map((a) => ({
|
|
610
|
+
name: a.file.name,
|
|
611
|
+
type: a.file.type,
|
|
612
|
+
preview: a.preview
|
|
613
|
+
}));
|
|
614
|
+
const updatedMessages = [
|
|
615
|
+
...baseMessages,
|
|
616
|
+
{
|
|
617
|
+
id: crypto.randomUUID(),
|
|
618
|
+
role: "user",
|
|
619
|
+
content: textToSend.replace(/^[\n\r]+|[\n\r]+$/g, ""),
|
|
620
|
+
...attachmentMeta.length > 0 ? { metadata: { attachments: attachmentMeta } } : {}
|
|
621
|
+
}
|
|
622
|
+
];
|
|
623
|
+
if (!overrideInput) setInput("");
|
|
624
|
+
setAttachments([]);
|
|
625
|
+
await runHandler(textToSend, updatedMessages, snapshotAttachments);
|
|
626
|
+
};
|
|
627
|
+
const resolveRegenerateTarget = (messageId) => {
|
|
628
|
+
if (!messageId) {
|
|
629
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
630
|
+
if (messages[i].role === "user") return i;
|
|
631
|
+
}
|
|
632
|
+
return -1;
|
|
633
|
+
}
|
|
634
|
+
const idx = messages.findIndex((m) => m.id === messageId);
|
|
635
|
+
if (idx === -1) return -1;
|
|
636
|
+
if (messages[idx].role === "user") return idx;
|
|
637
|
+
if (messages[idx].role === "assistant") {
|
|
638
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
639
|
+
if (messages[i].role === "user") return i;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return -1;
|
|
643
|
+
};
|
|
644
|
+
const regenerate = async (messageId) => {
|
|
645
|
+
const userIdx = resolveRegenerateTarget(messageId);
|
|
646
|
+
if (userIdx === -1) return;
|
|
647
|
+
const userMsg = messages[userIdx];
|
|
648
|
+
const baseMessages = messages.slice(0, userIdx + 1);
|
|
649
|
+
await runHandler(userMsg.content, baseMessages, []);
|
|
650
|
+
};
|
|
651
|
+
const editMessage = async (messageId, newContent) => {
|
|
652
|
+
const idx = messages.findIndex((m) => m.id === messageId);
|
|
653
|
+
if (idx === -1) return;
|
|
654
|
+
if (messages[idx].role !== "user") return;
|
|
655
|
+
const trimmed = newContent.replace(/^[\n\r]+|[\n\r]+$/g, "");
|
|
656
|
+
if (!trimmed.trim()) return;
|
|
657
|
+
const editedMessage = {
|
|
658
|
+
...messages[idx],
|
|
659
|
+
content: trimmed
|
|
660
|
+
};
|
|
661
|
+
const baseMessages = [
|
|
662
|
+
...messages.slice(0, idx),
|
|
663
|
+
editedMessage
|
|
664
|
+
];
|
|
665
|
+
await runHandler(trimmed, baseMessages, []);
|
|
666
|
+
};
|
|
667
|
+
return /* @__PURE__ */ jsx2(
|
|
292
668
|
ChatContext.Provider,
|
|
293
669
|
{
|
|
294
670
|
value: {
|
|
295
671
|
sendMessage,
|
|
672
|
+
regenerate,
|
|
673
|
+
editMessage,
|
|
296
674
|
scrollToBottom,
|
|
297
675
|
handleCopy,
|
|
298
676
|
messages,
|
|
@@ -303,9 +681,8 @@ var ChatCanvas = ({
|
|
|
303
681
|
isRetrying,
|
|
304
682
|
isInterrupted,
|
|
305
683
|
streamingReasoning,
|
|
306
|
-
|
|
684
|
+
streamingStatus,
|
|
307
685
|
thinkingStatus,
|
|
308
|
-
chatError,
|
|
309
686
|
showScrollButton,
|
|
310
687
|
setInputHeight: setInputHeightState,
|
|
311
688
|
messagesContainerRef,
|
|
@@ -317,12 +694,16 @@ var ChatCanvas = ({
|
|
|
317
694
|
onStop,
|
|
318
695
|
attachments,
|
|
319
696
|
addAttachments,
|
|
320
|
-
removeAttachment
|
|
697
|
+
removeAttachment,
|
|
698
|
+
allowedAttachments,
|
|
699
|
+
pendingApproval,
|
|
700
|
+
awaitingFreeformApproval,
|
|
701
|
+
resolveApproval
|
|
321
702
|
},
|
|
322
|
-
children: /* @__PURE__ */
|
|
703
|
+
children: /* @__PURE__ */ jsxs2("div", { className: styles.root, children: [
|
|
323
704
|
children,
|
|
324
|
-
showScrollButton && /* @__PURE__ */
|
|
325
|
-
|
|
705
|
+
showScrollButton && /* @__PURE__ */ jsx2(
|
|
706
|
+
Button2,
|
|
326
707
|
{
|
|
327
708
|
onClick: scrollToBottom,
|
|
328
709
|
className: styles.scrollToBottom,
|
|
@@ -330,10 +711,10 @@ var ChatCanvas = ({
|
|
|
330
711
|
style: {
|
|
331
712
|
bottom: inputHeight + 16,
|
|
332
713
|
backgroundColor: "transparent",
|
|
333
|
-
border: `1px solid ${
|
|
714
|
+
border: `1px solid ${tokens2.colorNeutralStroke3}`,
|
|
334
715
|
backdropFilter: "saturate(180%) blur(16px)"
|
|
335
716
|
},
|
|
336
|
-
icon: /* @__PURE__ */
|
|
717
|
+
icon: /* @__PURE__ */ jsx2(ArrowDown, { style: { fontSize: "16px" } })
|
|
337
718
|
}
|
|
338
719
|
)
|
|
339
720
|
] })
|
|
@@ -343,22 +724,34 @@ var ChatCanvas = ({
|
|
|
343
724
|
var Chat_Canvas_default = ChatCanvas;
|
|
344
725
|
|
|
345
726
|
// src/Chat.Messages.tsx
|
|
346
|
-
import
|
|
347
|
-
import {
|
|
348
|
-
|
|
349
|
-
|
|
727
|
+
import React5, { useEffect as useEffect4, useLayoutEffect as useLayoutEffect2, useRef as useRef4, useState as useState5 } from "react";
|
|
728
|
+
import {
|
|
729
|
+
Body1 as Body15,
|
|
730
|
+
Button as Button4,
|
|
731
|
+
Caption1 as Caption12,
|
|
732
|
+
makeStyles as makeStyles7,
|
|
733
|
+
tokens as tokens7
|
|
734
|
+
} from "@fluentui/react-components";
|
|
735
|
+
import {
|
|
736
|
+
ArrowClockwise,
|
|
737
|
+
Copy,
|
|
738
|
+
Edit as EditIcon,
|
|
739
|
+
Info
|
|
740
|
+
} from "@coral-ai/bundleicons";
|
|
741
|
+
import {
|
|
742
|
+
CoralMarkdown
|
|
743
|
+
} from "@coral-ai/markdown";
|
|
350
744
|
|
|
351
745
|
// src/Chat.ThinkingIndicator.tsx
|
|
352
746
|
import { useState as useState2, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
353
|
-
import { makeStyles as
|
|
354
|
-
import { jsx as
|
|
747
|
+
import { makeStyles as makeStyles3, Spinner, tokens as tokens3 } from "@fluentui/react-components";
|
|
748
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
355
749
|
var thinkingMessages = [
|
|
356
750
|
"Generating",
|
|
357
751
|
"Thinking",
|
|
358
752
|
"Optimizing",
|
|
359
753
|
"Processing",
|
|
360
754
|
"Analyzing",
|
|
361
|
-
"Reasoning",
|
|
362
755
|
"Drafting",
|
|
363
756
|
"Evaluating",
|
|
364
757
|
"Computing",
|
|
@@ -377,45 +770,13 @@ var retryingMessages = [
|
|
|
377
770
|
];
|
|
378
771
|
var TYPING_SPEED = 80;
|
|
379
772
|
var HOLD_DURATION = 1300;
|
|
380
|
-
var
|
|
381
|
-
var useStyles2 = makeStyles2({
|
|
773
|
+
var useStyles3 = makeStyles3({
|
|
382
774
|
root: {
|
|
383
775
|
display: "flex",
|
|
384
776
|
alignItems: "center",
|
|
385
777
|
gap: "8px",
|
|
386
778
|
width: "100%",
|
|
387
|
-
color:
|
|
388
|
-
// fontSize: "14px",
|
|
389
|
-
},
|
|
390
|
-
icon: {
|
|
391
|
-
width: "28px",
|
|
392
|
-
height: "28px",
|
|
393
|
-
flexShrink: 0,
|
|
394
|
-
border: `3px solid ${SPINNER_BG}`,
|
|
395
|
-
boxSizing: "border-box",
|
|
396
|
-
backgroundColor: "${SPINNER_BG}",
|
|
397
|
-
opacity: 0.8,
|
|
398
|
-
backgroundImage: [
|
|
399
|
-
`radial-gradient(farthest-side, ${tokens2.colorNeutralForeground1} 98%, ${SPINNER_BG})`,
|
|
400
|
-
`radial-gradient(farthest-side, ${tokens2.colorNeutralForeground1} 98%, ${SPINNER_BG})`,
|
|
401
|
-
`radial-gradient(farthest-side, ${tokens2.colorNeutralForeground1} 98%, ${SPINNER_BG})`,
|
|
402
|
-
`radial-gradient(farthest-side, ${tokens2.colorNeutralForeground1} 98%, ${SPINNER_BG})`,
|
|
403
|
-
`radial-gradient(farthest-side, ${tokens2.colorNeutralForeground1} 98%, ${SPINNER_BG})`,
|
|
404
|
-
`linear-gradient(${tokens2.colorNeutralForeground1} 0 0)`,
|
|
405
|
-
`linear-gradient(${tokens2.colorNeutralForeground1} 0 0)`
|
|
406
|
-
].join(", "),
|
|
407
|
-
backgroundSize: "7px 7px, 7px 7px, 7px 7px, 7px 7px, 7px 7px, 100% 3px, 3px 100%",
|
|
408
|
-
backgroundPosition: "50%, 50%, 50%, 50%, 50%, 50%, 50%",
|
|
409
|
-
backgroundRepeat: "no-repeat",
|
|
410
|
-
filter: "blur(2px) contrast(10)",
|
|
411
|
-
animationName: {
|
|
412
|
-
"100%": {
|
|
413
|
-
backgroundPosition: "50% -7px, -7px 50%, 22px 50%, 50% 22px, 50%, 50%, 50%"
|
|
414
|
-
}
|
|
415
|
-
},
|
|
416
|
-
animationDuration: "0.8s",
|
|
417
|
-
animationIterationCount: "infinite",
|
|
418
|
-
animationTimingFunction: "ease"
|
|
779
|
+
color: tokens3.colorNeutralForeground3
|
|
419
780
|
},
|
|
420
781
|
text: {
|
|
421
782
|
// fontFamily: tokens.fontFamilyBase,
|
|
@@ -428,7 +789,7 @@ var useStyles2 = makeStyles2({
|
|
|
428
789
|
display: "inline-block",
|
|
429
790
|
width: "6px",
|
|
430
791
|
height: "1em",
|
|
431
|
-
backgroundColor:
|
|
792
|
+
backgroundColor: tokens3.colorNeutralForeground3,
|
|
432
793
|
marginLeft: "1px",
|
|
433
794
|
verticalAlign: "text-bottom",
|
|
434
795
|
animationName: {
|
|
@@ -440,14 +801,14 @@ var useStyles2 = makeStyles2({
|
|
|
440
801
|
animationIterationCount: "infinite"
|
|
441
802
|
}
|
|
442
803
|
});
|
|
443
|
-
var ThinkingIndicator = ({ isRetrying = false, status }) => {
|
|
444
|
-
const styles =
|
|
804
|
+
var ThinkingIndicator = ({ isRetrying = false, status, messages }) => {
|
|
805
|
+
const styles = useStyles3();
|
|
445
806
|
const [displayText, setDisplayText] = useState2("");
|
|
446
807
|
const timerRef = useRef2(void 0);
|
|
447
808
|
const msgIdxRef = useRef2(0);
|
|
448
809
|
const charIdxRef = useRef2(0);
|
|
449
810
|
useEffect2(() => {
|
|
450
|
-
const pool = isRetrying ? retryingMessages : thinkingMessages;
|
|
811
|
+
const pool = isRetrying ? retryingMessages : messages && messages.length > 0 ? messages : thinkingMessages;
|
|
451
812
|
msgIdxRef.current = 0;
|
|
452
813
|
charIdxRef.current = 0;
|
|
453
814
|
const tick = () => {
|
|
@@ -466,32 +827,58 @@ var ThinkingIndicator = ({ isRetrying = false, status }) => {
|
|
|
466
827
|
};
|
|
467
828
|
tick();
|
|
468
829
|
return () => clearTimeout(timerRef.current);
|
|
469
|
-
}, [isRetrying]);
|
|
470
|
-
return /* @__PURE__ */
|
|
471
|
-
/* @__PURE__ */
|
|
472
|
-
/* @__PURE__ */
|
|
830
|
+
}, [isRetrying, messages]);
|
|
831
|
+
return /* @__PURE__ */ jsxs3("div", { className: styles.root, children: [
|
|
832
|
+
/* @__PURE__ */ jsx3(Spinner, { size: "tiny" }),
|
|
833
|
+
/* @__PURE__ */ jsxs3("span", { className: styles.text, children: [
|
|
473
834
|
status ?? displayText,
|
|
474
|
-
/* @__PURE__ */
|
|
835
|
+
/* @__PURE__ */ jsx3("span", { className: styles.cursor })
|
|
475
836
|
] })
|
|
476
837
|
] });
|
|
477
838
|
};
|
|
478
839
|
var Chat_ThinkingIndicator_default = ThinkingIndicator;
|
|
479
840
|
|
|
841
|
+
// src/Chat.TaskMessages.ts
|
|
842
|
+
var DEFAULT_TASK_MESSAGES = {
|
|
843
|
+
text: [
|
|
844
|
+
"Generating",
|
|
845
|
+
"Thinking",
|
|
846
|
+
"Optimizing",
|
|
847
|
+
"Processing",
|
|
848
|
+
"Analyzing",
|
|
849
|
+
"Drafting",
|
|
850
|
+
"Evaluating",
|
|
851
|
+
"Computing",
|
|
852
|
+
"Synthesizing",
|
|
853
|
+
"Reviewing",
|
|
854
|
+
"Refining"
|
|
855
|
+
],
|
|
856
|
+
photo: ["Looking", "Examining", "Reading image", "Analyzing visuals"],
|
|
857
|
+
file: ["Reading document", "Parsing", "Reviewing", "Scanning"],
|
|
858
|
+
voice: ["Listening", "Transcribing", "Processing audio"]
|
|
859
|
+
};
|
|
860
|
+
function resolveTaskMessages(name) {
|
|
861
|
+
if (name && name in DEFAULT_TASK_MESSAGES) {
|
|
862
|
+
return DEFAULT_TASK_MESSAGES[name];
|
|
863
|
+
}
|
|
864
|
+
return DEFAULT_TASK_MESSAGES.text;
|
|
865
|
+
}
|
|
866
|
+
|
|
480
867
|
// src/Chat.Callout.tsx
|
|
481
|
-
import { Body1, Button as
|
|
868
|
+
import { Body1 as Body12, Button as Button3, makeStyles as makeStyles4, mergeClasses, tokens as tokens4 } from "@fluentui/react-components";
|
|
482
869
|
import { ChatWarning20Regular } from "@fluentui/react-icons";
|
|
483
|
-
import { jsx as
|
|
484
|
-
var
|
|
870
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
871
|
+
var useStyles4 = makeStyles4({
|
|
485
872
|
root: {
|
|
486
873
|
display: "inline-flex",
|
|
487
874
|
alignItems: "center",
|
|
488
875
|
justifyContent: "space-between",
|
|
489
876
|
gap: "8px",
|
|
490
877
|
padding: "12px 12px",
|
|
491
|
-
borderRadius:
|
|
492
|
-
backgroundColor:
|
|
493
|
-
border: `1px solid ${
|
|
494
|
-
color:
|
|
878
|
+
borderRadius: tokens4.borderRadiusXLarge,
|
|
879
|
+
backgroundColor: tokens4.colorNeutralBackground3,
|
|
880
|
+
border: `1px solid ${tokens4.colorNeutralStroke2}`,
|
|
881
|
+
color: tokens4.colorNeutralForeground3,
|
|
495
882
|
width: "100%",
|
|
496
883
|
boxSizing: "border-box",
|
|
497
884
|
margin: "24px 0"
|
|
@@ -509,143 +896,25 @@ var useStyles3 = makeStyles3({
|
|
|
509
896
|
}
|
|
510
897
|
});
|
|
511
898
|
var ChatCallout = ({ message, onRetry, className }) => {
|
|
512
|
-
const styles = useStyles3();
|
|
513
|
-
return /* @__PURE__ */ jsxs3("div", { className: mergeClasses(styles.root, className), children: [
|
|
514
|
-
/* @__PURE__ */ jsxs3("span", { className: styles.left, children: [
|
|
515
|
-
/* @__PURE__ */ jsx3(ChatWarning20Regular, {}),
|
|
516
|
-
/* @__PURE__ */ jsx3(Body1, { className: styles.label, children: message })
|
|
517
|
-
] }),
|
|
518
|
-
onRetry && /* @__PURE__ */ jsx3(
|
|
519
|
-
Button2,
|
|
520
|
-
{
|
|
521
|
-
onClick: onRetry,
|
|
522
|
-
className: styles.retryButton,
|
|
523
|
-
size: "small",
|
|
524
|
-
appearance: "primary",
|
|
525
|
-
children: "Retry"
|
|
526
|
-
}
|
|
527
|
-
)
|
|
528
|
-
] });
|
|
529
|
-
};
|
|
530
|
-
var Chat_Callout_default = ChatCallout;
|
|
531
|
-
|
|
532
|
-
// src/Chat.Error.tsx
|
|
533
|
-
import { Body1 as Body12, Button as Button3, makeStyles as makeStyles4, tokens as tokens4 } from "@fluentui/react-components";
|
|
534
|
-
import {
|
|
535
|
-
ErrorCircle20Regular,
|
|
536
|
-
Clock20Regular,
|
|
537
|
-
LockClosed20Regular,
|
|
538
|
-
PlugDisconnected20Regular,
|
|
539
|
-
Warning20Regular
|
|
540
|
-
} from "@fluentui/react-icons";
|
|
541
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
542
|
-
function normalizeChatError(err) {
|
|
543
|
-
const anyErr = err;
|
|
544
|
-
const rawMessage = String(
|
|
545
|
-
anyErr?.message ?? anyErr?.error?.message ?? err ?? ""
|
|
546
|
-
).toLowerCase();
|
|
547
|
-
const status = Number(anyErr?.status ?? anyErr?.statusCode ?? 0);
|
|
548
|
-
const errorType = String(
|
|
549
|
-
anyErr?.error?.type ?? anyErr?.type ?? anyErr?.code ?? ""
|
|
550
|
-
);
|
|
551
|
-
const errorCode = String(
|
|
552
|
-
anyErr?.error?.code ?? anyErr?.code ?? ""
|
|
553
|
-
);
|
|
554
|
-
if (rawMessage.includes("credit") || rawMessage.includes("billing") || rawMessage.includes("insufficient_quota") || errorCode === "insufficient_quota" || errorType === "PERMISSION_DENIED") {
|
|
555
|
-
return {
|
|
556
|
-
category: "auth_billing",
|
|
557
|
-
message: "API credits exhausted. Add credits in your provider's billing dashboard."
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
if (errorType === "rate_limit_error" || rawMessage.includes("rate limit") || rawMessage.includes("too many requests")) {
|
|
561
|
-
return {
|
|
562
|
-
category: "rate_limit",
|
|
563
|
-
message: "You're being rate limited. Wait a moment and try again."
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
if (status === 529 || status === 503 || errorType === "overloaded_error" || errorType === "RESOURCE_EXHAUSTED" || rawMessage.includes("overload") || rawMessage.includes("capacity")) {
|
|
567
|
-
return {
|
|
568
|
-
category: "overloaded",
|
|
569
|
-
message: "The AI service is overloaded right now. Try again in a few seconds."
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
if (status === 404 || errorType === "NOT_FOUND" || rawMessage.includes("model_not_found") || rawMessage.includes("not found")) {
|
|
573
|
-
return {
|
|
574
|
-
category: "not_found",
|
|
575
|
-
message: "Model not found. Check the model name in your configuration."
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
if (status === 400 || errorType === "invalid_request_error" || errorType === "INVALID_ARGUMENT") {
|
|
579
|
-
return {
|
|
580
|
-
category: "bad_input",
|
|
581
|
-
message: "The request was invalid. Check your message or API configuration."
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
if (status >= 500 || errorType === "api_error" || errorType === "INTERNAL") {
|
|
585
|
-
return {
|
|
586
|
-
category: "server_error",
|
|
587
|
-
message: "The AI service hit an internal error. Try again shortly."
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
return {
|
|
591
|
-
category: "unknown",
|
|
592
|
-
message: "Something went wrong. Check your connection and try again."
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
var useStyles4 = makeStyles4({
|
|
596
|
-
root: {
|
|
597
|
-
display: "flex",
|
|
598
|
-
alignItems: "center",
|
|
599
|
-
justifyContent: "space-between",
|
|
600
|
-
gap: "8px",
|
|
601
|
-
padding: "12px",
|
|
602
|
-
borderRadius: tokens4.borderRadiusXLarge,
|
|
603
|
-
backgroundColor: tokens4.colorNeutralBackground3,
|
|
604
|
-
border: `1px solid ${tokens4.colorNeutralStroke2}`,
|
|
605
|
-
color: tokens4.colorNeutralForeground3,
|
|
606
|
-
width: "100%",
|
|
607
|
-
boxSizing: "border-box",
|
|
608
|
-
margin: "8px 0"
|
|
609
|
-
},
|
|
610
|
-
left: {
|
|
611
|
-
display: "flex",
|
|
612
|
-
alignItems: "center",
|
|
613
|
-
gap: "8px",
|
|
614
|
-
minWidth: 0
|
|
615
|
-
},
|
|
616
|
-
message: {
|
|
617
|
-
fontFamily: "var(--fontFamilyMonospace)"
|
|
618
|
-
}
|
|
619
|
-
});
|
|
620
|
-
var categoryIcon = {
|
|
621
|
-
auth_billing: /* @__PURE__ */ jsx4(LockClosed20Regular, {}),
|
|
622
|
-
rate_limit: /* @__PURE__ */ jsx4(Clock20Regular, {}),
|
|
623
|
-
overloaded: /* @__PURE__ */ jsx4(PlugDisconnected20Regular, {}),
|
|
624
|
-
bad_input: /* @__PURE__ */ jsx4(Warning20Regular, {}),
|
|
625
|
-
not_found: /* @__PURE__ */ jsx4(ErrorCircle20Regular, {}),
|
|
626
|
-
server_error: /* @__PURE__ */ jsx4(ErrorCircle20Regular, {}),
|
|
627
|
-
unknown: /* @__PURE__ */ jsx4(Warning20Regular, {})
|
|
628
|
-
};
|
|
629
|
-
var ChatError = ({ error, onRetry }) => {
|
|
630
899
|
const styles = useStyles4();
|
|
631
|
-
|
|
632
|
-
return /* @__PURE__ */ jsxs4("div", { className: styles.root, children: [
|
|
900
|
+
return /* @__PURE__ */ jsxs4("div", { className: mergeClasses(styles.root, className), children: [
|
|
633
901
|
/* @__PURE__ */ jsxs4("span", { className: styles.left, children: [
|
|
634
|
-
|
|
635
|
-
/* @__PURE__ */ jsx4(Body12, { className: styles.
|
|
902
|
+
/* @__PURE__ */ jsx4(ChatWarning20Regular, {}),
|
|
903
|
+
/* @__PURE__ */ jsx4(Body12, { className: styles.label, children: message })
|
|
636
904
|
] }),
|
|
637
905
|
onRetry && /* @__PURE__ */ jsx4(
|
|
638
906
|
Button3,
|
|
639
907
|
{
|
|
908
|
+
onClick: onRetry,
|
|
909
|
+
className: styles.retryButton,
|
|
640
910
|
size: "small",
|
|
641
911
|
appearance: "primary",
|
|
642
|
-
onClick: onRetry,
|
|
643
912
|
children: "Retry"
|
|
644
913
|
}
|
|
645
914
|
)
|
|
646
915
|
] });
|
|
647
916
|
};
|
|
648
|
-
var
|
|
917
|
+
var Chat_Callout_default = ChatCallout;
|
|
649
918
|
|
|
650
919
|
// src/Chat.ReasoningBlock.tsx
|
|
651
920
|
import { useState as useState3, useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
@@ -766,10 +1035,168 @@ var ReasoningBlock = ({
|
|
|
766
1035
|
};
|
|
767
1036
|
var Chat_ReasoningBlock_default = ReasoningBlock;
|
|
768
1037
|
|
|
769
|
-
// src/Chat.
|
|
770
|
-
import {
|
|
1038
|
+
// src/Chat.TodoList.tsx
|
|
1039
|
+
import { useState as useState4 } from "react";
|
|
1040
|
+
import {
|
|
1041
|
+
Body1 as Body14,
|
|
1042
|
+
Caption1,
|
|
1043
|
+
makeStyles as makeStyles6,
|
|
1044
|
+
Spinner as Spinner2,
|
|
1045
|
+
tokens as tokens6
|
|
1046
|
+
} from "@fluentui/react-components";
|
|
1047
|
+
import {
|
|
1048
|
+
CheckmarkCircle16Filled,
|
|
1049
|
+
ChevronRight16Regular as ChevronRight16Regular2,
|
|
1050
|
+
Circle16Regular
|
|
1051
|
+
} from "@fluentui/react-icons";
|
|
771
1052
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
772
1053
|
var useStyles6 = makeStyles6({
|
|
1054
|
+
root: {
|
|
1055
|
+
boxSizing: "border-box",
|
|
1056
|
+
display: "flex",
|
|
1057
|
+
flexDirection: "column",
|
|
1058
|
+
margin: "12px 0",
|
|
1059
|
+
borderRadius: "var(--borderRadiusXLarge)",
|
|
1060
|
+
backgroundColor: "var(--colorNeutralBackgroundAlpha2)",
|
|
1061
|
+
border: `1px solid ${tokens6.colorNeutralStroke2}`,
|
|
1062
|
+
overflow: "hidden"
|
|
1063
|
+
},
|
|
1064
|
+
header: {
|
|
1065
|
+
display: "flex",
|
|
1066
|
+
alignItems: "center",
|
|
1067
|
+
gap: "8px",
|
|
1068
|
+
padding: "10px 14px",
|
|
1069
|
+
cursor: "pointer",
|
|
1070
|
+
background: "none",
|
|
1071
|
+
border: "none",
|
|
1072
|
+
width: "100%",
|
|
1073
|
+
textAlign: "left"
|
|
1074
|
+
},
|
|
1075
|
+
label: {
|
|
1076
|
+
color: tokens6.colorNeutralForeground2,
|
|
1077
|
+
flexShrink: 0
|
|
1078
|
+
},
|
|
1079
|
+
count: {
|
|
1080
|
+
color: tokens6.colorNeutralForeground3,
|
|
1081
|
+
marginLeft: "auto",
|
|
1082
|
+
flexShrink: 0
|
|
1083
|
+
},
|
|
1084
|
+
chevron: {
|
|
1085
|
+
color: tokens6.colorNeutralForeground3,
|
|
1086
|
+
flexShrink: 0,
|
|
1087
|
+
transition: "transform 0.2s ease"
|
|
1088
|
+
},
|
|
1089
|
+
chevronExpanded: {
|
|
1090
|
+
transform: "rotate(90deg)"
|
|
1091
|
+
},
|
|
1092
|
+
list: {
|
|
1093
|
+
display: "flex",
|
|
1094
|
+
flexDirection: "column",
|
|
1095
|
+
gap: "8px",
|
|
1096
|
+
padding: "2px 14px 14px 14px"
|
|
1097
|
+
},
|
|
1098
|
+
item: {
|
|
1099
|
+
display: "flex",
|
|
1100
|
+
alignItems: "flex-start",
|
|
1101
|
+
gap: "8px"
|
|
1102
|
+
},
|
|
1103
|
+
iconCell: {
|
|
1104
|
+
display: "flex",
|
|
1105
|
+
alignItems: "center",
|
|
1106
|
+
justifyContent: "center",
|
|
1107
|
+
width: "16px",
|
|
1108
|
+
height: "20px",
|
|
1109
|
+
flexShrink: 0
|
|
1110
|
+
},
|
|
1111
|
+
pendingIcon: {
|
|
1112
|
+
color: tokens6.colorNeutralForeground4
|
|
1113
|
+
},
|
|
1114
|
+
doneIcon: {
|
|
1115
|
+
color: tokens6.colorStatusSuccessForeground1
|
|
1116
|
+
},
|
|
1117
|
+
text: {
|
|
1118
|
+
minWidth: 0,
|
|
1119
|
+
wordBreak: "break-word"
|
|
1120
|
+
},
|
|
1121
|
+
pendingText: {
|
|
1122
|
+
color: tokens6.colorNeutralForeground3
|
|
1123
|
+
},
|
|
1124
|
+
activeText: {
|
|
1125
|
+
color: tokens6.colorNeutralForeground1,
|
|
1126
|
+
fontWeight: tokens6.fontWeightSemibold
|
|
1127
|
+
},
|
|
1128
|
+
doneText: {
|
|
1129
|
+
color: tokens6.colorNeutralForeground3,
|
|
1130
|
+
textDecorationLine: "line-through"
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
var ChatTodoList = ({
|
|
1134
|
+
todos,
|
|
1135
|
+
className,
|
|
1136
|
+
style
|
|
1137
|
+
}) => {
|
|
1138
|
+
const styles = useStyles6();
|
|
1139
|
+
const total = todos.length;
|
|
1140
|
+
const completed = todos.filter((t) => t.status === "completed").length;
|
|
1141
|
+
const [expanded, setExpanded] = useState4(true);
|
|
1142
|
+
if (total === 0) return null;
|
|
1143
|
+
return /* @__PURE__ */ jsxs6(
|
|
1144
|
+
"div",
|
|
1145
|
+
{
|
|
1146
|
+
className: `${styles.root}${className ? ` ${className}` : ""}`,
|
|
1147
|
+
style,
|
|
1148
|
+
role: "group",
|
|
1149
|
+
"aria-label": `Task plan, ${completed} of ${total} done`,
|
|
1150
|
+
children: [
|
|
1151
|
+
/* @__PURE__ */ jsxs6(
|
|
1152
|
+
"button",
|
|
1153
|
+
{
|
|
1154
|
+
type: "button",
|
|
1155
|
+
className: styles.header,
|
|
1156
|
+
onClick: () => setExpanded((e) => !e),
|
|
1157
|
+
"aria-expanded": expanded,
|
|
1158
|
+
children: [
|
|
1159
|
+
/* @__PURE__ */ jsx6(
|
|
1160
|
+
ChevronRight16Regular2,
|
|
1161
|
+
{
|
|
1162
|
+
className: `${styles.chevron}${expanded ? ` ${styles.chevronExpanded}` : ""}`
|
|
1163
|
+
}
|
|
1164
|
+
),
|
|
1165
|
+
/* @__PURE__ */ jsx6(Body14, { className: styles.label, children: "Tasks" }),
|
|
1166
|
+
/* @__PURE__ */ jsxs6(Caption1, { className: styles.count, children: [
|
|
1167
|
+
completed,
|
|
1168
|
+
"/",
|
|
1169
|
+
total
|
|
1170
|
+
] })
|
|
1171
|
+
]
|
|
1172
|
+
}
|
|
1173
|
+
),
|
|
1174
|
+
expanded && /* @__PURE__ */ jsx6("div", { className: styles.list, role: "list", children: todos.map((todo, index) => {
|
|
1175
|
+
const text = todo.status === "in_progress" ? todo.activeForm ?? todo.content : todo.content;
|
|
1176
|
+
const textClass = todo.status === "completed" ? styles.doneText : todo.status === "in_progress" ? styles.activeText : styles.pendingText;
|
|
1177
|
+
return /* @__PURE__ */ jsxs6(
|
|
1178
|
+
"div",
|
|
1179
|
+
{
|
|
1180
|
+
className: styles.item,
|
|
1181
|
+
role: "listitem",
|
|
1182
|
+
children: [
|
|
1183
|
+
/* @__PURE__ */ jsx6("span", { className: styles.iconCell, children: todo.status === "completed" ? /* @__PURE__ */ jsx6(CheckmarkCircle16Filled, { className: styles.doneIcon }) : todo.status === "in_progress" ? /* @__PURE__ */ jsx6(Spinner2, { size: "extra-tiny" }) : /* @__PURE__ */ jsx6(Circle16Regular, { className: styles.pendingIcon }) }),
|
|
1184
|
+
/* @__PURE__ */ jsx6(Body14, { className: `${styles.text} ${textClass}`, children: text })
|
|
1185
|
+
]
|
|
1186
|
+
},
|
|
1187
|
+
todo.id ?? `todo-${index}`
|
|
1188
|
+
);
|
|
1189
|
+
}) })
|
|
1190
|
+
]
|
|
1191
|
+
}
|
|
1192
|
+
);
|
|
1193
|
+
};
|
|
1194
|
+
var Chat_TodoList_default = ChatTodoList;
|
|
1195
|
+
|
|
1196
|
+
// src/Chat.Messages.tsx
|
|
1197
|
+
import { CheckmarkCircle20Filled } from "@fluentui/react-icons";
|
|
1198
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1199
|
+
var useStyles7 = makeStyles7({
|
|
773
1200
|
messages: {
|
|
774
1201
|
flex: "1",
|
|
775
1202
|
overflowY: "auto",
|
|
@@ -790,43 +1217,119 @@ var useStyles6 = makeStyles6({
|
|
|
790
1217
|
padding: "0 16px",
|
|
791
1218
|
boxSizing: "border-box"
|
|
792
1219
|
},
|
|
793
|
-
|
|
1220
|
+
// ── User message column: holds attachments + bubble + hover-only footer ──
|
|
1221
|
+
userRow: {
|
|
794
1222
|
alignSelf: "flex-end",
|
|
1223
|
+
display: "flex",
|
|
1224
|
+
flexDirection: "column",
|
|
1225
|
+
alignItems: "flex-end",
|
|
795
1226
|
maxWidth: "80%",
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1227
|
+
"&:hover .coral-user-footer, &:focus-within .coral-user-footer": {
|
|
1228
|
+
opacity: 1
|
|
1229
|
+
}
|
|
1230
|
+
},
|
|
1231
|
+
userBubble: {
|
|
1232
|
+
backgroundColor: tokens7.colorNeutralBackground2,
|
|
1233
|
+
color: tokens7.colorNeutralForeground2,
|
|
1234
|
+
border: `1px solid ${tokens7.colorNeutralStroke2}`,
|
|
799
1235
|
padding: "8px 12px",
|
|
800
|
-
borderRadius:
|
|
1236
|
+
borderRadius: tokens7.borderRadiusXLarge,
|
|
801
1237
|
wordBreak: "break-word",
|
|
802
1238
|
boxSizing: "border-box"
|
|
803
1239
|
},
|
|
1240
|
+
userFooter: {
|
|
1241
|
+
display: "flex",
|
|
1242
|
+
justifyContent: "flex-end",
|
|
1243
|
+
marginTop: "6px",
|
|
1244
|
+
marginRight: "-10px",
|
|
1245
|
+
opacity: 0,
|
|
1246
|
+
transition: "opacity 120ms ease-in-out"
|
|
1247
|
+
},
|
|
1248
|
+
userActions: {
|
|
1249
|
+
display: "flex"
|
|
1250
|
+
},
|
|
1251
|
+
// ── Inline edit mode ──
|
|
1252
|
+
userEditWrap: {
|
|
1253
|
+
display: "flex",
|
|
1254
|
+
flexDirection: "column",
|
|
1255
|
+
alignItems: "stretch",
|
|
1256
|
+
width: "min(640px, 80vw)",
|
|
1257
|
+
gap: "8px"
|
|
1258
|
+
},
|
|
1259
|
+
userEditTextarea: {
|
|
1260
|
+
backgroundColor: tokens7.colorNeutralBackground2,
|
|
1261
|
+
color: tokens7.colorNeutralForeground2,
|
|
1262
|
+
border: `1px solid ${tokens7.colorNeutralStroke1}`,
|
|
1263
|
+
padding: "8px 12px",
|
|
1264
|
+
borderRadius: tokens7.borderRadiusLarge,
|
|
1265
|
+
fontFamily: tokens7.fontFamilyBase,
|
|
1266
|
+
fontSize: tokens7.fontSizeBase300,
|
|
1267
|
+
lineHeight: tokens7.lineHeightBase300,
|
|
1268
|
+
resize: "none",
|
|
1269
|
+
outline: "none",
|
|
1270
|
+
width: "100%",
|
|
1271
|
+
boxSizing: "border-box",
|
|
1272
|
+
minHeight: "40px",
|
|
1273
|
+
"&:focus": {
|
|
1274
|
+
border: `1px solid ${tokens7.colorNeutralStroke3}`
|
|
1275
|
+
}
|
|
1276
|
+
},
|
|
1277
|
+
userEditFooter: {
|
|
1278
|
+
display: "flex",
|
|
1279
|
+
marginTop: "8px",
|
|
1280
|
+
// marginLeft: "12px",
|
|
1281
|
+
color: `${tokens7.colorNeutralForeground3}`,
|
|
1282
|
+
gap: "12px",
|
|
1283
|
+
alignItems: "center"
|
|
1284
|
+
},
|
|
1285
|
+
userEditActions: {
|
|
1286
|
+
display: "flex",
|
|
1287
|
+
justifyContent: "flex-end",
|
|
1288
|
+
gap: "8px"
|
|
1289
|
+
},
|
|
1290
|
+
// ── Assistant message ──
|
|
804
1291
|
messageAssistant: {
|
|
805
1292
|
alignSelf: "flex-start",
|
|
806
1293
|
width: "100%",
|
|
807
|
-
color:
|
|
1294
|
+
color: tokens7.colorNeutralForeground2,
|
|
808
1295
|
wordBreak: "break-word",
|
|
809
|
-
boxSizing: "border-box"
|
|
1296
|
+
boxSizing: "border-box",
|
|
1297
|
+
"&:hover .coral-assistant-footer, &:focus-within .coral-assistant-footer": {
|
|
1298
|
+
opacity: 1
|
|
1299
|
+
}
|
|
810
1300
|
},
|
|
811
1301
|
assistantFooter: {
|
|
812
1302
|
display: "flex",
|
|
813
1303
|
justifyContent: "space-between",
|
|
814
1304
|
marginTop: "6px",
|
|
815
|
-
marginLeft: "-10px"
|
|
1305
|
+
marginLeft: "-10px",
|
|
1306
|
+
// Hover-only by default; the latest message overrides this to stay visible.
|
|
1307
|
+
opacity: 0,
|
|
1308
|
+
transition: "opacity 120ms ease-in-out"
|
|
816
1309
|
},
|
|
817
1310
|
assistantActions: {
|
|
818
1311
|
display: "flex"
|
|
1312
|
+
},
|
|
1313
|
+
copiedBadge: {
|
|
1314
|
+
display: "inline-flex",
|
|
1315
|
+
alignItems: "center",
|
|
1316
|
+
justifyContent: "center",
|
|
1317
|
+
width: "32px",
|
|
1318
|
+
height: "32px",
|
|
1319
|
+
color: tokens7.colorPaletteSeafoamForeground2
|
|
1320
|
+
},
|
|
1321
|
+
footerIconButton: {
|
|
1322
|
+
color: tokens7.colorNeutralForeground3
|
|
819
1323
|
}
|
|
820
1324
|
});
|
|
821
1325
|
var ChatMessages = ({
|
|
822
1326
|
components,
|
|
823
|
-
onFeedback,
|
|
824
1327
|
onCopy,
|
|
825
1328
|
onNavigate,
|
|
826
1329
|
fontSize,
|
|
827
1330
|
fontFamily
|
|
828
1331
|
}) => {
|
|
829
|
-
const styles =
|
|
1332
|
+
const styles = useStyles7();
|
|
830
1333
|
const ctx = useChatContext();
|
|
831
1334
|
const {
|
|
832
1335
|
messages,
|
|
@@ -835,35 +1338,204 @@ var ChatMessages = ({
|
|
|
835
1338
|
isRetrying,
|
|
836
1339
|
isInterrupted,
|
|
837
1340
|
streamingReasoning,
|
|
838
|
-
|
|
1341
|
+
streamingStatus,
|
|
839
1342
|
thinkingStatus,
|
|
840
|
-
chatError,
|
|
841
1343
|
sendMessage,
|
|
1344
|
+
regenerate,
|
|
1345
|
+
editMessage,
|
|
842
1346
|
handleCopy,
|
|
843
1347
|
lastUserInputRef,
|
|
844
1348
|
registerMessagesContainer,
|
|
845
1349
|
bottomRef
|
|
846
1350
|
} = ctx;
|
|
847
1351
|
const containerRef = useRef4(null);
|
|
848
|
-
const [
|
|
1352
|
+
const [surface, setSurface] = useState5(null);
|
|
1353
|
+
useLayoutEffect2(() => {
|
|
1354
|
+
const el = containerRef.current?.closest("[data-coral-surface]");
|
|
1355
|
+
setSurface(el?.getAttribute("data-coral-surface") ?? null);
|
|
1356
|
+
}, []);
|
|
1357
|
+
const isSidecar = surface === "panel-right";
|
|
1358
|
+
const [copiedKey, setCopiedKey] = useState5(null);
|
|
849
1359
|
const copiedTimerRef = useRef4(null);
|
|
1360
|
+
const [editingId, setEditingId] = useState5(null);
|
|
1361
|
+
const [editDraft, setEditDraft] = useState5("");
|
|
1362
|
+
const editTextareaRef = useRef4(null);
|
|
850
1363
|
useEffect4(() => {
|
|
851
1364
|
registerMessagesContainer(containerRef.current);
|
|
852
1365
|
return () => registerMessagesContainer(null);
|
|
853
1366
|
}, []);
|
|
854
|
-
|
|
1367
|
+
useLayoutEffect2(() => {
|
|
1368
|
+
if (editingId && editTextareaRef.current) {
|
|
1369
|
+
const ta = editTextareaRef.current;
|
|
1370
|
+
ta.focus();
|
|
1371
|
+
ta.setSelectionRange(ta.value.length, ta.value.length);
|
|
1372
|
+
ta.style.height = "auto";
|
|
1373
|
+
ta.style.height = `${ta.scrollHeight}px`;
|
|
1374
|
+
}
|
|
1375
|
+
}, [editingId]);
|
|
1376
|
+
const startEdit = (msg) => {
|
|
1377
|
+
if (!msg.id) return;
|
|
1378
|
+
setEditingId(msg.id);
|
|
1379
|
+
setEditDraft(msg.content);
|
|
1380
|
+
};
|
|
1381
|
+
const cancelEdit = () => {
|
|
1382
|
+
setEditingId(null);
|
|
1383
|
+
setEditDraft("");
|
|
1384
|
+
};
|
|
1385
|
+
const saveEdit = async () => {
|
|
1386
|
+
if (!editingId) return;
|
|
1387
|
+
const trimmed = editDraft.trim();
|
|
1388
|
+
if (!trimmed) return;
|
|
1389
|
+
const id = editingId;
|
|
1390
|
+
setEditingId(null);
|
|
1391
|
+
setEditDraft("");
|
|
1392
|
+
await editMessage(id, trimmed);
|
|
1393
|
+
};
|
|
1394
|
+
const handleEditTextareaInput = (e) => {
|
|
1395
|
+
setEditDraft(e.target.value);
|
|
1396
|
+
const ta = e.target;
|
|
1397
|
+
ta.style.height = "auto";
|
|
1398
|
+
ta.style.height = `${ta.scrollHeight}px`;
|
|
1399
|
+
};
|
|
1400
|
+
const handleEditKeyDown = (e) => {
|
|
1401
|
+
if (e.key === "Escape") {
|
|
1402
|
+
e.preventDefault();
|
|
1403
|
+
cancelEdit();
|
|
1404
|
+
} else if (e.key === "Enter" && !e.shiftKey) {
|
|
1405
|
+
e.preventDefault();
|
|
1406
|
+
void saveEdit();
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
const onCopyMerged = (content, key) => {
|
|
855
1410
|
handleCopy(content);
|
|
856
1411
|
onCopy?.(content);
|
|
857
1412
|
if (copiedTimerRef.current) clearTimeout(copiedTimerRef.current);
|
|
858
|
-
|
|
859
|
-
copiedTimerRef.current = setTimeout(() =>
|
|
1413
|
+
setCopiedKey(key);
|
|
1414
|
+
copiedTimerRef.current = setTimeout(() => setCopiedKey(null), 1500);
|
|
1415
|
+
};
|
|
1416
|
+
const renderUserMessage = (msg, index) => {
|
|
1417
|
+
const key = msg.id ?? `idx-${index}`;
|
|
1418
|
+
const isEditing = !!msg.id && editingId === msg.id;
|
|
1419
|
+
const attachments = Array.isArray(msg.metadata?.attachments) ? msg.metadata.attachments : null;
|
|
1420
|
+
return /* @__PURE__ */ jsxs7("div", { className: styles.userRow, children: [
|
|
1421
|
+
attachments && /* @__PURE__ */ jsx7(
|
|
1422
|
+
"div",
|
|
1423
|
+
{
|
|
1424
|
+
style: {
|
|
1425
|
+
display: "flex",
|
|
1426
|
+
gap: "6px",
|
|
1427
|
+
flexWrap: "wrap",
|
|
1428
|
+
marginBottom: "6px"
|
|
1429
|
+
},
|
|
1430
|
+
children: attachments.map(
|
|
1431
|
+
(a, i) => a.type.startsWith("image/") && a.preview ? /* @__PURE__ */ jsx7(
|
|
1432
|
+
"img",
|
|
1433
|
+
{
|
|
1434
|
+
src: a.preview,
|
|
1435
|
+
alt: a.name,
|
|
1436
|
+
style: {
|
|
1437
|
+
width: 64,
|
|
1438
|
+
height: 64,
|
|
1439
|
+
borderRadius: tokens7.borderRadiusMedium,
|
|
1440
|
+
objectFit: "cover"
|
|
1441
|
+
}
|
|
1442
|
+
},
|
|
1443
|
+
i
|
|
1444
|
+
) : /* @__PURE__ */ jsxs7(
|
|
1445
|
+
"span",
|
|
1446
|
+
{
|
|
1447
|
+
style: {
|
|
1448
|
+
fontSize: 12,
|
|
1449
|
+
padding: "2px 8px",
|
|
1450
|
+
borderRadius: tokens7.borderRadiusSmall,
|
|
1451
|
+
backgroundColor: tokens7.colorNeutralBackground3,
|
|
1452
|
+
color: tokens7.colorNeutralForeground2
|
|
1453
|
+
},
|
|
1454
|
+
children: [
|
|
1455
|
+
"\u{1F4C4} ",
|
|
1456
|
+
a.name
|
|
1457
|
+
]
|
|
1458
|
+
},
|
|
1459
|
+
i
|
|
1460
|
+
)
|
|
1461
|
+
)
|
|
1462
|
+
}
|
|
1463
|
+
),
|
|
1464
|
+
isEditing ? /* @__PURE__ */ jsxs7("div", { className: styles.userEditWrap, children: [
|
|
1465
|
+
/* @__PURE__ */ jsx7(
|
|
1466
|
+
"textarea",
|
|
1467
|
+
{
|
|
1468
|
+
ref: editTextareaRef,
|
|
1469
|
+
className: styles.userEditTextarea,
|
|
1470
|
+
value: editDraft,
|
|
1471
|
+
onChange: handleEditTextareaInput,
|
|
1472
|
+
onKeyDown: handleEditKeyDown,
|
|
1473
|
+
rows: 1
|
|
1474
|
+
}
|
|
1475
|
+
),
|
|
1476
|
+
/* @__PURE__ */ jsxs7("div", { className: styles.userEditFooter, children: [
|
|
1477
|
+
/* @__PURE__ */ jsx7(
|
|
1478
|
+
Info,
|
|
1479
|
+
{
|
|
1480
|
+
style: {
|
|
1481
|
+
marginRight: "6px",
|
|
1482
|
+
color: `${tokens7.colorNeutralForeground3}`,
|
|
1483
|
+
fontSize: "20px",
|
|
1484
|
+
boxSizing: "border-box",
|
|
1485
|
+
flexShrink: 0
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
),
|
|
1489
|
+
/* @__PURE__ */ jsx7(Caption12, { children: "Editing this message will discard the current response and regenerate from here." }),
|
|
1490
|
+
/* @__PURE__ */ jsxs7("div", { className: styles.userEditActions, children: [
|
|
1491
|
+
/* @__PURE__ */ jsx7(Button4, { appearance: "subtle", onClick: cancelEdit, children: "Cancel" }),
|
|
1492
|
+
/* @__PURE__ */ jsx7(
|
|
1493
|
+
Button4,
|
|
1494
|
+
{
|
|
1495
|
+
appearance: "primary",
|
|
1496
|
+
onClick: saveEdit,
|
|
1497
|
+
disabled: !editDraft.trim(),
|
|
1498
|
+
children: "Save"
|
|
1499
|
+
}
|
|
1500
|
+
)
|
|
1501
|
+
] })
|
|
1502
|
+
] })
|
|
1503
|
+
] }) : /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
1504
|
+
/* @__PURE__ */ jsx7("div", { className: styles.userBubble, children: /* @__PURE__ */ jsx7(Body15, { children: msg.content.split("\n").map((line, i, arr) => /* @__PURE__ */ jsxs7(React5.Fragment, { children: [
|
|
1505
|
+
line,
|
|
1506
|
+
i < arr.length - 1 && /* @__PURE__ */ jsx7("br", {})
|
|
1507
|
+
] }, i)) }) }),
|
|
1508
|
+
msg.id && /* @__PURE__ */ jsx7("div", { className: `${styles.userFooter} coral-user-footer`, children: /* @__PURE__ */ jsxs7("div", { className: styles.userActions, children: [
|
|
1509
|
+
copiedKey === `user-${key}` ? /* @__PURE__ */ jsx7("span", { className: styles.copiedBadge, children: /* @__PURE__ */ jsx7(CheckmarkCircle20Filled, {}) }) : /* @__PURE__ */ jsx7(
|
|
1510
|
+
Button4,
|
|
1511
|
+
{
|
|
1512
|
+
onClick: () => onCopyMerged(msg.content, `user-${key}`),
|
|
1513
|
+
title: "Copy message",
|
|
1514
|
+
appearance: "subtle",
|
|
1515
|
+
className: styles.footerIconButton,
|
|
1516
|
+
icon: /* @__PURE__ */ jsx7(Copy, {})
|
|
1517
|
+
}
|
|
1518
|
+
),
|
|
1519
|
+
/* @__PURE__ */ jsx7(
|
|
1520
|
+
Button4,
|
|
1521
|
+
{
|
|
1522
|
+
onClick: () => startEdit(msg),
|
|
1523
|
+
title: "Edit message",
|
|
1524
|
+
appearance: "subtle",
|
|
1525
|
+
className: styles.footerIconButton,
|
|
1526
|
+
icon: /* @__PURE__ */ jsx7(EditIcon, {})
|
|
1527
|
+
}
|
|
1528
|
+
)
|
|
1529
|
+
] }) })
|
|
1530
|
+
] })
|
|
1531
|
+
] }, key);
|
|
860
1532
|
};
|
|
861
1533
|
const renderAssistantMessage = (msg, index) => {
|
|
862
1534
|
const isLatest = index === messages.length - 1;
|
|
863
1535
|
const reasoning = isLatest && isTyping ? streamingReasoning : msg.metadata?.reasoning;
|
|
864
1536
|
if (msg.type && components?.[msg.type]) {
|
|
865
1537
|
const CustomRenderer = components[msg.type];
|
|
866
|
-
return /* @__PURE__ */
|
|
1538
|
+
return /* @__PURE__ */ jsx7(
|
|
867
1539
|
CustomRenderer,
|
|
868
1540
|
{
|
|
869
1541
|
message: msg,
|
|
@@ -878,131 +1550,197 @@ var ChatMessages = ({
|
|
|
878
1550
|
}
|
|
879
1551
|
);
|
|
880
1552
|
}
|
|
881
|
-
|
|
882
|
-
|
|
1553
|
+
const key = msg.id ?? `idx-${index}`;
|
|
1554
|
+
return /* @__PURE__ */ jsxs7("div", { children: [
|
|
1555
|
+
reasoning && /* @__PURE__ */ jsx7(
|
|
883
1556
|
Chat_ReasoningBlock_default,
|
|
884
1557
|
{
|
|
885
1558
|
content: reasoning,
|
|
886
1559
|
isStreaming: isLatest && isTyping && !msg.content
|
|
887
1560
|
}
|
|
888
1561
|
),
|
|
889
|
-
/* @__PURE__ */
|
|
890
|
-
|
|
1562
|
+
msg.todos && msg.todos.length > 0 && /* @__PURE__ */ jsx7(Chat_TodoList_default, { todos: msg.todos }),
|
|
1563
|
+
/* @__PURE__ */ jsx7(
|
|
1564
|
+
CoralMarkdown,
|
|
1565
|
+
{
|
|
1566
|
+
onNavigate,
|
|
1567
|
+
fontSize,
|
|
1568
|
+
fontFamily,
|
|
1569
|
+
children: msg.content
|
|
1570
|
+
}
|
|
1571
|
+
),
|
|
1572
|
+
msg.error && /* @__PURE__ */ jsx7(
|
|
1573
|
+
Chat_Error_default,
|
|
1574
|
+
{
|
|
1575
|
+
error: msg.error,
|
|
1576
|
+
onRetry: () => {
|
|
1577
|
+
const userMsg = messages[index - 1];
|
|
1578
|
+
if (userMsg?.role === "user") sendMessage(userMsg.content);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
),
|
|
1582
|
+
isInterrupted && !isTyping && isLatest && /* @__PURE__ */ jsx7(
|
|
891
1583
|
Chat_Callout_default,
|
|
892
1584
|
{
|
|
893
1585
|
message: "Response was interrupted",
|
|
894
1586
|
onRetry: () => sendMessage(lastUserInputRef.current)
|
|
895
1587
|
}
|
|
896
1588
|
),
|
|
897
|
-
!(isTyping && isLatest) &&
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
{
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1589
|
+
!(isTyping && isLatest) && msg.content.trim() && /* @__PURE__ */ jsx7(
|
|
1590
|
+
"div",
|
|
1591
|
+
{
|
|
1592
|
+
className: `${styles.assistantFooter} coral-assistant-footer`,
|
|
1593
|
+
style: isLatest ? { opacity: 1 } : void 0,
|
|
1594
|
+
children: /* @__PURE__ */ jsxs7("div", { className: styles.assistantActions, children: [
|
|
1595
|
+
copiedKey === `assistant-${key}` ? /* @__PURE__ */ jsx7("span", { className: styles.copiedBadge, children: /* @__PURE__ */ jsx7(CheckmarkCircle20Filled, {}) }) : /* @__PURE__ */ jsx7(
|
|
1596
|
+
Button4,
|
|
1597
|
+
{
|
|
1598
|
+
onClick: () => onCopyMerged(msg.content, `assistant-${key}`),
|
|
1599
|
+
title: "Copy response",
|
|
1600
|
+
appearance: "subtle",
|
|
1601
|
+
className: styles.footerIconButton,
|
|
1602
|
+
icon: /* @__PURE__ */ jsx7(Copy, {})
|
|
1603
|
+
}
|
|
1604
|
+
),
|
|
1605
|
+
isLatest && /* @__PURE__ */ jsx7(
|
|
1606
|
+
Button4,
|
|
1607
|
+
{
|
|
1608
|
+
onClick: () => regenerate(msg.id),
|
|
1609
|
+
title: "Regenerate response",
|
|
1610
|
+
appearance: "subtle",
|
|
1611
|
+
className: styles.footerIconButton,
|
|
1612
|
+
icon: /* @__PURE__ */ jsx7(ArrowClockwise, {})
|
|
1613
|
+
}
|
|
1614
|
+
)
|
|
1615
|
+
] })
|
|
1616
|
+
}
|
|
1617
|
+
)
|
|
1618
|
+
] });
|
|
1619
|
+
};
|
|
1620
|
+
return /* @__PURE__ */ jsx7("div", { className: styles.messages, ref: containerRef, children: /* @__PURE__ */ jsxs7(
|
|
1621
|
+
"div",
|
|
1622
|
+
{
|
|
1623
|
+
className: styles.messageWrapper,
|
|
1624
|
+
style: isSidecar ? { padding: "0 8px" } : void 0,
|
|
1625
|
+
children: [
|
|
1626
|
+
messages.map((msg, index) => {
|
|
1627
|
+
if (msg.role === "user") {
|
|
1628
|
+
return renderUserMessage(msg, index);
|
|
910
1629
|
}
|
|
911
|
-
|
|
912
|
-
|
|
1630
|
+
const key = msg.id ?? `idx-${index}`;
|
|
1631
|
+
const isLatest = index === messages.length - 1;
|
|
1632
|
+
return /* @__PURE__ */ jsx7(
|
|
1633
|
+
"div",
|
|
1634
|
+
{
|
|
1635
|
+
className: `${styles.messageAssistant} coral-message-assistant${isLatest ? " is-latest" : ""}`,
|
|
1636
|
+
children: renderAssistantMessage(msg, index)
|
|
1637
|
+
},
|
|
1638
|
+
key
|
|
1639
|
+
);
|
|
1640
|
+
}),
|
|
1641
|
+
isTyping && /* @__PURE__ */ jsx7(
|
|
1642
|
+
Chat_ThinkingIndicator_default,
|
|
913
1643
|
{
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
style: { color: tokens6.colorNeutralForeground3 },
|
|
918
|
-
icon: /* @__PURE__ */ jsx6(Copy, {})
|
|
1644
|
+
isRetrying,
|
|
1645
|
+
status: thinkingStatus,
|
|
1646
|
+
messages: resolveTaskMessages(streamingStatus)
|
|
919
1647
|
}
|
|
920
1648
|
),
|
|
921
|
-
/* @__PURE__ */
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
title: "Like",
|
|
926
|
-
appearance: "subtle",
|
|
927
|
-
style: { color: tokens6.colorNeutralForeground3 },
|
|
928
|
-
icon: /* @__PURE__ */ jsx6(Heart, {})
|
|
929
|
-
}
|
|
930
|
-
)
|
|
931
|
-
] }) })
|
|
932
|
-
] });
|
|
933
|
-
};
|
|
934
|
-
return /* @__PURE__ */ jsx6("div", { className: styles.messages, ref: containerRef, children: /* @__PURE__ */ jsxs6("div", { className: styles.messageWrapper, children: [
|
|
935
|
-
messages.map((msg, index) => /* @__PURE__ */ jsxs6(
|
|
936
|
-
"div",
|
|
937
|
-
{
|
|
938
|
-
className: msg.role === "user" ? styles.messageUser : `${styles.messageAssistant} coral-message-assistant${index === messages.length - 1 ? " is-latest" : ""}`,
|
|
939
|
-
children: [
|
|
940
|
-
msg.role === "user" && Array.isArray(msg.metadata?.attachments) && /* @__PURE__ */ jsx6("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap", marginBottom: "6px" }, children: msg.metadata.attachments.map(
|
|
941
|
-
(a, i) => a.type.startsWith("image/") && a.preview ? /* @__PURE__ */ jsx6(
|
|
942
|
-
"img",
|
|
943
|
-
{
|
|
944
|
-
src: a.preview,
|
|
945
|
-
alt: a.name,
|
|
946
|
-
style: { width: 64, height: 64, borderRadius: "var(--borderRadiusMedium)", objectFit: "cover" }
|
|
947
|
-
},
|
|
948
|
-
i
|
|
949
|
-
) : /* @__PURE__ */ jsxs6(
|
|
950
|
-
"span",
|
|
951
|
-
{
|
|
952
|
-
style: {
|
|
953
|
-
fontSize: 12,
|
|
954
|
-
padding: "2px 8px",
|
|
955
|
-
borderRadius: "var(--borderRadiusSmall)",
|
|
956
|
-
backgroundColor: tokens6.colorNeutralBackground3,
|
|
957
|
-
color: tokens6.colorNeutralForeground2
|
|
958
|
-
},
|
|
959
|
-
children: [
|
|
960
|
-
"\u{1F4C4} ",
|
|
961
|
-
a.name
|
|
962
|
-
]
|
|
963
|
-
},
|
|
964
|
-
i
|
|
965
|
-
)
|
|
966
|
-
) }),
|
|
967
|
-
/* @__PURE__ */ jsx6(Body14, { children: msg.role === "user" ? msg.content.split("\n").map((line, i, arr) => /* @__PURE__ */ jsxs6(React4.Fragment, { children: [
|
|
968
|
-
line,
|
|
969
|
-
i < arr.length - 1 && /* @__PURE__ */ jsx6("br", {})
|
|
970
|
-
] }, i)) : renderAssistantMessage(msg, index) })
|
|
971
|
-
]
|
|
972
|
-
},
|
|
973
|
-
msg.id ?? index
|
|
974
|
-
)),
|
|
975
|
-
chatError !== null && !isTyping && /* @__PURE__ */ jsx6(
|
|
976
|
-
Chat_Error_default,
|
|
977
|
-
{
|
|
978
|
-
error: chatError,
|
|
979
|
-
onRetry: () => sendMessage(lastUserInputRef.current)
|
|
980
|
-
}
|
|
981
|
-
),
|
|
982
|
-
isTyping && /* @__PURE__ */ jsx6(
|
|
983
|
-
Chat_ThinkingIndicator_default,
|
|
984
|
-
{
|
|
985
|
-
isRetrying,
|
|
986
|
-
status: streamingAgentName ? `${streamingAgentName}...` : thinkingStatus
|
|
987
|
-
}
|
|
988
|
-
),
|
|
989
|
-
/* @__PURE__ */ jsx6("div", { ref: bottomRef })
|
|
990
|
-
] }) });
|
|
1649
|
+
/* @__PURE__ */ jsx7("div", { ref: bottomRef })
|
|
1650
|
+
]
|
|
1651
|
+
}
|
|
1652
|
+
) });
|
|
991
1653
|
};
|
|
992
1654
|
var Chat_Messages_default = ChatMessages;
|
|
993
1655
|
|
|
994
1656
|
// src/Chat.Input.tsx
|
|
995
1657
|
import {
|
|
996
|
-
useRef as
|
|
997
|
-
useState as
|
|
1658
|
+
useRef as useRef6,
|
|
1659
|
+
useState as useState6,
|
|
998
1660
|
useEffect as useEffect5,
|
|
999
1661
|
forwardRef,
|
|
1000
1662
|
useImperativeHandle
|
|
1001
1663
|
} from "react";
|
|
1002
|
-
import { Body1 as
|
|
1664
|
+
import { Body1 as Body16, Button as Button6, Tag, makeStyles as makeStyles8, tokens as tokens8 } from "@fluentui/react-components";
|
|
1003
1665
|
import { Dismiss, Send, Stop } from "@coral-ai/bundleicons";
|
|
1004
|
-
|
|
1005
|
-
|
|
1666
|
+
|
|
1667
|
+
// src/Chat.AttachButton.tsx
|
|
1668
|
+
import { useRef as useRef5 } from "react";
|
|
1669
|
+
import {
|
|
1670
|
+
Button as Button5,
|
|
1671
|
+
Menu,
|
|
1672
|
+
MenuTrigger,
|
|
1673
|
+
MenuPopover,
|
|
1674
|
+
MenuList,
|
|
1675
|
+
MenuItem
|
|
1676
|
+
} from "@fluentui/react-components";
|
|
1677
|
+
import { Add, DocumentAdd, ImageAdd } from "@coral-ai/bundleicons";
|
|
1678
|
+
import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1679
|
+
var ChatAttachButton = ({
|
|
1680
|
+
types = ["files", "photos"]
|
|
1681
|
+
}) => {
|
|
1682
|
+
const { addAttachments } = useChatContext();
|
|
1683
|
+
const fileRef = useRef5(null);
|
|
1684
|
+
const photoRef = useRef5(null);
|
|
1685
|
+
const showFiles = types.includes("files");
|
|
1686
|
+
const showPhotos = types.includes("photos");
|
|
1687
|
+
return /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
1688
|
+
showFiles && /* @__PURE__ */ jsx8(
|
|
1689
|
+
"input",
|
|
1690
|
+
{
|
|
1691
|
+
ref: fileRef,
|
|
1692
|
+
type: "file",
|
|
1693
|
+
hidden: true,
|
|
1694
|
+
multiple: true,
|
|
1695
|
+
onChange: (e) => {
|
|
1696
|
+
if (e.target.files) addAttachments([...e.target.files]);
|
|
1697
|
+
e.target.value = "";
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
),
|
|
1701
|
+
showPhotos && /* @__PURE__ */ jsx8(
|
|
1702
|
+
"input",
|
|
1703
|
+
{
|
|
1704
|
+
ref: photoRef,
|
|
1705
|
+
type: "file",
|
|
1706
|
+
hidden: true,
|
|
1707
|
+
multiple: true,
|
|
1708
|
+
accept: "image/*",
|
|
1709
|
+
onChange: (e) => {
|
|
1710
|
+
if (e.target.files) addAttachments([...e.target.files]);
|
|
1711
|
+
e.target.value = "";
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
),
|
|
1715
|
+
/* @__PURE__ */ jsxs8(Menu, { children: [
|
|
1716
|
+
/* @__PURE__ */ jsx8(MenuTrigger, { children: /* @__PURE__ */ jsx8(Button5, { appearance: "subtle", icon: /* @__PURE__ */ jsx8(Add, {}) }) }),
|
|
1717
|
+
/* @__PURE__ */ jsx8(MenuPopover, { children: /* @__PURE__ */ jsx8(MenuList, { children: types.map(
|
|
1718
|
+
(t) => t === "files" ? /* @__PURE__ */ jsx8(
|
|
1719
|
+
MenuItem,
|
|
1720
|
+
{
|
|
1721
|
+
icon: /* @__PURE__ */ jsx8(DocumentAdd, {}),
|
|
1722
|
+
onClick: () => fileRef.current?.click(),
|
|
1723
|
+
children: "Add files"
|
|
1724
|
+
},
|
|
1725
|
+
"files"
|
|
1726
|
+
) : /* @__PURE__ */ jsx8(
|
|
1727
|
+
MenuItem,
|
|
1728
|
+
{
|
|
1729
|
+
icon: /* @__PURE__ */ jsx8(ImageAdd, {}),
|
|
1730
|
+
onClick: () => photoRef.current?.click(),
|
|
1731
|
+
children: "Add photos"
|
|
1732
|
+
},
|
|
1733
|
+
"photos"
|
|
1734
|
+
)
|
|
1735
|
+
) }) })
|
|
1736
|
+
] })
|
|
1737
|
+
] });
|
|
1738
|
+
};
|
|
1739
|
+
var Chat_AttachButton_default = ChatAttachButton;
|
|
1740
|
+
|
|
1741
|
+
// src/Chat.Input.tsx
|
|
1742
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1743
|
+
var useStyles8 = makeStyles8({
|
|
1006
1744
|
wrapper: {
|
|
1007
1745
|
width: "100%",
|
|
1008
1746
|
margin: "0 auto"
|
|
@@ -1022,7 +1760,7 @@ var useStyles7 = makeStyles7({
|
|
|
1022
1760
|
overflow: "hidden"
|
|
1023
1761
|
},
|
|
1024
1762
|
cardFocused: {
|
|
1025
|
-
boxShadow:
|
|
1763
|
+
boxShadow: tokens8.shadow28
|
|
1026
1764
|
},
|
|
1027
1765
|
cardDisabled: {
|
|
1028
1766
|
opacity: "0.3",
|
|
@@ -1041,7 +1779,7 @@ var useStyles7 = makeStyles7({
|
|
|
1041
1779
|
height: "128px",
|
|
1042
1780
|
borderRadius: "var(--borderRadiusMedium)",
|
|
1043
1781
|
overflow: "hidden",
|
|
1044
|
-
border: `1px solid ${
|
|
1782
|
+
border: `1px solid ${tokens8.colorNeutralStroke2}`,
|
|
1045
1783
|
backgroundColor: "var(--colorNeutralBackground3)",
|
|
1046
1784
|
display: "flex",
|
|
1047
1785
|
flexDirection: "column"
|
|
@@ -1127,18 +1865,20 @@ var ChatInput = forwardRef(
|
|
|
1127
1865
|
actionsStart,
|
|
1128
1866
|
actionsEnd,
|
|
1129
1867
|
attachments: attachmentsProp,
|
|
1130
|
-
removeAttachment: removeAttachmentProp
|
|
1868
|
+
removeAttachment: removeAttachmentProp,
|
|
1869
|
+
allowedAttachments: allowedAttachmentsProp
|
|
1131
1870
|
}, ref) => {
|
|
1132
|
-
const styles =
|
|
1133
|
-
const [isFocused, setIsFocused] =
|
|
1134
|
-
const textareaRef =
|
|
1135
|
-
const containerRef =
|
|
1871
|
+
const styles = useStyles8();
|
|
1872
|
+
const [isFocused, setIsFocused] = useState6(false);
|
|
1873
|
+
const textareaRef = useRef6(null);
|
|
1874
|
+
const containerRef = useRef6(null);
|
|
1136
1875
|
useImperativeHandle(ref, () => textareaRef.current);
|
|
1137
1876
|
const ctx = useChatContext();
|
|
1138
1877
|
const isInsideCanvas = ctx.setInput !== void 0;
|
|
1139
1878
|
const attachments = isInsideCanvas ? ctx.attachments : attachmentsProp ?? [];
|
|
1140
1879
|
const removeAttachment = isInsideCanvas ? ctx.removeAttachment : removeAttachmentProp ?? (() => {
|
|
1141
1880
|
});
|
|
1881
|
+
const allowedAttachments = allowedAttachmentsProp ?? (isInsideCanvas ? ctx.allowedAttachments : void 0);
|
|
1142
1882
|
const value = valueProp ?? (isInsideCanvas ? ctx.input : "");
|
|
1143
1883
|
const onChange = onChangeProp ?? (isInsideCanvas ? ctx.setInput : () => {
|
|
1144
1884
|
});
|
|
@@ -1173,53 +1913,53 @@ var ChatInput = forwardRef(
|
|
|
1173
1913
|
observer.observe(el);
|
|
1174
1914
|
return () => observer.disconnect();
|
|
1175
1915
|
}, []);
|
|
1176
|
-
const sendStopButton = isInsideCanvas ? ctx.isTyping ? /* @__PURE__ */
|
|
1177
|
-
|
|
1916
|
+
const sendStopButton = isInsideCanvas ? ctx.isTyping ? /* @__PURE__ */ jsx9(
|
|
1917
|
+
Button6,
|
|
1178
1918
|
{
|
|
1179
1919
|
appearance: "transparent",
|
|
1180
1920
|
onClick: () => {
|
|
1181
1921
|
ctx.shouldStopRef.current = true;
|
|
1182
1922
|
ctx.onStop?.();
|
|
1183
1923
|
},
|
|
1184
|
-
icon: /* @__PURE__ */
|
|
1924
|
+
icon: /* @__PURE__ */ jsx9(Stop, {}),
|
|
1185
1925
|
style: { cursor: "default" }
|
|
1186
1926
|
}
|
|
1187
|
-
) : /* @__PURE__ */
|
|
1188
|
-
|
|
1927
|
+
) : /* @__PURE__ */ jsx9(
|
|
1928
|
+
Button6,
|
|
1189
1929
|
{
|
|
1190
1930
|
appearance: "transparent",
|
|
1191
1931
|
onClick: () => ctx.sendMessage(),
|
|
1192
|
-
icon: /* @__PURE__ */
|
|
1932
|
+
icon: /* @__PURE__ */ jsx9(Send, {}),
|
|
1193
1933
|
disabled: !value.trim(),
|
|
1194
1934
|
style: { cursor: "default" }
|
|
1195
1935
|
}
|
|
1196
1936
|
) : null;
|
|
1197
|
-
return /* @__PURE__ */
|
|
1937
|
+
return /* @__PURE__ */ jsx9("div", { ref: containerRef, className: styles.wrapper, style, children: /* @__PURE__ */ jsxs9(
|
|
1198
1938
|
"div",
|
|
1199
1939
|
{
|
|
1200
1940
|
className: `${styles.card}${isFocused ? ` ${styles.cardFocused}` : ""}${disabled ? ` ${styles.cardDisabled}` : ""}`,
|
|
1201
1941
|
style: {
|
|
1202
|
-
border: isFocused ? `1px solid ${
|
|
1942
|
+
border: isFocused ? `1px solid ${tokens8.colorNeutralStroke1}` : `1px solid ${tokens8.colorNeutralStroke2}`
|
|
1203
1943
|
},
|
|
1204
1944
|
children: [
|
|
1205
|
-
attachments.length > 0 && /* @__PURE__ */
|
|
1945
|
+
attachments.length > 0 && /* @__PURE__ */ jsx9("div", { className: styles.attachmentsRow, children: attachments.map((a) => {
|
|
1206
1946
|
const ext = a.file.name.split(".").pop()?.toUpperCase() ?? "FILE";
|
|
1207
|
-
return /* @__PURE__ */
|
|
1208
|
-
a.preview ? /* @__PURE__ */
|
|
1947
|
+
return /* @__PURE__ */ jsxs9("div", { className: styles.attachmentItem, children: [
|
|
1948
|
+
a.preview ? /* @__PURE__ */ jsx9(
|
|
1209
1949
|
"img",
|
|
1210
1950
|
{
|
|
1211
1951
|
src: a.preview,
|
|
1212
1952
|
alt: a.file.name,
|
|
1213
1953
|
className: styles.attachmentImage
|
|
1214
1954
|
}
|
|
1215
|
-
) : /* @__PURE__ */
|
|
1216
|
-
/* @__PURE__ */
|
|
1217
|
-
/* @__PURE__ */
|
|
1955
|
+
) : /* @__PURE__ */ jsxs9("div", { className: styles.attachmentFile, children: [
|
|
1956
|
+
/* @__PURE__ */ jsx9(Body16, { className: styles.attachmentFileName, children: a.file.name }),
|
|
1957
|
+
/* @__PURE__ */ jsx9(Tag, { appearance: "outline", size: "small", children: ext })
|
|
1218
1958
|
] }),
|
|
1219
|
-
/* @__PURE__ */
|
|
1220
|
-
|
|
1959
|
+
/* @__PURE__ */ jsx9(
|
|
1960
|
+
Button6,
|
|
1221
1961
|
{
|
|
1222
|
-
icon: /* @__PURE__ */
|
|
1962
|
+
icon: /* @__PURE__ */ jsx9(Dismiss, {}),
|
|
1223
1963
|
size: "small",
|
|
1224
1964
|
onClick: () => removeAttachment(a.id),
|
|
1225
1965
|
className: styles.attachmentDismiss
|
|
@@ -1227,7 +1967,7 @@ var ChatInput = forwardRef(
|
|
|
1227
1967
|
)
|
|
1228
1968
|
] }, a.id);
|
|
1229
1969
|
}) }),
|
|
1230
|
-
/* @__PURE__ */
|
|
1970
|
+
/* @__PURE__ */ jsx9(
|
|
1231
1971
|
"textarea",
|
|
1232
1972
|
{
|
|
1233
1973
|
disabled,
|
|
@@ -1247,14 +1987,17 @@ var ChatInput = forwardRef(
|
|
|
1247
1987
|
className: styles.textarea
|
|
1248
1988
|
}
|
|
1249
1989
|
),
|
|
1250
|
-
/* @__PURE__ */
|
|
1251
|
-
/* @__PURE__ */
|
|
1252
|
-
|
|
1990
|
+
/* @__PURE__ */ jsxs9("div", { className: styles.toolbar, children: [
|
|
1991
|
+
/* @__PURE__ */ jsxs9("div", { className: styles.toolbarSide, children: [
|
|
1992
|
+
allowedAttachments && allowedAttachments.length > 0 && /* @__PURE__ */ jsx9(Chat_AttachButton_default, { types: allowedAttachments }),
|
|
1993
|
+
actionsStart
|
|
1994
|
+
] }),
|
|
1995
|
+
/* @__PURE__ */ jsxs9("div", { className: styles.toolbarSide, children: [
|
|
1253
1996
|
actionsEnd,
|
|
1254
1997
|
sendStopButton
|
|
1255
1998
|
] })
|
|
1256
1999
|
] }),
|
|
1257
|
-
/* @__PURE__ */
|
|
2000
|
+
/* @__PURE__ */ jsx9(
|
|
1258
2001
|
"span",
|
|
1259
2002
|
{
|
|
1260
2003
|
className: `${styles.focusBar} ${isFocused ? styles.focusBarVisible : styles.focusBarHidden}`
|
|
@@ -1267,9 +2010,112 @@ var ChatInput = forwardRef(
|
|
|
1267
2010
|
);
|
|
1268
2011
|
var Chat_Input_default = ChatInput;
|
|
1269
2012
|
|
|
2013
|
+
// src/Chat.Approval.tsx
|
|
2014
|
+
import { Body1 as Body17, Body1Strong, Button as Button7, Caption1 as Caption13, makeStyles as makeStyles9, tokens as tokens9 } from "@fluentui/react-components";
|
|
2015
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2016
|
+
var useStyles9 = makeStyles9({
|
|
2017
|
+
root: {
|
|
2018
|
+
width: "100%",
|
|
2019
|
+
boxSizing: "border-box",
|
|
2020
|
+
display: "flex",
|
|
2021
|
+
flexDirection: "column",
|
|
2022
|
+
gap: "24px",
|
|
2023
|
+
padding: "12px 14px",
|
|
2024
|
+
marginBottom: "8px",
|
|
2025
|
+
borderRadius: "var(--borderRadiusXLarge)",
|
|
2026
|
+
backgroundColor: "var(--colorNeutralBackgroundAlpha2)",
|
|
2027
|
+
border: `1px solid ${tokens9.colorNeutralStroke2}`
|
|
2028
|
+
},
|
|
2029
|
+
header: {
|
|
2030
|
+
display: "flex",
|
|
2031
|
+
flexDirection: "column",
|
|
2032
|
+
gap: "2px"
|
|
2033
|
+
},
|
|
2034
|
+
description: {
|
|
2035
|
+
color: tokens9.colorNeutralForeground3
|
|
2036
|
+
},
|
|
2037
|
+
optionsRow: {
|
|
2038
|
+
display: "flex",
|
|
2039
|
+
flexWrap: "wrap",
|
|
2040
|
+
gap: "6px"
|
|
2041
|
+
},
|
|
2042
|
+
optionsColumn: {
|
|
2043
|
+
display: "flex",
|
|
2044
|
+
flexDirection: "column",
|
|
2045
|
+
gap: "6px"
|
|
2046
|
+
},
|
|
2047
|
+
optionStacked: {
|
|
2048
|
+
justifyContent: "flex-start"
|
|
2049
|
+
},
|
|
2050
|
+
hint: {
|
|
2051
|
+
color: tokens9.colorNeutralForeground3
|
|
2052
|
+
// fontStyle: "italic",
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
2055
|
+
function shouldStackVertically(options) {
|
|
2056
|
+
if (options.length > 3) return true;
|
|
2057
|
+
const totalLength = options.reduce((sum, o) => sum + o.label.length, 0);
|
|
2058
|
+
if (totalLength > 40) return true;
|
|
2059
|
+
if (options.some((o) => !!o.description)) return true;
|
|
2060
|
+
return false;
|
|
2061
|
+
}
|
|
2062
|
+
var ChatApproval = ({
|
|
2063
|
+
approval: approvalProp,
|
|
2064
|
+
onSelect: onSelectProp,
|
|
2065
|
+
disabled = false,
|
|
2066
|
+
className,
|
|
2067
|
+
style
|
|
2068
|
+
}) => {
|
|
2069
|
+
const styles = useStyles9();
|
|
2070
|
+
const ctx = useChatContext();
|
|
2071
|
+
const approval = approvalProp ?? ctx.pendingApproval;
|
|
2072
|
+
if (!approval) return null;
|
|
2073
|
+
const onSelect = onSelectProp ?? ((optionId) => ctx.resolveApproval(approval.id, optionId));
|
|
2074
|
+
const answeredId = approval.answer?.optionId;
|
|
2075
|
+
const isAnswered = answeredId !== void 0;
|
|
2076
|
+
const awaitingFreeform = ctx.awaitingFreeformApproval?.approvalId === approval.id ? ctx.awaitingFreeformApproval : null;
|
|
2077
|
+
const stack = shouldStackVertically(approval.options);
|
|
2078
|
+
return /* @__PURE__ */ jsxs10(
|
|
2079
|
+
"div",
|
|
2080
|
+
{
|
|
2081
|
+
className: `${styles.root}${className ? ` ${className}` : ""}`,
|
|
2082
|
+
style,
|
|
2083
|
+
role: "group",
|
|
2084
|
+
"aria-label": approval.question,
|
|
2085
|
+
children: [
|
|
2086
|
+
/* @__PURE__ */ jsxs10("div", { className: styles.header, children: [
|
|
2087
|
+
/* @__PURE__ */ jsx10(Body1Strong, { children: approval.question }),
|
|
2088
|
+
approval.description && /* @__PURE__ */ jsx10(Body17, { className: styles.description, children: approval.description })
|
|
2089
|
+
] }),
|
|
2090
|
+
/* @__PURE__ */ jsx10("div", { className: stack ? styles.optionsColumn : styles.optionsRow, children: approval.options.map((opt) => {
|
|
2091
|
+
const selected = answeredId === opt.id || awaitingFreeform?.optionId === opt.id;
|
|
2092
|
+
const baseAppearance = opt.appearance ?? (opt.freeform ? "subtle" : "outline");
|
|
2093
|
+
const appearance = selected ? "primary" : baseAppearance;
|
|
2094
|
+
return /* @__PURE__ */ jsx10(
|
|
2095
|
+
Button7,
|
|
2096
|
+
{
|
|
2097
|
+
appearance,
|
|
2098
|
+
disabled: disabled || isAnswered,
|
|
2099
|
+
onClick: () => onSelect(opt.id),
|
|
2100
|
+
className: stack ? styles.optionStacked : void 0,
|
|
2101
|
+
style: {
|
|
2102
|
+
height: "40px"
|
|
2103
|
+
},
|
|
2104
|
+
children: opt.label
|
|
2105
|
+
},
|
|
2106
|
+
opt.id
|
|
2107
|
+
);
|
|
2108
|
+
}) }),
|
|
2109
|
+
awaitingFreeform && !isAnswered && /* @__PURE__ */ jsx10(Caption13, { className: styles.hint, children: "Type your reply in the message box below." })
|
|
2110
|
+
]
|
|
2111
|
+
}
|
|
2112
|
+
);
|
|
2113
|
+
};
|
|
2114
|
+
var Chat_Approval_default = ChatApproval;
|
|
2115
|
+
|
|
1270
2116
|
// src/Chat.tsx
|
|
1271
|
-
import { Fragment, jsx as
|
|
1272
|
-
var
|
|
2117
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2118
|
+
var useStyles10 = makeStyles10({
|
|
1273
2119
|
emptySlot: {
|
|
1274
2120
|
width: "100%",
|
|
1275
2121
|
maxWidth: "768px",
|
|
@@ -1298,50 +2144,52 @@ var useStyles8 = makeStyles8({
|
|
|
1298
2144
|
maxWidth: "768px",
|
|
1299
2145
|
padding: "0 16px 16px 16px",
|
|
1300
2146
|
boxSizing: "border-box"
|
|
1301
|
-
},
|
|
1302
|
-
starterRow: {
|
|
1303
|
-
display: "flex",
|
|
1304
|
-
flexWrap: "wrap",
|
|
1305
|
-
justifyContent: "center",
|
|
1306
|
-
gap: "6px",
|
|
1307
|
-
width: "100%",
|
|
1308
|
-
maxWidth: "768px",
|
|
1309
|
-
margin: "0 auto",
|
|
1310
|
-
padding: "0 16px",
|
|
1311
|
-
boxSizing: "border-box",
|
|
1312
|
-
overflow: "hidden"
|
|
1313
2147
|
}
|
|
1314
2148
|
});
|
|
2149
|
+
var remapFontSizeForSurface = (size, surface) => {
|
|
2150
|
+
if (surface === "panel-right" && (size === void 0 || size === "default")) {
|
|
2151
|
+
return "small";
|
|
2152
|
+
}
|
|
2153
|
+
return size;
|
|
2154
|
+
};
|
|
1315
2155
|
var ChatLayout = ({
|
|
1316
|
-
|
|
1317
|
-
promptStarters,
|
|
2156
|
+
welcomeMessage,
|
|
1318
2157
|
surfaceMotion,
|
|
1319
2158
|
placeholder,
|
|
1320
2159
|
disabled,
|
|
1321
2160
|
actionsStart,
|
|
1322
2161
|
actionsEnd,
|
|
1323
2162
|
components,
|
|
1324
|
-
onFeedback,
|
|
1325
2163
|
onCopy,
|
|
1326
2164
|
onNavigate,
|
|
1327
2165
|
fontSize,
|
|
1328
2166
|
fontFamily
|
|
1329
2167
|
}) => {
|
|
1330
|
-
const
|
|
1331
|
-
const
|
|
2168
|
+
const welcomeIsFill = welcomeMessage !== void 0 && typeof welcomeMessage !== "string";
|
|
2169
|
+
const welcomeNode = typeof welcomeMessage === "string" ? /* @__PURE__ */ jsx11(Title3, { children: welcomeMessage }) : welcomeMessage;
|
|
2170
|
+
const styles = useStyles10();
|
|
2171
|
+
const surfaceProbeRef = useRef7(null);
|
|
2172
|
+
const [surface, setSurface] = useState7(null);
|
|
2173
|
+
useLayoutEffect3(() => {
|
|
2174
|
+
const el = surfaceProbeRef.current?.closest("[data-coral-surface]");
|
|
2175
|
+
setSurface(el?.getAttribute("data-coral-surface") ?? null);
|
|
2176
|
+
}, []);
|
|
2177
|
+
const effectiveFontSize = remapFontSizeForSurface(fontSize, surface);
|
|
2178
|
+
const { messages, isTyping, pendingApproval, awaitingFreeformApproval } = useChatContext();
|
|
1332
2179
|
const empty = messages.every((m) => m.role === "system") && !isTyping;
|
|
2180
|
+
const approvalLocksInput = pendingApproval !== null && awaitingFreeformApproval === null;
|
|
2181
|
+
const inputDisabled = disabled || approvalLocksInput;
|
|
1333
2182
|
const motionEnabled = surfaceMotion !== null;
|
|
1334
2183
|
const curve = motionTokens.curveDecelerateMax;
|
|
1335
2184
|
const spacerTransition = motionEnabled ? `flex-grow 300ms ${curve}` : "none";
|
|
1336
2185
|
const gapTransition = motionEnabled ? `height 300ms ${curve}` : "none";
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
return /* @__PURE__ */ jsxs8(Fragment, { children: [
|
|
1340
|
-
/* @__PURE__ */ jsx8(
|
|
2186
|
+
return /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
2187
|
+
/* @__PURE__ */ jsx11(
|
|
1341
2188
|
"div",
|
|
1342
2189
|
{
|
|
2190
|
+
ref: surfaceProbeRef,
|
|
1343
2191
|
style: {
|
|
1344
|
-
flexGrow: empty ? 1 : 0,
|
|
2192
|
+
flexGrow: empty && !welcomeIsFill ? 1 : 0,
|
|
1345
2193
|
flexShrink: 1,
|
|
1346
2194
|
flexBasis: 0,
|
|
1347
2195
|
minHeight: 0,
|
|
@@ -1349,67 +2197,55 @@ var ChatLayout = ({
|
|
|
1349
2197
|
}
|
|
1350
2198
|
}
|
|
1351
2199
|
),
|
|
1352
|
-
/* @__PURE__ */
|
|
2200
|
+
/* @__PURE__ */ jsx11(
|
|
1353
2201
|
"div",
|
|
1354
2202
|
{
|
|
1355
2203
|
className: styles.contentArea,
|
|
1356
2204
|
style: {
|
|
1357
|
-
flex: empty ? "none" : 1,
|
|
1358
|
-
overflow: empty ? "visible" : "hidden"
|
|
2205
|
+
flex: empty && !welcomeIsFill ? "none" : 1,
|
|
2206
|
+
overflow: empty && !welcomeIsFill ? "visible" : "hidden"
|
|
1359
2207
|
},
|
|
1360
|
-
children: empty ? /* @__PURE__ */
|
|
2208
|
+
children: empty ? /* @__PURE__ */ jsx11(
|
|
2209
|
+
"div",
|
|
2210
|
+
{
|
|
2211
|
+
className: styles.emptySlot,
|
|
2212
|
+
style: welcomeIsFill ? {
|
|
2213
|
+
flex: 1,
|
|
2214
|
+
justifyContent: "center",
|
|
2215
|
+
alignContent: "center"
|
|
2216
|
+
} : void 0,
|
|
2217
|
+
children: welcomeNode
|
|
2218
|
+
}
|
|
2219
|
+
) : /* @__PURE__ */ jsx11(
|
|
1361
2220
|
Chat_Messages_default,
|
|
1362
2221
|
{
|
|
1363
2222
|
components,
|
|
1364
|
-
onFeedback,
|
|
1365
2223
|
onCopy,
|
|
1366
2224
|
onNavigate,
|
|
1367
|
-
fontSize,
|
|
2225
|
+
fontSize: effectiveFontSize,
|
|
1368
2226
|
fontFamily
|
|
1369
2227
|
}
|
|
1370
2228
|
)
|
|
1371
2229
|
}
|
|
1372
2230
|
),
|
|
1373
|
-
/* @__PURE__ */
|
|
1374
|
-
/* @__PURE__ */
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
style: {
|
|
1388
|
-
opacity: empty ? 1 : 0,
|
|
1389
|
-
maxHeight: empty ? 160 : 0,
|
|
1390
|
-
paddingBottom: empty ? 16 : 0,
|
|
1391
|
-
pointerEvents: empty ? "auto" : "none",
|
|
1392
|
-
transition: starterTransition,
|
|
1393
|
-
gap: "8px"
|
|
1394
|
-
},
|
|
1395
|
-
"aria-hidden": !empty,
|
|
1396
|
-
children: promptStarters.map((p) => /* @__PURE__ */ jsx8(
|
|
1397
|
-
InteractionTag,
|
|
1398
|
-
{
|
|
1399
|
-
appearance: "outline",
|
|
1400
|
-
onClick: () => sendMessage(p),
|
|
1401
|
-
tabIndex: empty ? 0 : -1,
|
|
1402
|
-
children: /* @__PURE__ */ jsx8(InteractionTagPrimary, { children: p })
|
|
1403
|
-
},
|
|
1404
|
-
p
|
|
1405
|
-
))
|
|
1406
|
-
}
|
|
1407
|
-
),
|
|
1408
|
-
/* @__PURE__ */ jsx8(
|
|
2231
|
+
/* @__PURE__ */ jsx11("div", { style: { height: empty ? 24 : 0, transition: gapTransition } }),
|
|
2232
|
+
/* @__PURE__ */ jsx11("div", { className: styles.inputRow, children: /* @__PURE__ */ jsxs11("div", { className: styles.inputInner, style: { flexDirection: "column" }, children: [
|
|
2233
|
+
pendingApproval && /* @__PURE__ */ jsx11(Chat_Approval_default, {}),
|
|
2234
|
+
/* @__PURE__ */ jsx11(
|
|
2235
|
+
Chat_Input_default,
|
|
2236
|
+
{
|
|
2237
|
+
placeholder: approvalLocksInput ? "Choose an option above to continue..." : placeholder,
|
|
2238
|
+
disabled: inputDisabled,
|
|
2239
|
+
actionsStart,
|
|
2240
|
+
actionsEnd
|
|
2241
|
+
}
|
|
2242
|
+
)
|
|
2243
|
+
] }) }),
|
|
2244
|
+
/* @__PURE__ */ jsx11(
|
|
1409
2245
|
"div",
|
|
1410
2246
|
{
|
|
1411
2247
|
style: {
|
|
1412
|
-
flexGrow: empty ? 1 : 0,
|
|
2248
|
+
flexGrow: empty && !welcomeIsFill ? 1 : 0,
|
|
1413
2249
|
flexShrink: 1,
|
|
1414
2250
|
flexBasis: 0,
|
|
1415
2251
|
minHeight: 0,
|
|
@@ -1417,8 +2253,8 @@ var ChatLayout = ({
|
|
|
1417
2253
|
}
|
|
1418
2254
|
}
|
|
1419
2255
|
),
|
|
1420
|
-
/* @__PURE__ */
|
|
1421
|
-
|
|
2256
|
+
/* @__PURE__ */ jsx11(
|
|
2257
|
+
Caption14,
|
|
1422
2258
|
{
|
|
1423
2259
|
style: {
|
|
1424
2260
|
display: "flex",
|
|
@@ -1433,8 +2269,8 @@ var ChatLayout = ({
|
|
|
1433
2269
|
] });
|
|
1434
2270
|
};
|
|
1435
2271
|
var Chat = ({
|
|
1436
|
-
|
|
1437
|
-
|
|
2272
|
+
api,
|
|
2273
|
+
allowedAttachments,
|
|
1438
2274
|
conversationId,
|
|
1439
2275
|
initialMessages,
|
|
1440
2276
|
systemPrompt,
|
|
@@ -1445,11 +2281,9 @@ var Chat = ({
|
|
|
1445
2281
|
autoSendMessage,
|
|
1446
2282
|
thinkingStatus,
|
|
1447
2283
|
onStop,
|
|
1448
|
-
|
|
1449
|
-
promptStarters,
|
|
2284
|
+
welcomeMessage,
|
|
1450
2285
|
surfaceMotion,
|
|
1451
2286
|
components,
|
|
1452
|
-
onFeedback,
|
|
1453
2287
|
onCopy,
|
|
1454
2288
|
onNavigate,
|
|
1455
2289
|
placeholder,
|
|
@@ -1459,11 +2293,14 @@ var Chat = ({
|
|
|
1459
2293
|
fontSize,
|
|
1460
2294
|
fontFamily
|
|
1461
2295
|
}) => {
|
|
1462
|
-
|
|
2296
|
+
const chatConfig = useCoralConfig()?.chat;
|
|
2297
|
+
const resolvedApi = api ?? chatConfig?.api;
|
|
2298
|
+
const resolvedAllowedAttachments = allowedAttachments ?? chatConfig?.attachments;
|
|
2299
|
+
return /* @__PURE__ */ jsx11(
|
|
1463
2300
|
Chat_Canvas_default,
|
|
1464
2301
|
{
|
|
1465
|
-
|
|
1466
|
-
|
|
2302
|
+
api: resolvedApi,
|
|
2303
|
+
allowedAttachments: resolvedAllowedAttachments,
|
|
1467
2304
|
conversationId,
|
|
1468
2305
|
initialMessages,
|
|
1469
2306
|
systemPrompt,
|
|
@@ -1474,18 +2311,16 @@ var Chat = ({
|
|
|
1474
2311
|
autoSendMessage,
|
|
1475
2312
|
thinkingStatus,
|
|
1476
2313
|
onStop,
|
|
1477
|
-
children: /* @__PURE__ */
|
|
2314
|
+
children: /* @__PURE__ */ jsx11(
|
|
1478
2315
|
ChatLayout,
|
|
1479
2316
|
{
|
|
1480
|
-
|
|
1481
|
-
promptStarters,
|
|
2317
|
+
welcomeMessage,
|
|
1482
2318
|
surfaceMotion,
|
|
1483
2319
|
placeholder,
|
|
1484
2320
|
disabled,
|
|
1485
2321
|
actionsStart,
|
|
1486
2322
|
actionsEnd,
|
|
1487
2323
|
components,
|
|
1488
|
-
onFeedback,
|
|
1489
2324
|
onCopy,
|
|
1490
2325
|
onNavigate,
|
|
1491
2326
|
fontSize,
|
|
@@ -1499,11 +2334,18 @@ var Chat_default = Chat;
|
|
|
1499
2334
|
|
|
1500
2335
|
// src/createHandler.ts
|
|
1501
2336
|
function createHandler({ endpoint, headers }) {
|
|
1502
|
-
return async function* (input, history,
|
|
2337
|
+
return async function* (input, history, files) {
|
|
2338
|
+
const attachments = await Promise.all(
|
|
2339
|
+
files.map(async (file) => ({
|
|
2340
|
+
name: file.name,
|
|
2341
|
+
mediaType: file.type,
|
|
2342
|
+
data: await fileToBase64(file)
|
|
2343
|
+
}))
|
|
2344
|
+
);
|
|
1503
2345
|
const res = await fetch(endpoint, {
|
|
1504
2346
|
method: "POST",
|
|
1505
2347
|
headers: { "Content-Type": "application/json", ...headers },
|
|
1506
|
-
body: JSON.stringify({ input, history,
|
|
2348
|
+
body: JSON.stringify({ input, history, attachments })
|
|
1507
2349
|
});
|
|
1508
2350
|
if (!res.ok || !res.body) {
|
|
1509
2351
|
throw Object.assign(new Error(`${res.status} ${res.statusText}`), {
|
|
@@ -1534,20 +2376,407 @@ function createHandler({ endpoint, headers }) {
|
|
|
1534
2376
|
};
|
|
1535
2377
|
}
|
|
1536
2378
|
|
|
2379
|
+
// src/Chat.Agent.ts
|
|
2380
|
+
var UPDATE_TODOS_TOOL = {
|
|
2381
|
+
name: "update_todos",
|
|
2382
|
+
description: "Display or update the multi-step task plan shown to the user as an inline checklist. Call this when you begin a task that takes 3 or more distinct steps, and again every time a step changes status. Always send the COMPLETE list of todos \u2014 it replaces the previous one. Keep exactly one item 'in_progress' at a time.",
|
|
2383
|
+
parameters: {
|
|
2384
|
+
type: "object",
|
|
2385
|
+
properties: {
|
|
2386
|
+
todos: {
|
|
2387
|
+
type: "array",
|
|
2388
|
+
description: "The full task list, in order.",
|
|
2389
|
+
items: {
|
|
2390
|
+
type: "object",
|
|
2391
|
+
properties: {
|
|
2392
|
+
content: {
|
|
2393
|
+
type: "string",
|
|
2394
|
+
description: "Imperative title, e.g. 'Optimize images'."
|
|
2395
|
+
},
|
|
2396
|
+
activeForm: {
|
|
2397
|
+
type: "string",
|
|
2398
|
+
description: "Present-tense title shown while this step is in progress, e.g. 'Optimizing images'."
|
|
2399
|
+
},
|
|
2400
|
+
status: {
|
|
2401
|
+
type: "string",
|
|
2402
|
+
enum: ["pending", "in_progress", "completed"]
|
|
2403
|
+
}
|
|
2404
|
+
},
|
|
2405
|
+
required: ["content", "status"]
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
},
|
|
2409
|
+
required: ["todos"]
|
|
2410
|
+
}
|
|
2411
|
+
};
|
|
2412
|
+
var REQUEST_APPROVAL_TOOL = {
|
|
2413
|
+
name: "request_approval",
|
|
2414
|
+
description: "Pause and ask the user to approve an action or choose between options before continuing. The conversation halts until the user responds \u2014 their choice comes back as this tool's result. Use this before any consequential or irreversible action (deleting, sending, purchasing, etc).",
|
|
2415
|
+
parameters: {
|
|
2416
|
+
type: "object",
|
|
2417
|
+
properties: {
|
|
2418
|
+
question: {
|
|
2419
|
+
type: "string",
|
|
2420
|
+
description: "The question or action to confirm."
|
|
2421
|
+
},
|
|
2422
|
+
description: {
|
|
2423
|
+
type: "string",
|
|
2424
|
+
description: "Optional extra context shown below the question."
|
|
2425
|
+
},
|
|
2426
|
+
options: {
|
|
2427
|
+
type: "array",
|
|
2428
|
+
description: "The choices offered to the user.",
|
|
2429
|
+
items: {
|
|
2430
|
+
type: "object",
|
|
2431
|
+
properties: {
|
|
2432
|
+
id: { type: "string" },
|
|
2433
|
+
label: { type: "string" },
|
|
2434
|
+
description: { type: "string" },
|
|
2435
|
+
freeform: {
|
|
2436
|
+
type: "boolean",
|
|
2437
|
+
description: "If true, choosing this option lets the user type a free-text reply instead of a canned choice."
|
|
2438
|
+
}
|
|
2439
|
+
},
|
|
2440
|
+
required: ["id", "label"]
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
},
|
|
2444
|
+
required: ["question", "options"]
|
|
2445
|
+
}
|
|
2446
|
+
};
|
|
2447
|
+
function createMemoryStore() {
|
|
2448
|
+
const map = /* @__PURE__ */ new Map();
|
|
2449
|
+
return {
|
|
2450
|
+
get: (id) => map.get(id),
|
|
2451
|
+
set: (id, snap) => {
|
|
2452
|
+
map.set(id, snap);
|
|
2453
|
+
},
|
|
2454
|
+
delete: (id) => {
|
|
2455
|
+
map.delete(id);
|
|
2456
|
+
}
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
function historyToAgentMessages(history) {
|
|
2460
|
+
const out = [];
|
|
2461
|
+
for (const m of history) {
|
|
2462
|
+
if (m.role === "user") out.push({ role: "user", content: m.content });
|
|
2463
|
+
else if (m.role === "assistant") out.push({ role: "assistant", content: m.content });
|
|
2464
|
+
}
|
|
2465
|
+
return out;
|
|
2466
|
+
}
|
|
2467
|
+
function extractTodos(input) {
|
|
2468
|
+
const todos = input?.todos;
|
|
2469
|
+
if (!Array.isArray(todos)) return [];
|
|
2470
|
+
return todos.map((t, i) => {
|
|
2471
|
+
const o = t ?? {};
|
|
2472
|
+
return {
|
|
2473
|
+
id: typeof o.id === "string" ? o.id : `todo-${i}`,
|
|
2474
|
+
content: String(o.content ?? ""),
|
|
2475
|
+
activeForm: typeof o.activeForm === "string" ? o.activeForm : void 0,
|
|
2476
|
+
status: o.status === "in_progress" || o.status === "completed" ? o.status : "pending"
|
|
2477
|
+
};
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
function buildApproval(tc) {
|
|
2481
|
+
const input = tc.input ?? {};
|
|
2482
|
+
const rawOptions = Array.isArray(input.options) ? input.options : [];
|
|
2483
|
+
const options = rawOptions.map((o, i) => {
|
|
2484
|
+
const obj = o ?? {};
|
|
2485
|
+
return {
|
|
2486
|
+
id: typeof obj.id === "string" ? obj.id : `opt-${i}`,
|
|
2487
|
+
label: String(obj.label ?? `Option ${i + 1}`),
|
|
2488
|
+
description: typeof obj.description === "string" ? obj.description : void 0,
|
|
2489
|
+
freeform: obj.freeform === true ? true : void 0
|
|
2490
|
+
};
|
|
2491
|
+
});
|
|
2492
|
+
return {
|
|
2493
|
+
// The tool-call id is the approval id — Coral round-trips it as
|
|
2494
|
+
// metadata.approvalId, which is the session-store key on resume.
|
|
2495
|
+
id: tc.id,
|
|
2496
|
+
question: String(input.question ?? "Approve?"),
|
|
2497
|
+
description: typeof input.description === "string" ? input.description : void 0,
|
|
2498
|
+
options
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
function createAgentHandler(options) {
|
|
2502
|
+
const { callModel, system, tools = [], maxSteps = 8 } = options;
|
|
2503
|
+
const store = options.sessionStore ?? createMemoryStore();
|
|
2504
|
+
const customByName = new Map(tools.map((t) => [t.spec.name, t]));
|
|
2505
|
+
const toolSpecs = [
|
|
2506
|
+
UPDATE_TODOS_TOOL,
|
|
2507
|
+
REQUEST_APPROVAL_TOOL,
|
|
2508
|
+
...tools.map((t) => t.spec)
|
|
2509
|
+
];
|
|
2510
|
+
return async function* (_input, history, attachments) {
|
|
2511
|
+
const sysFromHistory = history.filter((m) => m.role === "system").map((m) => m.content);
|
|
2512
|
+
const effectiveSystem = [system, ...sysFromHistory].filter(Boolean).join("\n\n") || void 0;
|
|
2513
|
+
const last = history[history.length - 1];
|
|
2514
|
+
let messages;
|
|
2515
|
+
let firstCallAttachments;
|
|
2516
|
+
const resumeId = last?.role === "user" && last.type === "approval_response" && typeof last.metadata?.approvalId === "string" ? last.metadata.approvalId : void 0;
|
|
2517
|
+
const snapshot = resumeId ? await store.get(resumeId) : void 0;
|
|
2518
|
+
if (resumeId && snapshot) {
|
|
2519
|
+
await store.delete(resumeId);
|
|
2520
|
+
const meta = last.metadata;
|
|
2521
|
+
const answer = typeof meta.text === "string" && meta.text || typeof meta.label === "string" && meta.label || last.content;
|
|
2522
|
+
messages = [
|
|
2523
|
+
...snapshot.messages,
|
|
2524
|
+
{
|
|
2525
|
+
role: "tool",
|
|
2526
|
+
toolCallId: snapshot.pendingToolCallId,
|
|
2527
|
+
content: `The user chose: ${answer}`
|
|
2528
|
+
}
|
|
2529
|
+
];
|
|
2530
|
+
} else {
|
|
2531
|
+
messages = historyToAgentMessages(history);
|
|
2532
|
+
firstCallAttachments = attachments.length > 0 ? attachments : void 0;
|
|
2533
|
+
}
|
|
2534
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
2535
|
+
const events = callModel({
|
|
2536
|
+
system: effectiveSystem,
|
|
2537
|
+
messages,
|
|
2538
|
+
tools: toolSpecs,
|
|
2539
|
+
attachments: step === 0 ? firstCallAttachments : void 0
|
|
2540
|
+
});
|
|
2541
|
+
let assistantText = "";
|
|
2542
|
+
let assistantRaw;
|
|
2543
|
+
const toolCalls = [];
|
|
2544
|
+
for await (const ev of events) {
|
|
2545
|
+
if (ev.type === "text") {
|
|
2546
|
+
assistantText += ev.delta;
|
|
2547
|
+
yield ev.delta;
|
|
2548
|
+
} else if (ev.type === "reasoning") {
|
|
2549
|
+
yield { type: "reasoning", content: ev.delta };
|
|
2550
|
+
} else if (ev.type === "tool_call") {
|
|
2551
|
+
toolCalls.push(ev.toolCall);
|
|
2552
|
+
} else if (ev.type === "raw") {
|
|
2553
|
+
assistantRaw = ev.raw;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
messages.push({
|
|
2557
|
+
role: "assistant",
|
|
2558
|
+
content: assistantText,
|
|
2559
|
+
...toolCalls.length > 0 ? { toolCalls } : {},
|
|
2560
|
+
...assistantRaw !== void 0 ? { raw: assistantRaw } : {}
|
|
2561
|
+
});
|
|
2562
|
+
if (toolCalls.length === 0) return;
|
|
2563
|
+
for (const tc of toolCalls) {
|
|
2564
|
+
if (tc.name === UPDATE_TODOS_TOOL.name) {
|
|
2565
|
+
yield { type: "todos", todos: extractTodos(tc.input) };
|
|
2566
|
+
messages.push({
|
|
2567
|
+
role: "tool",
|
|
2568
|
+
toolCallId: tc.id,
|
|
2569
|
+
content: "Task list updated and shown to the user."
|
|
2570
|
+
});
|
|
2571
|
+
} else if (tc.name === REQUEST_APPROVAL_TOOL.name) {
|
|
2572
|
+
yield { type: "approval", approval: buildApproval(tc) };
|
|
2573
|
+
await store.set(tc.id, { messages, pendingToolCallId: tc.id });
|
|
2574
|
+
return;
|
|
2575
|
+
} else {
|
|
2576
|
+
const tool = customByName.get(tc.name);
|
|
2577
|
+
let content;
|
|
2578
|
+
if (!tool) {
|
|
2579
|
+
content = `Error: unknown tool "${tc.name}".`;
|
|
2580
|
+
} else {
|
|
2581
|
+
try {
|
|
2582
|
+
content = (await tool.execute(tc.input)).content;
|
|
2583
|
+
} catch (err) {
|
|
2584
|
+
content = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
messages.push({ role: "tool", toolCallId: tc.id, content });
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
yield "\n\n_(Reached the tool-step limit for this turn.)_";
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
// src/Chat.Threads.tsx
|
|
2596
|
+
import * as React9 from "react";
|
|
2597
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
2598
|
+
var ChatThreadsContext = React9.createContext(
|
|
2599
|
+
null
|
|
2600
|
+
);
|
|
2601
|
+
function useChatThreads() {
|
|
2602
|
+
const value = React9.useContext(ChatThreadsContext);
|
|
2603
|
+
if (!value) {
|
|
2604
|
+
throw new Error(
|
|
2605
|
+
"useChatThreads must be used inside <ChatThreadsProvider>."
|
|
2606
|
+
);
|
|
2607
|
+
}
|
|
2608
|
+
return value;
|
|
2609
|
+
}
|
|
2610
|
+
function createMemoryThreadsAdapter(initialThreads = []) {
|
|
2611
|
+
let threads = [...initialThreads];
|
|
2612
|
+
return {
|
|
2613
|
+
list: () => threads,
|
|
2614
|
+
get: (id) => threads.find((t) => t.id === id),
|
|
2615
|
+
create: (initial) => {
|
|
2616
|
+
const thread = {
|
|
2617
|
+
id: initial?.id ?? crypto.randomUUID(),
|
|
2618
|
+
title: initial?.title ?? "New chat",
|
|
2619
|
+
messages: initial?.messages ?? [],
|
|
2620
|
+
createdAt: Date.now(),
|
|
2621
|
+
updatedAt: Date.now()
|
|
2622
|
+
};
|
|
2623
|
+
threads = [thread, ...threads];
|
|
2624
|
+
return thread;
|
|
2625
|
+
},
|
|
2626
|
+
save: (thread) => {
|
|
2627
|
+
const idx = threads.findIndex((t) => t.id === thread.id);
|
|
2628
|
+
const next = { ...thread, updatedAt: Date.now() };
|
|
2629
|
+
threads = idx >= 0 ? threads.map((t, i) => i === idx ? next : t) : [next, ...threads];
|
|
2630
|
+
},
|
|
2631
|
+
rename: (id, newTitle) => {
|
|
2632
|
+
threads = threads.map(
|
|
2633
|
+
(t) => t.id === id ? { ...t, title: newTitle, updatedAt: Date.now() } : t
|
|
2634
|
+
);
|
|
2635
|
+
},
|
|
2636
|
+
remove: (id) => {
|
|
2637
|
+
threads = threads.filter((t) => t.id !== id);
|
|
2638
|
+
}
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
function ChatThreadsProvider({
|
|
2642
|
+
adapter: providedAdapter,
|
|
2643
|
+
activeThreadId: controlledId,
|
|
2644
|
+
defaultActiveThreadId,
|
|
2645
|
+
onActiveThreadChange,
|
|
2646
|
+
children
|
|
2647
|
+
}) {
|
|
2648
|
+
const adapter = React9.useMemo(
|
|
2649
|
+
() => providedAdapter ?? createMemoryThreadsAdapter(),
|
|
2650
|
+
[providedAdapter]
|
|
2651
|
+
);
|
|
2652
|
+
const [threads, setThreads] = React9.useState([]);
|
|
2653
|
+
const [uncontrolledId, setUncontrolledId] = React9.useState(
|
|
2654
|
+
defaultActiveThreadId
|
|
2655
|
+
);
|
|
2656
|
+
const [isLoading, setIsLoading] = React9.useState(true);
|
|
2657
|
+
const isControlled = controlledId !== void 0;
|
|
2658
|
+
const activeThreadId = isControlled ? controlledId : uncontrolledId;
|
|
2659
|
+
const setActiveThreadId = React9.useCallback(
|
|
2660
|
+
(id) => {
|
|
2661
|
+
if (!isControlled) setUncontrolledId(id);
|
|
2662
|
+
onActiveThreadChange?.(id);
|
|
2663
|
+
},
|
|
2664
|
+
[isControlled, onActiveThreadChange]
|
|
2665
|
+
);
|
|
2666
|
+
React9.useEffect(() => {
|
|
2667
|
+
let cancelled = false;
|
|
2668
|
+
(async () => {
|
|
2669
|
+
setIsLoading(true);
|
|
2670
|
+
const result = await adapter.list();
|
|
2671
|
+
if (cancelled) return;
|
|
2672
|
+
setThreads(result);
|
|
2673
|
+
setIsLoading(false);
|
|
2674
|
+
})();
|
|
2675
|
+
return () => {
|
|
2676
|
+
cancelled = true;
|
|
2677
|
+
};
|
|
2678
|
+
}, [adapter]);
|
|
2679
|
+
const createThread = React9.useCallback(
|
|
2680
|
+
async (initial) => {
|
|
2681
|
+
const thread = await adapter.create(initial);
|
|
2682
|
+
setThreads((prev) => [thread, ...prev.filter((t) => t.id !== thread.id)]);
|
|
2683
|
+
return thread;
|
|
2684
|
+
},
|
|
2685
|
+
[adapter]
|
|
2686
|
+
);
|
|
2687
|
+
const renameThread = React9.useCallback(
|
|
2688
|
+
async (id, newTitle) => {
|
|
2689
|
+
await adapter.rename(id, newTitle);
|
|
2690
|
+
setThreads(
|
|
2691
|
+
(prev) => prev.map(
|
|
2692
|
+
(t) => t.id === id ? { ...t, title: newTitle, updatedAt: Date.now() } : t
|
|
2693
|
+
)
|
|
2694
|
+
);
|
|
2695
|
+
},
|
|
2696
|
+
[adapter]
|
|
2697
|
+
);
|
|
2698
|
+
const removeThread = React9.useCallback(
|
|
2699
|
+
async (id) => {
|
|
2700
|
+
await adapter.remove(id);
|
|
2701
|
+
setThreads((prev) => prev.filter((t) => t.id !== id));
|
|
2702
|
+
if (activeThreadId === id) setActiveThreadId(void 0);
|
|
2703
|
+
},
|
|
2704
|
+
[adapter, activeThreadId, setActiveThreadId]
|
|
2705
|
+
);
|
|
2706
|
+
const threadsRef = React9.useRef(threads);
|
|
2707
|
+
threadsRef.current = threads;
|
|
2708
|
+
const updateThreadMessages = React9.useCallback(
|
|
2709
|
+
(id, messages) => {
|
|
2710
|
+
const existing = threadsRef.current.find((t) => t.id === id);
|
|
2711
|
+
if (!existing) return;
|
|
2712
|
+
const next = {
|
|
2713
|
+
...existing,
|
|
2714
|
+
messages,
|
|
2715
|
+
updatedAt: Date.now()
|
|
2716
|
+
};
|
|
2717
|
+
setThreads((prev) => prev.map((t) => t.id === id ? next : t));
|
|
2718
|
+
void adapter.save(next);
|
|
2719
|
+
},
|
|
2720
|
+
[adapter]
|
|
2721
|
+
);
|
|
2722
|
+
const activeThread = React9.useMemo(
|
|
2723
|
+
() => threads.find((t) => t.id === activeThreadId),
|
|
2724
|
+
[threads, activeThreadId]
|
|
2725
|
+
);
|
|
2726
|
+
const value = React9.useMemo(
|
|
2727
|
+
() => ({
|
|
2728
|
+
threads,
|
|
2729
|
+
activeThreadId,
|
|
2730
|
+
activeThread,
|
|
2731
|
+
setActiveThreadId,
|
|
2732
|
+
createThread,
|
|
2733
|
+
renameThread,
|
|
2734
|
+
removeThread,
|
|
2735
|
+
updateThreadMessages,
|
|
2736
|
+
isLoading
|
|
2737
|
+
}),
|
|
2738
|
+
[
|
|
2739
|
+
threads,
|
|
2740
|
+
activeThreadId,
|
|
2741
|
+
activeThread,
|
|
2742
|
+
setActiveThreadId,
|
|
2743
|
+
createThread,
|
|
2744
|
+
renameThread,
|
|
2745
|
+
removeThread,
|
|
2746
|
+
updateThreadMessages,
|
|
2747
|
+
isLoading
|
|
2748
|
+
]
|
|
2749
|
+
);
|
|
2750
|
+
return /* @__PURE__ */ jsx12(ChatThreadsContext.Provider, { value, children });
|
|
2751
|
+
}
|
|
2752
|
+
|
|
1537
2753
|
// src/index.ts
|
|
1538
2754
|
var Chat2 = Object.assign(Chat_default, {
|
|
1539
2755
|
Canvas: Chat_Canvas_default,
|
|
1540
2756
|
Messages: Chat_Messages_default,
|
|
1541
2757
|
Input: Chat_Input_default,
|
|
1542
2758
|
Error: Chat_Error_default,
|
|
1543
|
-
Callout: Chat_Callout_default
|
|
2759
|
+
Callout: Chat_Callout_default,
|
|
2760
|
+
Approval: Chat_Approval_default,
|
|
2761
|
+
TodoList: Chat_TodoList_default,
|
|
2762
|
+
AttachButton: Chat_AttachButton_default,
|
|
2763
|
+
ThinkingIndicator: Chat_ThinkingIndicator_default
|
|
1544
2764
|
});
|
|
1545
2765
|
var normalizeChatError2 = normalizeChatError;
|
|
1546
2766
|
var createHandler2 = createHandler;
|
|
1547
2767
|
export {
|
|
1548
2768
|
Chat2 as Chat,
|
|
2769
|
+
ChatThreadsProvider,
|
|
2770
|
+
DEFAULT_TASK_MESSAGES,
|
|
2771
|
+
REQUEST_APPROVAL_TOOL,
|
|
2772
|
+
UPDATE_TODOS_TOOL,
|
|
2773
|
+
createAgentHandler,
|
|
1549
2774
|
createHandler2 as createHandler,
|
|
2775
|
+
createMemoryThreadsAdapter,
|
|
2776
|
+
fileToBase64,
|
|
1550
2777
|
normalizeChatError2 as normalizeChatError,
|
|
1551
|
-
|
|
2778
|
+
resolveTaskMessages,
|
|
2779
|
+
useChatContext,
|
|
2780
|
+
useChatThreads
|
|
1552
2781
|
};
|
|
1553
2782
|
//# sourceMappingURL=index.js.map
|