@usetheo/ui 0.5.1-next.0 → 0.6.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { clsx } from 'clsx';
2
2
  import { extendTailwindMerge } from 'tailwind-merge';
3
- import { createContext, forwardRef, useId, Children, isValidElement, cloneElement, useState, useMemo, Fragment as Fragment$1, useRef, useEffect, useContext, useCallback } from 'react';
3
+ import { createContext, forwardRef, useId, Children, isValidElement, cloneElement, useState, useMemo, Fragment as Fragment$1, memo, useRef, useEffect, useContext, createElement, useCallback } from 'react';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
5
  import * as DropdownMenu2 from '@radix-ui/react-dropdown-menu';
6
- import { X, AlertCircle, ChevronDown, ChevronUp, Check, Minus, Circle, Settings2, Eye, Lock, Plus, Trash2, Users, User, BookOpen, Sparkles, Globe, Pencil, Coins, TrendingUp, TrendingDown, CircleX, CheckCircle2, Loader2, CircleDashed, RotateCcw, Folder, FolderOpen, Brain, Zap, ShieldAlert, Clock, Play, Square, Server, Plug, Wrench, CalendarDays, CornerDownRight, Bot, MessageSquare, ChevronRight, ArrowRight, AlertOctagon, Network, KeyRound, ShieldOff, Database, GitBranch, ChevronsUpDown, FileText, FileCode, FileSpreadsheet, FileImage, File, Hammer, ShieldCheck, Edit3, FilePlus, FileSearch, Terminal, AlertTriangle, CircleDot, FileEdit, Cloud, RefreshCw, Maximize2, ArrowLeft, RotateCw, Search, Paperclip, Mic, Send, GitCommit, Activity, EyeOff, Copy, GitPullRequest, ExternalLink, ShieldX, ArrowDownLeft, TriangleAlert, Info, ShieldQuestion, Rocket, Image, Palette, Moon, Sun, Hash, Slash } from 'lucide-react';
6
+ import { X, AlertCircle, ChevronDown, ChevronUp, Check, Minus, Circle, Settings2, Eye, Lock, Plus, Trash2, Users, User, BookOpen, Sparkles, Globe, Pencil, Coins, TrendingUp, TrendingDown, CircleX, CheckCircle2, Loader2, CircleDashed, RotateCcw, Folder, FolderOpen, Brain, Zap, ShieldAlert, Clock, Play, Square, Server, Plug, Wrench, CalendarDays, CornerDownRight, Bot, MessageSquare, ChevronRight, ArrowRight, AlertOctagon, Network, KeyRound, ShieldOff, Database, GitBranch, ChevronsUpDown, FileText, FileCode, FileSpreadsheet, FileImage, File, Hammer, ShieldCheck, Edit3, FilePlus, FileSearch, Terminal, AlertTriangle, CircleDot, FileEdit, Cloud, RefreshCw, Maximize2, ArrowLeft, RotateCw, Search, Paperclip, Mic, Send, GitCommit, Activity, EyeOff, Copy, GitPullRequest, ExternalLink, ShieldX, ArrowDownLeft, TriangleAlert, Info, ShieldQuestion, BrainCircuitIcon, ImageIcon, FileIcon, ExternalLinkIcon, FileTextIcon, WrenchIcon, CodeIcon, ShieldIcon, AlertCircleIcon, CheckCircleIcon, LoaderIcon, Rocket, Image, Palette, Moon, Sun, ChevronLeftIcon, ChevronRightIcon, Hash, Slash, CheckIcon, CopyIcon } from 'lucide-react';
7
7
  import * as ToastPrimitive from '@radix-ui/react-toast';
8
8
  import { cva } from 'class-variance-authority';
9
9
  import { Slot } from '@radix-ui/react-slot';
@@ -1411,6 +1411,38 @@ function TheoUIProvider({ children, theme, toaster }) {
1411
1411
  return /* @__PURE__ */ jsx(ThemeProvider, { themes: themes ?? builtinThemes, ...restTheme, children: /* @__PURE__ */ jsx(Toaster, { ...toaster, children }) });
1412
1412
  }
1413
1413
  TheoUIProvider.displayName = "TheoUIProvider";
