cencori 1.4.0 → 1.4.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.
@@ -20,14 +20,352 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/react/index.ts
21
21
  var react_exports = {};
22
22
  __export(react_exports, {
23
+ Chat: () => Chat,
24
+ SpeakButton: () => SpeakButton,
23
25
  VisionFormatBanner: () => VisionFormatBanner,
24
- VisionUploader: () => VisionUploader
26
+ VisionUploader: () => VisionUploader,
27
+ VoiceRecorder: () => VoiceRecorder,
28
+ useMemory: () => useMemory,
29
+ useVoiceRecorder: () => useVoiceRecorder
25
30
  });
26
31
  module.exports = __toCommonJS(react_exports);
27
32
 
28
- // src/react/vision/vision-format-banner.tsx
33
+ // src/react/chat/chat.tsx
34
+ var import_react = require("react");
29
35
  var import_lucide_react = require("lucide-react");
30
36
  var import_jsx_runtime = require("react/jsx-runtime");
37
+ var DEFAULT_BASE_URL = "https://cencori.com";
38
+ function Chat({
39
+ model,
40
+ apiKey,
41
+ memory,
42
+ system,
43
+ initialMessages,
44
+ temperature,
45
+ maxTokens,
46
+ stream = true,
47
+ baseUrl = DEFAULT_BASE_URL,
48
+ placeholder = "Send a message\u2026",
49
+ onMemoryRetrieved,
50
+ onError,
51
+ emptyState,
52
+ className,
53
+ hideMemoryIndicator = false
54
+ }) {
55
+ const [messages, setMessages] = (0, import_react.useState)(initialMessages ?? []);
56
+ const [input, setInput] = (0, import_react.useState)("");
57
+ const [busy, setBusy] = (0, import_react.useState)(false);
58
+ const [error, setError] = (0, import_react.useState)(null);
59
+ const [recalled, setRecalled] = (0, import_react.useState)(null);
60
+ const scrollRef = (0, import_react.useRef)(null);
61
+ const endpoint = (0, import_react.useMemo)(
62
+ () => `${baseUrl.replace(/\/$/, "")}/api/v1/chat/completions`,
63
+ [baseUrl]
64
+ );
65
+ (0, import_react.useEffect)(() => {
66
+ scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: "smooth" });
67
+ }, [messages, busy]);
68
+ const send = (0, import_react.useCallback)(
69
+ async (text) => {
70
+ const trimmed = text.trim();
71
+ if (!trimmed || busy) return;
72
+ setError(null);
73
+ setBusy(true);
74
+ const userMsg = { role: "user", content: trimmed };
75
+ const history = [...messages, userMsg];
76
+ setMessages(history);
77
+ setInput("");
78
+ const outbound = system ? [{ role: "system", content: system }, ...history] : history;
79
+ const body = {
80
+ model,
81
+ messages: outbound,
82
+ memory,
83
+ temperature,
84
+ max_tokens: maxTokens,
85
+ stream
86
+ };
87
+ const headers = { "Content-Type": "application/json" };
88
+ if (apiKey) headers["CENCORI_API_KEY"] = apiKey;
89
+ try {
90
+ const res = await fetch(endpoint, {
91
+ method: "POST",
92
+ headers,
93
+ body: JSON.stringify(body)
94
+ });
95
+ if (!res.ok || stream && !res.body) {
96
+ const data = await res.json().catch(() => ({}));
97
+ const msg = typeof data.error === "object" ? data.error?.message : data.error;
98
+ throw new Error(msg || `Cencori API error (${res.status})`);
99
+ }
100
+ if (memory && !hideMemoryIndicator) {
101
+ const count = Number(res.headers.get("X-Cencori-Memory-Retrieved") || 0);
102
+ setRecalled(count);
103
+ onMemoryRetrieved?.(count);
104
+ }
105
+ if (stream && res.body) {
106
+ setMessages((prev) => [...prev, { role: "assistant", content: "" }]);
107
+ const reader = res.body.getReader();
108
+ const decoder = new TextDecoder();
109
+ let buffer = "";
110
+ for (; ; ) {
111
+ const { done, value } = await reader.read();
112
+ if (done) break;
113
+ buffer += decoder.decode(value, { stream: true });
114
+ const lines = buffer.split("\n");
115
+ buffer = lines.pop() ?? "";
116
+ for (const line of lines) {
117
+ const t = line.trim();
118
+ if (!t.startsWith("data: ")) continue;
119
+ const payload = t.slice(6);
120
+ if (payload === "[DONE]") break;
121
+ try {
122
+ const parsed = JSON.parse(payload);
123
+ const delta = parsed.choices?.[0]?.delta?.content ?? "";
124
+ if (!delta) continue;
125
+ setMessages((prev) => {
126
+ const next = [...prev];
127
+ const last = next[next.length - 1];
128
+ if (last?.role === "assistant") {
129
+ next[next.length - 1] = {
130
+ ...last,
131
+ content: last.content + delta
132
+ };
133
+ }
134
+ return next;
135
+ });
136
+ } catch {
137
+ }
138
+ }
139
+ }
140
+ } else {
141
+ const data = await res.json();
142
+ const reply = data.choices?.[0]?.message?.content ?? "";
143
+ setMessages((prev) => [...prev, { role: "assistant", content: reply }]);
144
+ }
145
+ } catch (err) {
146
+ const e = err instanceof Error ? err : new Error(String(err));
147
+ setError(e.message);
148
+ onError?.(e);
149
+ setMessages(
150
+ (prev) => prev.filter((m, i) => !(i === prev.length - 1 && m.role === "user" && m.content === trimmed))
151
+ );
152
+ setInput(trimmed);
153
+ } finally {
154
+ setBusy(false);
155
+ }
156
+ },
157
+ [
158
+ apiKey,
159
+ busy,
160
+ endpoint,
161
+ hideMemoryIndicator,
162
+ maxTokens,
163
+ memory,
164
+ messages,
165
+ model,
166
+ onError,
167
+ onMemoryRetrieved,
168
+ stream,
169
+ system,
170
+ temperature
171
+ ]
172
+ );
173
+ const onSubmit = (e) => {
174
+ e.preventDefault();
175
+ void send(input);
176
+ };
177
+ const visible = messages.filter((m) => m.role !== "system");
178
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
179
+ "div",
180
+ {
181
+ className: `flex flex-col h-full min-h-[24rem] rounded-xl border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-950 ${className ?? ""}`,
182
+ children: [
183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ref: scrollRef, className: "flex-1 overflow-y-auto p-4 space-y-3", children: [
184
+ visible.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-full flex items-center justify-center text-sm text-neutral-400", children: emptyState ?? "Start the conversation." }),
185
+ visible.map((m, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
186
+ "div",
187
+ {
188
+ className: `flex ${m.role === "user" ? "justify-end" : "justify-start"}`,
189
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
190
+ "div",
191
+ {
192
+ className: `max-w-[85%] whitespace-pre-wrap rounded-2xl px-3.5 py-2 text-sm ${m.role === "user" ? "bg-neutral-900 text-white dark:bg-white dark:text-neutral-900" : "bg-neutral-100 text-neutral-900 dark:bg-neutral-900 dark:text-neutral-100"}`,
193
+ children: m.content || (busy && i === visible.length - 1 ? "\u2026" : "")
194
+ }
195
+ )
196
+ },
197
+ i
198
+ ))
199
+ ] }),
200
+ memory && !hideMemoryIndicator && recalled !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-1.5 px-4 py-1.5 text-xs text-neutral-500 border-t border-neutral-100 dark:border-neutral-900", children: [
201
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Brain, { className: "h-3.5 w-3.5" }),
202
+ recalled > 0 ? `Recalled ${recalled} ${recalled === 1 ? "memory" : "memories"} about this user` : "No prior memories yet \u2014 learning"
203
+ ] }),
204
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "px-4 py-2 text-xs text-red-600 dark:text-red-400", children: error }),
205
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
206
+ "form",
207
+ {
208
+ onSubmit,
209
+ className: "flex items-center gap-2 border-t border-neutral-200 p-3 dark:border-neutral-800",
210
+ children: [
211
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
212
+ "input",
213
+ {
214
+ value: input,
215
+ onChange: (e) => setInput(e.target.value),
216
+ placeholder,
217
+ disabled: busy,
218
+ className: "flex-1 rounded-lg bg-neutral-100 px-3 py-2 text-sm outline-none placeholder:text-neutral-400 disabled:opacity-60 dark:bg-neutral-900"
219
+ }
220
+ ),
221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
222
+ "button",
223
+ {
224
+ type: "submit",
225
+ disabled: busy || !input.trim(),
226
+ className: "inline-flex h-9 w-9 items-center justify-center rounded-lg bg-neutral-900 text-white transition disabled:opacity-40 dark:bg-white dark:text-neutral-900",
227
+ "aria-label": "Send",
228
+ children: busy ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Send, { className: "h-4 w-4" })
229
+ }
230
+ )
231
+ ]
232
+ }
233
+ )
234
+ ]
235
+ }
236
+ );
237
+ }
238
+
239
+ // src/react/memory/use-memory.ts
240
+ var import_react2 = require("react");
241
+ var DEFAULT_BASE_URL2 = "https://cencori.com";
242
+ function useMemory(options) {
243
+ const {
244
+ userId,
245
+ sessionId,
246
+ scope = "user",
247
+ namespace,
248
+ apiKey,
249
+ baseUrl = DEFAULT_BASE_URL2,
250
+ limit = 50,
251
+ manual = false
252
+ } = options;
253
+ const [memories, setMemories] = (0, import_react2.useState)([]);
254
+ const [loading, setLoading] = (0, import_react2.useState)(!manual);
255
+ const [error, setError] = (0, import_react2.useState)(null);
256
+ const base = (0, import_react2.useMemo)(() => baseUrl.replace(/\/$/, ""), [baseUrl]);
257
+ const headers = (0, import_react2.useMemo)(() => {
258
+ const h = { "Content-Type": "application/json" };
259
+ if (apiKey) h["CENCORI_API_KEY"] = apiKey;
260
+ return h;
261
+ }, [apiKey]);
262
+ const scopeBody = (0, import_react2.useCallback)(
263
+ () => ({ userId, sessionId, scope, namespace }),
264
+ [userId, sessionId, scope, namespace]
265
+ );
266
+ const request = (0, import_react2.useCallback)(
267
+ async (path, init) => {
268
+ const res = await fetch(`${base}${path}`, { ...init, headers });
269
+ if (!res.ok) {
270
+ const data = await res.json().catch(() => ({}));
271
+ throw new Error(data.error?.message || data.message || `Request failed (${res.status})`);
272
+ }
273
+ return res.json();
274
+ },
275
+ [base, headers]
276
+ );
277
+ const refresh = (0, import_react2.useCallback)(async () => {
278
+ if (!userId && !sessionId) return;
279
+ setLoading(true);
280
+ setError(null);
281
+ try {
282
+ const params = new URLSearchParams();
283
+ if (userId) params.set("userId", userId);
284
+ if (sessionId) params.set("sessionId", sessionId);
285
+ params.set("scope", scope);
286
+ if (namespace) params.set("namespace", namespace);
287
+ params.set("limit", String(limit));
288
+ const data = await request(
289
+ `/v1/memory/list?${params.toString()}`,
290
+ { method: "GET" }
291
+ );
292
+ setMemories(data.memories ?? []);
293
+ } catch (err) {
294
+ setError(err instanceof Error ? err.message : String(err));
295
+ } finally {
296
+ setLoading(false);
297
+ }
298
+ }, [userId, sessionId, scope, namespace, limit, request]);
299
+ (0, import_react2.useEffect)(() => {
300
+ if (!manual) void refresh();
301
+ }, [manual, refresh]);
302
+ const write = (0, import_react2.useCallback)(
303
+ async (content, opts) => {
304
+ setError(null);
305
+ try {
306
+ await request("/v1/memory/write", {
307
+ method: "POST",
308
+ body: JSON.stringify({ ...scopeBody(), content, ...opts })
309
+ });
310
+ await refresh();
311
+ } catch (err) {
312
+ setError(err instanceof Error ? err.message : String(err));
313
+ throw err;
314
+ }
315
+ },
316
+ [request, scopeBody, refresh]
317
+ );
318
+ const search = (0, import_react2.useCallback)(
319
+ async (query, topK = 5) => {
320
+ const data = await request("/v1/memory/search", {
321
+ method: "POST",
322
+ body: JSON.stringify({ ...scopeBody(), query, topK })
323
+ });
324
+ return data.results ?? [];
325
+ },
326
+ [request, scopeBody]
327
+ );
328
+ const forget = (0, import_react2.useCallback)(
329
+ async (id) => {
330
+ setError(null);
331
+ setMemories((prev) => prev.filter((m) => m.id !== id));
332
+ try {
333
+ await request(`/v1/memory/${id}`, { method: "DELETE" });
334
+ } catch (err) {
335
+ setError(err instanceof Error ? err.message : String(err));
336
+ await refresh();
337
+ throw err;
338
+ }
339
+ },
340
+ [request, refresh]
341
+ );
342
+ const exportAll = (0, import_react2.useCallback)(async () => {
343
+ const params = new URLSearchParams();
344
+ if (userId) params.set("userId", userId);
345
+ if (sessionId) params.set("sessionId", sessionId);
346
+ params.set("scope", scope);
347
+ if (namespace) params.set("namespace", namespace);
348
+ params.set("limit", "1000");
349
+ const data = await request(
350
+ `/v1/memory/list?${params.toString()}`,
351
+ { method: "GET" }
352
+ );
353
+ const blob = new Blob([JSON.stringify(data.memories ?? [], null, 2)], {
354
+ type: "application/json"
355
+ });
356
+ const url = URL.createObjectURL(blob);
357
+ const a = document.createElement("a");
358
+ a.href = url;
359
+ a.download = `cencori-memory-${userId ?? sessionId ?? "export"}.json`;
360
+ a.click();
361
+ URL.revokeObjectURL(url);
362
+ }, [request, userId, sessionId, scope, namespace]);
363
+ return { memories, loading, error, refresh, write, search, forget, exportAll };
364
+ }
365
+
366
+ // src/react/vision/vision-format-banner.tsx
367
+ var import_lucide_react2 = require("lucide-react");
368
+ var import_jsx_runtime2 = require("react/jsx-runtime");
31
369
  var UNIVERSAL_FORMATS = ["JPEG", "PNG", "WEBP", "GIF"];
