@sarchauhan/chat 0.0.0-stage → 0.0.1
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 +130 -2
- package/dist/blocks.css +291 -0
- package/dist/chat.css +959 -0
- package/dist/chat.select.css +345 -0
- package/dist/chips.css +248 -0
- package/dist/chunk-5SNRYULC.mjs +2201 -0
- package/dist/chunk-5SNRYULC.mjs.map +1 -0
- package/dist/index-DabpPX_p.d.mts +177 -0
- package/dist/index-DabpPX_p.d.ts +177 -0
- package/dist/index.d.mts +382 -0
- package/dist/index.d.ts +382 -0
- package/dist/index.js +4386 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2247 -0
- package/dist/index.mjs.map +1 -0
- package/dist/markdown.css +247 -0
- package/dist/motion/error-state-shake.css +57 -0
- package/dist/motion/icon-swap.css +30 -0
- package/dist/motion/index.css +6 -0
- package/dist/motion/menu-dropdown.css +35 -0
- package/dist/motion/success-check.css +51 -0
- package/dist/motion/text-states-swap.css +28 -0
- package/dist/motion/tokens.css +96 -0
- package/dist/styles.css +11 -0
- package/dist/theme/tokens.css +253 -0
- package/dist/theme/typography.css +115 -0
- package/dist/widgets.css +570 -0
- package/dist/widgets.d.mts +4 -0
- package/dist/widgets.d.ts +4 -0
- package/dist/widgets.js +1996 -0
- package/dist/widgets.js.map +1 -0
- package/dist/widgets.mjs +48 -0
- package/dist/widgets.mjs.map +1 -0
- package/package.json +76 -4
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2247 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
ArtifactWidget,
|
|
4
|
+
ArtifactsWidget,
|
|
5
|
+
BaseWidget,
|
|
6
|
+
LoadingState,
|
|
7
|
+
MessageContent,
|
|
8
|
+
PatchWidget,
|
|
9
|
+
QuestionWidget,
|
|
10
|
+
ReadFileWidget,
|
|
11
|
+
ResponsiveDetail,
|
|
12
|
+
SearchWidget,
|
|
13
|
+
SubagentWidget,
|
|
14
|
+
TextSkeleton,
|
|
15
|
+
TextSwap,
|
|
16
|
+
ThemeProvider,
|
|
17
|
+
ThinkingBlock,
|
|
18
|
+
WidgetProvider,
|
|
19
|
+
WidgetRenderer,
|
|
20
|
+
calibrateCheckPath,
|
|
21
|
+
clearErrorShake,
|
|
22
|
+
closeDropdown,
|
|
23
|
+
cn,
|
|
24
|
+
computeDiff,
|
|
25
|
+
computeTextDiff,
|
|
26
|
+
computeToolState,
|
|
27
|
+
computeToolWidget,
|
|
28
|
+
createArtifactData,
|
|
29
|
+
createSubagentData,
|
|
30
|
+
createWidgetRegistry,
|
|
31
|
+
defineWidget,
|
|
32
|
+
dropdownOrigins,
|
|
33
|
+
formatArtifactSize,
|
|
34
|
+
formatElapsed,
|
|
35
|
+
formatElapsedWords,
|
|
36
|
+
getUserDisplayText,
|
|
37
|
+
iconSwapStates,
|
|
38
|
+
motion,
|
|
39
|
+
motionData,
|
|
40
|
+
motionRoot,
|
|
41
|
+
normalizeSearchResults,
|
|
42
|
+
openDropdown,
|
|
43
|
+
parseArtifact,
|
|
44
|
+
parseSubagent,
|
|
45
|
+
prebuiltWidgets,
|
|
46
|
+
prefersReducedMotion,
|
|
47
|
+
readDurationMs,
|
|
48
|
+
safeArtifactUrl,
|
|
49
|
+
safeResultUrl,
|
|
50
|
+
successCheckStates,
|
|
51
|
+
swapText,
|
|
52
|
+
triggerErrorShake,
|
|
53
|
+
useDropdownPresence,
|
|
54
|
+
useTheme,
|
|
55
|
+
useWidgets
|
|
56
|
+
} from "./chunk-5SNRYULC.mjs";
|
|
57
|
+
|
|
58
|
+
// src/components/Chat.tsx
|
|
59
|
+
import { PenSquare } from "lucide-react";
|
|
60
|
+
import { useCallback as useCallback6, useLayoutEffect, useMemo as useMemo7, useRef as useRef10 } from "react";
|
|
61
|
+
|
|
62
|
+
// src/theme/theme.toggle.tsx
|
|
63
|
+
import { Moon, Sun } from "lucide-react";
|
|
64
|
+
import { jsx } from "react/jsx-runtime";
|
|
65
|
+
function ThemeToggle({ className }) {
|
|
66
|
+
const { resolvedTheme, toggleTheme } = useTheme();
|
|
67
|
+
const isDark = resolvedTheme === "dark";
|
|
68
|
+
return /* @__PURE__ */ jsx(
|
|
69
|
+
"button",
|
|
70
|
+
{
|
|
71
|
+
type: "button",
|
|
72
|
+
className: ["chat-theme-toggle", className].filter(Boolean).join(" "),
|
|
73
|
+
onClick: toggleTheme,
|
|
74
|
+
"aria-label": isDark ? "Switch to light theme" : "Switch to dark theme",
|
|
75
|
+
title: isDark ? "Light mode" : "Dark mode",
|
|
76
|
+
children: isDark ? /* @__PURE__ */ jsx(Sun, { size: 16, strokeWidth: 1.75 }) : /* @__PURE__ */ jsx(Moon, { size: 16, strokeWidth: 1.75 })
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/components/Chat/chat.tsx
|
|
82
|
+
import { MessageCircle } from "lucide-react";
|
|
83
|
+
import { useEffect as useEffect6, useRef as useRef9, useState as useState7 } from "react";
|
|
84
|
+
|
|
85
|
+
// src/components/Chat/chat.attachments.tsx
|
|
86
|
+
import { FileText, Paperclip, X } from "lucide-react";
|
|
87
|
+
import { useRef as useRef6 } from "react";
|
|
88
|
+
|
|
89
|
+
// src/components/Chat/context/index.ts
|
|
90
|
+
import { useMemo as useMemo5 } from "react";
|
|
91
|
+
|
|
92
|
+
// src/components/Chat/context/composer.context.tsx
|
|
93
|
+
import {
|
|
94
|
+
createContext as createContext4,
|
|
95
|
+
useCallback as useCallback4,
|
|
96
|
+
useContext as useContext4,
|
|
97
|
+
useMemo as useMemo4,
|
|
98
|
+
useRef as useRef5,
|
|
99
|
+
useState as useState5
|
|
100
|
+
} from "react";
|
|
101
|
+
|
|
102
|
+
// src/components/Chat/chat.tooltip.tsx
|
|
103
|
+
import { memo, useEffect, useRef, useState } from "react";
|
|
104
|
+
import { createPortal } from "react-dom";
|
|
105
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
106
|
+
var isEditableSelection = (selection) => {
|
|
107
|
+
var _a, _b;
|
|
108
|
+
const anchorElement = (_a = selection.anchorNode) == null ? void 0 : _a.parentElement;
|
|
109
|
+
const focusElement = (_b = selection.focusNode) == null ? void 0 : _b.parentElement;
|
|
110
|
+
return Boolean(
|
|
111
|
+
(anchorElement == null ? void 0 : anchorElement.closest("textarea,input,[contenteditable='true']")) || (focusElement == null ? void 0 : focusElement.closest("textarea,input,[contenteditable='true']"))
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
var getSelectionRect = (range) => {
|
|
115
|
+
const rect = range.getBoundingClientRect();
|
|
116
|
+
const fallbackRect = range.getClientRects()[0];
|
|
117
|
+
return rect.width || rect.height ? rect : fallbackRect;
|
|
118
|
+
};
|
|
119
|
+
var positionFromRange = (range, container) => {
|
|
120
|
+
const selectionRect = getSelectionRect(range);
|
|
121
|
+
if (!selectionRect) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const containerRect = container.getBoundingClientRect();
|
|
125
|
+
return {
|
|
126
|
+
top: Math.max(8, selectionRect.top - containerRect.top + container.scrollTop),
|
|
127
|
+
left: selectionRect.left - containerRect.left + container.scrollLeft + selectionRect.width / 2
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
var readSelection = () => {
|
|
131
|
+
const selection = window.getSelection();
|
|
132
|
+
const text = selection == null ? void 0 : selection.toString().replace(/\s+/g, " ").trim();
|
|
133
|
+
if (!selection || !text || selection.rangeCount === 0 || selection.isCollapsed) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
if (isEditableSelection(selection)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
text,
|
|
141
|
+
range: selection.getRangeAt(0).cloneRange()
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
var getPortalContainer = (range) => {
|
|
145
|
+
const node = range.commonAncestorContainer;
|
|
146
|
+
const element = node instanceof Element ? node : node.parentElement;
|
|
147
|
+
const found = element == null ? void 0 : element.closest(".chat-messages");
|
|
148
|
+
return found instanceof HTMLElement ? found : null;
|
|
149
|
+
};
|
|
150
|
+
var ChatTooltip = memo(function ChatTooltip2({
|
|
151
|
+
onAddReference
|
|
152
|
+
}) {
|
|
153
|
+
const [selectionAction, setSelectionAction] = useState(null);
|
|
154
|
+
const rangeRef = useRef(null);
|
|
155
|
+
const textRef = useRef("");
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
const clearSelectionAction = () => {
|
|
158
|
+
rangeRef.current = null;
|
|
159
|
+
textRef.current = "";
|
|
160
|
+
setSelectionAction(null);
|
|
161
|
+
};
|
|
162
|
+
const syncPosition = () => {
|
|
163
|
+
const range = rangeRef.current;
|
|
164
|
+
if (!range) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const container = getPortalContainer(range);
|
|
168
|
+
if (!container) {
|
|
169
|
+
clearSelectionAction();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const position = positionFromRange(range, container);
|
|
173
|
+
if (!position) {
|
|
174
|
+
clearSelectionAction();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
setSelectionAction({
|
|
178
|
+
text: textRef.current,
|
|
179
|
+
top: position.top,
|
|
180
|
+
left: position.left,
|
|
181
|
+
container
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
const captureSelection = () => {
|
|
185
|
+
const next = readSelection();
|
|
186
|
+
if (!next) {
|
|
187
|
+
clearSelectionAction();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
rangeRef.current = next.range;
|
|
191
|
+
textRef.current = next.text;
|
|
192
|
+
syncPosition();
|
|
193
|
+
};
|
|
194
|
+
document.addEventListener("mouseup", captureSelection);
|
|
195
|
+
document.addEventListener("keyup", captureSelection);
|
|
196
|
+
document.addEventListener("selectionchange", captureSelection);
|
|
197
|
+
window.addEventListener("scroll", syncPosition, true);
|
|
198
|
+
window.addEventListener("resize", syncPosition);
|
|
199
|
+
return () => {
|
|
200
|
+
document.removeEventListener("mouseup", captureSelection);
|
|
201
|
+
document.removeEventListener("keyup", captureSelection);
|
|
202
|
+
document.removeEventListener("selectionchange", captureSelection);
|
|
203
|
+
window.removeEventListener("scroll", syncPosition, true);
|
|
204
|
+
window.removeEventListener("resize", syncPosition);
|
|
205
|
+
};
|
|
206
|
+
}, [onAddReference]);
|
|
207
|
+
if (!selectionAction) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
return createPortal(
|
|
211
|
+
/* @__PURE__ */ jsx2(
|
|
212
|
+
"button",
|
|
213
|
+
{
|
|
214
|
+
type: "button",
|
|
215
|
+
className: "chat-reference-action",
|
|
216
|
+
style: { top: selectionAction.top, left: selectionAction.left },
|
|
217
|
+
"aria-label": "Add selected text as reference",
|
|
218
|
+
onPointerDown: (event) => event.preventDefault(),
|
|
219
|
+
onClick: () => {
|
|
220
|
+
var _a;
|
|
221
|
+
onAddReference(selectionAction.text);
|
|
222
|
+
(_a = window.getSelection()) == null ? void 0 : _a.removeAllRanges();
|
|
223
|
+
rangeRef.current = null;
|
|
224
|
+
textRef.current = "";
|
|
225
|
+
setSelectionAction(null);
|
|
226
|
+
},
|
|
227
|
+
children: "Add reference"
|
|
228
|
+
}
|
|
229
|
+
),
|
|
230
|
+
selectionAction.container
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// src/components/Chat/context/message.helpers.ts
|
|
235
|
+
var normalizeReferenceText = (text) => text.replace(/\s+/g, " ").trim().slice(0, 4e3);
|
|
236
|
+
var createMessageId = (prefix) => {
|
|
237
|
+
var _a, _b, _c;
|
|
238
|
+
return (_c = (_b = (_a = globalThis.crypto) == null ? void 0 : _a.randomUUID) == null ? void 0 : _b.call(_a)) != null ? _c : `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
239
|
+
};
|
|
240
|
+
var createUserMessage = (text, files = []) => ({
|
|
241
|
+
id: createMessageId("msg"),
|
|
242
|
+
role: "user",
|
|
243
|
+
parts: [
|
|
244
|
+
...files.map((file) => ({
|
|
245
|
+
type: "file",
|
|
246
|
+
mediaType: file.mediaType,
|
|
247
|
+
url: file.url,
|
|
248
|
+
filename: file.filename
|
|
249
|
+
})),
|
|
250
|
+
...text ? [{ type: "text", text }] : []
|
|
251
|
+
]
|
|
252
|
+
});
|
|
253
|
+
var updateMessageText = (message, text) => ({
|
|
254
|
+
...message,
|
|
255
|
+
parts: [{ type: "text", text }]
|
|
256
|
+
});
|
|
257
|
+
var upsertAssistantMessage = (current, nextMessage) => {
|
|
258
|
+
const existingIndex = current.findIndex((message) => message.id === nextMessage.id);
|
|
259
|
+
if (existingIndex === -1) {
|
|
260
|
+
return [...current, nextMessage];
|
|
261
|
+
}
|
|
262
|
+
return current.map((message, index) => index === existingIndex ? nextMessage : message);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// src/components/Chat/context/messages.context.tsx
|
|
266
|
+
import {
|
|
267
|
+
createContext as createContext3,
|
|
268
|
+
useCallback as useCallback3,
|
|
269
|
+
useContext as useContext3,
|
|
270
|
+
useEffect as useEffect4,
|
|
271
|
+
useMemo as useMemo3,
|
|
272
|
+
useRef as useRef4,
|
|
273
|
+
useState as useState4
|
|
274
|
+
} from "react";
|
|
275
|
+
import { flushSync } from "react-dom";
|
|
276
|
+
|
|
277
|
+
// src/components/Chat/context/model.context.tsx
|
|
278
|
+
import {
|
|
279
|
+
createContext,
|
|
280
|
+
useCallback,
|
|
281
|
+
useContext,
|
|
282
|
+
useEffect as useEffect2,
|
|
283
|
+
useMemo,
|
|
284
|
+
useRef as useRef2,
|
|
285
|
+
useState as useState2
|
|
286
|
+
} from "react";
|
|
287
|
+
|
|
288
|
+
// src/components/Chat/context/types.ts
|
|
289
|
+
var ProviderId = /* @__PURE__ */ ((ProviderId2) => {
|
|
290
|
+
ProviderId2["SYMPHONY"] = "symphony";
|
|
291
|
+
ProviderId2["MOCK"] = "mock";
|
|
292
|
+
ProviderId2["OPENAI"] = "openai";
|
|
293
|
+
ProviderId2["GOOGLE"] = "google";
|
|
294
|
+
ProviderId2["GEMINI"] = "gemini";
|
|
295
|
+
ProviderId2["CLAUDE"] = "anthropic";
|
|
296
|
+
ProviderId2["GROK"] = "grok";
|
|
297
|
+
ProviderId2["OLLAMA"] = "ollama";
|
|
298
|
+
ProviderId2["LOCAL"] = "local";
|
|
299
|
+
return ProviderId2;
|
|
300
|
+
})(ProviderId || {});
|
|
301
|
+
var PROVIDER_IDS = new Set(Object.values(ProviderId));
|
|
302
|
+
var isProviderId = (value) => typeof value === "string" && PROVIDER_IDS.has(value);
|
|
303
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
304
|
+
var isRegistryModel = (value) => {
|
|
305
|
+
if (!isRecord(value) || typeof value.id !== "string" || typeof value.label !== "string") {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
if (value.description !== void 0 && typeof value.description !== "string") {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
if (value.thinkingLevels !== void 0 && (!Array.isArray(value.thinkingLevels) || !value.thinkingLevels.every((level) => typeof level === "string"))) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
return true;
|
|
315
|
+
};
|
|
316
|
+
var isRegistryProvider = (value) => isRecord(value) && typeof value.id === "string" && typeof value.label === "string" && typeof value.defaultModel === "string" && Array.isArray(value.models) && value.models.every(isRegistryModel) && (value.name === void 0 || typeof value.name === "string") && (value.logo === void 0 || typeof value.logo === "string");
|
|
317
|
+
var parseRegistryConfig = (value) => {
|
|
318
|
+
if (!isRecord(value) || typeof value.defaultProviderId !== "string" || !Array.isArray(value.providers)) {
|
|
319
|
+
return void 0;
|
|
320
|
+
}
|
|
321
|
+
const providers = value.providers.filter(isRegistryProvider);
|
|
322
|
+
if (providers.length === 0) {
|
|
323
|
+
return void 0;
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
defaultProviderId: value.defaultProviderId,
|
|
327
|
+
providers
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
var defaultRegistry = {
|
|
331
|
+
defaultProviderId: "mock" /* MOCK */,
|
|
332
|
+
providers: [
|
|
333
|
+
{
|
|
334
|
+
id: "symphony" /* SYMPHONY */,
|
|
335
|
+
label: "Symphony (custom harness)",
|
|
336
|
+
defaultModel: "",
|
|
337
|
+
models: []
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
id: "mock" /* MOCK */,
|
|
341
|
+
label: "Mock",
|
|
342
|
+
defaultModel: "canned",
|
|
343
|
+
models: [{ id: "canned", label: "Canned replies" }]
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
id: "openai" /* OPENAI */,
|
|
347
|
+
label: "OpenAI",
|
|
348
|
+
defaultModel: "gpt-4.1",
|
|
349
|
+
models: [{ id: "gpt-4.1", label: "GPT-4.1" }]
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: "anthropic" /* CLAUDE */,
|
|
353
|
+
label: "Anthropic",
|
|
354
|
+
defaultModel: "claude-3-7-sonnet-20250219",
|
|
355
|
+
models: [{ id: "claude-3-7-sonnet-20250219", label: "Claude 3.7 Sonnet" }]
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
id: "google" /* GOOGLE */,
|
|
359
|
+
label: "Google",
|
|
360
|
+
defaultModel: "gemini-2.5-flash",
|
|
361
|
+
models: [{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash" }]
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
id: "ollama" /* OLLAMA */,
|
|
365
|
+
label: "Ollama",
|
|
366
|
+
defaultModel: "smallthinker:latest",
|
|
367
|
+
models: [{ id: "smallthinker:latest", label: "smallthinker:latest" }]
|
|
368
|
+
}
|
|
369
|
+
]
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// src/components/Chat/context/model.context.tsx
|
|
373
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
374
|
+
var ModelContext = createContext(null);
|
|
375
|
+
function ModelProvider({
|
|
376
|
+
children,
|
|
377
|
+
defaultProvider = "mock" /* MOCK */,
|
|
378
|
+
registryUrl = "/api/ai/registry"
|
|
379
|
+
}) {
|
|
380
|
+
const [registry, setRegistry] = useState2(defaultRegistry);
|
|
381
|
+
const [provider, setProviderState] = useState2(defaultProvider);
|
|
382
|
+
const [model, setModelState] = useState2(
|
|
383
|
+
() => {
|
|
384
|
+
var _a, _b;
|
|
385
|
+
return (_b = (_a = defaultRegistry.providers.find((entry) => entry.id === defaultProvider)) == null ? void 0 : _a.defaultModel) != null ? _b : "";
|
|
386
|
+
}
|
|
387
|
+
);
|
|
388
|
+
const [thinkingLevels, setThinkingLevelsState] = useState2(() => {
|
|
389
|
+
const initial = {};
|
|
390
|
+
for (const p of defaultRegistry.providers) {
|
|
391
|
+
for (const m of p.models) {
|
|
392
|
+
const levels = m.thinkingLevels;
|
|
393
|
+
if (levels == null ? void 0 : levels.length) initial[`${p.id}::${m.id}`] = levels.includes("medium") ? "medium" : levels[0];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return initial;
|
|
397
|
+
});
|
|
398
|
+
const providerRef = useRef2(provider);
|
|
399
|
+
const modelRef = useRef2(model);
|
|
400
|
+
const thinkingLevelsRef = useRef2(thinkingLevels);
|
|
401
|
+
providerRef.current = provider;
|
|
402
|
+
modelRef.current = model;
|
|
403
|
+
thinkingLevelsRef.current = thinkingLevels;
|
|
404
|
+
useEffect2(() => {
|
|
405
|
+
let cancelled = false;
|
|
406
|
+
const loadRegistry = async () => {
|
|
407
|
+
var _a;
|
|
408
|
+
try {
|
|
409
|
+
const response = await fetch(registryUrl, { cache: "no-store" });
|
|
410
|
+
if (!response.ok) {
|
|
411
|
+
throw new Error("Failed to load registry");
|
|
412
|
+
}
|
|
413
|
+
const data = parseRegistryConfig(await response.json());
|
|
414
|
+
if (cancelled || !data) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
setRegistry(data);
|
|
418
|
+
const nextThinking = {};
|
|
419
|
+
for (const p of data.providers) {
|
|
420
|
+
for (const m of p.models) {
|
|
421
|
+
const levels = m.thinkingLevels;
|
|
422
|
+
if (levels == null ? void 0 : levels.length) nextThinking[`${p.id}::${m.id}`] = levels.includes("medium") ? "medium" : levels[0];
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
setThinkingLevelsState((prev) => ({ ...nextThinking, ...prev }));
|
|
426
|
+
const preferred = (_a = data.providers.find((entry) => entry.id === data.defaultProviderId)) != null ? _a : data.providers[0];
|
|
427
|
+
if (preferred) {
|
|
428
|
+
setProviderState(preferred.id);
|
|
429
|
+
setModelState(preferred.defaultModel);
|
|
430
|
+
}
|
|
431
|
+
} catch {
|
|
432
|
+
if (!cancelled) {
|
|
433
|
+
setRegistry(defaultRegistry);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
void loadRegistry();
|
|
438
|
+
return () => {
|
|
439
|
+
cancelled = true;
|
|
440
|
+
};
|
|
441
|
+
}, [registryUrl]);
|
|
442
|
+
useEffect2(() => {
|
|
443
|
+
var _a, _b;
|
|
444
|
+
const nextProvider = (_b = (_a = registry.providers.find((entry) => entry.id === provider)) != null ? _a : registry.providers.find((entry) => entry.id === registry.defaultProviderId)) != null ? _b : registry.providers[0];
|
|
445
|
+
if (!nextProvider) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const hasModel = nextProvider.models.some((entry) => entry.id === model);
|
|
449
|
+
if (!hasModel) {
|
|
450
|
+
setProviderState(nextProvider.id);
|
|
451
|
+
setModelState(nextProvider.defaultModel);
|
|
452
|
+
}
|
|
453
|
+
}, [model, provider, registry]);
|
|
454
|
+
const setProvider = useCallback((nextProvider, nextModel) => {
|
|
455
|
+
setProviderState(nextProvider);
|
|
456
|
+
if (nextModel) {
|
|
457
|
+
setModelState(nextModel);
|
|
458
|
+
}
|
|
459
|
+
}, []);
|
|
460
|
+
const getThinkingLevel = useCallback(
|
|
461
|
+
(providerId, modelId) => thinkingLevels[`${providerId}::${modelId}`],
|
|
462
|
+
[thinkingLevels]
|
|
463
|
+
);
|
|
464
|
+
const setThinkingLevel = useCallback((providerId, modelId, level) => {
|
|
465
|
+
setThinkingLevelsState((prev) => ({ ...prev, [`${providerId}::${modelId}`]: level }));
|
|
466
|
+
}, []);
|
|
467
|
+
const setModel = useCallback((nextModel) => {
|
|
468
|
+
setModelState(nextModel);
|
|
469
|
+
}, []);
|
|
470
|
+
const value = useMemo(
|
|
471
|
+
() => ({
|
|
472
|
+
registry,
|
|
473
|
+
provider,
|
|
474
|
+
model,
|
|
475
|
+
thinkingLevels,
|
|
476
|
+
getThinkingLevel,
|
|
477
|
+
setThinkingLevel,
|
|
478
|
+
setProvider,
|
|
479
|
+
setModel,
|
|
480
|
+
providerRef,
|
|
481
|
+
modelRef,
|
|
482
|
+
thinkingLevelsRef
|
|
483
|
+
}),
|
|
484
|
+
[model, provider, registry, setModel, setProvider, thinkingLevels, getThinkingLevel, setThinkingLevel]
|
|
485
|
+
);
|
|
486
|
+
return /* @__PURE__ */ jsx3(ModelContext.Provider, { value, children });
|
|
487
|
+
}
|
|
488
|
+
function useModel() {
|
|
489
|
+
const context = useContext(ModelContext);
|
|
490
|
+
if (!context) {
|
|
491
|
+
throw new Error("useModel must be used inside ModelProvider");
|
|
492
|
+
}
|
|
493
|
+
return context;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// src/components/Chat/context/thread.context.tsx
|
|
497
|
+
import {
|
|
498
|
+
createContext as createContext2,
|
|
499
|
+
useCallback as useCallback2,
|
|
500
|
+
useContext as useContext2,
|
|
501
|
+
useEffect as useEffect3,
|
|
502
|
+
useMemo as useMemo2,
|
|
503
|
+
useRef as useRef3,
|
|
504
|
+
useState as useState3
|
|
505
|
+
} from "react";
|
|
506
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
507
|
+
var ThreadContext = createContext2(null);
|
|
508
|
+
function ThreadProvider({
|
|
509
|
+
adapter,
|
|
510
|
+
children,
|
|
511
|
+
defaultThreadId
|
|
512
|
+
}) {
|
|
513
|
+
const [threads, setThreads] = useState3([]);
|
|
514
|
+
const [activeThreadId, setActiveThreadId] = useState3(defaultThreadId != null ? defaultThreadId : null);
|
|
515
|
+
const [isLoadingThread, setIsLoadingThread] = useState3(true);
|
|
516
|
+
const activeThreadIdRef = useRef3(activeThreadId);
|
|
517
|
+
activeThreadIdRef.current = activeThreadId;
|
|
518
|
+
const adapterRef = useRef3(adapter);
|
|
519
|
+
adapterRef.current = adapter;
|
|
520
|
+
useEffect3(() => {
|
|
521
|
+
let cancelled = false;
|
|
522
|
+
const bootstrap = async () => {
|
|
523
|
+
setIsLoadingThread(true);
|
|
524
|
+
if (defaultThreadId) {
|
|
525
|
+
setThreads(
|
|
526
|
+
(current) => current.some((thread2) => thread2.id === defaultThreadId) ? current : [{ id: defaultThreadId, title: "New chat" }, ...current]
|
|
527
|
+
);
|
|
528
|
+
setActiveThreadId(defaultThreadId);
|
|
529
|
+
setIsLoadingThread(false);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const currentAdapter = adapterRef.current;
|
|
533
|
+
if (!currentAdapter.listThreads || !currentAdapter.createThread) {
|
|
534
|
+
const localThread = { id: createMessageId("thread"), title: "New chat" };
|
|
535
|
+
setThreads([localThread]);
|
|
536
|
+
setActiveThreadId(localThread.id);
|
|
537
|
+
setIsLoadingThread(false);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const nextThreads = await currentAdapter.listThreads();
|
|
541
|
+
if (cancelled) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (nextThreads.length > 0) {
|
|
545
|
+
setThreads(nextThreads);
|
|
546
|
+
setActiveThreadId(nextThreads[0].id);
|
|
547
|
+
setIsLoadingThread(false);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const thread = await currentAdapter.createThread();
|
|
551
|
+
if (cancelled) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
setThreads([thread]);
|
|
555
|
+
setActiveThreadId(thread.id);
|
|
556
|
+
setIsLoadingThread(false);
|
|
557
|
+
};
|
|
558
|
+
bootstrap().catch(() => {
|
|
559
|
+
if (!cancelled) {
|
|
560
|
+
setIsLoadingThread(false);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
return () => {
|
|
564
|
+
cancelled = true;
|
|
565
|
+
};
|
|
566
|
+
}, [defaultThreadId]);
|
|
567
|
+
const selectThread = useCallback2(async (threadId) => {
|
|
568
|
+
setActiveThreadId((current) => current === threadId ? current : threadId);
|
|
569
|
+
}, []);
|
|
570
|
+
const createThread = useCallback2(async () => {
|
|
571
|
+
const currentAdapter = adapterRef.current;
|
|
572
|
+
const thread = currentAdapter.createThread ? await currentAdapter.createThread() : { id: createMessageId("thread"), title: "New chat" };
|
|
573
|
+
setThreads((current) => [thread, ...current]);
|
|
574
|
+
setActiveThreadId(thread.id);
|
|
575
|
+
}, []);
|
|
576
|
+
const deleteThread = useCallback2(
|
|
577
|
+
async (threadId) => {
|
|
578
|
+
var _a, _b;
|
|
579
|
+
await ((_b = (_a = adapterRef.current).deleteThread) == null ? void 0 : _b.call(_a, threadId));
|
|
580
|
+
setThreads((current) => {
|
|
581
|
+
const remaining = current.filter((thread) => thread.id !== threadId);
|
|
582
|
+
setActiveThreadId((active) => {
|
|
583
|
+
var _a2, _b2;
|
|
584
|
+
if (active !== threadId) {
|
|
585
|
+
return active;
|
|
586
|
+
}
|
|
587
|
+
const nextActive = (_b2 = (_a2 = remaining[0]) == null ? void 0 : _a2.id) != null ? _b2 : null;
|
|
588
|
+
if (!nextActive) {
|
|
589
|
+
void createThread();
|
|
590
|
+
}
|
|
591
|
+
return nextActive;
|
|
592
|
+
});
|
|
593
|
+
return remaining;
|
|
594
|
+
});
|
|
595
|
+
},
|
|
596
|
+
[createThread]
|
|
597
|
+
);
|
|
598
|
+
const value = useMemo2(
|
|
599
|
+
() => ({
|
|
600
|
+
threads,
|
|
601
|
+
activeThreadId,
|
|
602
|
+
isLoadingThread,
|
|
603
|
+
activeThreadIdRef,
|
|
604
|
+
setThreads,
|
|
605
|
+
setIsLoadingThread,
|
|
606
|
+
selectThread,
|
|
607
|
+
createThread,
|
|
608
|
+
deleteThread
|
|
609
|
+
}),
|
|
610
|
+
[
|
|
611
|
+
activeThreadId,
|
|
612
|
+
createThread,
|
|
613
|
+
deleteThread,
|
|
614
|
+
isLoadingThread,
|
|
615
|
+
selectThread,
|
|
616
|
+
threads
|
|
617
|
+
]
|
|
618
|
+
);
|
|
619
|
+
return /* @__PURE__ */ jsx4(ThreadContext.Provider, { value, children });
|
|
620
|
+
}
|
|
621
|
+
function useThread() {
|
|
622
|
+
const context = useContext2(ThreadContext);
|
|
623
|
+
if (!context) {
|
|
624
|
+
throw new Error("useThread must be used inside ThreadProvider");
|
|
625
|
+
}
|
|
626
|
+
return context;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/components/Chat/context/messages.context.tsx
|
|
630
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
631
|
+
var MINIMUM_RESPONSE_LOADING_MS = 350;
|
|
632
|
+
var wait = (milliseconds) => new Promise((resolve) => window.setTimeout(resolve, milliseconds));
|
|
633
|
+
var hasVisibleAssistantContent = (message) => message.parts.some((part) => {
|
|
634
|
+
if (part.type === "text") {
|
|
635
|
+
return "text" in part && typeof part.text === "string" && part.text.trim().length > 0;
|
|
636
|
+
}
|
|
637
|
+
if (part.type === "data") {
|
|
638
|
+
return "name" in part && part.name !== "usage" && part.name !== "context" && part.name !== "context-warning";
|
|
639
|
+
}
|
|
640
|
+
return ["reasoning", "tool", "widget", "source-url", "source-document", "file", "unknown"].includes(part.type);
|
|
641
|
+
});
|
|
642
|
+
var MessagesContext = createContext3(null);
|
|
643
|
+
function MessagesProvider({ adapter, children }) {
|
|
644
|
+
const { activeThreadId, activeThreadIdRef, setThreads, setIsLoadingThread } = useThread();
|
|
645
|
+
const { providerRef, modelRef, thinkingLevelsRef } = useModel();
|
|
646
|
+
const [messages, setMessages] = useState4([]);
|
|
647
|
+
const [status, setStatus] = useState4("ready");
|
|
648
|
+
const [isWaitingForResponse, setIsWaitingForResponse] = useState4(false);
|
|
649
|
+
const messagesRef = useRef4(messages);
|
|
650
|
+
const statusRef = useRef4(status);
|
|
651
|
+
const adapterRef = useRef4(adapter);
|
|
652
|
+
const abortRef = useRef4(null);
|
|
653
|
+
messagesRef.current = messages;
|
|
654
|
+
statusRef.current = status;
|
|
655
|
+
adapterRef.current = adapter;
|
|
656
|
+
useEffect4(() => {
|
|
657
|
+
let cancelled = false;
|
|
658
|
+
const loadMessages = async () => {
|
|
659
|
+
if (!activeThreadId) {
|
|
660
|
+
setMessages([]);
|
|
661
|
+
setIsLoadingThread(false);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const currentAdapter = adapterRef.current;
|
|
665
|
+
if (!currentAdapter.loadMessages) {
|
|
666
|
+
setIsLoadingThread(false);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
setIsLoadingThread(true);
|
|
670
|
+
try {
|
|
671
|
+
const nextMessages = await currentAdapter.loadMessages(activeThreadId);
|
|
672
|
+
if (cancelled) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
setMessages(nextMessages);
|
|
676
|
+
setIsLoadingThread(false);
|
|
677
|
+
} catch {
|
|
678
|
+
if (!cancelled) {
|
|
679
|
+
setMessages([]);
|
|
680
|
+
setIsLoadingThread(false);
|
|
681
|
+
setStatus("error");
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
void loadMessages();
|
|
686
|
+
return () => {
|
|
687
|
+
cancelled = true;
|
|
688
|
+
};
|
|
689
|
+
}, [activeThreadId, setIsLoadingThread]);
|
|
690
|
+
const streamMessage = useCallback3(
|
|
691
|
+
async ({
|
|
692
|
+
message,
|
|
693
|
+
nextMessages,
|
|
694
|
+
provider,
|
|
695
|
+
model,
|
|
696
|
+
thinkingLevel
|
|
697
|
+
}) => {
|
|
698
|
+
var _a;
|
|
699
|
+
const threadId = activeThreadIdRef.current;
|
|
700
|
+
if (!threadId) {
|
|
701
|
+
setIsWaitingForResponse(false);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
(_a = abortRef.current) == null ? void 0 : _a.abort();
|
|
705
|
+
const abortController = new AbortController();
|
|
706
|
+
abortRef.current = abortController;
|
|
707
|
+
setStatus("submitted");
|
|
708
|
+
const responseStartedAt = Date.now();
|
|
709
|
+
let receivedVisibleResponse = false;
|
|
710
|
+
try {
|
|
711
|
+
for await (const assistantMessage of adapterRef.current.sendMessage({
|
|
712
|
+
threadId,
|
|
713
|
+
message,
|
|
714
|
+
messages: nextMessages,
|
|
715
|
+
provider,
|
|
716
|
+
model,
|
|
717
|
+
thinkingLevel,
|
|
718
|
+
signal: abortController.signal
|
|
719
|
+
})) {
|
|
720
|
+
if (!receivedVisibleResponse && hasVisibleAssistantContent(assistantMessage)) {
|
|
721
|
+
receivedVisibleResponse = true;
|
|
722
|
+
const remaining = MINIMUM_RESPONSE_LOADING_MS - (Date.now() - responseStartedAt);
|
|
723
|
+
if (remaining > 0) {
|
|
724
|
+
await wait(remaining);
|
|
725
|
+
}
|
|
726
|
+
if (abortController.signal.aborted) {
|
|
727
|
+
setIsWaitingForResponse(false);
|
|
728
|
+
setStatus("ready");
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (receivedVisibleResponse) {
|
|
733
|
+
setIsWaitingForResponse(false);
|
|
734
|
+
setStatus("streaming");
|
|
735
|
+
}
|
|
736
|
+
setMessages((current) => upsertAssistantMessage(current, assistantMessage));
|
|
737
|
+
}
|
|
738
|
+
setStatus("ready");
|
|
739
|
+
} catch (error) {
|
|
740
|
+
setIsWaitingForResponse(false);
|
|
741
|
+
if (abortController.signal.aborted) {
|
|
742
|
+
setStatus("ready");
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
setStatus("error");
|
|
746
|
+
throw error;
|
|
747
|
+
} finally {
|
|
748
|
+
setIsWaitingForResponse(false);
|
|
749
|
+
if (abortRef.current === abortController) {
|
|
750
|
+
abortRef.current = null;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
[activeThreadIdRef]
|
|
755
|
+
);
|
|
756
|
+
const sendMessage = useCallback3(
|
|
757
|
+
async (message, options) => {
|
|
758
|
+
var _a, _b, _c, _d, _e;
|
|
759
|
+
const text = message.text.trim();
|
|
760
|
+
const files = (_a = message.files) != null ? _a : [];
|
|
761
|
+
const threadId = activeThreadIdRef.current;
|
|
762
|
+
if (!text && files.length === 0 || !threadId) {
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
const userMessage = createUserMessage(text, files);
|
|
766
|
+
const nextMessages = [...messagesRef.current, userMessage];
|
|
767
|
+
flushSync(() => {
|
|
768
|
+
setMessages(nextMessages);
|
|
769
|
+
setIsWaitingForResponse(true);
|
|
770
|
+
});
|
|
771
|
+
const resolvedProvider = (_c = (_b = options == null ? void 0 : options.body) == null ? void 0 : _b.provider) != null ? _c : providerRef.current;
|
|
772
|
+
const resolvedModel = (_e = (_d = options == null ? void 0 : options.body) == null ? void 0 : _d.model) != null ? _e : modelRef.current;
|
|
773
|
+
const thinkingLevel = thinkingLevelsRef.current[`${resolvedProvider}::${resolvedModel}`];
|
|
774
|
+
await streamMessage({
|
|
775
|
+
message: userMessage,
|
|
776
|
+
nextMessages,
|
|
777
|
+
provider: resolvedProvider,
|
|
778
|
+
model: resolvedModel,
|
|
779
|
+
thinkingLevel
|
|
780
|
+
});
|
|
781
|
+
},
|
|
782
|
+
[activeThreadIdRef, modelRef, providerRef, streamMessage]
|
|
783
|
+
);
|
|
784
|
+
const editAndResendMessage = useCallback3(
|
|
785
|
+
async (messageId, text) => {
|
|
786
|
+
var _a;
|
|
787
|
+
const trimmedText = text.trim();
|
|
788
|
+
const threadId = activeThreadIdRef.current;
|
|
789
|
+
const isSending2 = statusRef.current === "submitted" || statusRef.current === "streaming";
|
|
790
|
+
if (!trimmedText || !threadId || isSending2) {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const previousMessages = messagesRef.current;
|
|
794
|
+
const messageIndex = previousMessages.findIndex((message2) => message2.id === messageId);
|
|
795
|
+
const message = previousMessages[messageIndex];
|
|
796
|
+
if (!message || message.role !== "user") {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
let editedMessages = previousMessages.slice(0, messageIndex + 1).map(
|
|
800
|
+
(currentMessage) => currentMessage.id === messageId ? updateMessageText(currentMessage, trimmedText) : currentMessage
|
|
801
|
+
);
|
|
802
|
+
setThreads((current) => {
|
|
803
|
+
const firstUserMessage = editedMessages.find(
|
|
804
|
+
(currentMessage) => currentMessage.role === "user"
|
|
805
|
+
);
|
|
806
|
+
const firstUserTitle = firstUserMessage ? getUserDisplayText(firstUserMessage).trim().slice(0, 60) : "";
|
|
807
|
+
return current.map(
|
|
808
|
+
(thread) => thread.id === threadId && firstUserTitle ? { ...thread, title: firstUserTitle } : thread
|
|
809
|
+
);
|
|
810
|
+
});
|
|
811
|
+
try {
|
|
812
|
+
if (adapterRef.current.editMessage) {
|
|
813
|
+
editedMessages = await adapterRef.current.editMessage({
|
|
814
|
+
threadId,
|
|
815
|
+
messageId,
|
|
816
|
+
text: trimmedText
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
const editedMessage = (_a = editedMessages.find((currentMessage) => currentMessage.id === messageId)) != null ? _a : updateMessageText(message, trimmedText);
|
|
820
|
+
setMessages(editedMessages);
|
|
821
|
+
setIsWaitingForResponse(true);
|
|
822
|
+
const thinkingLevel = thinkingLevelsRef.current[`${providerRef.current}::${modelRef.current}`];
|
|
823
|
+
await streamMessage({
|
|
824
|
+
message: editedMessage,
|
|
825
|
+
nextMessages: editedMessages,
|
|
826
|
+
provider: providerRef.current,
|
|
827
|
+
model: modelRef.current,
|
|
828
|
+
thinkingLevel
|
|
829
|
+
});
|
|
830
|
+
} catch (error) {
|
|
831
|
+
setIsWaitingForResponse(false);
|
|
832
|
+
setMessages(previousMessages);
|
|
833
|
+
throw error;
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
[activeThreadIdRef, modelRef, providerRef, setThreads, streamMessage]
|
|
837
|
+
);
|
|
838
|
+
const stopResponse = useCallback3(() => {
|
|
839
|
+
var _a;
|
|
840
|
+
const isSending2 = statusRef.current === "submitted" || statusRef.current === "streaming";
|
|
841
|
+
if (!isSending2) {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
(_a = abortRef.current) == null ? void 0 : _a.abort();
|
|
845
|
+
setIsWaitingForResponse(false);
|
|
846
|
+
setStatus("ready");
|
|
847
|
+
}, []);
|
|
848
|
+
const isSending = status === "submitted" || status === "streaming";
|
|
849
|
+
const value = useMemo3(
|
|
850
|
+
() => ({
|
|
851
|
+
messages,
|
|
852
|
+
status,
|
|
853
|
+
isSending,
|
|
854
|
+
isWaitingForResponse,
|
|
855
|
+
sendMessage,
|
|
856
|
+
editAndResendMessage,
|
|
857
|
+
stopResponse
|
|
858
|
+
}),
|
|
859
|
+
[editAndResendMessage, isSending, isWaitingForResponse, messages, sendMessage, status, stopResponse]
|
|
860
|
+
);
|
|
861
|
+
return /* @__PURE__ */ jsx5(MessagesContext.Provider, { value, children });
|
|
862
|
+
}
|
|
863
|
+
function useMessages() {
|
|
864
|
+
const context = useContext3(MessagesContext);
|
|
865
|
+
if (!context) {
|
|
866
|
+
throw new Error("useMessages must be used inside MessagesProvider");
|
|
867
|
+
}
|
|
868
|
+
return context;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// src/components/Chat/context/composer.context.tsx
|
|
872
|
+
import { jsx as jsx6, jsxs } from "react/jsx-runtime";
|
|
873
|
+
var readFileAsDataUrl = (file) => new Promise((resolve, reject) => {
|
|
874
|
+
const reader = new FileReader();
|
|
875
|
+
reader.onload = () => {
|
|
876
|
+
if (typeof reader.result === "string") {
|
|
877
|
+
resolve(reader.result);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
reject(new Error("Could not read file"));
|
|
881
|
+
};
|
|
882
|
+
reader.onerror = () => {
|
|
883
|
+
var _a;
|
|
884
|
+
reject((_a = reader.error) != null ? _a : new Error("Could not read file"));
|
|
885
|
+
};
|
|
886
|
+
reader.readAsDataURL(file);
|
|
887
|
+
});
|
|
888
|
+
var ComposerContext = createContext4(null);
|
|
889
|
+
function ComposerProvider({ children }) {
|
|
890
|
+
const { activeThreadId, activeThreadIdRef, isLoadingThread, setThreads } = useThread();
|
|
891
|
+
const { providerRef, modelRef } = useModel();
|
|
892
|
+
const { sendMessage, isSending } = useMessages();
|
|
893
|
+
const [input, setInputState] = useState5("");
|
|
894
|
+
const [references, setReferences] = useState5([]);
|
|
895
|
+
const [attachments, setAttachments] = useState5([]);
|
|
896
|
+
const inputRef = useRef5(input);
|
|
897
|
+
const referencesRef = useRef5(references);
|
|
898
|
+
const attachmentsRef = useRef5(attachments);
|
|
899
|
+
inputRef.current = input;
|
|
900
|
+
referencesRef.current = references;
|
|
901
|
+
attachmentsRef.current = attachments;
|
|
902
|
+
const setInput = useCallback4((value2) => {
|
|
903
|
+
setInputState(value2);
|
|
904
|
+
}, []);
|
|
905
|
+
const addReference = useCallback4((text) => {
|
|
906
|
+
const normalized = normalizeReferenceText(text);
|
|
907
|
+
if (!normalized) {
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
setReferences((current) => {
|
|
911
|
+
if (current.some((reference) => reference.text === normalized)) {
|
|
912
|
+
return current;
|
|
913
|
+
}
|
|
914
|
+
return [
|
|
915
|
+
...current,
|
|
916
|
+
{
|
|
917
|
+
id: createMessageId("ref"),
|
|
918
|
+
text: normalized
|
|
919
|
+
}
|
|
920
|
+
];
|
|
921
|
+
});
|
|
922
|
+
}, []);
|
|
923
|
+
const removeReference = useCallback4((id) => {
|
|
924
|
+
setReferences((current) => current.filter((reference) => reference.id !== id));
|
|
925
|
+
}, []);
|
|
926
|
+
const clearReferences = useCallback4(() => {
|
|
927
|
+
setReferences([]);
|
|
928
|
+
}, []);
|
|
929
|
+
const addFiles = useCallback4(async (list) => {
|
|
930
|
+
const incoming = Array.from(list);
|
|
931
|
+
const next = [];
|
|
932
|
+
for (const file of incoming) {
|
|
933
|
+
try {
|
|
934
|
+
const url = await readFileAsDataUrl(file);
|
|
935
|
+
next.push({
|
|
936
|
+
id: createMessageId("file"),
|
|
937
|
+
filename: file.name || "file",
|
|
938
|
+
mediaType: file.type || "application/octet-stream",
|
|
939
|
+
url
|
|
940
|
+
});
|
|
941
|
+
} catch {
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
if (next.length === 0) {
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
setAttachments((current) => [...current, ...next]);
|
|
948
|
+
}, []);
|
|
949
|
+
const removeAttachment = useCallback4((id) => {
|
|
950
|
+
setAttachments((current) => current.filter((attachment) => attachment.id !== id));
|
|
951
|
+
}, []);
|
|
952
|
+
const disabled = isSending || isLoadingThread || !activeThreadId;
|
|
953
|
+
const canSend = !disabled && (!!input.trim() || attachments.length > 0);
|
|
954
|
+
const disabledRef = useRef5(disabled);
|
|
955
|
+
disabledRef.current = disabled;
|
|
956
|
+
const submitInput = useCallback4(async () => {
|
|
957
|
+
var _a;
|
|
958
|
+
const text = inputRef.current.trim();
|
|
959
|
+
const threadId = activeThreadIdRef.current;
|
|
960
|
+
const attachmentsSnapshot = attachmentsRef.current;
|
|
961
|
+
if (!text && attachmentsSnapshot.length === 0 || !threadId || disabledRef.current) {
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const nextTitle = (text || ((_a = attachmentsSnapshot[0]) == null ? void 0 : _a.filename) || "").slice(0, 60);
|
|
965
|
+
const referencesSnapshot = referencesRef.current;
|
|
966
|
+
const referenceText = referencesSnapshot.map(
|
|
967
|
+
(reference, index) => `<reference ${index + 1}>
|
|
968
|
+
${reference.text}
|
|
969
|
+
</reference ${index + 1}>`
|
|
970
|
+
).join("\n\n");
|
|
971
|
+
const messageText = referenceText ? `Use the following selected references as context:
|
|
972
|
+
|
|
973
|
+
${referenceText}
|
|
974
|
+
|
|
975
|
+
User message:
|
|
976
|
+
${text}` : text;
|
|
977
|
+
setInputState("");
|
|
978
|
+
setReferences([]);
|
|
979
|
+
setAttachments([]);
|
|
980
|
+
setThreads((current) => {
|
|
981
|
+
const next = current.map(
|
|
982
|
+
(thread) => thread.id === threadId && thread.title === "New chat" ? { ...thread, title: nextTitle || thread.title } : thread
|
|
983
|
+
);
|
|
984
|
+
const selected = next.find((thread) => thread.id === threadId);
|
|
985
|
+
const remaining = next.filter((thread) => thread.id !== threadId);
|
|
986
|
+
return selected ? [selected, ...remaining] : next;
|
|
987
|
+
});
|
|
988
|
+
try {
|
|
989
|
+
await sendMessage(
|
|
990
|
+
{
|
|
991
|
+
text: messageText,
|
|
992
|
+
files: attachmentsSnapshot.map((file) => ({
|
|
993
|
+
mediaType: file.mediaType,
|
|
994
|
+
url: file.url,
|
|
995
|
+
filename: file.filename
|
|
996
|
+
}))
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
body: {
|
|
1000
|
+
provider: providerRef.current,
|
|
1001
|
+
model: modelRef.current
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
);
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
setInputState(text);
|
|
1007
|
+
setReferences(referencesSnapshot);
|
|
1008
|
+
setAttachments(attachmentsSnapshot);
|
|
1009
|
+
throw error;
|
|
1010
|
+
}
|
|
1011
|
+
}, [activeThreadIdRef, modelRef, providerRef, sendMessage, setThreads]);
|
|
1012
|
+
const value = useMemo4(
|
|
1013
|
+
() => ({
|
|
1014
|
+
input,
|
|
1015
|
+
setInput,
|
|
1016
|
+
references,
|
|
1017
|
+
addReference,
|
|
1018
|
+
removeReference,
|
|
1019
|
+
clearReferences,
|
|
1020
|
+
attachments,
|
|
1021
|
+
addFiles,
|
|
1022
|
+
removeAttachment,
|
|
1023
|
+
disabled,
|
|
1024
|
+
canSend,
|
|
1025
|
+
submitInput
|
|
1026
|
+
}),
|
|
1027
|
+
[
|
|
1028
|
+
addReference,
|
|
1029
|
+
canSend,
|
|
1030
|
+
clearReferences,
|
|
1031
|
+
disabled,
|
|
1032
|
+
input,
|
|
1033
|
+
addFiles,
|
|
1034
|
+
attachments,
|
|
1035
|
+
references,
|
|
1036
|
+
removeAttachment,
|
|
1037
|
+
removeReference,
|
|
1038
|
+
setInput,
|
|
1039
|
+
submitInput
|
|
1040
|
+
]
|
|
1041
|
+
);
|
|
1042
|
+
return /* @__PURE__ */ jsxs(ComposerContext.Provider, { value, children: [
|
|
1043
|
+
children,
|
|
1044
|
+
/* @__PURE__ */ jsx6(ChatTooltip, { onAddReference: addReference })
|
|
1045
|
+
] });
|
|
1046
|
+
}
|
|
1047
|
+
function useComposer() {
|
|
1048
|
+
const context = useContext4(ComposerContext);
|
|
1049
|
+
if (!context) {
|
|
1050
|
+
throw new Error("useComposer must be used inside ComposerProvider");
|
|
1051
|
+
}
|
|
1052
|
+
return context;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// src/components/Chat/context/chat.provider.tsx
|
|
1056
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1057
|
+
function ChatContextProvider({
|
|
1058
|
+
adapter,
|
|
1059
|
+
children,
|
|
1060
|
+
defaultProvider = "mock" /* MOCK */,
|
|
1061
|
+
defaultThreadId,
|
|
1062
|
+
registryUrl = "/api/ai/registry"
|
|
1063
|
+
}) {
|
|
1064
|
+
return /* @__PURE__ */ jsx7(ModelProvider, { defaultProvider, registryUrl, children: /* @__PURE__ */ jsx7(ThreadProvider, { adapter, defaultThreadId, children: /* @__PURE__ */ jsx7(MessagesProvider, { adapter, children: /* @__PURE__ */ jsx7(ComposerProvider, { children }) }) }) });
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// src/components/Chat/context/index.ts
|
|
1068
|
+
function useChat() {
|
|
1069
|
+
const composer = useComposer();
|
|
1070
|
+
const messages = useMessages();
|
|
1071
|
+
const model = useModel();
|
|
1072
|
+
const thread = useThread();
|
|
1073
|
+
return useMemo5(() => {
|
|
1074
|
+
const dispatch = (action) => {
|
|
1075
|
+
switch (action.type) {
|
|
1076
|
+
case "setInput":
|
|
1077
|
+
composer.setInput(action.data.input);
|
|
1078
|
+
break;
|
|
1079
|
+
case "setProvider":
|
|
1080
|
+
model.setProvider(action.data.provider, action.data.model);
|
|
1081
|
+
break;
|
|
1082
|
+
case "setModel":
|
|
1083
|
+
model.setModel(action.data.model);
|
|
1084
|
+
break;
|
|
1085
|
+
case "addReference":
|
|
1086
|
+
composer.addReference(action.data.text);
|
|
1087
|
+
break;
|
|
1088
|
+
case "removeReference":
|
|
1089
|
+
composer.removeReference(action.data.id);
|
|
1090
|
+
break;
|
|
1091
|
+
case "clearReferences":
|
|
1092
|
+
composer.clearReferences();
|
|
1093
|
+
break;
|
|
1094
|
+
default:
|
|
1095
|
+
break;
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
return {
|
|
1099
|
+
state: {
|
|
1100
|
+
input: composer.input,
|
|
1101
|
+
provider: model.provider,
|
|
1102
|
+
model: model.model,
|
|
1103
|
+
references: composer.references,
|
|
1104
|
+
disabled: composer.disabled,
|
|
1105
|
+
sendDisabled: !composer.canSend
|
|
1106
|
+
},
|
|
1107
|
+
dispatch,
|
|
1108
|
+
messages: messages.messages,
|
|
1109
|
+
status: messages.status,
|
|
1110
|
+
activeThreadId: thread.activeThreadId,
|
|
1111
|
+
threads: thread.threads,
|
|
1112
|
+
isLoadingThread: thread.isLoadingThread,
|
|
1113
|
+
registry: model.registry,
|
|
1114
|
+
sendMessage: messages.sendMessage,
|
|
1115
|
+
submitInput: composer.submitInput,
|
|
1116
|
+
editAndResendMessage: messages.editAndResendMessage,
|
|
1117
|
+
stopResponse: messages.stopResponse,
|
|
1118
|
+
selectThread: thread.selectThread,
|
|
1119
|
+
createThread: thread.createThread,
|
|
1120
|
+
deleteThread: thread.deleteThread
|
|
1121
|
+
};
|
|
1122
|
+
}, [composer, messages, model, thread]);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// src/components/Chat/chat.attachments.tsx
|
|
1126
|
+
import { Fragment, jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1127
|
+
var isImageMediaType = (mediaType) => mediaType.startsWith("image/");
|
|
1128
|
+
var ChatAttachments = () => {
|
|
1129
|
+
const { attachments, removeAttachment } = useComposer();
|
|
1130
|
+
if (attachments.length === 0) {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
return /* @__PURE__ */ jsx8("div", { className: "chat-attachments", children: attachments.map((attachment) => {
|
|
1134
|
+
const image = isImageMediaType(attachment.mediaType);
|
|
1135
|
+
return /* @__PURE__ */ jsxs2(
|
|
1136
|
+
"div",
|
|
1137
|
+
{
|
|
1138
|
+
className: image ? "chat-attachment chat-attachment-image" : "chat-attachment chat-attachment-file",
|
|
1139
|
+
children: [
|
|
1140
|
+
image ? /* @__PURE__ */ jsx8("img", { src: attachment.url, alt: attachment.filename }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1141
|
+
/* @__PURE__ */ jsx8(FileText, { size: 14, "aria-hidden": "true" }),
|
|
1142
|
+
/* @__PURE__ */ jsx8("span", { className: "chat-attachment-name", children: attachment.filename })
|
|
1143
|
+
] }),
|
|
1144
|
+
/* @__PURE__ */ jsx8(
|
|
1145
|
+
"button",
|
|
1146
|
+
{
|
|
1147
|
+
type: "button",
|
|
1148
|
+
className: "chat-attachment-remove",
|
|
1149
|
+
"aria-label": `Remove ${attachment.filename}`,
|
|
1150
|
+
onClick: () => removeAttachment(attachment.id),
|
|
1151
|
+
children: /* @__PURE__ */ jsx8(X, { size: 12 })
|
|
1152
|
+
}
|
|
1153
|
+
)
|
|
1154
|
+
]
|
|
1155
|
+
},
|
|
1156
|
+
attachment.id
|
|
1157
|
+
);
|
|
1158
|
+
}) });
|
|
1159
|
+
};
|
|
1160
|
+
var ChatAttachButton = () => {
|
|
1161
|
+
const { disabled, addFiles } = useComposer();
|
|
1162
|
+
const inputRef = useRef6(null);
|
|
1163
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1164
|
+
/* @__PURE__ */ jsx8(
|
|
1165
|
+
"button",
|
|
1166
|
+
{
|
|
1167
|
+
type: "button",
|
|
1168
|
+
className: "chat-attach",
|
|
1169
|
+
"aria-label": "Add attachment",
|
|
1170
|
+
disabled,
|
|
1171
|
+
onClick: () => {
|
|
1172
|
+
var _a;
|
|
1173
|
+
return (_a = inputRef.current) == null ? void 0 : _a.click();
|
|
1174
|
+
},
|
|
1175
|
+
children: /* @__PURE__ */ jsx8(Paperclip, { size: 16 })
|
|
1176
|
+
}
|
|
1177
|
+
),
|
|
1178
|
+
/* @__PURE__ */ jsx8(
|
|
1179
|
+
"input",
|
|
1180
|
+
{
|
|
1181
|
+
ref: inputRef,
|
|
1182
|
+
className: "chat-attach-input",
|
|
1183
|
+
type: "file",
|
|
1184
|
+
multiple: true,
|
|
1185
|
+
tabIndex: -1,
|
|
1186
|
+
"aria-hidden": "true",
|
|
1187
|
+
onChange: (event) => {
|
|
1188
|
+
const files = event.currentTarget.files;
|
|
1189
|
+
if (files && files.length > 0) {
|
|
1190
|
+
void addFiles(files);
|
|
1191
|
+
}
|
|
1192
|
+
event.currentTarget.value = "";
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
)
|
|
1196
|
+
] });
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
// src/components/Chat/chat.input.tsx
|
|
1200
|
+
import { useEffect as useEffect5, useRef as useRef7 } from "react";
|
|
1201
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1202
|
+
var ChatInput = ({
|
|
1203
|
+
placeholder = "Message...",
|
|
1204
|
+
expanded = true
|
|
1205
|
+
}) => {
|
|
1206
|
+
const { input, setInput, disabled, submitInput, attachments } = useComposer();
|
|
1207
|
+
const ref = useRef7(null);
|
|
1208
|
+
const hasDraft = input.trim().length > 0 || attachments.length > 0;
|
|
1209
|
+
useEffect5(() => {
|
|
1210
|
+
const el = ref.current;
|
|
1211
|
+
if (!el) return;
|
|
1212
|
+
if (!expanded) {
|
|
1213
|
+
el.style.height = "";
|
|
1214
|
+
el.style.overflowY = "hidden";
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
el.style.height = "auto";
|
|
1218
|
+
const shell = el.closest(".chat-composer-shell");
|
|
1219
|
+
const isSmall = (shell == null ? void 0 : shell.dataset.variant) === "small";
|
|
1220
|
+
const max = isSmall ? 128 : 320;
|
|
1221
|
+
el.style.height = `${Math.min(el.scrollHeight, max)}px`;
|
|
1222
|
+
el.style.overflowY = el.scrollHeight > max ? "auto" : "hidden";
|
|
1223
|
+
}, [expanded, input]);
|
|
1224
|
+
return /* @__PURE__ */ jsx9("div", { className: "chat-composer-input", children: /* @__PURE__ */ jsx9(
|
|
1225
|
+
"textarea",
|
|
1226
|
+
{
|
|
1227
|
+
ref,
|
|
1228
|
+
value: input,
|
|
1229
|
+
onChange: (event) => {
|
|
1230
|
+
const wrap = event.currentTarget.closest(`.${motion.inputWrap}`);
|
|
1231
|
+
if (wrap) {
|
|
1232
|
+
clearErrorShake(wrap);
|
|
1233
|
+
}
|
|
1234
|
+
setInput(event.target.value);
|
|
1235
|
+
},
|
|
1236
|
+
placeholder,
|
|
1237
|
+
disabled,
|
|
1238
|
+
rows: 1,
|
|
1239
|
+
onKeyDown: (event) => {
|
|
1240
|
+
if (event.key !== "Enter" || event.shiftKey) return;
|
|
1241
|
+
if (event.nativeEvent.isComposing) return;
|
|
1242
|
+
event.preventDefault();
|
|
1243
|
+
if (disabled) {
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
if (!hasDraft) {
|
|
1247
|
+
const wrap = event.currentTarget.closest(`.${motion.inputWrap}`);
|
|
1248
|
+
if (wrap) {
|
|
1249
|
+
triggerErrorShake(wrap);
|
|
1250
|
+
}
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
void submitInput();
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
) });
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
// src/components/Chat/chat.references.tsx
|
|
1260
|
+
import { X as X2 } from "lucide-react";
|
|
1261
|
+
import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1262
|
+
var ChatReferences = () => {
|
|
1263
|
+
const { references, removeReference } = useComposer();
|
|
1264
|
+
if (references.length === 0) {
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
return /* @__PURE__ */ jsx10("div", { className: "chat-references", children: references.map((reference, index) => /* @__PURE__ */ jsxs3("div", { className: "chat-reference", title: reference.text, children: [
|
|
1268
|
+
/* @__PURE__ */ jsx10("span", { className: "chat-reference-text", children: reference.text }),
|
|
1269
|
+
/* @__PURE__ */ jsx10(
|
|
1270
|
+
"button",
|
|
1271
|
+
{
|
|
1272
|
+
type: "button",
|
|
1273
|
+
className: "chat-icon-button",
|
|
1274
|
+
"aria-label": `Remove reference ${index + 1}`,
|
|
1275
|
+
onClick: () => removeReference(reference.id),
|
|
1276
|
+
children: /* @__PURE__ */ jsx10(X2, { size: 12 })
|
|
1277
|
+
}
|
|
1278
|
+
)
|
|
1279
|
+
] }, reference.id)) });
|
|
1280
|
+
};
|
|
1281
|
+
|
|
1282
|
+
// src/components/Chat/chat.send.tsx
|
|
1283
|
+
import { Send, Square } from "lucide-react";
|
|
1284
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1285
|
+
var ChatSend = () => {
|
|
1286
|
+
const { canSend, submitInput } = useComposer();
|
|
1287
|
+
const { status, stopResponse, isSending } = useMessages();
|
|
1288
|
+
return /* @__PURE__ */ jsx11(
|
|
1289
|
+
"button",
|
|
1290
|
+
{
|
|
1291
|
+
className: "chat-send",
|
|
1292
|
+
type: "button",
|
|
1293
|
+
"data-state": isSending ? "stop" : "send",
|
|
1294
|
+
onClick: isSending ? stopResponse : submitInput,
|
|
1295
|
+
disabled: isSending ? false : !canSend,
|
|
1296
|
+
"aria-label": isSending ? "Stop response" : "Send message",
|
|
1297
|
+
children: isSending ? /* @__PURE__ */ jsx11(Square, { size: 14, fill: "currentColor" }) : /* @__PURE__ */ jsx11(Send, { size: 16 })
|
|
1298
|
+
}
|
|
1299
|
+
);
|
|
1300
|
+
};
|
|
1301
|
+
|
|
1302
|
+
// src/components/Chat/chat.select.tsx
|
|
1303
|
+
import { Fragment as Fragment2, useCallback as useCallback5, useMemo as useMemo6, useRef as useRef8, useState as useState6 } from "react";
|
|
1304
|
+
|
|
1305
|
+
// src/ui/select.tsx
|
|
1306
|
+
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
1307
|
+
import { Select as SelectPrimitive } from "radix-ui";
|
|
1308
|
+
import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1309
|
+
function Select({
|
|
1310
|
+
...props
|
|
1311
|
+
}) {
|
|
1312
|
+
return /* @__PURE__ */ jsx12(SelectPrimitive.Root, { "data-slot": "select", ...props });
|
|
1313
|
+
}
|
|
1314
|
+
function SelectGroup({
|
|
1315
|
+
...props
|
|
1316
|
+
}) {
|
|
1317
|
+
return /* @__PURE__ */ jsx12(SelectPrimitive.Group, { "data-slot": "select-group", ...props });
|
|
1318
|
+
}
|
|
1319
|
+
function SelectTrigger({
|
|
1320
|
+
className,
|
|
1321
|
+
size = "default",
|
|
1322
|
+
children,
|
|
1323
|
+
...props
|
|
1324
|
+
}) {
|
|
1325
|
+
return /* @__PURE__ */ jsxs4(
|
|
1326
|
+
SelectPrimitive.Trigger,
|
|
1327
|
+
{
|
|
1328
|
+
"data-slot": "select-trigger",
|
|
1329
|
+
"data-size": size,
|
|
1330
|
+
className: cn(
|
|
1331
|
+
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
1332
|
+
className
|
|
1333
|
+
),
|
|
1334
|
+
...props,
|
|
1335
|
+
children: [
|
|
1336
|
+
children,
|
|
1337
|
+
/* @__PURE__ */ jsx12(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx12(ChevronDownIcon, { className: "size-4 opacity-50" }) })
|
|
1338
|
+
]
|
|
1339
|
+
}
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
function SelectContent({
|
|
1343
|
+
className,
|
|
1344
|
+
children,
|
|
1345
|
+
portalContainer,
|
|
1346
|
+
position = "item-aligned",
|
|
1347
|
+
align = "center",
|
|
1348
|
+
...props
|
|
1349
|
+
}) {
|
|
1350
|
+
return /* @__PURE__ */ jsx12(SelectPrimitive.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs4(
|
|
1351
|
+
SelectPrimitive.Content,
|
|
1352
|
+
{
|
|
1353
|
+
"data-slot": "select-content",
|
|
1354
|
+
className: cn(
|
|
1355
|
+
"bg-popover text-popover-foreground relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
|
1356
|
+
position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
1357
|
+
className
|
|
1358
|
+
),
|
|
1359
|
+
position,
|
|
1360
|
+
align,
|
|
1361
|
+
...props,
|
|
1362
|
+
children: [
|
|
1363
|
+
/* @__PURE__ */ jsx12(SelectScrollUpButton, {}),
|
|
1364
|
+
/* @__PURE__ */ jsx12(
|
|
1365
|
+
SelectPrimitive.Viewport,
|
|
1366
|
+
{
|
|
1367
|
+
"data-slot": "select-viewport",
|
|
1368
|
+
className: cn(
|
|
1369
|
+
"p-1",
|
|
1370
|
+
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
|
1371
|
+
),
|
|
1372
|
+
children
|
|
1373
|
+
}
|
|
1374
|
+
),
|
|
1375
|
+
/* @__PURE__ */ jsx12(SelectScrollDownButton, {})
|
|
1376
|
+
]
|
|
1377
|
+
}
|
|
1378
|
+
) });
|
|
1379
|
+
}
|
|
1380
|
+
function SelectLabel({
|
|
1381
|
+
className,
|
|
1382
|
+
...props
|
|
1383
|
+
}) {
|
|
1384
|
+
return /* @__PURE__ */ jsx12(
|
|
1385
|
+
SelectPrimitive.Label,
|
|
1386
|
+
{
|
|
1387
|
+
"data-slot": "select-label",
|
|
1388
|
+
className: cn("text-muted-foreground px-2 py-1.5 text-xs", className),
|
|
1389
|
+
...props
|
|
1390
|
+
}
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
function SelectItem({
|
|
1394
|
+
className,
|
|
1395
|
+
children,
|
|
1396
|
+
...props
|
|
1397
|
+
}) {
|
|
1398
|
+
return /* @__PURE__ */ jsxs4(
|
|
1399
|
+
SelectPrimitive.Item,
|
|
1400
|
+
{
|
|
1401
|
+
"data-slot": "select-item",
|
|
1402
|
+
className: cn(
|
|
1403
|
+
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
1404
|
+
className
|
|
1405
|
+
),
|
|
1406
|
+
...props,
|
|
1407
|
+
children: [
|
|
1408
|
+
/* @__PURE__ */ jsx12(
|
|
1409
|
+
"span",
|
|
1410
|
+
{
|
|
1411
|
+
"data-slot": "select-item-indicator",
|
|
1412
|
+
className: "absolute right-2 flex size-3.5 items-center justify-center",
|
|
1413
|
+
children: /* @__PURE__ */ jsx12(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx12(CheckIcon, { className: "size-4" }) })
|
|
1414
|
+
}
|
|
1415
|
+
),
|
|
1416
|
+
/* @__PURE__ */ jsx12(SelectPrimitive.ItemText, { children })
|
|
1417
|
+
]
|
|
1418
|
+
}
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
function SelectScrollUpButton({
|
|
1422
|
+
className,
|
|
1423
|
+
...props
|
|
1424
|
+
}) {
|
|
1425
|
+
return /* @__PURE__ */ jsx12(
|
|
1426
|
+
SelectPrimitive.ScrollUpButton,
|
|
1427
|
+
{
|
|
1428
|
+
"data-slot": "select-scroll-up-button",
|
|
1429
|
+
className: cn(
|
|
1430
|
+
"flex cursor-default items-center justify-center py-1",
|
|
1431
|
+
className
|
|
1432
|
+
),
|
|
1433
|
+
...props,
|
|
1434
|
+
children: /* @__PURE__ */ jsx12(ChevronUpIcon, { className: "size-4" })
|
|
1435
|
+
}
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
function SelectScrollDownButton({
|
|
1439
|
+
className,
|
|
1440
|
+
...props
|
|
1441
|
+
}) {
|
|
1442
|
+
return /* @__PURE__ */ jsx12(
|
|
1443
|
+
SelectPrimitive.ScrollDownButton,
|
|
1444
|
+
{
|
|
1445
|
+
"data-slot": "select-scroll-down-button",
|
|
1446
|
+
className: cn(
|
|
1447
|
+
"flex cursor-default items-center justify-center py-1",
|
|
1448
|
+
className
|
|
1449
|
+
),
|
|
1450
|
+
...props,
|
|
1451
|
+
children: /* @__PURE__ */ jsx12(ChevronDownIcon, { className: "size-4" })
|
|
1452
|
+
}
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// src/components/Chat/chat.select.tsx
|
|
1457
|
+
import { Check, Ellipsis } from "lucide-react";
|
|
1458
|
+
import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1459
|
+
var SELECT_ORIGIN = "top-left";
|
|
1460
|
+
var THINKING_ORIGIN = "top-right";
|
|
1461
|
+
var ProviderBadge = ({ logo, name }) => {
|
|
1462
|
+
if (logo) {
|
|
1463
|
+
return /* @__PURE__ */ jsx13(
|
|
1464
|
+
"img",
|
|
1465
|
+
{
|
|
1466
|
+
className: "chat-model-provider-logo",
|
|
1467
|
+
src: logo,
|
|
1468
|
+
alt: "",
|
|
1469
|
+
"aria-hidden": "true"
|
|
1470
|
+
}
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
return /* @__PURE__ */ jsx13("span", { className: "chat-model-provider-logo chat-model-provider-fallback", "aria-hidden": "true", children: name.slice(0, 1) });
|
|
1474
|
+
};
|
|
1475
|
+
var ChatSelect = () => {
|
|
1476
|
+
var _a, _b, _c;
|
|
1477
|
+
const { provider, model, registry, setProvider, getThinkingLevel, setThinkingLevel } = useModel();
|
|
1478
|
+
const { isSending } = useMessages();
|
|
1479
|
+
const [portalContainer, setPortalContainer] = useState6(null);
|
|
1480
|
+
const [query, setQuery] = useState6("");
|
|
1481
|
+
const [thinkingOpen, setThinkingOpen] = useState6(null);
|
|
1482
|
+
const [menuOpen, setMenuOpen] = useState6(false);
|
|
1483
|
+
const rootRef = useRef8(null);
|
|
1484
|
+
const menu = useDropdownPresence(menuOpen, rootRef);
|
|
1485
|
+
const selectedValue = `${provider}::${model}`;
|
|
1486
|
+
const selectedProvider = registry.providers.find((entry) => entry.id === provider);
|
|
1487
|
+
const selectedModel = selectedProvider == null ? void 0 : selectedProvider.models.find((entry) => entry.id === model);
|
|
1488
|
+
const providerName = (_b = (_a = selectedProvider == null ? void 0 : selectedProvider.name) != null ? _a : selectedProvider == null ? void 0 : selectedProvider.label) != null ? _b : provider;
|
|
1489
|
+
const modelLabel = (_c = selectedModel == null ? void 0 : selectedModel.label) != null ? _c : model || "Loading models\u2026";
|
|
1490
|
+
const filteredProviders = useMemo6(() => {
|
|
1491
|
+
const q = query.trim().toLowerCase();
|
|
1492
|
+
if (!q) return registry.providers;
|
|
1493
|
+
return registry.providers.map((entry) => ({
|
|
1494
|
+
...entry,
|
|
1495
|
+
models: entry.models.filter(
|
|
1496
|
+
(item) => item.label.toLowerCase().includes(q) || item.id.toLowerCase().includes(q) || item.description && item.description.toLowerCase().includes(q) || entry.label.toLowerCase().includes(q)
|
|
1497
|
+
)
|
|
1498
|
+
})).filter((entry) => entry.models.length > 0);
|
|
1499
|
+
}, [registry.providers, query]);
|
|
1500
|
+
const setRootElement = useCallback5((node) => {
|
|
1501
|
+
const found = node == null ? void 0 : node.closest(".chat-root");
|
|
1502
|
+
rootRef.current = found instanceof HTMLElement ? found : null;
|
|
1503
|
+
setPortalContainer(found instanceof HTMLElement ? found : null);
|
|
1504
|
+
}, []);
|
|
1505
|
+
return /* @__PURE__ */ jsx13("div", { ref: setRootElement, children: /* @__PURE__ */ jsxs5(
|
|
1506
|
+
Select,
|
|
1507
|
+
{
|
|
1508
|
+
open: menuOpen || menu.mounted,
|
|
1509
|
+
onOpenChange: setMenuOpen,
|
|
1510
|
+
value: selectedValue,
|
|
1511
|
+
onValueChange: (value) => {
|
|
1512
|
+
const separator = value.indexOf("::");
|
|
1513
|
+
if (separator <= 0) {
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
const nextProvider = value.slice(0, separator);
|
|
1517
|
+
const nextModel = value.slice(separator + 2);
|
|
1518
|
+
const match = registry.providers.find((entry) => entry.id === nextProvider);
|
|
1519
|
+
if (!match || !nextModel) {
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
setProvider(match.id, nextModel);
|
|
1523
|
+
},
|
|
1524
|
+
disabled: isSending,
|
|
1525
|
+
children: [
|
|
1526
|
+
/* @__PURE__ */ jsx13(SelectTrigger, { className: "chat-model-trigger", children: /* @__PURE__ */ jsxs5("span", { className: "chat-model-selected", children: [
|
|
1527
|
+
selectedModel ? /* @__PURE__ */ jsx13(ProviderBadge, { logo: selectedProvider == null ? void 0 : selectedProvider.logo, name: providerName }) : null,
|
|
1528
|
+
/* @__PURE__ */ jsx13("span", { children: modelLabel })
|
|
1529
|
+
] }) }),
|
|
1530
|
+
/* @__PURE__ */ jsxs5(
|
|
1531
|
+
SelectContent,
|
|
1532
|
+
{
|
|
1533
|
+
align: "start",
|
|
1534
|
+
position: "popper",
|
|
1535
|
+
sideOffset: 6,
|
|
1536
|
+
collisionPadding: 12,
|
|
1537
|
+
className: cn("chat-model-content", motion.dropdown, menu.className),
|
|
1538
|
+
"data-origin": SELECT_ORIGIN,
|
|
1539
|
+
portalContainer,
|
|
1540
|
+
onCloseAutoFocus: (event) => {
|
|
1541
|
+
event.preventDefault();
|
|
1542
|
+
setQuery("");
|
|
1543
|
+
setThinkingOpen(null);
|
|
1544
|
+
},
|
|
1545
|
+
onPointerDownOutside: () => setThinkingOpen(null),
|
|
1546
|
+
children: [
|
|
1547
|
+
/* @__PURE__ */ jsx13("div", { className: "chat-model-search", children: /* @__PURE__ */ jsx13(
|
|
1548
|
+
"input",
|
|
1549
|
+
{
|
|
1550
|
+
autoFocus: true,
|
|
1551
|
+
placeholder: "Search models",
|
|
1552
|
+
value: query,
|
|
1553
|
+
onChange: (event) => setQuery(event.target.value),
|
|
1554
|
+
onKeyDown: (event) => event.stopPropagation(),
|
|
1555
|
+
className: "chat-model-search-input"
|
|
1556
|
+
}
|
|
1557
|
+
) }),
|
|
1558
|
+
/* @__PURE__ */ jsx13("div", { className: "chat-model-list", children: filteredProviders.length === 0 ? /* @__PURE__ */ jsx13("div", { className: "chat-model-empty", children: "No models found" }) : filteredProviders.map((entry) => {
|
|
1559
|
+
var _a2;
|
|
1560
|
+
const providerName2 = (_a2 = entry.name) != null ? _a2 : entry.label;
|
|
1561
|
+
const hideLabel = filteredProviders.length === 1;
|
|
1562
|
+
return /* @__PURE__ */ jsx13(Fragment2, { children: /* @__PURE__ */ jsxs5(SelectGroup, { className: "chat-model-group", children: [
|
|
1563
|
+
hideLabel ? null : /* @__PURE__ */ jsxs5(SelectLabel, { className: "chat-model-label", children: [
|
|
1564
|
+
/* @__PURE__ */ jsx13(ProviderBadge, { logo: entry.logo, name: providerName2 }),
|
|
1565
|
+
/* @__PURE__ */ jsx13("span", { children: providerName2 })
|
|
1566
|
+
] }),
|
|
1567
|
+
entry.models.map((item) => {
|
|
1568
|
+
var _a3;
|
|
1569
|
+
const key = `${entry.id}::${item.id}`;
|
|
1570
|
+
const levels = item.thinkingLevels;
|
|
1571
|
+
const defaultThinking = levels && levels.length > 0 ? levels.includes("medium") ? "medium" : levels[0] : void 0;
|
|
1572
|
+
const thinking = defaultThinking && levels ? { levels } : void 0;
|
|
1573
|
+
const activeThinking = defaultThinking ? (_a3 = getThinkingLevel(entry.id, item.id)) != null ? _a3 : defaultThinking : void 0;
|
|
1574
|
+
const isOpen = thinkingOpen === key;
|
|
1575
|
+
return /* @__PURE__ */ jsx13(
|
|
1576
|
+
ModelRow,
|
|
1577
|
+
{
|
|
1578
|
+
itemKey: key,
|
|
1579
|
+
item,
|
|
1580
|
+
thinking,
|
|
1581
|
+
activeThinking,
|
|
1582
|
+
isOpen,
|
|
1583
|
+
onToggleThinking: () => setThinkingOpen(isOpen ? null : key),
|
|
1584
|
+
onPickThinking: (level) => {
|
|
1585
|
+
setThinkingLevel(entry.id, item.id, level);
|
|
1586
|
+
setThinkingOpen(null);
|
|
1587
|
+
}
|
|
1588
|
+
},
|
|
1589
|
+
key
|
|
1590
|
+
);
|
|
1591
|
+
})
|
|
1592
|
+
] }) }, entry.id);
|
|
1593
|
+
}) })
|
|
1594
|
+
]
|
|
1595
|
+
}
|
|
1596
|
+
)
|
|
1597
|
+
]
|
|
1598
|
+
}
|
|
1599
|
+
) });
|
|
1600
|
+
};
|
|
1601
|
+
var ModelRow = ({
|
|
1602
|
+
itemKey,
|
|
1603
|
+
item,
|
|
1604
|
+
thinking,
|
|
1605
|
+
activeThinking,
|
|
1606
|
+
isOpen,
|
|
1607
|
+
onToggleThinking,
|
|
1608
|
+
onPickThinking
|
|
1609
|
+
}) => {
|
|
1610
|
+
const menu = useDropdownPresence(isOpen);
|
|
1611
|
+
return /* @__PURE__ */ jsxs5("div", { className: "chat-model-row", children: [
|
|
1612
|
+
/* @__PURE__ */ jsxs5(SelectItem, { value: itemKey, className: "chat-model-item", children: [
|
|
1613
|
+
/* @__PURE__ */ jsx13("span", { className: "chat-model-item-label", children: item.label }),
|
|
1614
|
+
item.description ? /* @__PURE__ */ jsx13("span", { className: "chat-model-item-hint", children: item.description }) : thinking && activeThinking ? /* @__PURE__ */ jsx13("span", { className: "chat-model-item-hint", children: activeThinking }) : null
|
|
1615
|
+
] }),
|
|
1616
|
+
thinking ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1617
|
+
/* @__PURE__ */ jsx13(
|
|
1618
|
+
"button",
|
|
1619
|
+
{
|
|
1620
|
+
type: "button",
|
|
1621
|
+
"aria-label": "Thinking settings",
|
|
1622
|
+
className: "chat-model-thinking-trigger",
|
|
1623
|
+
"data-open": isOpen ? "true" : "false",
|
|
1624
|
+
onPointerDown: (event) => {
|
|
1625
|
+
event.preventDefault();
|
|
1626
|
+
event.stopPropagation();
|
|
1627
|
+
},
|
|
1628
|
+
onClick: (event) => {
|
|
1629
|
+
event.preventDefault();
|
|
1630
|
+
event.stopPropagation();
|
|
1631
|
+
onToggleThinking();
|
|
1632
|
+
},
|
|
1633
|
+
children: /* @__PURE__ */ jsx13(Ellipsis, { className: "chat-model-thinking-icon" })
|
|
1634
|
+
}
|
|
1635
|
+
),
|
|
1636
|
+
menu.mounted ? /* @__PURE__ */ jsx13(
|
|
1637
|
+
"div",
|
|
1638
|
+
{
|
|
1639
|
+
className: cn("chat-model-thinking-menu", motion.dropdown, menu.className),
|
|
1640
|
+
"data-origin": THINKING_ORIGIN,
|
|
1641
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
1642
|
+
onClick: (event) => event.stopPropagation(),
|
|
1643
|
+
children: thinking.levels.map((level) => {
|
|
1644
|
+
const isActive = level === activeThinking;
|
|
1645
|
+
return /* @__PURE__ */ jsxs5(
|
|
1646
|
+
"button",
|
|
1647
|
+
{
|
|
1648
|
+
type: "button",
|
|
1649
|
+
className: "chat-model-thinking-option",
|
|
1650
|
+
"data-active": isActive ? "true" : "false",
|
|
1651
|
+
onClick: (event) => {
|
|
1652
|
+
event.preventDefault();
|
|
1653
|
+
event.stopPropagation();
|
|
1654
|
+
onPickThinking(level);
|
|
1655
|
+
},
|
|
1656
|
+
children: [
|
|
1657
|
+
/* @__PURE__ */ jsx13("span", { children: level }),
|
|
1658
|
+
isActive ? /* @__PURE__ */ jsx13(Check, { className: "chat-model-thinking-check" }) : null
|
|
1659
|
+
]
|
|
1660
|
+
},
|
|
1661
|
+
level
|
|
1662
|
+
);
|
|
1663
|
+
})
|
|
1664
|
+
}
|
|
1665
|
+
) : null
|
|
1666
|
+
] }) : null
|
|
1667
|
+
] });
|
|
1668
|
+
};
|
|
1669
|
+
|
|
1670
|
+
// src/components/Chat/chat.tsx
|
|
1671
|
+
import { jsx as jsx14, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1672
|
+
var focusStaysInComposer = (shell, node) => {
|
|
1673
|
+
if (node && shell.contains(node)) {
|
|
1674
|
+
return true;
|
|
1675
|
+
}
|
|
1676
|
+
return node instanceof Element && node.closest("[data-slot='select-content']") !== null;
|
|
1677
|
+
};
|
|
1678
|
+
var ChatComposer = ({
|
|
1679
|
+
showModelSelector = true,
|
|
1680
|
+
collapsedVariant = "pill",
|
|
1681
|
+
defaultExpanded = false
|
|
1682
|
+
}) => {
|
|
1683
|
+
const { status } = useMessages();
|
|
1684
|
+
const { input, attachments, references } = useComposer();
|
|
1685
|
+
const shellRef = useRef9(null);
|
|
1686
|
+
const [focused, setFocused] = useState7(defaultExpanded);
|
|
1687
|
+
const expanded = focused || input.trim().length > 0 || attachments.length > 0 || references.length > 0;
|
|
1688
|
+
useEffect6(() => {
|
|
1689
|
+
if (status !== "error" || !shellRef.current) {
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
triggerErrorShake(shellRef.current);
|
|
1693
|
+
}, [status]);
|
|
1694
|
+
useEffect6(() => {
|
|
1695
|
+
if (!focused) {
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
const onFocusIn = () => {
|
|
1699
|
+
const shell = shellRef.current;
|
|
1700
|
+
if (!shell) {
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
if (focusStaysInComposer(shell, document.activeElement)) {
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
setFocused(false);
|
|
1707
|
+
};
|
|
1708
|
+
document.addEventListener("focusin", onFocusIn, true);
|
|
1709
|
+
return () => document.removeEventListener("focusin", onFocusIn, true);
|
|
1710
|
+
}, [focused]);
|
|
1711
|
+
return /* @__PURE__ */ jsxs6(
|
|
1712
|
+
"div",
|
|
1713
|
+
{
|
|
1714
|
+
ref: shellRef,
|
|
1715
|
+
className: cn("chat-composer-shell", motion.inputWrap, motion.input),
|
|
1716
|
+
"data-expanded": expanded ? "true" : "false",
|
|
1717
|
+
"data-collapsed": collapsedVariant,
|
|
1718
|
+
onFocus: () => setFocused(true),
|
|
1719
|
+
onBlur: (event) => {
|
|
1720
|
+
const shell = event.currentTarget;
|
|
1721
|
+
const next = event.relatedTarget;
|
|
1722
|
+
if (next instanceof Node && focusStaysInComposer(shell, next)) {
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
window.setTimeout(() => {
|
|
1726
|
+
if (focusStaysInComposer(shell, document.activeElement)) {
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
setFocused(false);
|
|
1730
|
+
}, 0);
|
|
1731
|
+
},
|
|
1732
|
+
children: [
|
|
1733
|
+
expanded || collapsedVariant !== "circle" ? null : /* @__PURE__ */ jsx14(MessageCircle, { className: "chat-composer-collapsed-icon", size: 18, "aria-hidden": "true" }),
|
|
1734
|
+
/* @__PURE__ */ jsx14(ChatAttachments, {}),
|
|
1735
|
+
/* @__PURE__ */ jsx14(ChatReferences, {}),
|
|
1736
|
+
/* @__PURE__ */ jsx14(ChatInput, { expanded }),
|
|
1737
|
+
/* @__PURE__ */ jsxs6("div", { className: "chat-composer-row", children: [
|
|
1738
|
+
/* @__PURE__ */ jsxs6("div", { className: "chat-composer-tools", children: [
|
|
1739
|
+
/* @__PURE__ */ jsx14(ChatAttachButton, {}),
|
|
1740
|
+
showModelSelector && expanded && collapsedVariant === "circle" ? /* @__PURE__ */ jsx14(ChatSelect, {}) : null
|
|
1741
|
+
] }),
|
|
1742
|
+
/* @__PURE__ */ jsx14(ChatSend, {})
|
|
1743
|
+
] })
|
|
1744
|
+
]
|
|
1745
|
+
}
|
|
1746
|
+
);
|
|
1747
|
+
};
|
|
1748
|
+
|
|
1749
|
+
// src/components/Message/message.item.tsx
|
|
1750
|
+
import { Pencil } from "lucide-react";
|
|
1751
|
+
import { useState as useState10 } from "react";
|
|
1752
|
+
import { normalizeAgentParts } from "@sarchauhan/protocol";
|
|
1753
|
+
|
|
1754
|
+
// src/components/Message/message.feedback.tsx
|
|
1755
|
+
import { ThumbsDown, ThumbsUp } from "lucide-react";
|
|
1756
|
+
import { useState as useState9 } from "react";
|
|
1757
|
+
|
|
1758
|
+
// src/components/Message/message.copy.tsx
|
|
1759
|
+
import { Check as Check2, Copy } from "lucide-react";
|
|
1760
|
+
import { useState as useState8 } from "react";
|
|
1761
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1762
|
+
var MessageCopy = ({ text, disabled = false }) => {
|
|
1763
|
+
const [isCopied, setIsCopied] = useState8(false);
|
|
1764
|
+
const copyMessage = async () => {
|
|
1765
|
+
await navigator.clipboard.writeText(text);
|
|
1766
|
+
setIsCopied(true);
|
|
1767
|
+
window.setTimeout(() => setIsCopied(false), 1500);
|
|
1768
|
+
};
|
|
1769
|
+
return /* @__PURE__ */ jsx15(
|
|
1770
|
+
"button",
|
|
1771
|
+
{
|
|
1772
|
+
type: "button",
|
|
1773
|
+
className: "chat-feedback-button",
|
|
1774
|
+
title: isCopied ? "Copied" : "Copy response",
|
|
1775
|
+
"aria-label": isCopied ? "Copied" : "Copy response",
|
|
1776
|
+
onClick: () => void copyMessage(),
|
|
1777
|
+
disabled,
|
|
1778
|
+
children: isCopied ? /* @__PURE__ */ jsx15(Check2, { size: 16 }) : /* @__PURE__ */ jsx15(Copy, { size: 16 })
|
|
1779
|
+
}
|
|
1780
|
+
);
|
|
1781
|
+
};
|
|
1782
|
+
|
|
1783
|
+
// src/components/Message/message.regenerate.tsx
|
|
1784
|
+
import { RotateCcw } from "lucide-react";
|
|
1785
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
1786
|
+
var MessageRegenerate = ({ onRegenerate, disabled = false }) => /* @__PURE__ */ jsx16(
|
|
1787
|
+
"button",
|
|
1788
|
+
{
|
|
1789
|
+
type: "button",
|
|
1790
|
+
className: "chat-feedback-button",
|
|
1791
|
+
title: "Regenerate response",
|
|
1792
|
+
"aria-label": "Regenerate response",
|
|
1793
|
+
disabled,
|
|
1794
|
+
onClick: onRegenerate,
|
|
1795
|
+
children: /* @__PURE__ */ jsx16(RotateCcw, { size: 17 })
|
|
1796
|
+
}
|
|
1797
|
+
);
|
|
1798
|
+
|
|
1799
|
+
// src/components/Message/message.feedback.tsx
|
|
1800
|
+
import { jsx as jsx17, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1801
|
+
var MessageFeedback = ({
|
|
1802
|
+
responseText,
|
|
1803
|
+
onRegenerate,
|
|
1804
|
+
canRegenerate,
|
|
1805
|
+
disabled = false
|
|
1806
|
+
}) => {
|
|
1807
|
+
const [feedback, setFeedback] = useState9(null);
|
|
1808
|
+
if (!responseText.trim()) {
|
|
1809
|
+
return null;
|
|
1810
|
+
}
|
|
1811
|
+
return /* @__PURE__ */ jsxs7("div", { className: "chat-message-feedback", "aria-label": "Message actions", children: [
|
|
1812
|
+
/* @__PURE__ */ jsx17(MessageCopy, { text: responseText, disabled }),
|
|
1813
|
+
/* @__PURE__ */ jsx17(
|
|
1814
|
+
MessageRegenerate,
|
|
1815
|
+
{
|
|
1816
|
+
onRegenerate,
|
|
1817
|
+
disabled: !canRegenerate || disabled
|
|
1818
|
+
}
|
|
1819
|
+
),
|
|
1820
|
+
/* @__PURE__ */ jsx17(
|
|
1821
|
+
"button",
|
|
1822
|
+
{
|
|
1823
|
+
type: "button",
|
|
1824
|
+
className: `chat-feedback-button${feedback === "up" ? " is-selected" : ""}`,
|
|
1825
|
+
title: "Good response",
|
|
1826
|
+
"aria-label": "Good response",
|
|
1827
|
+
"aria-pressed": feedback === "up",
|
|
1828
|
+
onClick: () => setFeedback(feedback === "up" ? null : "up"),
|
|
1829
|
+
disabled,
|
|
1830
|
+
children: /* @__PURE__ */ jsx17(ThumbsUp, { size: 18 })
|
|
1831
|
+
}
|
|
1832
|
+
),
|
|
1833
|
+
/* @__PURE__ */ jsx17(
|
|
1834
|
+
"button",
|
|
1835
|
+
{
|
|
1836
|
+
type: "button",
|
|
1837
|
+
className: `chat-feedback-button${feedback === "down" ? " is-selected" : ""}`,
|
|
1838
|
+
title: "Poor response",
|
|
1839
|
+
"aria-label": "Poor response",
|
|
1840
|
+
"aria-pressed": feedback === "down",
|
|
1841
|
+
onClick: () => setFeedback(feedback === "down" ? null : "down"),
|
|
1842
|
+
disabled,
|
|
1843
|
+
children: /* @__PURE__ */ jsx17(ThumbsDown, { size: 18 })
|
|
1844
|
+
}
|
|
1845
|
+
)
|
|
1846
|
+
] });
|
|
1847
|
+
};
|
|
1848
|
+
|
|
1849
|
+
// src/components/Message/message.usage.tsx
|
|
1850
|
+
import {
|
|
1851
|
+
parseContext,
|
|
1852
|
+
parseUsage
|
|
1853
|
+
} from "@sarchauhan/protocol";
|
|
1854
|
+
import { Fragment as Fragment4, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1855
|
+
var formatTokens = (value) => new Intl.NumberFormat("en-US").format(value);
|
|
1856
|
+
var isTokenCount = (value) => typeof value === "number" && Number.isFinite(value);
|
|
1857
|
+
var getContextMetrics = (context) => {
|
|
1858
|
+
const currentTokens = context == null ? void 0 : context.current_tokens;
|
|
1859
|
+
const contextWindow = context == null ? void 0 : context.context_window;
|
|
1860
|
+
return isTokenCount(currentTokens) && isTokenCount(contextWindow) ? { currentTokens, contextWindow } : null;
|
|
1861
|
+
};
|
|
1862
|
+
var MessageUsage = ({ parts }) => {
|
|
1863
|
+
var _a, _b;
|
|
1864
|
+
const usage = parseUsage((_a = parts.find((part) => part.name === "usage")) == null ? void 0 : _a.data);
|
|
1865
|
+
const context = parseContext((_b = parts.find((part) => part.name === "context")) == null ? void 0 : _b.data);
|
|
1866
|
+
const contextMetrics = getContextMetrics(context);
|
|
1867
|
+
const tokenMetrics = usage ? [
|
|
1868
|
+
["in", usage.input_tokens],
|
|
1869
|
+
["out", usage.output_tokens],
|
|
1870
|
+
["reasoning", usage.reasoning_tokens],
|
|
1871
|
+
["total", usage.total_tokens]
|
|
1872
|
+
].filter((metric) => isTokenCount(metric[1])) : [];
|
|
1873
|
+
if (!contextMetrics && tokenMetrics.length === 0) return null;
|
|
1874
|
+
return /* @__PURE__ */ jsxs8("p", { className: "chat-message-usage", "aria-label": "Context and token usage", children: [
|
|
1875
|
+
contextMetrics ? /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
1876
|
+
"Context ",
|
|
1877
|
+
formatTokens(contextMetrics.currentTokens),
|
|
1878
|
+
" / ",
|
|
1879
|
+
formatTokens(contextMetrics.contextWindow),
|
|
1880
|
+
" (",
|
|
1881
|
+
Math.round(contextMetrics.currentTokens / contextMetrics.contextWindow * 100),
|
|
1882
|
+
"%)"
|
|
1883
|
+
] }) : null,
|
|
1884
|
+
tokenMetrics.length > 0 ? /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
1885
|
+
contextMetrics ? " \xB7 " : null,
|
|
1886
|
+
"Tokens ",
|
|
1887
|
+
tokenMetrics.map(([label, value]) => `${label} ${formatTokens(value)}`).join(" \xB7 ")
|
|
1888
|
+
] }) : null
|
|
1889
|
+
] });
|
|
1890
|
+
};
|
|
1891
|
+
|
|
1892
|
+
// src/components/Message/message.item.tsx
|
|
1893
|
+
import { jsx as jsx18, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1894
|
+
var getMessageTargetId = (messageId) => `chat-message-${messageId}`;
|
|
1895
|
+
var MessageItem = ({ message }) => {
|
|
1896
|
+
var _a;
|
|
1897
|
+
const { editAndResendMessage, isSending, messages } = useMessages();
|
|
1898
|
+
const [isEditing, setIsEditing] = useState10(false);
|
|
1899
|
+
const [draft, setDraft] = useState10(() => getUserDisplayText(message));
|
|
1900
|
+
const [isSubmittingEdit, setIsSubmittingEdit] = useState10(false);
|
|
1901
|
+
const isUser = message.role === "user";
|
|
1902
|
+
const isActiveAssistantMessage = isSending && ((_a = messages[messages.length - 1]) == null ? void 0 : _a.id) === message.id;
|
|
1903
|
+
const assistantText = message.parts.filter((part) => part.type === "text").map((part) => "text" in part ? String(part.text) : "").join("\n").trim();
|
|
1904
|
+
const dataParts = normalizeAgentParts(message.parts).filter(
|
|
1905
|
+
(part) => part.type === "data"
|
|
1906
|
+
);
|
|
1907
|
+
const previousUserMessage = [...messages].reverse().find((item) => item.role === "user");
|
|
1908
|
+
const cancelEdit = () => {
|
|
1909
|
+
setDraft(getUserDisplayText(message));
|
|
1910
|
+
setIsEditing(false);
|
|
1911
|
+
};
|
|
1912
|
+
const submitEdit = async () => {
|
|
1913
|
+
if (!draft.trim() || isSending || isSubmittingEdit) {
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
setIsSubmittingEdit(true);
|
|
1917
|
+
try {
|
|
1918
|
+
await editAndResendMessage(message.id, draft);
|
|
1919
|
+
setIsEditing(false);
|
|
1920
|
+
} finally {
|
|
1921
|
+
setIsSubmittingEdit(false);
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1924
|
+
if (isUser) {
|
|
1925
|
+
return /* @__PURE__ */ jsx18("div", { id: getMessageTargetId(message.id), className: "chat-message chat-message-user", children: /* @__PURE__ */ jsxs9("div", { className: `chat-message-user-inner${isEditing ? " is-editing" : ""}`, children: [
|
|
1926
|
+
/* @__PURE__ */ jsx18("div", { className: "chat-message-bubble", children: isEditing ? /* @__PURE__ */ jsx18(
|
|
1927
|
+
"textarea",
|
|
1928
|
+
{
|
|
1929
|
+
className: "chat-edit-area",
|
|
1930
|
+
value: draft,
|
|
1931
|
+
disabled: isSubmittingEdit,
|
|
1932
|
+
autoFocus: true,
|
|
1933
|
+
onChange: (event) => setDraft(event.target.value),
|
|
1934
|
+
onKeyDown: (event) => {
|
|
1935
|
+
if (event.key !== "Enter" || event.shiftKey) return;
|
|
1936
|
+
if (event.nativeEvent.isComposing) return;
|
|
1937
|
+
event.preventDefault();
|
|
1938
|
+
void submitEdit();
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
) : /* @__PURE__ */ jsx18(MessageContent, { parts: message.parts, isUser: true }) }),
|
|
1942
|
+
isEditing ? /* @__PURE__ */ jsxs9("div", { className: "chat-message-actions", children: [
|
|
1943
|
+
/* @__PURE__ */ jsx18(
|
|
1944
|
+
"button",
|
|
1945
|
+
{
|
|
1946
|
+
type: "button",
|
|
1947
|
+
className: "chat-text-button",
|
|
1948
|
+
onClick: cancelEdit,
|
|
1949
|
+
disabled: isSubmittingEdit,
|
|
1950
|
+
children: "Cancel"
|
|
1951
|
+
}
|
|
1952
|
+
),
|
|
1953
|
+
/* @__PURE__ */ jsx18(
|
|
1954
|
+
"button",
|
|
1955
|
+
{
|
|
1956
|
+
type: "button",
|
|
1957
|
+
className: "chat-text-button chat-text-button-done",
|
|
1958
|
+
onClick: submitEdit,
|
|
1959
|
+
disabled: !draft.trim() || isSubmittingEdit || isSending,
|
|
1960
|
+
children: "Done"
|
|
1961
|
+
}
|
|
1962
|
+
)
|
|
1963
|
+
] }) : /* @__PURE__ */ jsx18(
|
|
1964
|
+
"button",
|
|
1965
|
+
{
|
|
1966
|
+
type: "button",
|
|
1967
|
+
className: "chat-icon-button",
|
|
1968
|
+
onClick: () => {
|
|
1969
|
+
setDraft(getUserDisplayText(message));
|
|
1970
|
+
setIsEditing(true);
|
|
1971
|
+
},
|
|
1972
|
+
disabled: isSending,
|
|
1973
|
+
"aria-label": "Edit and resend message",
|
|
1974
|
+
children: /* @__PURE__ */ jsx18(Pencil, { size: 14 })
|
|
1975
|
+
}
|
|
1976
|
+
)
|
|
1977
|
+
] }) });
|
|
1978
|
+
}
|
|
1979
|
+
return /* @__PURE__ */ jsx18("div", { className: "chat-message chat-message-assistant", children: /* @__PURE__ */ jsxs9("div", { className: "chat-message-assistant-inner", children: [
|
|
1980
|
+
/* @__PURE__ */ jsx18(MessageContent, { parts: message.parts, isUser: false }),
|
|
1981
|
+
/* @__PURE__ */ jsx18(MessageUsage, { parts: dataParts }),
|
|
1982
|
+
isActiveAssistantMessage ? null : /* @__PURE__ */ jsx18(
|
|
1983
|
+
MessageFeedback,
|
|
1984
|
+
{
|
|
1985
|
+
responseText: assistantText,
|
|
1986
|
+
canRegenerate: !!previousUserMessage,
|
|
1987
|
+
disabled: isSending,
|
|
1988
|
+
onRegenerate: () => {
|
|
1989
|
+
if (previousUserMessage) {
|
|
1990
|
+
void editAndResendMessage(
|
|
1991
|
+
previousUserMessage.id,
|
|
1992
|
+
getUserDisplayText(previousUserMessage)
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
)
|
|
1998
|
+
] }) });
|
|
1999
|
+
};
|
|
2000
|
+
|
|
2001
|
+
// src/components/Message/message.tsx
|
|
2002
|
+
import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2003
|
+
var WaitingForResponse = () => /* @__PURE__ */ jsx19("div", { className: "chat-response-status", role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx19(TextSkeleton, {}) });
|
|
2004
|
+
var Message = () => {
|
|
2005
|
+
const { messages, isWaitingForResponse } = useMessages();
|
|
2006
|
+
const { isLoadingThread } = useThread();
|
|
2007
|
+
if (isLoadingThread) {
|
|
2008
|
+
return /* @__PURE__ */ jsx19("div", { className: "chat-messages-inner", children: /* @__PURE__ */ jsx19("div", { className: "chat-loading chat-loading-initial", role: "status", "aria-live": "polite", children: "Loading conversation\u2026" }) });
|
|
2009
|
+
}
|
|
2010
|
+
if (messages.length === 0) {
|
|
2011
|
+
return /* @__PURE__ */ jsx19("div", { className: "chat-messages-inner", children: /* @__PURE__ */ jsx19("div", { className: "chat-empty", children: /* @__PURE__ */ jsxs10("div", { children: [
|
|
2012
|
+
/* @__PURE__ */ jsx19("p", { className: "chat-empty-title", children: "How can I help?" }),
|
|
2013
|
+
/* @__PURE__ */ jsx19("p", { className: "chat-empty-copy", children: "Ask a question, explore ideas, or use tools and widgets when the agent needs them." })
|
|
2014
|
+
] }) }) });
|
|
2015
|
+
}
|
|
2016
|
+
return /* @__PURE__ */ jsxs10("div", { className: "chat-messages-inner", children: [
|
|
2017
|
+
messages.map((message) => /* @__PURE__ */ jsx19(MessageItem, { message }, message.id)),
|
|
2018
|
+
isWaitingForResponse ? /* @__PURE__ */ jsx19(WaitingForResponse, {}) : null
|
|
2019
|
+
] });
|
|
2020
|
+
};
|
|
2021
|
+
|
|
2022
|
+
// src/components/Chat.tsx
|
|
2023
|
+
import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2024
|
+
function ChatShell({
|
|
2025
|
+
className,
|
|
2026
|
+
style,
|
|
2027
|
+
widgets,
|
|
2028
|
+
showThemeToggle = true,
|
|
2029
|
+
showModelSelector = true,
|
|
2030
|
+
collapsedVariant = "pill",
|
|
2031
|
+
defaultExpanded = false
|
|
2032
|
+
}) {
|
|
2033
|
+
const { sendMessage, isSending, messages, status } = useMessages();
|
|
2034
|
+
const { createThread } = useThread();
|
|
2035
|
+
const { resolvedTheme } = useTheme();
|
|
2036
|
+
const registry = useMemo7(() => createWidgetRegistry(widgets), [widgets]);
|
|
2037
|
+
const messagesRef = useRef10(null);
|
|
2038
|
+
useLayoutEffect(() => {
|
|
2039
|
+
const container = messagesRef.current;
|
|
2040
|
+
if (!container) {
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2043
|
+
container.scrollTop = container.scrollHeight;
|
|
2044
|
+
}, [messages, status]);
|
|
2045
|
+
useLayoutEffect(() => {
|
|
2046
|
+
const container = messagesRef.current;
|
|
2047
|
+
if (!container || status !== "submitted" && status !== "streaming") {
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
const scrollToEnd = () => {
|
|
2051
|
+
container.scrollTop = container.scrollHeight;
|
|
2052
|
+
};
|
|
2053
|
+
const observer = new ResizeObserver(scrollToEnd);
|
|
2054
|
+
observer.observe(container);
|
|
2055
|
+
const inner = container.firstElementChild;
|
|
2056
|
+
if (inner) {
|
|
2057
|
+
observer.observe(inner);
|
|
2058
|
+
}
|
|
2059
|
+
scrollToEnd();
|
|
2060
|
+
return () => observer.disconnect();
|
|
2061
|
+
}, [status]);
|
|
2062
|
+
const respondToWidget = useCallback6(
|
|
2063
|
+
async (response) => {
|
|
2064
|
+
var _a, _b;
|
|
2065
|
+
const text = (_b = (_a = response.label) != null ? _a : response.actionId) != null ? _b : typeof response.value === "string" ? response.value : JSON.stringify(response.value);
|
|
2066
|
+
if (!text.trim()) {
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
await sendMessage({ text });
|
|
2070
|
+
},
|
|
2071
|
+
[sendMessage]
|
|
2072
|
+
);
|
|
2073
|
+
return /* @__PURE__ */ jsx20(
|
|
2074
|
+
WidgetProvider,
|
|
2075
|
+
{
|
|
2076
|
+
widgets: registry,
|
|
2077
|
+
respondToWidget,
|
|
2078
|
+
disabled: isSending,
|
|
2079
|
+
children: /* @__PURE__ */ jsxs11(
|
|
2080
|
+
"div",
|
|
2081
|
+
{
|
|
2082
|
+
className: ["chat-root", className].filter(Boolean).join(" "),
|
|
2083
|
+
style,
|
|
2084
|
+
"data-theme": resolvedTheme,
|
|
2085
|
+
children: [
|
|
2086
|
+
/* @__PURE__ */ jsxs11("div", { className: "chat-toolbar", children: [
|
|
2087
|
+
/* @__PURE__ */ jsx20(
|
|
2088
|
+
"button",
|
|
2089
|
+
{
|
|
2090
|
+
type: "button",
|
|
2091
|
+
className: "chat-theme-toggle",
|
|
2092
|
+
onClick: () => void createThread(),
|
|
2093
|
+
"aria-label": "Start a new chat",
|
|
2094
|
+
title: "New chat",
|
|
2095
|
+
disabled: isSending,
|
|
2096
|
+
children: /* @__PURE__ */ jsx20(PenSquare, { size: 16, strokeWidth: 1.75 })
|
|
2097
|
+
}
|
|
2098
|
+
),
|
|
2099
|
+
showThemeToggle && /* @__PURE__ */ jsx20(ThemeToggle, {})
|
|
2100
|
+
] }),
|
|
2101
|
+
/* @__PURE__ */ jsx20("div", { className: "chat-messages", ref: messagesRef, children: /* @__PURE__ */ jsx20(Message, {}) }),
|
|
2102
|
+
/* @__PURE__ */ jsx20("div", { className: "chat-composer", children: /* @__PURE__ */ jsx20(
|
|
2103
|
+
ChatComposer,
|
|
2104
|
+
{
|
|
2105
|
+
showModelSelector,
|
|
2106
|
+
collapsedVariant,
|
|
2107
|
+
defaultExpanded
|
|
2108
|
+
}
|
|
2109
|
+
) })
|
|
2110
|
+
]
|
|
2111
|
+
}
|
|
2112
|
+
)
|
|
2113
|
+
}
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
function Chat({
|
|
2117
|
+
adapter,
|
|
2118
|
+
className,
|
|
2119
|
+
defaultThreadId,
|
|
2120
|
+
defaultProvider,
|
|
2121
|
+
registryUrl,
|
|
2122
|
+
style,
|
|
2123
|
+
widgets,
|
|
2124
|
+
theme,
|
|
2125
|
+
defaultTheme = "system",
|
|
2126
|
+
onThemeChange,
|
|
2127
|
+
showThemeToggle = true,
|
|
2128
|
+
showModelSelector = true,
|
|
2129
|
+
collapsedVariant = "pill",
|
|
2130
|
+
defaultExpanded = false
|
|
2131
|
+
}) {
|
|
2132
|
+
return /* @__PURE__ */ jsx20(
|
|
2133
|
+
ThemeProvider,
|
|
2134
|
+
{
|
|
2135
|
+
theme,
|
|
2136
|
+
defaultTheme,
|
|
2137
|
+
onThemeChange,
|
|
2138
|
+
children: /* @__PURE__ */ jsx20(
|
|
2139
|
+
ChatContextProvider,
|
|
2140
|
+
{
|
|
2141
|
+
adapter,
|
|
2142
|
+
defaultThreadId,
|
|
2143
|
+
defaultProvider,
|
|
2144
|
+
registryUrl,
|
|
2145
|
+
children: /* @__PURE__ */ jsx20(
|
|
2146
|
+
ChatShell,
|
|
2147
|
+
{
|
|
2148
|
+
className,
|
|
2149
|
+
style,
|
|
2150
|
+
widgets,
|
|
2151
|
+
showThemeToggle,
|
|
2152
|
+
showModelSelector,
|
|
2153
|
+
collapsedVariant,
|
|
2154
|
+
defaultExpanded
|
|
2155
|
+
}
|
|
2156
|
+
)
|
|
2157
|
+
}
|
|
2158
|
+
)
|
|
2159
|
+
}
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// src/index.ts
|
|
2164
|
+
import { defineAdapter } from "@sarchauhan/adapter";
|
|
2165
|
+
import {
|
|
2166
|
+
applyAgentEvent,
|
|
2167
|
+
createAgentMessageState,
|
|
2168
|
+
createWidgetData,
|
|
2169
|
+
normalizeAgentMessage,
|
|
2170
|
+
normalizeAgentParts as normalizeAgentParts2,
|
|
2171
|
+
reduceAgentEvents
|
|
2172
|
+
} from "@sarchauhan/protocol";
|
|
2173
|
+
export {
|
|
2174
|
+
ArtifactWidget,
|
|
2175
|
+
ArtifactsWidget,
|
|
2176
|
+
BaseWidget,
|
|
2177
|
+
Chat,
|
|
2178
|
+
ChatComposer,
|
|
2179
|
+
ChatContextProvider,
|
|
2180
|
+
LoadingState,
|
|
2181
|
+
Message,
|
|
2182
|
+
MessageContent,
|
|
2183
|
+
MessageFeedback,
|
|
2184
|
+
MessageUsage,
|
|
2185
|
+
PatchWidget,
|
|
2186
|
+
ProviderId,
|
|
2187
|
+
QuestionWidget,
|
|
2188
|
+
ReadFileWidget,
|
|
2189
|
+
ResponsiveDetail,
|
|
2190
|
+
SearchWidget,
|
|
2191
|
+
SubagentWidget,
|
|
2192
|
+
TextSwap,
|
|
2193
|
+
ThemeProvider,
|
|
2194
|
+
ThemeToggle,
|
|
2195
|
+
ThinkingBlock,
|
|
2196
|
+
WidgetProvider,
|
|
2197
|
+
WidgetRenderer,
|
|
2198
|
+
applyAgentEvent,
|
|
2199
|
+
calibrateCheckPath,
|
|
2200
|
+
clearErrorShake,
|
|
2201
|
+
closeDropdown,
|
|
2202
|
+
computeDiff,
|
|
2203
|
+
computeTextDiff,
|
|
2204
|
+
computeToolState,
|
|
2205
|
+
computeToolWidget,
|
|
2206
|
+
createAgentMessageState,
|
|
2207
|
+
createArtifactData,
|
|
2208
|
+
createSubagentData,
|
|
2209
|
+
createWidgetData,
|
|
2210
|
+
createWidgetRegistry,
|
|
2211
|
+
defineAdapter,
|
|
2212
|
+
defineWidget,
|
|
2213
|
+
dropdownOrigins,
|
|
2214
|
+
formatArtifactSize,
|
|
2215
|
+
formatElapsed,
|
|
2216
|
+
formatElapsedWords,
|
|
2217
|
+
iconSwapStates,
|
|
2218
|
+
isProviderId,
|
|
2219
|
+
motion,
|
|
2220
|
+
motionData,
|
|
2221
|
+
motionRoot,
|
|
2222
|
+
normalizeAgentMessage,
|
|
2223
|
+
normalizeAgentParts2 as normalizeAgentParts,
|
|
2224
|
+
normalizeSearchResults,
|
|
2225
|
+
openDropdown,
|
|
2226
|
+
parseArtifact,
|
|
2227
|
+
parseRegistryConfig,
|
|
2228
|
+
parseSubagent,
|
|
2229
|
+
prebuiltWidgets,
|
|
2230
|
+
prefersReducedMotion,
|
|
2231
|
+
readDurationMs,
|
|
2232
|
+
reduceAgentEvents,
|
|
2233
|
+
safeArtifactUrl,
|
|
2234
|
+
safeResultUrl,
|
|
2235
|
+
successCheckStates,
|
|
2236
|
+
swapText,
|
|
2237
|
+
triggerErrorShake,
|
|
2238
|
+
useChat,
|
|
2239
|
+
useComposer,
|
|
2240
|
+
useDropdownPresence,
|
|
2241
|
+
useMessages,
|
|
2242
|
+
useModel,
|
|
2243
|
+
useTheme,
|
|
2244
|
+
useThread,
|
|
2245
|
+
useWidgets
|
|
2246
|
+
};
|
|
2247
|
+
//# sourceMappingURL=index.mjs.map
|