1414
+
1415
+ // src/types/chat.ts
1416
+ function isTextUIPart(part) {
1417
+ return part.type === "text";
1418
+ }
1419
+ function isReasoningUIPart(part) {
1420
+ return part.type === "reasoning";
1421
+ }
1422
+ function isFileUIPart(part) {
1423
+ return part.type === "file";
1424
+ }
1425
+ function isReasoningFileUIPart(part) {
1426
+ return part.type === "reasoning-file";
1427
+ }
1428
+ function isSourceUrlUIPart(part) {
1429
+ return part.type === "source-url";
1430
+ }
1431
+ function isSourceDocumentUIPart(part) {
1432
+ return part.type === "source-document";
1433
+ }
1434
+ function isStepStartUIPart(part) {
1435
+ return part.type === "step-start";
1436
+ }
1437
+ function isCustomContentUIPart(part) {
1438
+ return part.type === "custom";
1439
+ }
1440
+ function isToolUIPart(part) {
1441
+ return part.type === "dynamic-tool" || part.type.startsWith("tool-");
1442
+ }
1443
+ function isDataUIPart(part) {
1444
+ return part.type.startsWith("data-");
1445
+ }
1414
1446
  var buttonVariants = cva(
1415
1447
  [
1416
1448
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg",
@@ -5135,83 +5167,986 @@ var SessionListItem = forwardRef(
5135
5167
  )
5136
5168
  );
5137
5169
  SessionListItem.displayName = "SessionListItem";
5138
- var ChatMessage = forwardRef(
5139
- ({ className, message, avatar, actions, ...props }, ref) => {
5140
- if (message.role === "user") {
5141
- return /* @__PURE__ */ jsxs(
5142
- "article",
5143
- {
5144
- ref,
5145
- className: cn("flex justify-end gap-3", className),
5146
- "aria-label": "user message",
5147
- ...props,
5148
- children: [
5149
- /* @__PURE__ */ jsxs(
5150
- "div",
5151
- {
5152
- className: cn(
5153
- "max-w-[70%] rounded-2xl rounded-tr-md border border-border/40 bg-secondary",
5154
- "px-4 py-3 text-body-md text-secondary-foreground"
5155
- ),
5156
- children: [
5157
- message.content,
5158
- message.timestamp ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-right font-mono text-label text-muted-foreground", children: message.timestamp }) : null
5159
- ]
5160
- }
5161
- ),
5162
- avatar ? /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar }) : null
5163
- ]
5164
- }
5165
- );
5170
+ function deriveDataName(part) {
5171
+ return part.type.slice("data-".length);
5172
+ }
5173
+ function DataPart({ part, renderers }) {
5174
+ const name = deriveDataName(part);
5175
+ const renderer = renderers?.[part.type] ?? renderers?.[name];
5176
+ if (renderer) return renderer(part.data, part);
5177
+ return /* @__PURE__ */ jsxs(
5178
+ "details",
5179
+ {
5180
+ className: cn("my-2 rounded-md border border-border bg-muted/20 px-3 py-1.5 text-body-sm"),
5181
+ "data-theo-data": name,
5182
+ children: [
5183
+ /* @__PURE__ */ jsxs("summary", { className: "flex cursor-pointer items-center gap-1.5 font-mono text-label-caps text-muted-foreground uppercase tracking-wider", children: [
5184
+ /* @__PURE__ */ jsx(CodeIcon, { className: "size-3", "aria-hidden": "true" }),
5185
+ /* @__PURE__ */ jsxs("span", { children: [
5186
+ "data-",
5187
+ name
5188
+ ] })
5189
+ ] }),
5190
+ /* @__PURE__ */ jsx("pre", { className: "mt-2 overflow-x-auto border-border border-t pt-2 text-code-sm", children: /* @__PURE__ */ jsx("code", { children: safeStringify(part.data) }) })
5191
+ ]
5166
5192
  }
5167
- if (message.role === "system") {
5168
- return /* @__PURE__ */ jsx(
5169
- "article",
5193
+ );
5194
+ }
5195
+ function safeStringify(value) {
5196
+ try {
5197
+ return JSON.stringify(value, null, 2);
5198
+ } catch {
5199
+ return String(value);
5200
+ }
5201
+ }
5202
+ function isImage(mediaType) {
5203
+ return mediaType.startsWith("image/") || mediaType === "image";
5204
+ }
5205
+ function FilePart({ part }) {
5206
+ const safeUrl = safeHref(part.url);
5207
+ const label = part.filename ?? part.url.split("/").pop() ?? "file";
5208
+ if (isImage(part.mediaType)) {
5209
+ if (!safeUrl) {
5210
+ return /* @__PURE__ */ jsxs(
5211
+ "div",
5170
5212
  {
5171
- ref,
5172
5213
  className: cn(
5173
- "rounded-lg border border-accent-deep/40 border-l-4 bg-accent/10 px-4 py-2",
5174
- "text-body-sm text-foreground",
5175
- className
5214
+ "my-2 inline-flex items-center gap-2 rounded-md border border-border bg-muted/30 px-3 py-2",
5215
+ "text-body-sm text-muted-foreground"
5176
5216
  ),
5177
- "aria-label": "system message",
5178
- ...props,
5179
- children: message.content
5217
+ children: [
5218
+ /* @__PURE__ */ jsx(ImageIcon, { className: "size-4", "aria-hidden": "true" }),
5219
+ /* @__PURE__ */ jsx("span", { children: label }),
5220
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "(blocked)" })
5221
+ ]
5180
5222
  }
5181
5223
  );
5182
5224
  }
5183
5225
  return /* @__PURE__ */ jsxs(
5184
- "article",
5226
+ "figure",
5185
5227
  {
5186
- ref,
5187
- className: cn("flex gap-3", className),
5188
- "aria-label": "assistant message",
5189
- ...props,
5228
+ className: "my-3 overflow-hidden rounded-lg border border-border",
5229
+ "data-theo-file": "image",
5190
5230
  children: [
5191
- avatar ? /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar }) : null,
5231
+ /* @__PURE__ */ jsx("img", { src: safeUrl, alt: label, className: "block max-w-full", loading: "lazy" }),
5232
+ part.filename ? /* @__PURE__ */ jsx("figcaption", { className: "border-border border-t bg-muted/30 px-3 py-1.5 font-mono text-label-caps text-muted-foreground uppercase tracking-wider", children: part.filename }) : null
5233
+ ]
5234
+ }
5235
+ );
5236
+ }
5237
+ return /* @__PURE__ */ jsxs(
5238
+ "div",
5239
+ {
5240
+ className: cn(
5241
+ "my-2 inline-flex items-center gap-2 rounded-md border border-border bg-card px-3 py-2",
5242
+ "text-body-sm"
5243
+ ),
5244
+ "data-theo-file": "generic",
5245
+ children: [
5246
+ /* @__PURE__ */ jsx(FileIcon, { className: "size-4 text-muted-foreground", "aria-hidden": "true" }),
5247
+ safeUrl ? /* @__PURE__ */ jsx("a", { href: safeUrl, className: "text-primary hover:text-primary-deep hover:underline", children: label }) : /* @__PURE__ */ jsx("span", { children: label }),
5248
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-label-caps text-muted-foreground uppercase tracking-wider", children: part.mediaType })
5249
+ ]
5250
+ }
5251
+ );
5252
+ }
5253
+ var DEFAULT_THEMES = { light: "github-light", dark: "github-dark" };
5254
+ var cachedHighlighter = null;
5255
+ var highlighterFailed = false;
5256
+ async function getHighlighter(themes) {
5257
+ if (cachedHighlighter) return cachedHighlighter;
5258
+ if (highlighterFailed) return null;
5259
+ try {
5260
+ const shiki = await import('shiki');
5261
+ cachedHighlighter = await shiki.createHighlighter({
5262
+ themes: [themes.light, themes.dark],
5263
+ langs: [
5264
+ "ts",
5265
+ "tsx",
5266
+ "js",
5267
+ "jsx",
5268
+ "python",
5269
+ "go",
5270
+ "rust",
5271
+ "java",
5272
+ "json",
5273
+ "yaml",
5274
+ "bash",
5275
+ "shell",
5276
+ "html",
5277
+ "css",
5278
+ "sql",
5279
+ "markdown"
5280
+ ]
5281
+ });
5282
+ return cachedHighlighter;
5283
+ } catch {
5284
+ highlighterFailed = true;
5285
+ return null;
5286
+ }
5287
+ }
5288
+ function CodeBlock({ code, language, themes, className }) {
5289
+ const [html, setHtml] = useState(null);
5290
+ const [copied, setCopied] = useState(false);
5291
+ const effectiveThemes = themes ?? DEFAULT_THEMES;
5292
+ useEffect(() => {
5293
+ let cancelled = false;
5294
+ if (!language) return;
5295
+ getHighlighter(effectiveThemes).then((hl) => {
5296
+ if (cancelled || !hl) return;
5297
+ try {
5298
+ const out = hl.codeToHtml(code, {
5299
+ lang: language,
5300
+ themes: { light: effectiveThemes.light, dark: effectiveThemes.dark },
5301
+ defaultColor: "light"
5302
+ });
5303
+ setHtml(out);
5304
+ } catch {
5305
+ }
5306
+ }).catch(() => {
5307
+ });
5308
+ return () => {
5309
+ cancelled = true;
5310
+ };
5311
+ }, [code, language, effectiveThemes.light, effectiveThemes.dark, effectiveThemes]);
5312
+ const handleCopy = async () => {
5313
+ try {
5314
+ await navigator.clipboard.writeText(code);
5315
+ setCopied(true);
5316
+ setTimeout(() => setCopied(false), 2e3);
5317
+ } catch {
5318
+ }
5319
+ };
5320
+ return /* @__PURE__ */ jsxs(
5321
+ "div",
5322
+ {
5323
+ className: cn(
5324
+ "group relative my-4 overflow-hidden rounded-lg border border-border bg-muted/30",
5325
+ className
5326
+ ),
5327
+ "data-theo-code-block": "",
5328
+ children: [
5329
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-border border-b bg-muted/50 px-3 py-1.5", children: [
5330
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-label-caps text-muted-foreground uppercase tracking-wider", children: language || "text" }),
5331
+ /* @__PURE__ */ jsx(
5332
+ "button",
5333
+ {
5334
+ type: "button",
5335
+ onClick: handleCopy,
5336
+ className: cn(
5337
+ "inline-flex h-7 items-center gap-1.5 rounded-md px-2 text-label",
5338
+ "text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground",
5339
+ "focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
5340
+ ),
5341
+ "aria-label": copied ? "Copied" : "Copy code",
5342
+ children: copied ? /* @__PURE__ */ jsxs(Fragment, { children: [
5343
+ /* @__PURE__ */ jsx(CheckIcon, { className: "size-3.5", "aria-hidden": "true" }),
5344
+ /* @__PURE__ */ jsx("span", { children: "Copied" })
5345
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5346
+ /* @__PURE__ */ jsx(CopyIcon, { className: "size-3.5", "aria-hidden": "true" }),
5347
+ /* @__PURE__ */ jsx("span", { children: "Copy" })
5348
+ ] })
5349
+ }
5350
+ )
5351
+ ] }),
5352
+ html ? /* @__PURE__ */ jsx(
5353
+ "div",
5354
+ {
5355
+ className: "[&_pre]:!bg-transparent [&_pre]:!m-0 [&_pre]:!p-0 overflow-x-auto p-3 text-code-sm",
5356
+ dangerouslySetInnerHTML: { __html: html }
5357
+ }
5358
+ ) : /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto p-3 text-code-sm", children: /* @__PURE__ */ jsx("code", { className: language ? `language-${language}` : void 0, children: code }) })
5359
+ ]
5360
+ }
5361
+ );
5362
+ }
5363
+ function InlineCode({ className, children, ...props }) {
5364
+ return /* @__PURE__ */ jsx(
5365
+ "code",
5366
+ {
5367
+ className: cn(
5368
+ "rounded bg-muted px-1.5 py-0.5 font-mono text-code-sm text-foreground",
5369
+ className
5370
+ ),
5371
+ ...props,
5372
+ children
5373
+ }
5374
+ );
5375
+ }
5376
+
5377
+ // src/lib/markdown/streaming-preprocess.ts
5378
+ function preprocessStreaming(markdown, isStreaming = true) {
5379
+ if (!isStreaming) return markdown;
5380
+ let buf = markdown;
5381
+ const fenceCount = countTripleBackticks(buf);
5382
+ if (fenceCount % 2 === 1) {
5383
+ buf = `${buf.endsWith("\n") ? buf : `${buf}
5384
+ `}\`\`\``;
5385
+ return buf;
5386
+ }
5387
+ const blockMathCount = countOccurrences(buf, "$$");
5388
+ if (blockMathCount % 2 === 1) {
5389
+ buf = `${buf}$$`;
5390
+ return buf;
5391
+ }
5392
+ const inlineBackticks = countSingleBackticks(buf);
5393
+ if (inlineBackticks % 2 === 1) {
5394
+ buf = `${buf}\``;
5395
+ }
5396
+ const inlineDollars = countSingleDollars(buf);
5397
+ if (inlineDollars % 2 === 1) {
5398
+ buf = `${buf}$`;
5399
+ }
5400
+ for (const marker of ["**", "__", "*", "_"]) {
5401
+ if (countMarker(buf, marker) % 2 === 1) {
5402
+ buf = `${buf}${marker}`;
5403
+ }
5404
+ }
5405
+ buf = closeUnclosedLink(buf);
5406
+ return buf;
5407
+ }
5408
+ function countTripleBackticks(s) {
5409
+ let count = 0;
5410
+ let i = 0;
5411
+ while (i < s.length) {
5412
+ if (s[i] === "`" && s[i + 1] === "`" && s[i + 2] === "`") {
5413
+ count++;
5414
+ i += 3;
5415
+ } else {
5416
+ i++;
5417
+ }
5418
+ }
5419
+ return count;
5420
+ }
5421
+ function countSingleBackticks(s) {
5422
+ let count = 0;
5423
+ let i = 0;
5424
+ while (i < s.length) {
5425
+ if (s[i] === "`") {
5426
+ if (s[i + 1] === "`" && s[i + 2] === "`") {
5427
+ i += 3;
5428
+ continue;
5429
+ }
5430
+ count++;
5431
+ }
5432
+ i++;
5433
+ }
5434
+ return count;
5435
+ }
5436
+ function countOccurrences(s, needle) {
5437
+ if (needle.length === 0) return 0;
5438
+ let count = 0;
5439
+ let i = s.indexOf(needle);
5440
+ while (i !== -1) {
5441
+ count++;
5442
+ i = s.indexOf(needle, i + needle.length);
5443
+ }
5444
+ return count;
5445
+ }
5446
+ function countSingleDollars(s) {
5447
+ let count = 0;
5448
+ let i = 0;
5449
+ while (i < s.length) {
5450
+ if (s[i] === "$") {
5451
+ if (s[i + 1] === "$") {
5452
+ i += 2;
5453
+ continue;
5454
+ }
5455
+ if (i > 0 && s[i - 1] === "\\") {
5456
+ i++;
5457
+ continue;
5458
+ }
5459
+ count++;
5460
+ }
5461
+ i++;
5462
+ }
5463
+ return count;
5464
+ }
5465
+ function countMarker(s, marker) {
5466
+ if (marker.length === 0) return 0;
5467
+ if (marker.length === 1) {
5468
+ let count2 = 0;
5469
+ let i2 = 0;
5470
+ while (i2 < s.length) {
5471
+ if (s[i2] === marker) {
5472
+ if (s[i2 + 1] === marker) {
5473
+ i2 += 2;
5474
+ continue;
5475
+ }
5476
+ if (i2 > 0 && s[i2 - 1] === "\\") {
5477
+ i2++;
5478
+ continue;
5479
+ }
5480
+ count2++;
5481
+ }
5482
+ i2++;
5483
+ }
5484
+ return count2;
5485
+ }
5486
+ let count = 0;
5487
+ let i = 0;
5488
+ while (i <= s.length - marker.length) {
5489
+ if (s.substring(i, i + marker.length) === marker) {
5490
+ count++;
5491
+ i += marker.length;
5492
+ } else {
5493
+ i++;
5494
+ }
5495
+ }
5496
+ return count;
5497
+ }
5498
+ function closeUnclosedLink(s) {
5499
+ const lastOpenParen = s.lastIndexOf("(");
5500
+ const lastCloseParen = s.lastIndexOf(")");
5501
+ if (lastOpenParen === -1 || lastOpenParen <= lastCloseParen) return s;
5502
+ if (s[lastOpenParen - 1] !== "]") return s;
5503
+ const closingBracket = lastOpenParen - 1;
5504
+ const openingBracket = s.lastIndexOf("[", closingBracket - 1);
5505
+ if (openingBracket === -1) return s;
5506
+ return `${s})`;
5507
+ }
5508
+
5509
+ // src/lib/markdown/parser.ts
5510
+ async function parseBody(body) {
5511
+ const [{ fromMarkdown }, { gfmFromMarkdown }, { gfm }] = await Promise.all([
5512
+ import('mdast-util-from-markdown'),
5513
+ import('mdast-util-gfm'),
5514
+ import('micromark-extension-gfm')
5515
+ ]);
5516
+ return fromMarkdown(body, {
5517
+ extensions: [gfm()],
5518
+ mdastExtensions: [gfmFromMarkdown()]
5519
+ });
5520
+ }
5521
+ async function mdastToHast(tree) {
5522
+ const { toHast } = await import('mdast-util-to-hast');
5523
+ const hast = toHast(tree, { allowDangerousHtml: false });
5524
+ if (!hast || hast.type !== "root") {
5525
+ return { type: "root", children: hast ? [hast] : [] };
5526
+ }
5527
+ return hast;
5528
+ }
5529
+ async function sanitizeHast(tree) {
5530
+ const { sanitize, defaultSchema } = await import('hast-util-sanitize');
5531
+ const schema = {
5532
+ ...defaultSchema,
5533
+ attributes: {
5534
+ ...defaultSchema.attributes ?? {},
5535
+ code: [...defaultSchema.attributes?.code ?? [], ["className", /^language-./]],
5536
+ pre: [...defaultSchema.attributes?.pre ?? [], ["className", /./]],
5537
+ span: [...defaultSchema.attributes?.span ?? [], ["className", /./], ["style"]]
5538
+ }
5539
+ };
5540
+ const safe2 = sanitize(tree, schema);
5541
+ return safe2.type === "root" ? safe2 : { type: "root", children: [safe2] };
5542
+ }
5543
+ async function hastToReact(tree, components) {
5544
+ const { Fragment: Fragment17, jsx: jsx117, jsxs: jsxs95 } = await import('react/jsx-runtime');
5545
+ const { toJsxRuntime } = await import('hast-util-to-jsx-runtime');
5546
+ return toJsxRuntime(tree, {
5547
+ Fragment: Fragment17,
5548
+ jsx: jsx117,
5549
+ jsxs: jsxs95,
5550
+ components
5551
+ });
5552
+ }
5553
+ async function parseMarkdownToReact(markdown, opts = {}) {
5554
+ const preprocessed = preprocessStreaming(markdown, opts.isStreaming ?? false);
5555
+ const mdast = await parseBody(preprocessed);
5556
+ const hast = await mdastToHast(mdast);
5557
+ const safe2 = await sanitizeHast(hast);
5558
+ return hastToReact(safe2, opts.components);
5559
+ }
5560
+ async function parseMarkdownToReactSafe(markdown, opts = {}) {
5561
+ try {
5562
+ return await parseMarkdownToReact(markdown, opts);
5563
+ } catch {
5564
+ return createElement("span", { className: "whitespace-pre-wrap" }, markdown);
5565
+ }
5566
+ }
5567
+ function isFenced(props) {
5568
+ const cls = props.className;
5569
+ if (typeof cls === "string") return cls.startsWith("language-");
5570
+ if (Array.isArray(cls))
5571
+ return cls.some((c) => typeof c === "string" && c.startsWith("language-"));
5572
+ return false;
5573
+ }
5574
+ function extractLanguage(props) {
5575
+ const cls = props.className;
5576
+ const list = typeof cls === "string" ? [cls] : Array.isArray(cls) ? cls : [];
5577
+ for (const c of list) {
5578
+ if (typeof c === "string" && c.startsWith("language-")) {
5579
+ return c.slice("language-".length);
5580
+ }
5581
+ }
5582
+ return void 0;
5583
+ }
5584
+ function extractText(children) {
5585
+ if (typeof children === "string") return children;
5586
+ if (Array.isArray(children)) return children.map(extractText).join("");
5587
+ if (children && typeof children === "object" && "props" in children && children.props) {
5588
+ return extractText(children.props.children);
5589
+ }
5590
+ return "";
5591
+ }
5592
+ var MARKDOWN_COMPONENTS = {
5593
+ code: (props) => {
5594
+ if (isFenced(props)) {
5595
+ const language = extractLanguage(props);
5596
+ const code = extractText(props.children);
5597
+ return /* @__PURE__ */ jsx(CodeBlock, { code, language });
5598
+ }
5599
+ return /* @__PURE__ */ jsx(InlineCode, { ...props, children: props.children });
5600
+ },
5601
+ // Strip the default `<pre>` since `<CodeBlock>` ships its own wrapper.
5602
+ // Inline `<pre>` still works for raw whitespace-preserving text.
5603
+ pre: ({ children }) => {
5604
+ return /* @__PURE__ */ jsx(Fragment, { children });
5605
+ }
5606
+ };
5607
+ function ChatMessageResponseImpl({
5608
+ text,
5609
+ isStreaming = false,
5610
+ className
5611
+ }) {
5612
+ const [tree, setTree] = useState(null);
5613
+ useEffect(() => {
5614
+ let cancelled = false;
5615
+ parseMarkdownToReactSafe(text, {
5616
+ isStreaming,
5617
+ components: MARKDOWN_COMPONENTS
5618
+ }).then((next) => {
5619
+ if (!cancelled) setTree(next);
5620
+ });
5621
+ return () => {
5622
+ cancelled = true;
5623
+ };
5624
+ }, [text, isStreaming]);
5625
+ return /* @__PURE__ */ jsx(
5626
+ "div",
5627
+ {
5628
+ className: cn(
5629
+ "prose-theo max-w-none text-body-md text-foreground leading-relaxed",
5630
+ // First/last child margin reset — fork from vercel/ai-elements
5631
+ "[&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
5632
+ // Heading sizes inside chat use our typescale, not browser defaults
5633
+ "[&_h1]:mt-4 [&_h1]:mb-2 [&_h1]:font-semibold [&_h1]:text-title-lg",
5634
+ "[&_h2]:mt-3 [&_h2]:mb-2 [&_h2]:font-semibold [&_h2]:text-title-md",
5635
+ "[&_h3]:mt-3 [&_h3]:mb-1.5 [&_h3]:font-semibold [&_h3]:text-body-lg",
5636
+ "[&_p]:my-2",
5637
+ "[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5",
5638
+ "[&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5",
5639
+ "[&_li]:my-0.5",
5640
+ "[&_blockquote]:my-2 [&_blockquote]:border-primary/40 [&_blockquote]:border-l-2 [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground",
5641
+ "[&_a:hover]:text-primary-deep [&_a]:text-primary [&_a]:underline",
5642
+ "[&_table]:my-3 [&_table]:w-full [&_table]:border-collapse",
5643
+ "[&_th]:border [&_th]:border-border [&_th]:bg-muted/40 [&_th]:px-3 [&_th]:py-1.5 [&_th]:text-left",
5644
+ "[&_td]:border [&_td]:border-border [&_td]:px-3 [&_td]:py-1.5",
5645
+ "[&_hr]:my-4 [&_hr]:border-border",
5646
+ className
5647
+ ),
5648
+ "data-theo-chat-response": "",
5649
+ children: tree
5650
+ }
5651
+ );
5652
+ }
5653
+ var ChatMessageResponse = memo(ChatMessageResponseImpl, (prev, next) => {
5654
+ return prev.text === next.text && prev.isStreaming === next.isStreaming;
5655
+ });
5656
+ ChatMessageResponse.displayName = "ChatMessageResponse";
5657
+ function ReasoningPart({ part, defaultOpen }) {
5658
+ const isStreaming = part.state === "streaming";
5659
+ const open = defaultOpen ?? isStreaming;
5660
+ return /* @__PURE__ */ jsxs(
5661
+ "details",
5662
+ {
5663
+ className: cn(
5664
+ "my-2 rounded-md border border-border bg-muted/20 px-3 py-2",
5665
+ "[&[open]]:bg-muted/40"
5666
+ ),
5667
+ open,
5668
+ "data-theo-reasoning": "",
5669
+ children: [
5670
+ /* @__PURE__ */ jsxs(
5671
+ "summary",
5672
+ {
5673
+ className: cn(
5674
+ "cursor-pointer list-none font-mono text-label-caps text-muted-foreground uppercase tracking-wider",
5675
+ "flex items-center gap-1.5 marker:hidden",
5676
+ "transition-colors hover:text-foreground"
5677
+ ),
5678
+ children: [
5679
+ /* @__PURE__ */ jsx(BrainCircuitIcon, { className: "size-3.5", "aria-hidden": "true" }),
5680
+ /* @__PURE__ */ jsx("span", { children: "Reasoning" }),
5681
+ isStreaming ? /* @__PURE__ */ jsx("span", { className: "text-primary/80", children: "\u2026" }) : null
5682
+ ]
5683
+ }
5684
+ ),
5685
+ /* @__PURE__ */ jsx("div", { className: "mt-2 border-border border-t pt-2", children: /* @__PURE__ */ jsx(ChatMessageResponse, { text: part.text, isStreaming }) })
5686
+ ]
5687
+ }
5688
+ );
5689
+ }
5690
+ function SourceUrlPart({ part }) {
5691
+ const safe2 = safeHref(part.url);
5692
+ const label = part.title || part.url;
5693
+ return /* @__PURE__ */ jsxs(
5694
+ "span",
5695
+ {
5696
+ className: cn(
5697
+ "my-1 inline-flex max-w-full items-center gap-1.5 rounded-md border border-border bg-card px-2 py-1",
5698
+ "align-middle font-mono text-label"
5699
+ ),
5700
+ "data-theo-source": "url",
5701
+ children: [
5702
+ /* @__PURE__ */ jsx(ExternalLinkIcon, { className: "size-3 text-muted-foreground", "aria-hidden": "true" }),
5703
+ safe2 ? /* @__PURE__ */ jsx(
5704
+ "a",
5705
+ {
5706
+ href: safe2,
5707
+ className: "truncate text-primary hover:text-primary-deep hover:underline",
5708
+ rel: "noopener noreferrer",
5709
+ target: "_blank",
5710
+ children: label
5711
+ }
5712
+ ) : /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: label })
5713
+ ]
5714
+ }
5715
+ );
5716
+ }
5717
+ function SourceDocumentPart({ part }) {
5718
+ return /* @__PURE__ */ jsxs(
5719
+ "span",
5720
+ {
5721
+ className: cn(
5722
+ "my-1 inline-flex max-w-full items-center gap-1.5 rounded-md border border-border bg-card px-2 py-1",
5723
+ "align-middle font-mono text-label"
5724
+ ),
5725
+ "data-theo-source": "document",
5726
+ children: [
5727
+ /* @__PURE__ */ jsx(FileTextIcon, { className: "size-3 text-muted-foreground", "aria-hidden": "true" }),
5728
+ /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: part.title }),
5729
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\xB7" }),
5730
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: part.mediaType })
5731
+ ]
5732
+ }
5733
+ );
5734
+ }
5735
+ function TextPart({ part }) {
5736
+ return /* @__PURE__ */ jsx(ChatMessageResponse, { text: part.text, isStreaming: part.state === "streaming" });
5737
+ }
5738
+ function deriveToolName(part) {
5739
+ if (part.toolName) return part.toolName;
5740
+ if (part.type === "dynamic-tool") return "dynamic-tool";
5741
+ return part.type.slice("tool-".length);
5742
+ }
5743
+ function stateBadge(state) {
5744
+ switch (state) {
5745
+ case "input-streaming":
5746
+ return {
5747
+ icon: /* @__PURE__ */ jsx(LoaderIcon, { className: "size-3.5 animate-spin", "aria-hidden": "true" }),
5748
+ label: "Streaming input",
5749
+ tone: "text-muted-foreground"
5750
+ };
5751
+ case "input-available":
5752
+ return {
5753
+ icon: /* @__PURE__ */ jsx(WrenchIcon, { className: "size-3.5", "aria-hidden": "true" }),
5754
+ label: "Ready to call",
5755
+ tone: "text-primary"
5756
+ };
5757
+ case "approval-requested":
5758
+ return {
5759
+ icon: /* @__PURE__ */ jsx(ShieldIcon, { className: "size-3.5", "aria-hidden": "true" }),
5760
+ label: "Awaiting approval",
5761
+ tone: "text-warning"
5762
+ };
5763
+ case "approval-responded":
5764
+ return {
5765
+ icon: /* @__PURE__ */ jsx(ShieldIcon, { className: "size-3.5", "aria-hidden": "true" }),
5766
+ label: "Approval responded",
5767
+ tone: "text-primary"
5768
+ };
5769
+ case "output-available":
5770
+ return {
5771
+ icon: /* @__PURE__ */ jsx(CheckCircleIcon, { className: "size-3.5", "aria-hidden": "true" }),
5772
+ label: "Completed",
5773
+ tone: "text-success"
5774
+ };
5775
+ case "output-error":
5776
+ return {
5777
+ icon: /* @__PURE__ */ jsx(AlertCircleIcon, { className: "size-3.5", "aria-hidden": "true" }),
5778
+ label: "Error",
5779
+ tone: "text-destructive"
5780
+ };
5781
+ case "output-denied":
5782
+ return {
5783
+ icon: /* @__PURE__ */ jsx(ShieldIcon, { className: "size-3.5", "aria-hidden": "true" }),
5784
+ label: "Denied",
5785
+ tone: "text-destructive"
5786
+ };
5787
+ default:
5788
+ return {
5789
+ icon: /* @__PURE__ */ jsx(WrenchIcon, { className: "size-3.5", "aria-hidden": "true" }),
5790
+ label: "Unknown",
5791
+ tone: "text-muted-foreground"
5792
+ };
5793
+ }
5794
+ }
5795
+ function safeStringify2(value) {
5796
+ if (value === void 0) return "";
5797
+ if (typeof value === "string") return value;
5798
+ try {
5799
+ return JSON.stringify(value, null, 2);
5800
+ } catch {
5801
+ return String(value);
5802
+ }
5803
+ }
5804
+ function ToolCallPart({ part }) {
5805
+ const toolName = deriveToolName(part);
5806
+ const badge = stateBadge(part.state);
5807
+ const inputStr = safeStringify2(part.input);
5808
+ const outputStr = part.state === "output-available" ? safeStringify2(part.output) : part.errorText ?? "";
5809
+ return /* @__PURE__ */ jsxs(
5810
+ "div",
5811
+ {
5812
+ className: cn("my-3 overflow-hidden rounded-lg border border-border bg-card", "shadow-sm"),
5813
+ "data-theo-tool-call": part.state,
5814
+ children: [
5815
+ /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between gap-3 border-border border-b bg-muted/30 px-3 py-1.5", children: [
5816
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
5817
+ /* @__PURE__ */ jsx(WrenchIcon, { className: "size-3.5 shrink-0 text-muted-foreground", "aria-hidden": "true" }),
5818
+ /* @__PURE__ */ jsx("span", { className: "truncate font-mono text-foreground text-label", children: toolName })
5819
+ ] }),
5192
5820
  /* @__PURE__ */ jsxs(
5193
- "div",
5821
+ "span",
5194
5822
  {
5195
5823
  className: cn(
5196
- "min-w-0 flex-1 rounded-2xl rounded-tl-md border border-border/40 border-l-2 border-l-primary",
5197
- "bg-card px-5 py-4 shadow-sm"
5824
+ "inline-flex items-center gap-1 text-label-caps uppercase tracking-wider",
5825
+ badge.tone
5198
5826
  ),
5199
5827
  children: [
5200
- /* @__PURE__ */ jsxs("header", { className: "mb-2 flex items-center justify-between gap-3", children: [
5201
- message.model ? /* @__PURE__ */ jsx("span", { className: "font-mono text-label-caps text-primary uppercase tracking-wider", children: message.model }) : /* @__PURE__ */ jsx("span", { className: "font-mono text-label-caps text-muted-foreground uppercase tracking-wider", children: "Assistant" }),
5202
- message.timestamp ? /* @__PURE__ */ jsx("span", { className: "font-mono text-label text-muted-foreground", children: message.timestamp }) : null
5203
- ] }),
5204
- /* @__PURE__ */ jsx("div", { className: "text-body-md text-foreground leading-relaxed", children: message.content }),
5205
- actions ? /* @__PURE__ */ jsx("div", { className: "mt-3 flex items-center gap-1", children: actions }) : null
5828
+ badge.icon,
5829
+ /* @__PURE__ */ jsx("span", { children: badge.label })
5206
5830
  ]
5207
5831
  }
5208
5832
  )
5833
+ ] }),
5834
+ inputStr ? /* @__PURE__ */ jsxs("details", { className: "border-border border-b", open: part.state === "input-streaming", children: [
5835
+ /* @__PURE__ */ jsx("summary", { className: "cursor-pointer px-3 py-1.5 font-mono text-label-caps text-muted-foreground uppercase tracking-wider hover:text-foreground", children: "Input" }),
5836
+ /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto bg-muted/20 px-3 py-2 text-code-sm", children: /* @__PURE__ */ jsx("code", { children: inputStr }) })
5837
+ ] }) : null,
5838
+ outputStr ? /* @__PURE__ */ jsxs("details", { open: part.state === "output-error" || part.state === "output-available", children: [
5839
+ /* @__PURE__ */ jsx(
5840
+ "summary",
5841
+ {
5842
+ className: cn(
5843
+ "cursor-pointer px-3 py-1.5 font-mono text-label-caps uppercase tracking-wider hover:text-foreground",
5844
+ part.state === "output-error" ? "text-destructive" : "text-muted-foreground"
5845
+ ),
5846
+ children: part.state === "output-error" ? "Error" : "Output"
5847
+ }
5848
+ ),
5849
+ /* @__PURE__ */ jsx(
5850
+ "pre",
5851
+ {
5852
+ className: cn(
5853
+ "overflow-x-auto px-3 py-2 text-code-sm",
5854
+ part.state === "output-error" ? "bg-destructive/5" : "bg-muted/20"
5855
+ ),
5856
+ children: /* @__PURE__ */ jsx("code", { children: outputStr })
5857
+ }
5858
+ )
5859
+ ] }) : null
5860
+ ]
5861
+ }
5862
+ );
5863
+ }
5864
+ var ChatMessageRoot = forwardRef(
5865
+ ({ className, from, children, ...props }, ref) => /* @__PURE__ */ jsx(
5866
+ "div",
5867
+ {
5868
+ ref,
5869
+ className: cn(
5870
+ "group flex w-full max-w-[95%] flex-col gap-2",
5871
+ from === "user" ? "is-user ml-auto justify-end" : from === "assistant" ? "is-assistant" : "is-system",
5872
+ className
5873
+ ),
5874
+ "data-theo-chat-message": from,
5875
+ ...props,
5876
+ children
5877
+ }
5878
+ )
5879
+ );
5880
+ ChatMessageRoot.displayName = "ChatMessageRoot";
5881
+ var ChatMessageContent = forwardRef(
5882
+ ({ className, variant, children, ...props }, ref) => /* @__PURE__ */ jsx(
5883
+ "div",
5884
+ {
5885
+ ref,
5886
+ className: cn(
5887
+ "flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-body-md",
5888
+ // User bubble — secondary surface, right-aligned (within the `is-user` group)
5889
+ "group-[.is-user]:ml-auto",
5890
+ variant !== "flat" && "group-[.is-user]:rounded-2xl group-[.is-user]:rounded-tr-md group-[.is-user]:border group-[.is-user]:border-border/40 group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3",
5891
+ // Assistant card — primary accent border-left
5892
+ variant === "contained" && "group-[.is-assistant]:rounded-2xl group-[.is-assistant]:rounded-tl-md group-[.is-assistant]:border group-[.is-assistant]:border-border/40 group-[.is-assistant]:border-l-2 group-[.is-assistant]:border-l-primary group-[.is-assistant]:bg-card group-[.is-assistant]:px-5 group-[.is-assistant]:py-4 group-[.is-assistant]:shadow-sm",
5893
+ // System callout — accent-deep border
5894
+ "group-[.is-system]:rounded-lg group-[.is-system]:border group-[.is-system]:border-accent-deep/40 group-[.is-system]:border-l-4 group-[.is-system]:bg-accent/10 group-[.is-system]:px-4 group-[.is-system]:py-2 group-[.is-system]:text-body-sm",
5895
+ "group-[.is-assistant]:text-foreground group-[.is-user]:text-secondary-foreground",
5896
+ className
5897
+ ),
5898
+ "data-theo-chat-content": "",
5899
+ ...props,
5900
+ children
5901
+ }
5902
+ )
5903
+ );
5904
+ ChatMessageContent.displayName = "ChatMessageContent";
5905
+ function renderPart(part, opts = {}) {
5906
+ const overrides = opts.partRenderers ?? {};
5907
+ if (isTextUIPart(part)) {
5908
+ return overrides.text?.(part) ?? /* @__PURE__ */ jsx(TextPart, { part });
5909
+ }
5910
+ if (isReasoningUIPart(part)) {
5911
+ return overrides.reasoning?.(part) ?? /* @__PURE__ */ jsx(ReasoningPart, { part });
5912
+ }
5913
+ if (isReasoningFileUIPart(part)) {
5914
+ return overrides["reasoning-file"]?.(part) ?? null;
5915
+ }
5916
+ if (isFileUIPart(part)) {
5917
+ return overrides.file?.(part) ?? /* @__PURE__ */ jsx(FilePart, { part });
5918
+ }
5919
+ if (isSourceUrlUIPart(part)) {
5920
+ return overrides["source-url"]?.(part) ?? /* @__PURE__ */ jsx(SourceUrlPart, { part });
5921
+ }
5922
+ if (isSourceDocumentUIPart(part)) {
5923
+ return overrides["source-document"]?.(part) ?? /* @__PURE__ */ jsx(SourceDocumentPart, { part });
5924
+ }
5925
+ if (isToolUIPart(part)) {
5926
+ return overrides.tool?.(part) ?? /* @__PURE__ */ jsx(ToolCallPart, { part });
5927
+ }
5928
+ if (isDataUIPart(part)) {
5929
+ return overrides.data?.(part) ?? /* @__PURE__ */ jsx(DataPart, { part, renderers: opts.dataRenderers });
5930
+ }
5931
+ if (isStepStartUIPart(part)) {
5932
+ return overrides["step-start"]?.() ?? /* @__PURE__ */ jsx("hr", { className: "my-3 border-border", "aria-label": "Step boundary" });
5933
+ }
5934
+ return null;
5935
+ }
5936
+ var ChatMessage = forwardRef(
5937
+ ({ message, avatar, actions, variant, partRenderers, dataRenderers, className, ...props }, ref) => {
5938
+ const inner = /* @__PURE__ */ jsxs(
5939
+ ChatMessageContent,
5940
+ {
5941
+ variant: variant ?? (message.role === "assistant" ? "contained" : void 0),
5942
+ children: [
5943
+ message.parts.map((part, idx) => /* @__PURE__ */ jsx("div", { children: renderPart(part, { dataRenderers, partRenderers }) }, `${part.type}-${idx}`)),
5944
+ actions
5209
5945
  ]
5210
5946
  }
5211
5947
  );
5948
+ if (message.role === "user") {
5949
+ return /* @__PURE__ */ jsxs(ChatMessageRoot, { ref, from: "user", className, ...props, children: [
5950
+ inner,
5951
+ avatar ? /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar }) : null
5952
+ ] });
5953
+ }
5954
+ return /* @__PURE__ */ jsxs(ChatMessageRoot, { ref, from: message.role, className, ...props, children: [
5955
+ avatar ? /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar }) : null,
5956
+ inner
5957
+ ] });
5212
5958
  }