32
370
  var PROVIDER_LIMITS = {
33
371
  openai: { formats: ["JPEG", "PNG", "WEBP", "GIF"], maxMB: 20, notes: "Non-animated GIFs only." },
@@ -46,7 +384,7 @@ function VisionFormatBanner({
46
384
  const info = provider ? PROVIDER_LIMITS[provider] : { formats: UNIVERSAL_FORMATS, maxMB: 5, notes: "These formats work across every supported provider." };
47
385
  const heading = title ?? (provider ? `Supported for ${providerLabel(provider)}` : "Supported image formats");
48
386
  const base = variant === "info" ? "bg-blue-50 border-blue-200 text-blue-900 dark:bg-blue-950/40 dark:border-blue-900 dark:text-blue-100" : "bg-neutral-50 border-neutral-200 text-neutral-800 dark:bg-neutral-900 dark:border-neutral-800 dark:text-neutral-100";
49
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
387
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
50
388
  "div",
51
389
  {
52
390
  role: "note",
@@ -56,23 +394,23 @@ function VisionFormatBanner({
56
394
  className
57
395
  ),
58
396
  children: [
59
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex-shrink-0 pt-0.5", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Info, { className: "h-5 w-5 opacity-70", "aria-hidden": true }) }),
60
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-2", children: [
61
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-medium", children: heading }),
62
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-wrap gap-1.5", children: info.formats.map((f) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
397
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-shrink-0 pt-0.5", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Info, { className: "h-5 w-5 opacity-70", "aria-hidden": true }) }),
398
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2", children: [
399
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-medium", children: heading }),
400
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-wrap gap-1.5", children: info.formats.map((f) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
63
401
  "span",
64
402
  {
65
403
  className: "inline-flex items-center gap-1 rounded-md border border-current/20 bg-white/50 px-2 py-0.5 text-xs font-medium dark:bg-black/20",
66
404
  children: [
67
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ImageIcon, { className: "h-3 w-3 opacity-70", "aria-hidden": true }),
405
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.ImageIcon, { className: "h-3 w-3 opacity-70", "aria-hidden": true }),
68
406
  f
69
407
  ]
70
408
  },
71
409
  f
72
410
  )) }),