5213
5959
  );
5214
5960
  ChatMessage.displayName = "ChatMessage";
5961
+ function ChatMessageActions({
5962
+ className,
5963
+ children,
5964
+ ...props
5965
+ }) {
5966
+ return /* @__PURE__ */ jsx("div", { className: cn("flex items-center gap-1", className), "data-theo-chat-actions": "", ...props, children });
5967
+ }
5968
+ function ChatMessageAction({
5969
+ tooltip,
5970
+ label,
5971
+ variant = "ghost",
5972
+ size = "icon",
5973
+ className,
5974
+ children,
5975
+ ...props
5976
+ }) {
5977
+ return /* @__PURE__ */ jsxs(
5978
+ Button,
5979
+ {
5980
+ type: "button",
5981
+ variant,
5982
+ size,
5983
+ title: tooltip,
5984
+ className: cn(className),
5985
+ ...props,
5986
+ children: [
5987
+ children,
5988
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: label || tooltip })
5989
+ ]
5990
+ }
5991
+ );
5992
+ }
5993
+ function ChatMessageToolbar({
5994
+ className,
5995
+ children,
5996
+ ...props
5997
+ }) {
5998
+ return /* @__PURE__ */ jsx(
5999
+ "div",
6000
+ {
6001
+ className: cn("mt-3 flex w-full items-center justify-between gap-3", className),
6002
+ "data-theo-chat-toolbar": "",
6003
+ ...props,
6004
+ children
6005
+ }
6006
+ );
6007
+ }
6008
+ var MessageBranchContext = createContext(null);
6009
+ function useMessageBranch() {
6010
+ const ctx = useContext(MessageBranchContext);
6011
+ if (!ctx) {
6012
+ throw new Error("ChatMessageBranch* components must be wrapped in <ChatMessageBranch>.");
6013
+ }
6014
+ return ctx;
6015
+ }
6016
+ function ChatMessageBranch({
6017
+ defaultBranch = 0,
6018
+ onBranchChange,
6019
+ className,
6020
+ ...props
6021
+ }) {
6022
+ const [currentBranch, setCurrentBranch] = useState(defaultBranch);
6023
+ const [branches, setBranches] = useState([]);
6024
+ const handleChange = useCallback(
6025
+ (next) => {
6026
+ setCurrentBranch(next);
6027
+ onBranchChange?.(next);
6028
+ },
6029
+ [onBranchChange]
6030
+ );
6031
+ const goToPrevious = useCallback(() => {
6032
+ handleChange(currentBranch > 0 ? currentBranch - 1 : branches.length - 1);
6033
+ }, [currentBranch, branches.length, handleChange]);
6034
+ const goToNext = useCallback(() => {
6035
+ handleChange(currentBranch < branches.length - 1 ? currentBranch + 1 : 0);
6036
+ }, [currentBranch, branches.length, handleChange]);
6037
+ const value = useMemo(
6038
+ () => ({
6039
+ branches,
6040
+ currentBranch,
6041
+ goToNext,
6042
+ goToPrevious,
6043
+ setBranches,
6044
+ totalBranches: branches.length
6045
+ }),
6046
+ [branches, currentBranch, goToNext, goToPrevious]
6047
+ );
6048
+ return /* @__PURE__ */ jsx(MessageBranchContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { className: cn("grid w-full gap-2", className), ...props }) });
6049
+ }
6050
+ function ChatMessageBranchContent({
6051
+ children,
6052
+ ...props
6053
+ }) {
6054
+ const { currentBranch, setBranches, branches } = useMessageBranch();
6055
+ const childrenArray = useMemo(
6056
+ () => Array.isArray(children) ? children : [children],
6057
+ [children]
6058
+ );
6059
+ useEffect(() => {
6060
+ if (branches.length !== childrenArray.length) {
6061
+ setBranches(childrenArray);
6062
+ }
6063
+ }, [childrenArray, branches, setBranches]);
6064
+ return /* @__PURE__ */ jsx(Fragment, { children: childrenArray.map((branch, idx) => /* @__PURE__ */ jsx(
6065
+ "div",
6066
+ {
6067
+ className: cn("grid gap-2 overflow-hidden", idx === currentBranch ? "block" : "hidden"),
6068
+ ...props,
6069
+ children: branch
6070
+ },
6071
+ // Prefer a stable element key; fall back to index
6072
+ branch?.key ?? `branch-${idx}`
6073
+ )) });
6074
+ }
6075
+ function ChatMessageBranchSelector({
6076
+ className,
6077
+ ...props
6078
+ }) {
6079
+ const { totalBranches } = useMessageBranch();
6080
+ if (totalBranches <= 1) return null;
6081
+ return /* @__PURE__ */ jsx(
6082
+ "div",
6083
+ {
6084
+ className: cn("inline-flex items-center gap-0.5 rounded-md border border-border", className),
6085
+ role: "group",
6086
+ "aria-label": "Branch selector",
6087
+ ...props
6088
+ }
6089
+ );
6090
+ }
6091
+ function ChatMessageBranchPrevious({
6092
+ children,
6093
+ ...props
6094
+ }) {
6095
+ const { goToPrevious, totalBranches } = useMessageBranch();
6096
+ return /* @__PURE__ */ jsx(
6097
+ Button,
6098
+ {
6099
+ type: "button",
6100
+ variant: "ghost",
6101
+ size: "icon",
6102
+ "aria-label": "Previous branch",
6103
+ disabled: totalBranches <= 1,
6104
+ onClick: goToPrevious,
6105
+ ...props,
6106
+ children: children ?? /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-3.5", "aria-hidden": "true" })
6107
+ }
6108
+ );
6109
+ }
6110
+ function ChatMessageBranchNext({
6111
+ children,
6112
+ ...props
6113
+ }) {
6114
+ const { goToNext, totalBranches } = useMessageBranch();
6115
+ return /* @__PURE__ */ jsx(
6116
+ Button,
6117
+ {
6118
+ type: "button",
6119
+ variant: "ghost",
6120
+ size: "icon",
6121
+ "aria-label": "Next branch",
6122
+ disabled: totalBranches <= 1,
6123
+ onClick: goToNext,
6124
+ ...props,
6125
+ children: children ?? /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5", "aria-hidden": "true" })
6126
+ }
6127
+ );
6128
+ }
6129
+ function ChatMessageBranchPage({
6130
+ className,
6131
+ ...props
6132
+ }) {
6133
+ const { currentBranch, totalBranches } = useMessageBranch();
6134
+ return /* @__PURE__ */ jsxs(
6135
+ "span",
6136
+ {
6137
+ className: cn(
6138
+ "inline-flex items-center px-2 font-mono text-label-caps text-muted-foreground",
6139
+ className
6140
+ ),
6141
+ ...props,
6142
+ children: [
6143
+ currentBranch + 1,
6144
+ " of ",
6145
+ totalBranches
6146
+ ]
6147
+ }
6148
+ );
6149
+ }
5215
6150
  var ChatThread = forwardRef(
5216
6151
  ({ className, ...props }, ref) => /* @__PURE__ */ jsx(LiveRegionProvider, { value: true, children: /* @__PURE__ */ jsx(
5217
6152
  "div",
@@ -8582,6 +9517,6 @@ function CommandPalette({
8582
9517
  ] }) });
8583
9518
  }