73
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-xs opacity-80", children: [
411
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-xs opacity-80", children: [
74
412
  "Up to ",
75
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
413
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("strong", { children: [
76
414
  info.maxMB,
77
415
  "MB"
78
416
  ] }),
@@ -91,8 +429,8 @@ function providerLabel(p) {
91
429
  }
92
430
 
93
431
  // src/react/vision/vision-uploader.tsx
94
- var import_react = require("react");
95
- var import_lucide_react2 = require("lucide-react");
432
+ var import_react3 = require("react");
433
+ var import_lucide_react3 = require("lucide-react");
96
434
 
97
435
  // src/react/vision/downscale.ts
98
436
  var DOWNSCALABLE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
@@ -153,7 +491,7 @@ function renameToJpeg(name) {
153
491
  }
154
492
 
155
493
  // src/react/vision/vision-uploader.tsx
156
- var import_jsx_runtime2 = require("react/jsx-runtime");
494
+ var import_jsx_runtime3 = require("react/jsx-runtime");
157
495
  var UNIVERSAL_FORMATS2 = ["image/jpeg", "image/png", "image/webp", "image/gif"];
158
496
  var PROVIDER_RULES = {
159
497
  openai: { formats: UNIVERSAL_FORMATS2, maxBytes: 20 * 1024 * 1024 },
@@ -188,17 +526,17 @@ function VisionUploader({
188
526
  headers,
189
527
  className
190
528
  }) {
191
- const [file, setFile] = (0, import_react.useState)(null);
192
- const [preview, setPreview] = (0, import_react.useState)(null);
193
- const [error, setError] = (0, import_react.useState)(null);
194
- const [loading, setLoading] = (0, import_react.useState)(false);
195
- const [result, setResult] = (0, import_react.useState)(null);
196
- const [dragging, setDragging] = (0, import_react.useState)(false);
197
- const [notice, setNotice] = (0, import_react.useState)(null);
198
- const inputRef = (0, import_react.useRef)(null);
199
- const rules = (0, import_react.useMemo)(() => rulesFor(provider), [provider]);
529
+ const [file, setFile] = (0, import_react3.useState)(null);
530
+ const [preview, setPreview] = (0, import_react3.useState)(null);
531
+ const [error, setError] = (0, import_react3.useState)(null);
532
+ const [loading, setLoading] = (0, import_react3.useState)(false);
533
+ const [result, setResult] = (0, import_react3.useState)(null);
534
+ const [dragging, setDragging] = (0, import_react3.useState)(false);
535
+ const [notice, setNotice] = (0, import_react3.useState)(null);
536
+ const inputRef = (0, import_react3.useRef)(null);
537
+ const rules = (0, import_react3.useMemo)(() => rulesFor(provider), [provider]);
200
538
  const targetUrl = task === "analyze" ? endpoint : `${endpoint.replace(/\/$/, "")}/${task}`;
201
- const acceptFile = (0, import_react.useCallback)(async (f) => {
539
+ const acceptFile = (0, import_react3.useCallback)(async (f) => {
202
540
  setResult(null);
203
541
  setNotice(null);
204
542
  let effective = f;
@@ -249,13 +587,13 @@ function VisionUploader({
249
587
  setFile(effective);
250
588
  setPreview(URL.createObjectURL(effective));
251
589
  }, [rules, provider, autoCompress, onError]);
252
- const handleDrop = (0, import_react.useCallback)((e) => {
590
+ const handleDrop = (0, import_react3.useCallback)((e) => {
253
591
  e.preventDefault();
254
592
  setDragging(false);
255
593
  const f = e.dataTransfer.files?.[0];
256
594
  if (f) acceptFile(f);
257
595
  }, [acceptFile]);
258
- const handleSubmit = (0, import_react.useCallback)(async () => {
596
+ const handleSubmit = (0, import_react3.useCallback)(async () => {
259
597
  if (!file) return;
260
598
  setLoading(true);
261
599
  setError(null);
@@ -292,7 +630,7 @@ function VisionUploader({
292
630
  setLoading(false);
293
631
  }
294
632
  }, [file, prompt, model, task, targetUrl, apiKey, headers, onResult, onError]);
295
- const clear = (0, import_react.useCallback)(() => {
633
+ const clear = (0, import_react3.useCallback)(() => {
296
634
  setFile(null);
297
635
  setPreview(null);
298
636
  setResult(null);
@@ -300,9 +638,9 @@ function VisionUploader({
300
638
  setNotice(null);
301
639
  if (inputRef.current) inputRef.current.value = "";
302
640
  }, []);
303
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: joinClasses2("flex flex-col gap-3", className), children: [
304
- !hideBanner && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VisionFormatBanner, { provider }),
305
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
641
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: joinClasses2("flex flex-col gap-3", className), children: [
642
+ !hideBanner && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(VisionFormatBanner, { provider }),
643
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
306
644
  "div",
307
645
  {
308
646
  onDragOver: (e) => {
@@ -320,9 +658,9 @@ function VisionUploader({
320
658
  dragging ? "border-blue-500 bg-blue-50 dark:bg-blue-950/30" : "border-neutral-300 hover:border-neutral-400 dark:border-neutral-700 dark:hover:border-neutral-600"
321
659
  ),
322
660
  children: [
323
- preview ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative", children: [
324
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: preview, alt: "Preview", className: "max-h-64 rounded-lg object-contain" }),
325
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
661
+ preview ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative", children: [
662
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src: preview, alt: "Preview", className: "max-h-64 rounded-lg object-contain" }),
663
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
326
664
  "button",
327
665
  {
328
666
  type: "button",
@@ -332,19 +670,19 @@ function VisionUploader({
332
670
  },
333
671
  className: "absolute -right-2 -top-2 rounded-full bg-white p-1 shadow ring-1 ring-neutral-200 hover:bg-neutral-100 dark:bg-neutral-900 dark:ring-neutral-700",
334
672
  "aria-label": "Remove image",
335
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.X, { className: "h-4 w-4" })
673
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react3.X, { className: "h-4 w-4" })
336
674
  }
337
675
  )
338
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
339
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Upload, { className: "h-8 w-8 text-neutral-400", "aria-hidden": true }),
340
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-sm font-medium", children: "Drop an image or click to upload" }),
341
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-xs text-neutral-500", children: [
676
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
677
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react3.Upload, { className: "h-8 w-8 text-neutral-400", "aria-hidden": true }),
678
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm font-medium", children: "Drop an image or click to upload" }),
679
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "text-xs text-neutral-500", children: [
342
680
  friendlyFormats(rules.formats),
343
681
  " \xB7 up to ",
344
682
  formatBytes(rules.maxBytes)
345
683
  ] })
346
684
  ] }),
347
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
685
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
348
686
  "input",
349
687
  {
350
688
  ref: inputRef,
@@ -360,15 +698,15 @@ function VisionUploader({
360
698
  ]
361
699
  }
362
700
  ),
363
- error && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { role: "alert", className: "flex items-start gap-2 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-900 dark:border-red-900 dark:bg-red-950/40 dark:text-red-100", children: [
364
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.AlertCircle, { className: "mt-0.5 h-4 w-4 flex-shrink-0", "aria-hidden": true }),
365
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
366
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-medium", children: "Can't process this image" }),
367
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs opacity-90", children: error.message })
701
+ error && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { role: "alert", className: "flex items-start gap-2 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-900 dark:border-red-900 dark:bg-red-950/40 dark:text-red-100", children: [
702
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react3.AlertCircle, { className: "mt-0.5 h-4 w-4 flex-shrink-0", "aria-hidden": true }),
703
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
704
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "font-medium", children: "Can't process this image" }),
705
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs opacity-90", children: error.message })
368
706
  ] })
369
707
  ] }),
370
- notice && !error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs text-neutral-500 dark:text-neutral-400", children: notice }),
371
- file && !loading && !result && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
708
+ notice && !error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs text-neutral-500 dark:text-neutral-400", children: notice }),
709
+ file && !loading && !result && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
372
710
  "button",
373
711
  {
374
712
  type: "button",
@@ -377,19 +715,359 @@ function VisionUploader({
377
715
  children: "Analyze image"
378
716
  }
379
717
  ),
380
- loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "inline-flex items-center gap-2 text-sm text-neutral-600 dark:text-neutral-400", children: [
381
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Loader2, { className: "h-4 w-4 animate-spin", "aria-hidden": true }),
718
+ loading && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "inline-flex items-center gap-2 text-sm text-neutral-600 dark:text-neutral-400", children: [
719
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react3.Loader2, { className: "h-4 w-4 animate-spin", "aria-hidden": true }),
382
720
  "Analyzing\u2026"
383
721
  ] }),
384
- result !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rounded-lg border border-neutral-200 bg-neutral-50 p-3 text-sm dark:border-neutral-800 dark:bg-neutral-900", children: [
385
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mb-1 text-xs font-medium uppercase text-neutral-500", children: "Result" }),
386
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("pre", { className: "overflow-auto whitespace-pre-wrap break-words text-xs", children: typeof result === "string" ? result : JSON.stringify(result, null, 2) })
722
+ result !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "rounded-lg border border-neutral-200 bg-neutral-50 p-3 text-sm dark:border-neutral-800 dark:bg-neutral-900", children: [
723
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mb-1 text-xs font-medium uppercase text-neutral-500", children: "Result" }),
724
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("pre", { className: "overflow-auto whitespace-pre-wrap break-words text-xs", children: typeof result === "string" ? result : JSON.stringify(result, null, 2) })
725
+ ] })
726
+ ] });
727
+ }
728
+
729
+ // src/react/voice/voice-recorder.tsx
730
+ var import_react5 = require("react");
731
+ var import_lucide_react4 = require("lucide-react");
732
+
733
+ // src/react/voice/use-voice-recorder.ts
734
+ var import_react4 = require("react");
735
+ function pickMimeType() {
736
+ if (typeof MediaRecorder === "undefined") return void 0;
737
+ const candidates = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4", "audio/ogg"];
738
+ return candidates.find((t) => MediaRecorder.isTypeSupported(t));
739
+ }
740
+ function useVoiceRecorder() {
741
+ const [state, setState] = (0, import_react4.useState)("idle");
742
+ const [seconds, setSeconds] = (0, import_react4.useState)(0);
743
+ const [mimeType, setMimeType] = (0, import_react4.useState)(null);
744
+ const [error, setError] = (0, import_react4.useState)(null);
745
+ const [blob, setBlob] = (0, import_react4.useState)(null);
746
+ const recorderRef = (0, import_react4.useRef)(null);
747
+ const streamRef = (0, import_react4.useRef)(null);
748
+ const chunksRef = (0, import_react4.useRef)([]);
749
+ const timerRef = (0, import_react4.useRef)(null);
750
+ const cleanupStream = (0, import_react4.useCallback)(() => {
751
+ if (timerRef.current) {
752
+ clearInterval(timerRef.current);
753
+ timerRef.current = null;
754
+ }
755
+ streamRef.current?.getTracks().forEach((t) => t.stop());
756
+ streamRef.current = null;
757
+ }, []);
758
+ (0, import_react4.useEffect)(() => cleanupStream, [cleanupStream]);
759
+ const start = (0, import_react4.useCallback)(async () => {
760
+ setError(null);
761
+ setBlob(null);
762
+ setSeconds(0);
763
+ if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
764
+ setState("error");
765
+ setError("Microphone recording is not supported in this browser.");
766
+ return;
767
+ }
768
+ try {
769
+ setState("requesting");
770
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
771
+ streamRef.current = stream;
772
+ const type = pickMimeType();
773
+ const recorder = new MediaRecorder(stream, type ? { mimeType: type } : void 0);
774
+ chunksRef.current = [];
775
+ recorder.ondataavailable = (e) => {
776
+ if (e.data.size > 0) chunksRef.current.push(e.data);
777
+ };
778
+ recorder.start();
779
+ recorderRef.current = recorder;
780
+ setMimeType(recorder.mimeType || type || "audio/webm");
781
+ setState("recording");
782
+ timerRef.current = setInterval(() => setSeconds((s) => s + 1), 1e3);
783
+ } catch (e) {
784
+ cleanupStream();
785
+ setState("error");
786
+ setError(e instanceof Error ? e.message : "Could not access the microphone.");
787
+ }
788
+ }, [cleanupStream]);
789
+ const stop = (0, import_react4.useCallback)(() => {
790
+ const recorder = recorderRef.current;
791
+ if (!recorder || recorder.state === "inactive") {
792
+ return Promise.resolve(null);
793
+ }
794
+ return new Promise((resolve) => {
795
+ recorder.onstop = () => {
796
+ const type = recorder.mimeType || "audio/webm";
797
+ const recorded = new Blob(chunksRef.current, { type });
798
+ cleanupStream();
799
+ recorderRef.current = null;
800
+ setBlob(recorded);
801
+ setState("stopped");
802
+ resolve(recorded);
803
+ };
804
+ recorder.stop();
805
+ });
806
+ }, [cleanupStream]);
807
+ const reset = (0, import_react4.useCallback)(() => {
808
+ cleanupStream();
809
+ recorderRef.current = null;
810
+ chunksRef.current = [];
811
+ setBlob(null);
812
+ setSeconds(0);
813
+ setError(null);
814
+ setState("idle");
815
+ }, [cleanupStream]);
816
+ return { state, seconds, mimeType, error, blob, start, stop, reset };
817
+ }
818
+
819
+ // src/react/voice/voice-recorder.tsx
820
+ var import_jsx_runtime4 = require("react/jsx-runtime");
821
+ function joinClasses3(...parts) {
822
+ return parts.filter(Boolean).join(" ");
823
+ }
824
+ function formatClock(total) {
825
+ const m = Math.floor(total / 60);
826
+ const s = total % 60;
827
+ return `${m}:${String(s).padStart(2, "0")}`;
828
+ }
829
+ function VoiceRecorder({
830
+ endpoint = "/api/ai/audio/transcriptions",
831
+ apiKey,
832
+ model = "whisper-1",
833
+ language,
834
+ diarize = false,
835
+ onTranscript,
836
+ onError,
837
+ headers,
838
+ className
839
+ }) {
840
+ const rec = useVoiceRecorder();
841
+ const [uploading, setUploading] = (0, import_react5.useState)(false);
842
+ const [transcript, setTranscript] = (0, import_react5.useState)(null);
843
+ const [error, setError] = (0, import_react5.useState)(null);
844
+ const transcribe = (0, import_react5.useCallback)(async (blob) => {
845
+ setUploading(true);
846
+ setError(null);
847
+ try {
848
+ const form = new FormData();
849
+ const ext = (blob.type.split("/")[1] || "webm").split(";")[0];
850
+ form.append("file", blob, `recording.${ext}`);
851
+ form.append("model", model);
852
+ form.append("response_format", diarize ? "verbose_json" : "json");
853
+ if (language) form.append("language", language);
854
+ if (diarize) form.append("diarize", "true");
855
+ const res = await fetch(endpoint, {
856
+ method: "POST",
857
+ headers: { ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}, ...headers },
858
+ body: form
859
+ });
860
+ const data = await res.json().catch(() => ({}));
861
+ if (!res.ok) {
862
+ const err = {
863
+ code: typeof data.error === "string" ? data.error : "request_failed",
864
+ message: typeof data.message === "string" ? data.message : `Transcription failed (${res.status})`
865
+ };
866
+ setError(err);
867
+ onError?.(err);
868
+ return;
869
+ }
870
+ const full = data;
871
+ setTranscript(full);
872
+ onTranscript?.(full.text ?? "", full);
873
+ } catch (e) {
874
+ const err = { code: "network_error", message: e instanceof Error ? e.message : "Network error" };
875
+ setError(err);
876
+ onError?.(err);
877
+ } finally {
878
+ setUploading(false);
879
+ }
880
+ }, [endpoint, apiKey, model, language, diarize, headers, onTranscript, onError]);
881
+ const toggle = (0, import_react5.useCallback)(async () => {
882
+ if (rec.state === "recording") {
883
+ const blob = await rec.stop();
884
+ if (blob) await transcribe(blob);
885
+ } else {
886
+ setTranscript(null);
887
+ await rec.start();
888
+ }
889
+ }, [rec, transcribe]);
890
+ const recording = rec.state === "recording";
891
+ const busy = rec.state === "requesting" || uploading;
892
+ const activeError = error || (rec.error ? { code: "mic_error", message: rec.error } : null);
893
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: joinClasses3("cencori-voice-recorder", className), style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
894
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
895
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
896
+ "button",
897
+ {
898
+ type: "button",
899
+ onClick: toggle,
900
+ disabled: busy,
901
+ "aria-label": recording ? "Stop recording" : "Start recording",
902
+ className: joinClasses3("cencori-voice-btn", recording && "is-recording"),
903
+ style: {
904
+ display: "inline-flex",
905
+ alignItems: "center",
906
+ justifyContent: "center",
907
+ width: 56,
908
+ height: 56,
909
+ borderRadius: "9999px",
910
+ border: "none",
911
+ cursor: busy ? "default" : "pointer",
912
+ background: recording ? "#dc2626" : "#111827",
913
+ color: "#fff",
914
+ opacity: busy ? 0.6 : 1,
915
+ transition: "background 0.15s"
916
+ },
917
+ children: busy ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react4.Loader2, { size: 22, className: "animate-spin" }) : recording ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react4.Square, { size: 20 }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react4.Mic, { size: 22 })
918
+ }
919
+ ),
920
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", flexDirection: "column" }, children: [
921
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { fontVariantNumeric: "tabular-nums", fontWeight: 600 }, children: recording ? formatClock(rec.seconds) : uploading ? "Transcribing\u2026" : "Tap to record" }),
922
+ recording && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: { fontSize: 12, color: "#dc2626", display: "inline-flex", alignItems: "center", gap: 6 }, children: [
923
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { width: 8, height: 8, borderRadius: "9999px", background: "#dc2626", display: "inline-block" } }),
924
+ "recording"
925
+ ] })
926
+ ] })
927
+ ] }),
928
+ activeError && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { role: "alert", style: { display: "flex", gap: 8, alignItems: "flex-start", color: "#b91c1c", fontSize: 13 }, children: [
929
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react4.AlertCircle, { size: 16, style: { flexShrink: 0, marginTop: 2 } }),
930
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: activeError.message })
931
+ ] }),
932
+ transcript && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "cencori-voice-result", style: { fontSize: 14, lineHeight: 1.5 }, children: transcript.segments && transcript.segments.some((s) => s.speaker) ? transcript.segments.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { style: { margin: "4px 0" }, children: [
933
+ s.speaker && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("strong", { style: { marginRight: 6 }, children: [
934
+ s.speaker,
935
+ ":"
936
+ ] }),
937
+ s.text
938
+ ] }, i)) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { margin: 0 }, children: transcript.text }) })
939
+ ] });
940
+ }
941
+
942
+ // src/react/voice/speak-button.tsx
943
+ var import_react6 = require("react");
944
+ var import_lucide_react5 = require("lucide-react");
945
+ var import_jsx_runtime5 = require("react/jsx-runtime");
946
+ function joinClasses4(...parts) {
947
+ return parts.filter(Boolean).join(" ");
948
+ }
949
+ function SpeakButton({
950
+ text,
951
+ endpoint = "/api/ai/audio/speech",
952
+ apiKey,
953
+ model = "tts-1",
954
+ voice,
955
+ language,
956
+ label = "Listen",
957
+ headers,
958
+ onError,
959
+ className
960
+ }) {
961
+ const [state, setState] = (0, import_react6.useState)("idle");
962
+ const [error, setError] = (0, import_react6.useState)(null);
963
+ const audioRef = (0, import_react6.useRef)(null);
964
+ const urlRef = (0, import_react6.useRef)(null);
965
+ const cleanup = (0, import_react6.useCallback)(() => {
966
+ if (audioRef.current) {
967
+ audioRef.current.pause();
968
+ audioRef.current = null;
969
+ }
970
+ if (urlRef.current) {
971
+ URL.revokeObjectURL(urlRef.current);
972
+ urlRef.current = null;
973
+ }
974
+ }, []);
975
+ (0, import_react6.useEffect)(() => cleanup, [cleanup]);
976
+ const play = (0, import_react6.useCallback)(async () => {
977
+ if (state === "playing") {
978
+ cleanup();
979
+ setState("idle");
980
+ return;
981
+ }
982
+ if (!text.trim()) return;
983
+ setState("loading");
984
+ setError(null);
985
+ try {
986
+ const res = await fetch(endpoint, {
987
+ method: "POST",
988
+ headers: {
989
+ "Content-Type": "application/json",
990
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
991
+ ...headers
992
+ },
993
+ body: JSON.stringify({ input: text, model, voice, language })
994
+ });
995
+ if (!res.ok) {
996
+ const data = await res.json().catch(() => ({}));
997
+ const err = {
998
+ code: typeof data.error === "string" ? data.error : "request_failed",
999
+ message: typeof data.message === "string" ? data.message : `Speech failed (${res.status})`
1000
+ };
1001
+ setError(err);
1002
+ onError?.(err);
1003
+ setState("idle");
1004
+ return;
1005
+ }
1006
+ const blob = await res.blob();
1007
+ cleanup();
1008
+ const url = URL.createObjectURL(blob);
1009
+ urlRef.current = url;
1010
+ const audio = new Audio(url);
1011
+ audioRef.current = audio;
1012
+ audio.onended = () => {
1013
+ setState("idle");
1014
+ cleanup();
1015
+ };
1016
+ audio.onerror = () => {
1017
+ setState("idle");
1018
+ cleanup();
1019
+ };
1020
+ await audio.play();
1021
+ setState("playing");
1022
+ } catch (e) {
1023
+ const err = { code: "network_error", message: e instanceof Error ? e.message : "Network error" };
1024
+ setError(err);
1025
+ onError?.(err);
1026
+ setState("idle");
1027
+ }
1028
+ }, [state, text, endpoint, apiKey, model, voice, language, headers, onError, cleanup]);
1029
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { display: "inline-flex", flexDirection: "column", gap: 4 }, children: [
1030
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1031
+ "button",
1032
+ {
1033
+ type: "button",
1034
+ onClick: play,
1035
+ disabled: state === "loading" || !text.trim(),
1036
+ "aria-label": label ?? (state === "playing" ? "Pause" : "Listen"),
1037
+ className: joinClasses4("cencori-speak-btn", className),
1038
+ style: {
1039
+ display: "inline-flex",
1040
+ alignItems: "center",
1041
+ gap: 6,
1042
+ padding: label ? "6px 12px" : 8,
1043
+ borderRadius: 8,
1044
+ border: "1px solid #e5e7eb",
1045
+ background: "#fff",
1046
+ color: "#111827",
1047
+ cursor: state === "loading" ? "default" : "pointer",
1048
+ fontSize: 14,
1049
+ fontWeight: 500
1050
+ },
1051
+ children: [
1052
+ state === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react5.Loader2, { size: 16, className: "animate-spin" }) : state === "playing" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react5.Pause, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react5.Volume2, { size: 16 }),
1053
+ label && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: label })
1054
+ ]
1055
+ }
1056
+ ),
1057
+ error && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { role: "alert", style: { display: "inline-flex", gap: 4, alignItems: "center", color: "#b91c1c", fontSize: 12 }, children: [
1058
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react5.AlertCircle, { size: 13 }),
1059
+ error.message
387
1060
  ] })
388
1061
  ] });
389
1062
  }
390
1063
  // Annotate the CommonJS export names for ESM import in node:
391
1064
  0 && (module.exports = {
1065
+ Chat,
1066
+ SpeakButton,
392
1067
  VisionFormatBanner,
393
- VisionUploader
1068
+ VisionUploader,
1069
+ VoiceRecorder,
1070
+ useMemory,
1071
+ useVoiceRecorder
394
1072
  });
395
1073
  //# sourceMappingURL=index.js.map