8584
9519
 
8585
- export { ALL_MODES, AgentComposer, AgentEditor, AgentErrorCard, AgentEvent, AgentHandoff, AgentProfile, AgentStartingState, AgentStream, AgentStreaming, AgentTimeline, ApprovalCard, ArtifactPreview, AttachmentChip, AuditLogEntry, AutoCompactNotice, Avatar, BadgeWithDot as Badge, BrowserControls, BuildLogStream, Button, CapabilityIndicator, Card, ChatComposer, ChatMessage, ChatThread, Checkbox, CommandPalette, ContextCard, ContextWindowBar, CostMeter, CreatedFilesCard, CronJobCard, CronJobsList, DeploymentRow, Dialog, DiffViewer, DomainConfig, EmptyState, EnvVarEditor, FolderContextCard, FolderSelector, FormField, HOOK_EVENTS, HookConfig, HookEventLog, Input, IntentSelector, Label, LaneBoard, LoginSplit, MCPServerCard, MCPServerList, MODE_LABEL, MemoryEditor, MentionMenu, MetricsPanel, ModelCard, ModelSelector, PermissionMatrix, PermissionModal, PreviewEnvCard, PreviewPanel, ProgressChecklist, ProjectCard, ProjectSwitcher, QuickActionChips, RadioGroup, RecentFoldersList, RollbackUI, RuleCard, RuleEditor, RunStats, RunningTasksPanel, ScrollArea, Select, SessionListItem, SessionTimeline, Sheet, Sidebar, Skeleton, SkillCard, SkillEditor, SkillsList, SocialAuthRow, StepsRail, SubAgentDispatch, Switch, SystemPromptEditor, Tabs, TaskHeader, TaskNode, TaskPlan, TerminalPanel, Textarea, ThemeProvider, ThemeScript, ThemeSwitcher, TheoUIProvider, Toast, Toaster, TokenUsageChart, ToolCall, ToolCallCard, ToolResult, ToolsList, TooltipWithStatics as Tooltip, TopNav, anthropicStyle, auroraTerminal, avatarVariants, badgeVariants, builtinThemes, buttonVariants, capabilityPresets, classicPaper, cn, defineTheme, dracula, githubDark, hex, linearGlass, modelCapabilityPresets, oneDark, openaiStyle, rgb, sheetVariants, useDensity, useTheme, useToast, vercelMono, violetForge };
9520
+ export { ALL_MODES, AgentComposer, AgentEditor, AgentErrorCard, AgentEvent, AgentHandoff, AgentProfile, AgentStartingState, AgentStream, AgentStreaming, AgentTimeline, ApprovalCard, ArtifactPreview, AttachmentChip, AuditLogEntry, AutoCompactNotice, Avatar, BadgeWithDot as Badge, BrowserControls, BuildLogStream, Button, CapabilityIndicator, Card, ChatComposer, ChatMessage, ChatMessageAction, ChatMessageActions, ChatMessageBranch, ChatMessageBranchContent, ChatMessageBranchNext, ChatMessageBranchPage, ChatMessageBranchPrevious, ChatMessageBranchSelector, ChatMessageContent, ChatMessageResponse, ChatMessageRoot, ChatMessageToolbar, ChatThread, Checkbox, CommandPalette, ContextCard, ContextWindowBar, CostMeter, CreatedFilesCard, CronJobCard, CronJobsList, DataPart, DeploymentRow, Dialog, DiffViewer, DomainConfig, EmptyState, EnvVarEditor, FilePart, FolderContextCard, FolderSelector, FormField, HOOK_EVENTS, HookConfig, HookEventLog, Input, IntentSelector, Label, LaneBoard, LoginSplit, MCPServerCard, MCPServerList, MODE_LABEL, MemoryEditor, MentionMenu, MetricsPanel, ModelCard, ModelSelector, PermissionMatrix, PermissionModal, PreviewEnvCard, PreviewPanel, ProgressChecklist, ProjectCard, ProjectSwitcher, QuickActionChips, RadioGroup, ReasoningPart, RecentFoldersList, RollbackUI, RuleCard, RuleEditor, RunStats, RunningTasksPanel, ScrollArea, Select, SessionListItem, SessionTimeline, Sheet, Sidebar, Skeleton, SkillCard, SkillEditor, SkillsList, SocialAuthRow, SourceDocumentPart, SourceUrlPart, StepsRail, SubAgentDispatch, Switch, SystemPromptEditor, Tabs, TaskHeader, TaskNode, TaskPlan, TerminalPanel, TextPart, Textarea, ThemeProvider, ThemeScript, ThemeSwitcher, TheoUIProvider, Toast, Toaster, TokenUsageChart, ToolCall, ToolCallCard, ToolCallPart, ToolResult, ToolsList, TooltipWithStatics as Tooltip, TopNav, anthropicStyle, auroraTerminal, avatarVariants, badgeVariants, builtinThemes, buttonVariants, capabilityPresets, classicPaper, cn, defineTheme, dracula, githubDark, hex, isCustomContentUIPart, isDataUIPart, isFileUIPart, isReasoningFileUIPart, isReasoningUIPart, isSourceDocumentUIPart, isSourceUrlUIPart, isStepStartUIPart, isTextUIPart, isToolUIPart, linearGlass, modelCapabilityPresets, oneDark, openaiStyle, renderPart, rgb, sheetVariants, useDensity, useTheme, useToast, vercelMono, violetForge };
8586
9521
  //# sourceMappingURL=index.js.map
8587
9522
  //# sourceMappingURL=index.js.map