@schlenkr/quassel 0.5.6 → 0.7.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/index.js CHANGED
@@ -42,6 +42,11 @@ var englishTexts = {
42
42
  send: "Send",
43
43
  sendIntoRun: "Feed into the running turn",
44
44
  stop: "Stop work",
45
+ microphone: "Speak",
46
+ microphoneHint: "Hold to speak or click to start recording",
47
+ microphonePreparing: "Connecting microphone ...",
48
+ microphoneRecording: "Finish recording",
49
+ microphoneTranscribing: "Transcribing ...",
45
50
  placeholder: "Write a message ...",
46
51
  steeringPlaceholder: "Interject ...",
47
52
  steered: "Fed into the running turn",
@@ -112,6 +117,11 @@ var germanTexts = {
112
117
  send: "Senden",
113
118
  sendIntoRun: "In die laufende Antwort einreihen",
114
119
  stop: "Bearbeitung stoppen",
120
+ microphone: "Sprechen",
121
+ microphoneHint: "Zum Sprechen halten oder zum Aufnehmen antippen",
122
+ microphonePreparing: "Mikrofon wird verbunden ...",
123
+ microphoneRecording: "Aufnahme beenden",
124
+ microphoneTranscribing: "Wird transkribiert ...",
115
125
  placeholder: "Nachricht schreiben ...",
116
126
  steeringPlaceholder: "Nachsteuern ...",
117
127
  steered: "In die laufende Antwort eingereiht",
@@ -164,7 +174,7 @@ var defaultTexts = englishTexts;
164
174
 
165
175
  // packages/quassel/src/ui/cn.ts
166
176
  import { createCn } from "cn/config";
167
- var cn = createCn({ prefix: "qsl", extend: { theme: { text: ["meta", "trace", "timestamp", "label", "chat"] } } });
177
+ var cn = createCn({ prefix: "qsl", extend: { theme: { text: ["body", "small"] } } });
168
178
 
169
179
  // packages/quassel/src/ChatMessages.tsx
170
180
  import { useCallback as useCallback2, useContext as useContext3, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
@@ -236,7 +246,7 @@ import { Button as ButtonPrimitive } from "@base-ui/react/button";
236
246
  import { cva } from "class-variance-authority";
237
247
  import { jsx } from "react/jsx-runtime";
238
248
  var buttonVariants = cva(
239
- "qsl:group/button qsl:inline-flex qsl:shrink-0 qsl:items-center qsl:justify-center qsl:rounded-lg qsl:border qsl:border-transparent qsl:bg-clip-padding qsl:text-sm qsl:font-medium qsl:whitespace-nowrap qsl:transition-all qsl:outline-none qsl:select-none qsl:focus-visible:border-ring qsl:focus-visible:ring-3 qsl:focus-visible:ring-ring/50 qsl:active:not-aria-[haspopup]:translate-y-px qsl:disabled:pointer-events-none qsl:disabled:opacity-50 qsl:aria-invalid:border-destructive qsl:aria-invalid:ring-3 qsl:aria-invalid:ring-destructive/20 qsl:dark:aria-invalid:border-destructive/50 qsl:dark:aria-invalid:ring-destructive/40 qsl:[&_svg]:pointer-events-none qsl:[&_svg]:shrink-0 qsl:[&_svg:not([class*='size-'])]:size-4",
249
+ "qsl:group/button qsl:inline-flex qsl:shrink-0 qsl:items-center qsl:justify-center qsl:rounded-lg qsl:border qsl:border-transparent qsl:bg-clip-padding qsl:text-body qsl:font-medium qsl:whitespace-nowrap qsl:transition-all qsl:outline-none qsl:select-none qsl:focus-visible:border-ring qsl:focus-visible:ring-3 qsl:focus-visible:ring-ring/50 qsl:active:not-aria-[haspopup]:translate-y-px qsl:disabled:pointer-events-none qsl:disabled:opacity-50 qsl:aria-invalid:border-destructive qsl:aria-invalid:ring-3 qsl:aria-invalid:ring-destructive/20 qsl:dark:aria-invalid:border-destructive/50 qsl:dark:aria-invalid:ring-destructive/40 qsl:[&_svg]:pointer-events-none qsl:[&_svg]:shrink-0 qsl:[&_svg:not([class*='size-'])]:size-4",
240
250
  {
241
251
  variants: {
242
252
  variant: {
@@ -249,7 +259,7 @@ var buttonVariants = cva(
249
259
  },
250
260
  size: {
251
261
  default: "qsl:h-8 qsl:gap-1.5 qsl:px-2.5 qsl:has-data-[icon=inline-end]:pr-2 qsl:has-data-[icon=inline-start]:pl-2",
252
- xs: "qsl:h-6 qsl:gap-1 qsl:rounded-[min(--theme(--radius-md),10px)] qsl:px-2 qsl:text-xs qsl:in-data-[slot=button-group]:rounded-lg qsl:has-data-[icon=inline-end]:pr-1.5 qsl:has-data-[icon=inline-start]:pl-1.5 qsl:[&_svg:not([class*='size-'])]:size-3",
262
+ xs: "qsl:h-6 qsl:gap-1 qsl:rounded-[min(--theme(--radius-md),10px)] qsl:px-2 qsl:in-data-[slot=button-group]:rounded-lg qsl:has-data-[icon=inline-end]:pr-1.5 qsl:has-data-[icon=inline-start]:pl-1.5 qsl:[&_svg:not([class*='size-'])]:size-3",
253
263
  sm: "qsl:h-7 qsl:gap-1 qsl:rounded-[min(--theme(--radius-md),12px)] qsl:px-2.5 qsl:in-data-[slot=button-group]:rounded-lg qsl:has-data-[icon=inline-end]:pr-1.5 qsl:has-data-[icon=inline-start]:pl-1.5 qsl:[&_svg:not([class*='size-'])]:size-3.5",
254
264
  lg: "qsl:h-9 qsl:gap-1.5 qsl:px-2.5 qsl:has-data-[icon=inline-end]:pr-2 qsl:has-data-[icon=inline-start]:pl-2",
255
265
  icon: "qsl:size-8",
@@ -295,7 +305,7 @@ function Card({
295
305
  "data-slot": "card",
296
306
  "data-size": size,
297
307
  className: cn(
298
- "qsl:group/card qsl:flex qsl:flex-col qsl:gap-(--qsl-card-spacing) qsl:overflow-hidden qsl:rounded-panel qsl:border qsl:border-border qsl:bg-card qsl:py-(--qsl-card-spacing) qsl:text-sm qsl:text-card-foreground qsl:shadow-bar qsl:[--qsl-card-spacing:--spacing(4)] qsl:has-data-[slot=card-footer]:pb-0 qsl:has-[>img:first-child]:pt-0 qsl:data-[size=sm]:[--qsl-card-spacing:--spacing(3)] qsl:data-[size=sm]:has-data-[slot=card-footer]:pb-0 qsl:*:[img:first-child]:rounded-t-xl qsl:*:[img:last-child]:rounded-b-xl",
308
+ "qsl:group/card qsl:flex qsl:flex-col qsl:gap-(--qsl-card-spacing) qsl:overflow-hidden qsl:rounded-panel qsl:border qsl:border-border qsl:bg-card qsl:py-(--qsl-card-spacing) qsl:text-body qsl:text-card-foreground qsl:shadow-bar qsl:[--qsl-card-spacing:--spacing(4)] qsl:has-data-[slot=card-footer]:pb-0 qsl:has-[>img:first-child]:pt-0 qsl:data-[size=sm]:[--qsl-card-spacing:--spacing(3)] qsl:data-[size=sm]:has-data-[slot=card-footer]:pb-0 qsl:*:[img:first-child]:rounded-t-xl qsl:*:[img:last-child]:rounded-b-xl",
299
309
  className
300
310
  ),
301
311
  ...props
@@ -324,6 +334,7 @@ function PopoverContent({
324
334
  return /* @__PURE__ */ jsx3(PopoverPrimitive.Portal, { container: portalContainer, keepMounted, children: /* @__PURE__ */ jsx3(
325
335
  PopoverPrimitive.Positioner,
326
336
  {
337
+ "data-quassel": "",
327
338
  align,
328
339
  alignOffset,
329
340
  anchor,
@@ -338,7 +349,7 @@ function PopoverContent({
338
349
  "data-quassel-popover": "",
339
350
  "data-slot": "popover-content",
340
351
  className: cn(
341
- "qsl:z-[110] qsl:flex qsl:w-72 qsl:origin-(--transform-origin) qsl:flex-col qsl:gap-2.5 qsl:rounded-lg qsl:bg-popover qsl:p-2.5 qsl:text-sm qsl:text-popover-foreground qsl:shadow-md qsl:ring-1 qsl:ring-foreground/10 qsl:outline-hidden qsl:duration-100 qsl:data-[side=bottom]:slide-in-from-top-2 qsl:data-[side=inline-end]:slide-in-from-left-2 qsl:data-[side=inline-start]:slide-in-from-right-2 qsl:data-[side=left]:slide-in-from-right-2 qsl:data-[side=right]:slide-in-from-left-2 qsl:data-[side=top]:slide-in-from-bottom-2 qsl:data-open:animate-in qsl:data-open:fade-in-0 qsl:data-open:zoom-in-95 qsl:data-closed:animate-out qsl:data-closed:fade-out-0 qsl:data-closed:zoom-out-95",
352
+ "qsl:z-[110] qsl:flex qsl:w-72 qsl:origin-(--transform-origin) qsl:flex-col qsl:gap-2.5 qsl:rounded-lg qsl:bg-popover qsl:p-2.5 qsl:text-small qsl:text-popover-foreground qsl:shadow-md qsl:ring-1 qsl:ring-foreground/10 qsl:outline-hidden qsl:duration-100 qsl:data-[side=bottom]:slide-in-from-top-2 qsl:data-[side=inline-end]:slide-in-from-left-2 qsl:data-[side=inline-start]:slide-in-from-right-2 qsl:data-[side=left]:slide-in-from-right-2 qsl:data-[side=right]:slide-in-from-left-2 qsl:data-[side=top]:slide-in-from-bottom-2 qsl:data-open:animate-in qsl:data-open:fade-in-0 qsl:data-open:zoom-in-95 qsl:data-closed:animate-out qsl:data-closed:fade-out-0 qsl:data-closed:zoom-out-95",
342
353
  className
343
354
  ),
344
355
  ...props
@@ -373,7 +384,7 @@ import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
373
384
  import { cva as cva2 } from "class-variance-authority";
374
385
  import { jsx as jsx6 } from "react/jsx-runtime";
375
386
  var toggleVariants = cva2(
376
- "qsl:group/toggle qsl:inline-flex qsl:items-center qsl:justify-center qsl:gap-1 qsl:rounded-lg qsl:text-sm qsl:font-medium qsl:whitespace-nowrap qsl:transition-all qsl:outline-none qsl:hover:bg-muted qsl:hover:text-foreground qsl:focus-visible:border-ring qsl:focus-visible:ring-[3px] qsl:focus-visible:ring-ring/50 qsl:disabled:pointer-events-none qsl:disabled:opacity-50 qsl:aria-invalid:border-destructive qsl:aria-invalid:ring-destructive/20 qsl:aria-pressed:bg-muted qsl:data-[state=on]:bg-muted qsl:dark:aria-invalid:ring-destructive/40 qsl:[&_svg]:pointer-events-none qsl:[&_svg]:shrink-0 qsl:[&_svg:not([class*='size-'])]:size-4",
387
+ "qsl:group/toggle qsl:inline-flex qsl:items-center qsl:justify-center qsl:gap-1 qsl:rounded-lg qsl:text-body qsl:font-medium qsl:whitespace-nowrap qsl:transition-all qsl:outline-none qsl:hover:bg-muted qsl:hover:text-foreground qsl:focus-visible:border-ring qsl:focus-visible:ring-[3px] qsl:focus-visible:ring-ring/50 qsl:disabled:pointer-events-none qsl:disabled:opacity-50 qsl:aria-invalid:border-destructive qsl:aria-invalid:ring-destructive/20 qsl:aria-pressed:bg-muted qsl:data-[state=on]:bg-muted qsl:dark:aria-invalid:ring-destructive/40 qsl:[&_svg]:pointer-events-none qsl:[&_svg]:shrink-0 qsl:[&_svg:not([class*='size-'])]:size-4",
377
388
  {
378
389
  variants: {
379
390
  variant: {
@@ -493,7 +504,7 @@ function CodeBlock({ children }) {
493
504
  {
494
505
  ref: code,
495
506
  "data-chat": "code-block",
496
- className: cn(scrollbar, "qsl:my-2 qsl:overflow-auto qsl:overscroll-contain qsl:rounded-lg qsl:bg-secondary qsl:px-3 qsl:py-2 qsl:font-mono qsl:text-[length:1em] qsl:leading-relaxed qsl:in-data-[tone=on-color]:bg-black/30 qsl:in-data-[tone=on-color]:text-inherit"),
507
+ className: cn(scrollbar, "qsl:overflow-auto qsl:overscroll-contain"),
497
508
  style: { maxHeight: options?.maxHeight, whiteSpace: options?.wrap ? "pre-wrap" : void 0, overflowWrap: options?.wrap ? "anywhere" : void 0 },
498
509
  children
499
510
  }
@@ -516,7 +527,7 @@ function CodeBlock({ children }) {
516
527
  children: action.status === "done" ? /* @__PURE__ */ jsx8(CheckIcon, {}) : /* @__PURE__ */ jsx8(CopyIcon, {})
517
528
  }
518
529
  ),
519
- action.error && /* @__PURE__ */ jsx8("p", { className: "qsl:text-sm qsl:text-destructive", role: "alert", children: action.error })
530
+ action.error && /* @__PURE__ */ jsx8("p", { className: "qsl:text-small qsl:text-destructive", role: "alert", children: action.error })
520
531
  ] });
521
532
  }
522
533
  function MarkdownLinks({ children, onLinkClick }) {
@@ -530,7 +541,6 @@ function MarkdownLink({ href, children }) {
530
541
  return /* @__PURE__ */ jsx8(
531
542
  "a",
532
543
  {
533
- className: "qsl:text-primary qsl:[text-underline-offset:2px] qsl:in-data-[tone=on-color]:text-inherit qsl:in-data-[tone=on-color]:underline",
534
544
  href,
535
545
  onClick: (event) => {
536
546
  if (href && onLinkClick?.(href, event.currentTarget.textContent ?? "")) {
@@ -545,34 +555,32 @@ function MarkdownLink({ href, children }) {
545
555
  }
546
556
  var scrollbar = "qsl:[scrollbar-width:thin] qsl:[&::-webkit-scrollbar]:block qsl:[&::-webkit-scrollbar]:h-2 qsl:[&::-webkit-scrollbar-thumb]:rounded-full qsl:[&::-webkit-scrollbar-thumb]:bg-border";
547
557
  var scrollCover = "qsl:bg-[linear-gradient(to_right,var(--qsl-scroll-cover,var(--qsl-background))_50%,transparent)_left/20px_100%_no-repeat_local,linear-gradient(to_left,var(--qsl-scroll-cover,var(--qsl-background))_50%,transparent)_right/20px_100%_no-repeat_local,linear-gradient(to_right,#00000047,transparent)_left/14px_100%_no-repeat_scroll,linear-gradient(to_left,#00000047,transparent)_right/14px_100%_no-repeat_scroll]";
548
- var headingClasses = "qsl:mt-3 qsl:mb-1 qsl:text-[length:1.1em] qsl:font-medium";
549
- var cellClasses = "qsl:max-w-[44ch] qsl:border qsl:border-border qsl:px-2 qsl:py-1 qsl:text-left qsl:align-top qsl:in-data-[tone=on-color]:border-white/35";
550
558
  var components = {
551
- h1: ({ children }) => /* @__PURE__ */ jsx8("h1", { className: "qsl:mt-3 qsl:mb-1 qsl:text-[length:1.2em] qsl:font-semibold", children }),
552
- h2: ({ children }) => /* @__PURE__ */ jsx8("h2", { className: headingClasses, children }),
553
- h3: ({ children }) => /* @__PURE__ */ jsx8("h3", { className: headingClasses, children }),
554
- h4: ({ children }) => /* @__PURE__ */ jsx8("h4", { className: "qsl:mt-2 qsl:mb-1 qsl:font-medium", children }),
555
- h5: ({ children }) => /* @__PURE__ */ jsx8("h5", { className: "qsl:mt-2 qsl:mb-1 qsl:font-medium", children }),
556
- h6: ({ children }) => /* @__PURE__ */ jsx8("h6", { className: "qsl:mt-2 qsl:mb-1 qsl:font-medium", children }),
557
- p: ({ children }) => /* @__PURE__ */ jsx8("p", { className: "qsl:[p+&]:mt-3", children }),
558
- ul: ({ children }) => /* @__PURE__ */ jsx8("ul", { className: "qsl:my-3 qsl:list-disc qsl:pl-6 qsl:[li>&]:my-0", children }),
559
- ol: ({ children }) => /* @__PURE__ */ jsx8("ol", { className: "qsl:my-3 qsl:list-decimal qsl:pl-6 qsl:[li>&]:my-0", children }),
560
- li: ({ children }) => /* @__PURE__ */ jsx8("li", { className: "qsl:[li+&]:mt-1 qsl:marker:text-muted-foreground qsl:in-data-[tone=on-color]:marker:text-inherit", children }),
561
- hr: () => /* @__PURE__ */ jsx8("hr", { className: "qsl:my-3 qsl:border-t qsl:border-border" }),
559
+ h1: ({ children }) => /* @__PURE__ */ jsx8("h1", { children }),
560
+ h2: ({ children }) => /* @__PURE__ */ jsx8("h2", { children }),
561
+ h3: ({ children }) => /* @__PURE__ */ jsx8("h3", { children }),
562
+ h4: ({ children }) => /* @__PURE__ */ jsx8("h4", { children }),
563
+ h5: ({ children }) => /* @__PURE__ */ jsx8("h5", { children }),
564
+ h6: ({ children }) => /* @__PURE__ */ jsx8("h6", { children }),
565
+ p: ({ children }) => /* @__PURE__ */ jsx8("p", { children }),
566
+ ul: ({ children }) => /* @__PURE__ */ jsx8("ul", { children }),
567
+ ol: ({ children }) => /* @__PURE__ */ jsx8("ol", { children }),
568
+ li: ({ children }) => /* @__PURE__ */ jsx8("li", { children }),
569
+ hr: () => /* @__PURE__ */ jsx8("hr", {}),
562
570
  img: ({ src, alt }) => /* @__PURE__ */ jsx8("img", { alt, className: "qsl:h-auto qsl:max-w-full", src: typeof src === "string" ? src : void 0 }),
563
- blockquote: ({ children }) => /* @__PURE__ */ jsx8("blockquote", { className: "qsl:my-1 qsl:border-l-2 qsl:border-border qsl:pl-3 qsl:text-muted-foreground qsl:in-data-[tone=on-color]:border-white/50 qsl:in-data-[tone=on-color]:text-inherit", children }),
564
- strong: ({ children }) => /* @__PURE__ */ jsx8("strong", { className: "qsl:font-medium", children }),
571
+ blockquote: ({ children }) => /* @__PURE__ */ jsx8("blockquote", { children }),
572
+ strong: ({ children }) => /* @__PURE__ */ jsx8("strong", { children }),
565
573
  em: ({ children }) => /* @__PURE__ */ jsx8("em", { children }),
566
574
  a: ({ href, children }) => /* @__PURE__ */ jsx8(MarkdownLink, { href, children }),
567
575
  pre: ({ children }) => /* @__PURE__ */ jsx8(CodeBlock, { children }),
568
- code: ({ children, className }) => /* @__PURE__ */ jsx8("code", { className: cn(className, "qsl:rounded-sm qsl:bg-secondary qsl:px-1 qsl:py-0.5 qsl:font-mono qsl:text-[length:1em] qsl:[overflow-wrap:anywhere] qsl:in-data-[tone=on-color]:bg-white/20 qsl:in-data-[tone=on-color]:text-inherit", "qsl:[pre>&]:bg-transparent qsl:[pre>&]:p-0"), children }),
576
+ code: ({ children, className }) => /* @__PURE__ */ jsx8("code", { className, children }),
569
577
  // Without max-content the table shrinks to the container and stacks letters.
570
- table: ({ children }) => /* @__PURE__ */ jsx8("div", { className: cn(scrollbar, scrollCover, "qsl:my-2 qsl:overflow-x-auto qsl:overscroll-x-contain"), children: /* @__PURE__ */ jsx8("table", { className: "qsl:w-max qsl:min-w-full qsl:border-collapse qsl:text-sm", children }) }),
578
+ table: ({ children }) => /* @__PURE__ */ jsx8("div", { className: cn(scrollbar, scrollCover, "qsl:overflow-x-auto qsl:overscroll-x-contain"), "data-quassel-table": "", children: /* @__PURE__ */ jsx8("table", { className: "qsl:w-max qsl:min-w-full qsl:border-collapse", children }) }),
571
579
  thead: ({ children }) => /* @__PURE__ */ jsx8("thead", { children }),
572
580
  tbody: ({ children }) => /* @__PURE__ */ jsx8("tbody", { children }),
573
581
  tr: ({ children }) => /* @__PURE__ */ jsx8("tr", { children }),
574
- th: ({ children }) => /* @__PURE__ */ jsx8("th", { className: cn(cellClasses, "qsl:bg-secondary qsl:font-medium qsl:in-data-[tone=on-color]:bg-white/15 qsl:in-data-[tone=on-color]:text-inherit"), children }),
575
- td: ({ children }) => /* @__PURE__ */ jsx8("td", { className: cellClasses, children })
582
+ th: ({ children }) => /* @__PURE__ */ jsx8("th", { children }),
583
+ td: ({ children }) => /* @__PURE__ */ jsx8("td", { children })
576
584
  };
577
585
  function transformUrl(url, allowUrl, resolveUrl) {
578
586
  const resolved = resolveUrl ? resolveUrl(url) : url;
@@ -584,10 +592,9 @@ function Markdown({ text, streaming = false }) {
584
592
  const allowUrl = useAllowUrl();
585
593
  const resolveUrl = useResolveUrl();
586
594
  const urlTransform = useCallback((url) => transformUrl(url, allowUrl, resolveUrl), [allowUrl, resolveUrl]);
587
- return /* @__PURE__ */ jsx8(
595
+ return /* @__PURE__ */ jsx8("div", { "data-quassel": "", "data-quassel-markdown": "", children: /* @__PURE__ */ jsx8(
588
596
  Streamdown,
589
597
  {
590
- className: "space-y-0 text-inherit",
591
598
  prefix: "qsl",
592
599
  mode: streaming ? "streaming" : "static",
593
600
  isAnimating: streaming,
@@ -599,7 +606,7 @@ function Markdown({ text, streaming = false }) {
599
606
  skipHtml: true,
600
607
  children: text
601
608
  }
602
- );
609
+ ) });
603
610
  }
604
611
  function markdownPlainText(text) {
605
612
  return text.split("\n").filter((line) => !/^\s*```\w*\s*$/.test(line) && !isTableDivider(line)).map((line) => (line.trim().startsWith("|") ? splitTableRow(line).join(", ") : line).replace(/^(#{1,6})\s+/, "").replace(/^\s*>\s?/, "").replace(/^\s*([-*+]|\d+[.)])\s+/, "").replace(/!?\[([^\]]*)\]\(([^)]+)\)|\*\*([^*]+)\*\*|__([^_]+)__|\*([^*]+)\*|`([^`]+)`/g, (_, link, _href, bold, boldUnderscore, italic, code) => link ?? bold ?? boldUnderscore ?? italic ?? code).trim()).filter((line) => line !== "").join("\n");
@@ -627,7 +634,7 @@ var stepPopoverOrigin = (event) => ({
627
634
  y: event.clientY,
628
635
  base: getComputedStyle(event.currentTarget).getPropertyValue("--qsl-base")
629
636
  });
630
- var preClasses = "qsl:rounded-lg qsl:bg-secondary qsl:p-3 qsl:font-mono qsl:text-trace qsl:leading-[1.625] qsl:break-all qsl:whitespace-pre-wrap";
637
+ var preClasses = "qsl:rounded-lg qsl:bg-secondary qsl:p-3 qsl:font-mono qsl:text-small qsl:break-all qsl:whitespace-pre-wrap";
631
638
  function StepPopover({
632
639
  message,
633
640
  position,
@@ -658,7 +665,7 @@ function StepPopover({
658
665
  anchor,
659
666
  "aria-label": title,
660
667
  className: cn(
661
- "qsl:w-[min(680px,calc(100vw-32px))] qsl:gap-0 qsl:overflow-hidden qsl:p-0 qsl:text-base qsl:leading-normal qsl:shadow-pop",
668
+ "qsl:w-[min(680px,calc(100vw-32px))] qsl:gap-0 qsl:overflow-hidden qsl:p-0 qsl:text-body qsl:shadow-pop",
662
669
  "qsl:max-h-[min(70dvh,620px,var(--available-height))]"
663
670
  ),
664
671
  collisionPadding: 16,
@@ -672,10 +679,10 @@ function StepPopover({
672
679
  /* @__PURE__ */ jsx9("span", { className: "qsl:min-w-0 qsl:flex-1 qsl:overflow-hidden qsl:text-ellipsis qsl:whitespace-nowrap qsl:font-medium", children: title }),
673
680
  /* @__PURE__ */ jsx9(Button2, { "aria-label": texts.close, className: "qsl:flex-none", onClick: onClose, size: "icon-sm", title: texts.close, variant: "ghost", children: /* @__PURE__ */ jsx9(XIcon, {}) })
674
681
  ] }),
675
- /* @__PURE__ */ jsx9("div", { className: "qsl:min-h-0 qsl:flex-1 qsl:overflow-auto qsl:overscroll-contain qsl:p-4", children: thinking ? /* @__PURE__ */ jsx9("div", { className: "qsl:text-sm qsl:leading-[1.625] qsl:whitespace-pre-wrap", children: message.text }) : tool ? /* @__PURE__ */ jsxs2("div", { className: "qsl:flex qsl:flex-col qsl:gap-4", children: [
676
- /* @__PURE__ */ jsx9("div", { className: "qsl:font-mono qsl:text-sm", children: message.text }),
682
+ /* @__PURE__ */ jsx9("div", { className: "qsl:min-h-0 qsl:flex-1 qsl:overflow-auto qsl:overscroll-contain qsl:p-4", children: thinking ? /* @__PURE__ */ jsx9("div", { className: "qsl:text-small qsl:whitespace-pre-wrap", children: message.text }) : tool ? /* @__PURE__ */ jsxs2("div", { className: "qsl:flex qsl:flex-col qsl:gap-4", children: [
683
+ /* @__PURE__ */ jsx9("div", { className: "qsl:font-mono qsl:text-small", children: message.text }),
677
684
  /* @__PURE__ */ jsx9(PopoverSection, { label: texts.argumentsLabel, value: toolArgumentsText(tool) }),
678
- tool.result === void 0 ? /* @__PURE__ */ jsx9("div", { className: "qsl:text-sm qsl:text-muted-foreground", children: texts.toolStillRunning }) : /* @__PURE__ */ jsx9(PopoverSection, { error: tool.isError, label: texts.resultLabel, value: prettyJson(tool.result) })
685
+ tool.result === void 0 ? /* @__PURE__ */ jsx9("div", { className: "qsl:text-small qsl:text-muted-foreground", children: texts.toolStillRunning }) : /* @__PURE__ */ jsx9(PopoverSection, { error: tool.isError, label: texts.resultLabel, value: prettyJson(tool.result) })
679
686
  ] }) : null })
680
687
  ]
681
688
  }
@@ -683,7 +690,7 @@ function StepPopover({
683
690
  }
684
691
  function PopoverSection({ label, value, error = false }) {
685
692
  return /* @__PURE__ */ jsxs2("section", { children: [
686
- /* @__PURE__ */ jsx9("h3", { className: "qsl:mb-1.5 qsl:text-trace qsl:font-semibold qsl:tracking-[0.04em] qsl:text-muted-foreground qsl:uppercase", children: label }),
693
+ /* @__PURE__ */ jsx9("h3", { className: "qsl:mb-1.5 qsl:text-small qsl:font-semibold qsl:tracking-[0.04em] qsl:text-muted-foreground qsl:uppercase", children: label }),
687
694
  /* @__PURE__ */ jsx9("pre", { className: cn(preClasses, error && "qsl:text-destructive"), children: value })
688
695
  ] });
689
696
  }
@@ -691,7 +698,7 @@ function PopoverSection({ label, value, error = false }) {
691
698
  // packages/quassel/src/PendingActionCard.tsx
692
699
  import { CheckIcon as CheckIcon2, XIcon as XIcon2 } from "lucide-react";
693
700
  import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
694
- var cardClasses = "qsl:gap-2.5 qsl:bg-background qsl:px-3.5 qsl:text-sm";
701
+ var cardClasses = "qsl:gap-2.5 qsl:bg-background qsl:px-3.5 qsl:text-body";
695
702
  var resultText = (result) => typeof result === "string" ? result : result === null || result === void 0 ? "" : JSON.stringify(result);
696
703
  function PendingActionCard({
697
704
  text,
@@ -704,17 +711,17 @@ function PendingActionCard({
704
711
  const { Button: Button2, Card: Card2 } = useQuasselComponents();
705
712
  if (action.status !== void 0) {
706
713
  return /* @__PURE__ */ jsxs3(Card2, { className: cardClasses, size: "sm", children: [
707
- /* @__PURE__ */ jsx10("div", { className: "qsl:leading-normal", children: text }),
708
- /* @__PURE__ */ jsxs3("div", { className: "qsl:flex qsl:items-center qsl:gap-1.5 qsl:text-sm qsl:text-muted-foreground", "data-action": "resolved", children: [
714
+ /* @__PURE__ */ jsx10("div", { className: "qsl:text-body", children: text }),
715
+ /* @__PURE__ */ jsxs3("div", { className: "qsl:flex qsl:items-center qsl:gap-1.5 qsl:text-body qsl:text-muted-foreground", "data-action": "resolved", children: [
709
716
  action.status === "approved" ? /* @__PURE__ */ jsx10(CheckIcon2, { className: "qsl:text-success", size: 12 }) : /* @__PURE__ */ jsx10(XIcon2, { size: 12 }),
710
717
  action.status === "approved" ? resultText(action.result) : dismissedLabel
711
718
  ] })
712
719
  ] });
713
720
  }
714
721
  return /* @__PURE__ */ jsxs3(Card2, { className: cardClasses, size: "sm", children: [
715
- /* @__PURE__ */ jsx10("div", { className: "qsl:leading-normal", children: text }),
722
+ /* @__PURE__ */ jsx10("div", { className: "qsl:text-body", children: text }),
716
723
  /* @__PURE__ */ jsxs3("div", { className: "qsl:flex qsl:items-center qsl:justify-between qsl:gap-2", children: [
717
- /* @__PURE__ */ jsx10("span", { className: "qsl:text-sm qsl:text-muted-foreground", "data-action": "waiting", children: waitingLabel }),
724
+ /* @__PURE__ */ jsx10("span", { className: "qsl:text-small qsl:text-muted-foreground", "data-action": "waiting", children: waitingLabel }),
718
725
  onDismiss && /* @__PURE__ */ jsx10(Button2, { onClick: onDismiss, size: "sm", variant: "outline", children: dismissLabel })
719
726
  ] })
720
727
  ] });
@@ -903,7 +910,7 @@ function WorkingScenes({ label = "Working ...", compact = false }) {
903
910
  return () => window.clearInterval(timer);
904
911
  }, [reducedMotion]);
905
912
  const frame = scene.draw(Math.floor(frameTick / scene.tick) + (scene.moving ? offset : 0));
906
- return /* @__PURE__ */ jsxs4("span", { "aria-label": label, className: cn("qsl:flex qsl:items-center qsl:gap-2.5 qsl:text-chat", compact ? "qsl:text-inherit" : "qsl:mt-4 qsl:text-foreground"), "data-chat": "working", role: "status", children: [
913
+ return /* @__PURE__ */ jsxs4("span", { "aria-label": label, className: cn("qsl:flex qsl:items-center qsl:gap-2.5 qsl:text-body", compact ? "qsl:text-inherit" : "qsl:mt-4 qsl:text-foreground"), "data-chat": "working", role: "status", children: [
907
914
  !compact && /* @__PURE__ */ jsx11("span", { "aria-hidden": "true", className: "qsl:text-muted-foreground qsl:opacity-70", children: ">" }),
908
915
  /* @__PURE__ */ jsx11("span", { "aria-hidden": "true", className: cn("qsl:inline-block qsl:flex-none qsl:font-mono qsl:[font-variant-ligatures:none]", compact ? "qsl:w-[2.4em] qsl:text-center" : "qsl:w-[15rem]"), children: /* @__PURE__ */ jsx11("span", { className: "qsl:tracking-[0.02em] qsl:whitespace-pre qsl:motion-reduce:opacity-60", children: typeof frame === "string" ? frame : frame.map((cell, index) => /* @__PURE__ */ jsx11("span", { className: "qsl:inline-block qsl:w-[1.35em] qsl:text-center", children: cell }, index)) }) }, scene.id)
909
916
  ] });
@@ -957,12 +964,8 @@ import { CheckIcon as CheckIcon4, ChevronDownIcon, ChevronRightIcon, ChevronUpIc
957
964
  // packages/quassel/src/options.ts
958
965
  var cssLength = (value) => typeof value === "number" ? `${value}px` : value;
959
966
  var appearanceStyle = (appearance) => ({
960
- "--qsl-font-size": cssLength(appearance?.fontSize),
961
- "--qsl-chat-font-size": cssLength(appearance?.fontSize),
962
- "--qsl-chat-line-height": appearance?.lineHeight,
963
967
  "--qsl-chat-message-gap": cssLength(appearance?.messageGap),
964
968
  "--qsl-chat-dense-message-gap": cssLength(appearance?.denseMessageGap),
965
- "--qsl-tool-font-scale": appearance?.toolFontScale,
966
969
  "--qsl-tool-group-gap": cssLength(appearance?.toolGroupGap),
967
970
  "--qsl-tool-row-padding": cssLength(appearance?.toolRowPadding)
968
971
  });
@@ -1110,7 +1113,7 @@ function MessageActions({ message, options, texts, contentRef, side = message.ro
1110
1113
  item.id
1111
1114
  );
1112
1115
  }) }),
1113
- action.error && /* @__PURE__ */ jsx12("p", { className: "qsl:mt-2 qsl:text-sm qsl:text-destructive", role: "alert", children: action.error })
1116
+ action.error && /* @__PURE__ */ jsx12("p", { className: "qsl:mt-2 qsl:text-small qsl:text-destructive", role: "alert", children: action.error })
1114
1117
  ] });
1115
1118
  }
1116
1119
 
@@ -1198,7 +1201,7 @@ function announce(text) {
1198
1201
  import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
1199
1202
  var stepClasses = "qsl:mt-[var(--qsl-chat-message-gap,10px)]";
1200
1203
  var denseStepClasses = "qsl:mt-[var(--qsl-chat-dense-message-gap,3px)]";
1201
- var traceClasses = "qsl:flex qsl:w-full qsl:gap-2 qsl:py-[var(--qsl-tool-row-padding,1px)] qsl:pl-6 qsl:text-left qsl:text-trace qsl:leading-[1.375] qsl:text-muted-foreground";
1204
+ var traceClasses = "qsl:flex qsl:w-full qsl:gap-2 qsl:py-[var(--qsl-tool-row-padding,1px)] qsl:pl-6 qsl:text-left qsl:text-small qsl:text-muted-foreground";
1202
1205
  var traceButtonClasses = "qsl:cursor-pointer qsl:rounded-md qsl:pr-2 qsl:transition-colors qsl:hover:bg-secondary qsl:hover:text-foreground qsl:focus-visible:bg-secondary qsl:focus-visible:text-foreground qsl:focus-visible:outline-none";
1203
1206
  var traceIconClasses = "qsl:mt-0.5 qsl:flex-none qsl:opacity-60";
1204
1207
  var traceErrorIconClasses = "qsl:mt-0.5 qsl:flex-none qsl:text-destructive qsl:opacity-100";
@@ -1209,7 +1212,7 @@ var rowErrorIconClasses = cn(traceErrorIconClasses, "qsl:mt-0");
1209
1212
  var rowLineClasses = cn(traceLineClasses, "qsl:self-auto");
1210
1213
  var chipClasses = cn(
1211
1214
  "qsl:inline-flex qsl:max-w-[220px] qsl:items-center qsl:gap-[5px] qsl:rounded-full qsl:border qsl:border-border-soft qsl:px-2.5 qsl:py-0.5",
1212
- "qsl:text-meta qsl:leading-[1.6] qsl:text-muted-foreground qsl:transition-colors",
1215
+ "qsl:text-small qsl:text-muted-foreground qsl:transition-colors",
1213
1216
  "qsl:data-[state=running]:border-[color-mix(in_srgb,var(--qsl-primary)_40%,var(--qsl-border-soft))]",
1214
1217
  "qsl:data-[state=thinking]:border-[color-mix(in_srgb,var(--qsl-primary)_40%,var(--qsl-border-soft))]",
1215
1218
  "qsl:data-[state=error]:border-[color-mix(in_srgb,var(--qsl-destructive)_45%,var(--qsl-border-soft))]",
@@ -1218,7 +1221,7 @@ var chipClasses = cn(
1218
1221
  var chipInteractiveClasses = "qsl:cursor-pointer qsl:hover:border-border qsl:hover:bg-secondary qsl:hover:text-foreground qsl:focus-visible:border-border qsl:focus-visible:bg-secondary qsl:focus-visible:text-foreground qsl:focus-visible:outline-none qsl:data-[state=done]:hover:opacity-100 qsl:data-[state=done]:focus-visible:opacity-100";
1219
1222
  var collapseAtBottomFrom = 11;
1220
1223
  var maxAnnouncementsAtOnce = 2;
1221
- var bubbleBodyClasses = "qsl:relative qsl:max-w-[86%] qsl:px-3 qsl:py-2 qsl:leading-[var(--qsl-chat-line-height,1.65)] qsl:[overflow-wrap:anywhere] qsl:@max-[620px]/chat:max-w-full";
1224
+ var bubbleBodyClasses = "qsl:relative qsl:max-w-[86%] qsl:px-3 qsl:py-2 qsl:text-body qsl:[overflow-wrap:anywhere] qsl:@max-[620px]/chat:max-w-full";
1222
1225
  function isStep(message) {
1223
1226
  return !!message && (message.role === "thinking" || message.role === "tool");
1224
1227
  }
@@ -1380,7 +1383,7 @@ function ChatMessages({
1380
1383
  "data-timestamp-position": timestampOptions?.position ?? "aside",
1381
1384
  children: [
1382
1385
  /* @__PURE__ */ jsx13("time", { dateTime: messageDate(at)?.toISOString(), className: cn(
1383
- "qsl:flex-none qsl:text-timestamp qsl:leading-[1.375] qsl:tabular-nums qsl:text-muted-foreground qsl:opacity-85",
1386
+ "qsl:flex-none qsl:text-small qsl:tabular-nums qsl:text-muted-foreground qsl:opacity-85",
1384
1387
  timestampOptions?.position === "above" ? "qsl:self-start" : timestampOptions?.format && timestampOptions.format !== "time" ? "qsl:w-[clamp(60px,20%,120px)]" : "qsl:min-w-[5ch] qsl:whitespace-nowrap"
1385
1388
  ), children: messageDate(at) ? timestampLabel(messageDate(at), timestampOptions, now) : "" }),
1386
1389
  /* @__PURE__ */ jsx13("div", { className: "qsl:min-w-0 qsl:flex-1 qsl:[&>*:first-child]:mt-0", children: content })
@@ -1435,7 +1438,7 @@ function ChatMessages({
1435
1438
  const day = timestampDay(date, timestampOptions);
1436
1439
  if (day.key !== lastDay) {
1437
1440
  closeGroup();
1438
- blocks.push(/* @__PURE__ */ jsx13("div", { className: cn(stepClasses, "qsl:text-center qsl:text-xs qsl:text-muted-foreground"), "data-chat": "day-separator", "data-day": day.key, children: day.label }, `day-${message.key}`));
1441
+ blocks.push(/* @__PURE__ */ jsx13("div", { className: cn(stepClasses, "qsl:text-center qsl:text-small qsl:text-muted-foreground"), "data-chat": "day-separator", "data-day": day.key, children: day.label }, `day-${message.key}`));
1439
1442
  lastDay = day.key;
1440
1443
  }
1441
1444
  }
@@ -1483,15 +1486,14 @@ function ChatMessages({
1483
1486
  const transcript = /* @__PURE__ */ jsxs6("div", { className: cn("qsl:@container/chat qsl:relative qsl:flex qsl:min-h-0 qsl:flex-1 qsl:flex-col", className), "data-quassel": "", style: {
1484
1487
  ...appearanceStyle(appearance),
1485
1488
  "--qsl-chat-content-max-width": typeof maxWidth === "number" ? `${maxWidth}px` : maxWidth,
1486
- "--qsl-chat-horizontal-padding": typeof horizontalPadding === "number" ? `${horizontalPadding}px` : horizontalPadding,
1487
- "--qsl-timestamp-font-size": timestampOptions?.fontScale === void 0 ? void 0 : `calc(var(--qsl-base) * ${timestampOptions.fontScale})`
1489
+ "--qsl-chat-horizontal-padding": typeof horizontalPadding === "number" ? `${horizontalPadding}px` : horizontalPadding
1488
1490
  }, children: [
1489
1491
  /* @__PURE__ */ jsx13(
1490
1492
  "div",
1491
1493
  {
1492
1494
  "aria-label": allTexts.transcript,
1493
1495
  className: cn(
1494
- "qsl:min-h-0 qsl:flex-1 qsl:cursor-default qsl:overflow-y-auto qsl:overscroll-contain qsl:text-chat qsl:text-foreground",
1496
+ "qsl:min-h-0 qsl:flex-1 qsl:cursor-default qsl:overflow-y-auto qsl:overscroll-contain qsl:text-body qsl:text-foreground",
1495
1497
  "qsl:[--qsl-fade-edge:calc(100%_-_var(--qsl-composer-height,0px))]",
1496
1498
  "qsl:[-webkit-mask-image:linear-gradient(to_bottom,#000_calc(var(--qsl-fade-edge)_-_40px),transparent_calc(var(--qsl-fade-edge)_+_16px))]",
1497
1499
  "qsl:[mask-image:linear-gradient(to_bottom,#000_calc(var(--qsl-fade-edge)_-_40px),transparent_calc(var(--qsl-fade-edge)_+_16px))]",
@@ -1549,7 +1551,7 @@ function ChatMessages({
1549
1551
  }
1550
1552
  }, children: /* @__PURE__ */ jsx13(ChevronDownIcon, {}) }) }),
1551
1553
  dock && openActions.length > 0 && createPortal(
1552
- /* @__PURE__ */ jsx13("div", { className: "qsl:flex qsl:flex-col qsl:gap-[var(--qsl-chat-dense-message-gap,3px)] qsl:text-chat qsl:text-foreground", style: appearanceStyle(appearance), children: openActions.map((message) => /* @__PURE__ */ jsx13("div", { children: /* @__PURE__ */ jsx13(ActionContent, { action: message.action, onDismissAction, renderAction, text: message.text, texts: allTexts }) }, message.key)) }),
1554
+ /* @__PURE__ */ jsx13("div", { className: "qsl:flex qsl:flex-col qsl:gap-[var(--qsl-chat-dense-message-gap,3px)] qsl:text-body qsl:text-foreground", style: appearanceStyle(appearance), children: openActions.map((message) => /* @__PURE__ */ jsx13("div", { children: /* @__PURE__ */ jsx13(ActionContent, { action: message.action, onDismissAction, renderAction, text: message.text, texts: allTexts }) }, message.key)) }),
1553
1555
  dock
1554
1556
  )
1555
1557
  ] });
@@ -1776,9 +1778,9 @@ function Bubble({
1776
1778
  /* @__PURE__ */ jsxs6("div", { className: "qsl:min-w-0 qsl:flex-1", children: [
1777
1779
  /* @__PURE__ */ jsx13("div", { className: cn(traceLineClasses, thinking && "qsl:italic qsl:whitespace-pre-wrap"), children: line }),
1778
1780
  tool && /* @__PURE__ */ jsxs6("div", { className: "qsl:mt-0.5 qsl:border-l-2 qsl:border-border-soft qsl:pl-2", children: [
1779
- /* @__PURE__ */ jsx13("pre", { className: "qsl:mb-0.5 qsl:max-h-[208px] qsl:overflow-auto qsl:overscroll-contain qsl:font-mono qsl:text-meta qsl:leading-[1.375] qsl:break-all qsl:whitespace-pre-wrap qsl:opacity-80", children: toolArgumentsText(tool) }),
1781
+ /* @__PURE__ */ jsx13("pre", { className: "qsl:mb-0.5 qsl:max-h-[208px] qsl:overflow-auto qsl:overscroll-contain qsl:font-mono qsl:text-small qsl:break-all qsl:whitespace-pre-wrap qsl:opacity-80", children: toolArgumentsText(tool) }),
1780
1782
  tool.result !== void 0 && /* @__PURE__ */ jsx13("pre", { className: cn(
1781
- "qsl:mb-0.5 qsl:max-h-[208px] qsl:overflow-auto qsl:overscroll-contain qsl:font-mono qsl:text-meta qsl:leading-[1.375] qsl:break-all qsl:whitespace-pre-wrap qsl:opacity-80",
1783
+ "qsl:mb-0.5 qsl:max-h-[208px] qsl:overflow-auto qsl:overscroll-contain qsl:font-mono qsl:text-small qsl:break-all qsl:whitespace-pre-wrap qsl:opacity-80",
1782
1784
  tool.isError && "qsl:text-destructive qsl:opacity-100"
1783
1785
  ), children: prettyJson(tool.result) })
1784
1786
  ] })
@@ -1789,7 +1791,7 @@ function Bubble({
1789
1791
  return /* @__PURE__ */ jsx13("div", { className: stepSpacing, children: /* @__PURE__ */ jsx13(ActionContent, { action: message.action, onDismissAction, renderAction, text: message.text, texts }) });
1790
1792
  }
1791
1793
  if (message.role === "system") {
1792
- return /* @__PURE__ */ jsxs6("div", { className: cn(stepSpacing, "qsl:border-l-2 qsl:border-destructive/50 qsl:pl-3 qsl:text-sm qsl:text-muted-foreground"), "data-message": "system", children: [
1794
+ return /* @__PURE__ */ jsxs6("div", { className: cn(stepSpacing, "qsl:border-l-2 qsl:border-destructive/50 qsl:pl-3 qsl:text-small qsl:text-muted-foreground"), "data-message": "system", children: [
1793
1795
  /* @__PURE__ */ jsx13(Markdown, { text: message.text }),
1794
1796
  /* @__PURE__ */ jsx13(MessageAttachments, { message, texts })
1795
1797
  ] });
@@ -1802,15 +1804,15 @@ function Bubble({
1802
1804
  const showSender = bubbleOptions?.showSender ?? (!!message.bubble?.label && !plain);
1803
1805
  const content = /* @__PURE__ */ jsxs6(Fragment2, { children: [
1804
1806
  showSender && sender && /* @__PURE__ */ jsx13("span", { className: cn(
1805
- "qsl:mb-[5px] qsl:table qsl:text-label qsl:leading-[1.45] qsl:font-semibold",
1807
+ "qsl:mb-[5px] qsl:table qsl:text-small qsl:font-semibold",
1806
1808
  useBubble && message.bubble && "qsl:rounded-full qsl:border qsl:border-white/30 qsl:bg-black/15 qsl:px-[7px] qsl:py-px qsl:tracking-[0.02em]"
1807
1809
  ), "data-chat": "sender", children: sender }),
1808
1810
  /* @__PURE__ */ jsx13("div", { "data-chat": "message-text", ref: textRef, children: /* @__PURE__ */ jsx13(Markdown, { text: message.text, streaming: message.role === "assistant" && !message.closed }) }),
1809
1811
  /* @__PURE__ */ jsx13(MessageAttachments, { message, texts }),
1810
- message.steered && /* @__PURE__ */ jsx13("small", { className: "qsl:mt-1 qsl:block qsl:text-label qsl:leading-[1.45] qsl:opacity-70", "data-chat": "steered", children: texts.steered })
1812
+ message.steered && /* @__PURE__ */ jsx13("small", { className: "qsl:mt-1 qsl:block qsl:text-small qsl:opacity-70", "data-chat": "steered", children: texts.steered })
1811
1813
  ] });
1812
1814
  const cursor = message.role === "assistant" && message.textCursor ? JSON.stringify(message.textCursor) : void 0;
1813
- if (!useBubble) return /* @__PURE__ */ jsxs6("div", { "data-chat-text-cursor": cursor, className: cn(stepSpacing, "qsl:group/message qsl:relative qsl:w-full qsl:leading-[var(--qsl-chat-line-height,1.65)] qsl:text-foreground"), "data-message": "answer", children: [
1815
+ if (!useBubble) return /* @__PURE__ */ jsxs6("div", { "data-chat-text-cursor": cursor, className: cn(stepSpacing, "qsl:group/message qsl:relative qsl:w-full qsl:text-foreground"), "data-message": "answer", children: [
1814
1816
  content,
1815
1817
  /* @__PURE__ */ jsx13(MessageActions, { contentRef: textRef, message, options: messageActions, side, texts })
1816
1818
  ] });
@@ -1868,18 +1870,20 @@ function MessageAttachments({ message, texts }) {
1868
1870
  return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1869
1871
  /* @__PURE__ */ jsx13("ul", { className: "qsl:mt-2.5 qsl:flex qsl:flex-wrap qsl:gap-2", "aria-label": texts.attachments, children: message.attachments.map((attachment, index) => {
1870
1872
  const isImage = attachment.mediaType.startsWith("image/");
1871
- return /* @__PURE__ */ jsxs6("li", { className: cn("qsl:relative qsl:flex qsl:max-w-full qsl:min-w-0 qsl:flex-col qsl:overflow-hidden qsl:rounded-lg qsl:border qsl:border-border qsl:text-sm", isImage ? "qsl:w-[320px]" : "qsl:w-[190px]"), children: [
1872
- isImage && /* @__PURE__ */ jsx13(
1873
+ if (isImage)
1874
+ return /* @__PURE__ */ jsx13("li", { className: "qsl:relative qsl:overflow-hidden qsl:rounded-lg qsl:border qsl:border-border", children: /* @__PURE__ */ jsx13(
1873
1875
  "button",
1874
1876
  {
1875
1877
  type: "button",
1876
- className: "qsl:block qsl:w-full qsl:cursor-zoom-in qsl:bg-secondary",
1878
+ className: "qsl:block qsl:cursor-zoom-in qsl:bg-secondary",
1877
1879
  "aria-label": fillText(texts.enlargeImage ?? defaultTexts.enlargeImage, { name: attachment.name }),
1880
+ title: attachment.name,
1878
1881
  onClick: () => setEnlarged(attachment),
1879
1882
  "data-chat": "attachment-image",
1880
- children: /* @__PURE__ */ jsx13("img", { className: "qsl:mx-auto qsl:block qsl:max-h-[240px] qsl:max-w-full qsl:object-contain", src: attachment.url, alt: attachment.name, loading: "lazy" })
1883
+ children: /* @__PURE__ */ jsx13("img", { className: "qsl:block qsl:size-24 qsl:object-cover", src: attachment.url, alt: attachment.name, loading: "lazy" })
1881
1884
  }
1882
- ),
1885
+ ) }, `${attachment.url}-${index}`);
1886
+ return /* @__PURE__ */ jsxs6("li", { className: "qsl:relative qsl:flex qsl:w-[190px] qsl:max-w-full qsl:min-w-0 qsl:flex-col qsl:overflow-hidden qsl:rounded-lg qsl:border qsl:border-border qsl:text-small", children: [
1883
1887
  attachment.mediaType.startsWith("video/") && /* @__PURE__ */ jsx13("video", { className: "qsl:block qsl:h-[112px] qsl:w-full qsl:bg-secondary qsl:object-contain", src: attachment.url, controls: true, preload: "metadata", "aria-label": attachment.name }),
1884
1888
  /* @__PURE__ */ jsxs6("div", { className: "qsl:flex qsl:flex-col qsl:gap-1 qsl:p-2 qsl:[overflow-wrap:anywhere]", children: [
1885
1889
  /* @__PURE__ */ jsx13("a", { className: "qsl:font-medium", href: attachmentDownloadUrl(attachment.url), download: attachment.name, children: attachment.name }),
@@ -1903,7 +1907,7 @@ function ImageViewer({ attachment, onClose, texts }) {
1903
1907
  "data-chat": "image-viewer",
1904
1908
  children: attachment && /* @__PURE__ */ jsxs6(Fragment2, { children: [
1905
1909
  /* @__PURE__ */ jsx13("img", { className: "qsl:block qsl:max-h-[calc(100vh-6rem)] qsl:max-w-full qsl:rounded-md qsl:bg-background qsl:object-contain qsl:shadow-lg", src: attachment.url, alt: attachment.name }),
1906
- /* @__PURE__ */ jsxs6("div", { className: "qsl:flex qsl:items-center qsl:gap-4 qsl:text-sm qsl:text-white", children: [
1910
+ /* @__PURE__ */ jsxs6("div", { className: "qsl:flex qsl:items-center qsl:gap-4 qsl:text-small qsl:text-white", children: [
1907
1911
  /* @__PURE__ */ jsx13("span", { className: "qsl:[overflow-wrap:anywhere]", children: attachment.name }),
1908
1912
  /* @__PURE__ */ jsx13("a", { className: "qsl:underline", href: attachmentDownloadUrl(attachment.url), download: attachment.name, children: texts.downloadAttachment ?? defaultTexts.downloadAttachment }),
1909
1913
  /* @__PURE__ */ jsx13(Dialog.Close, { className: "qsl:cursor-pointer qsl:underline", children: texts.close })
@@ -1994,7 +1998,7 @@ function ChatPanel({
1994
1998
  }
1995
1999
 
1996
2000
  // packages/quassel/src/ChatInputToolbar.tsx
1997
- import { PaperclipIcon, SendIcon, XIcon as XIcon3 } from "lucide-react";
2001
+ import { PaperclipIcon, SendIcon as SendIcon2, XIcon as XIcon3 } from "lucide-react";
1998
2002
 
1999
2003
  // packages/quassel/src/ui/useFileInput.ts
2000
2004
  import { useRef as useRef5, useState as useState6 } from "react";
@@ -2045,12 +2049,217 @@ function useFileInput({ disabled, onFiles, onPasteText }) {
2045
2049
  }
2046
2050
 
2047
2051
  // packages/quassel/src/ChatInputToolbar.tsx
2048
- import { useCallback as useCallback3, useContext as useContext4, useEffect as useEffect5, useImperativeHandle, useLayoutEffect as useLayoutEffect3, useRef as useRef6, useState as useState7 } from "react";
2052
+ import { useCallback as useCallback3, useContext as useContext4, useEffect as useEffect6, useImperativeHandle, useLayoutEffect as useLayoutEffect5, useRef as useRef8, useState as useState8 } from "react";
2053
+ import { createPortal as createPortal3 } from "react-dom";
2054
+
2055
+ // packages/quassel/src/ComposerPrimaryAction.tsx
2056
+ import { LoaderCircleIcon, MicIcon, SendIcon, SquareIcon as SquareIcon2 } from "lucide-react";
2057
+ import { useEffect as useEffect5, useLayoutEffect as useLayoutEffect4, useRef as useRef7, useState as useState7 } from "react";
2058
+
2059
+ // packages/quassel/src/RecordingAura.tsx
2060
+ import { useLayoutEffect as useLayoutEffect3, useRef as useRef6 } from "react";
2049
2061
  import { createPortal as createPortal2 } from "react-dom";
2050
- import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
2062
+ import { jsx as jsx15 } from "react/jsx-runtime";
2063
+ function RecordingAura({ anchor, level = 0 }) {
2064
+ const aura = useRef6(null);
2065
+ const volume = Number.isFinite(level) ? Math.max(0, Math.min(1, level)) : 0;
2066
+ useLayoutEffect3(() => {
2067
+ const element = aura.current;
2068
+ const view = anchor.ownerDocument.defaultView;
2069
+ if (!element || !view) return;
2070
+ let frame = 0;
2071
+ let visible = true;
2072
+ const observer = new view.IntersectionObserver(([entry]) => {
2073
+ visible = entry.isIntersecting;
2074
+ });
2075
+ observer.observe(anchor);
2076
+ const update = () => {
2077
+ const rect = anchor.getBoundingClientRect();
2078
+ const style = view.getComputedStyle(anchor);
2079
+ element.style.left = `${rect.left}px`;
2080
+ element.style.top = `${rect.top}px`;
2081
+ element.style.width = `${rect.width}px`;
2082
+ element.style.height = `${rect.height}px`;
2083
+ element.style.visibility = visible && rect.width && rect.height && anchor.checkVisibility() ? "visible" : "hidden";
2084
+ element.style.setProperty("--qsl-pill-aura-color", style.getPropertyValue("--qsl-pill-aura-color") || style.getPropertyValue("--qsl-destructive"));
2085
+ element.style.setProperty("--qsl-pill-aura-ring-color", style.getPropertyValue("--qsl-pill-aura-ring-color") || style.getPropertyValue("--qsl-pill-aura-color") || style.getPropertyValue("--qsl-destructive"));
2086
+ element.style.zIndex = style.getPropertyValue("--qsl-pill-aura-z-index") || "1000";
2087
+ frame = view.requestAnimationFrame(update);
2088
+ };
2089
+ update();
2090
+ return () => {
2091
+ view.cancelAnimationFrame(frame);
2092
+ observer.disconnect();
2093
+ };
2094
+ }, [anchor]);
2095
+ return createPortal2(/* @__PURE__ */ jsx15(
2096
+ "span",
2097
+ {
2098
+ "aria-hidden": "true",
2099
+ className: "qsl:recording-aura",
2100
+ "data-quassel": "",
2101
+ "data-recording-aura": "",
2102
+ ref: aura,
2103
+ style: { transform: `scale(${1.2 + volume * 0.65})`, opacity: 0.4 + volume * 0.4 }
2104
+ }
2105
+ ), anchor.ownerDocument.body);
2106
+ }
2107
+
2108
+ // packages/quassel/src/ComposerPrimaryAction.tsx
2109
+ import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
2110
+ function ComposerPrimaryAction({ hasContent, running: running2, sending, disabled, sendDisabled, microphone, texts, onSend, onStop }) {
2111
+ const state = microphone && microphone.state !== "idle" ? microphone.state : hasContent ? "send" : running2 ? "stop" : microphone && !sending ? "microphone" : "send";
2112
+ const voice = state === "microphone" || state === "preparing" || state === "recording";
2113
+ const busy = state === "preparing" || state === "transcribing";
2114
+ const blocked = disabled || state === "transcribing" || state === "send" && (sendDisabled || sending || !hasContent) || state === "stop" && !onStop;
2115
+ const label = state === "microphone" ? microphone?.label ?? texts.microphone : state === "preparing" ? texts.microphonePreparing : state === "recording" ? texts.microphoneRecording : state === "transcribing" ? texts.microphoneTranscribing : state === "stop" ? texts.stop : running2 ? texts.sendIntoRun : texts.send;
2116
+ const [button, setButton] = useState7(null);
2117
+ const press = useRef7(null);
2118
+ const suppressClick = useRef7(false);
2119
+ const clickReset = useRef7(void 0);
2120
+ const currentMicrophone = useRef7(microphone);
2121
+ const resetClick = () => {
2122
+ clearTimeout(clickReset.current);
2123
+ clickReset.current = setTimeout(() => {
2124
+ suppressClick.current = false;
2125
+ }, 0);
2126
+ };
2127
+ const cancel = () => {
2128
+ const active = press.current;
2129
+ press.current = null;
2130
+ if (active) resetClick();
2131
+ const current = currentMicrophone.current;
2132
+ if (active) active.microphone.onCancel();
2133
+ else if (current?.state === "recording" || current?.state === "preparing") current.onCancel();
2134
+ };
2135
+ useLayoutEffect4(() => {
2136
+ currentMicrophone.current = microphone;
2137
+ }, [microphone]);
2138
+ useEffect5(() => {
2139
+ if (disabled) cancel();
2140
+ }, [disabled]);
2141
+ useEffect5(() => {
2142
+ if (!button) return;
2143
+ const document2 = button.ownerDocument;
2144
+ const view = document2.defaultView;
2145
+ const onVisibility = () => {
2146
+ if (document2.hidden) cancel();
2147
+ };
2148
+ const onEscape = (event) => {
2149
+ if (event.key === "Escape" && (press.current || currentMicrophone.current?.state === "recording" || currentMicrophone.current?.state === "preparing")) {
2150
+ event.preventDefault();
2151
+ cancel();
2152
+ }
2153
+ };
2154
+ view.addEventListener("blur", cancel);
2155
+ document2.addEventListener("visibilitychange", onVisibility);
2156
+ document2.addEventListener("keydown", onEscape);
2157
+ return () => {
2158
+ view.removeEventListener("blur", cancel);
2159
+ document2.removeEventListener("visibilitychange", onVisibility);
2160
+ document2.removeEventListener("keydown", onEscape);
2161
+ cancel();
2162
+ clearTimeout(clickReset.current);
2163
+ };
2164
+ }, [button]);
2165
+ const start = (source, timeStamp, pointerId, key) => {
2166
+ if (!voice || state === "preparing" || blocked || !microphone || press.current) return;
2167
+ press.current = { source, pointerId, key, microphone };
2168
+ clearTimeout(clickReset.current);
2169
+ suppressClick.current = true;
2170
+ microphone.onPressStart({ source, timeStamp });
2171
+ };
2172
+ const end = (timeStamp) => {
2173
+ const active = press.current;
2174
+ press.current = null;
2175
+ resetClick();
2176
+ active?.microphone.onPressEnd({ source: active.source, timeStamp });
2177
+ };
2178
+ const activationKey = (event) => (event.key === " " || event.key === "Enter") && !event.nativeEvent.isComposing && event.nativeEvent.keyCode !== 229;
2179
+ return /* @__PURE__ */ jsxs8(Fragment3, { children: [
2180
+ state === "recording" && button && /* @__PURE__ */ jsx16(RecordingAura, { anchor: button, level: microphone?.level }),
2181
+ /* @__PURE__ */ jsxs8(
2182
+ "button",
2183
+ {
2184
+ "aria-busy": busy || void 0,
2185
+ "aria-label": label,
2186
+ "aria-pressed": voice ? state === "recording" : void 0,
2187
+ className: "qsl:primary-action",
2188
+ "data-primary-action": state,
2189
+ disabled: blocked,
2190
+ onBlur: () => {
2191
+ if (press.current?.source === "keyboard") cancel();
2192
+ },
2193
+ onClick: (event) => {
2194
+ if (suppressClick.current) {
2195
+ suppressClick.current = false;
2196
+ return;
2197
+ }
2198
+ if (voice && event.detail === 0) {
2199
+ start("virtual", event.timeStamp);
2200
+ end(event.timeStamp);
2201
+ suppressClick.current = false;
2202
+ } else if (state === "send") onSend();
2203
+ else if (state === "stop") onStop?.();
2204
+ },
2205
+ onContextMenu: (event) => {
2206
+ if (voice) event.preventDefault();
2207
+ },
2208
+ onKeyDown: (event) => {
2209
+ if (activationKey(event) && event.repeat) {
2210
+ event.preventDefault();
2211
+ return;
2212
+ }
2213
+ if (!voice) {
2214
+ suppressClick.current = false;
2215
+ return;
2216
+ }
2217
+ if (activationKey(event)) {
2218
+ event.preventDefault();
2219
+ start("keyboard", event.timeStamp, void 0, event.key);
2220
+ }
2221
+ },
2222
+ onKeyUp: (event) => {
2223
+ if (activationKey(event) && (voice || press.current?.source === "keyboard")) {
2224
+ event.preventDefault();
2225
+ if (press.current?.source === "keyboard" && press.current.key === event.key) end(event.timeStamp);
2226
+ }
2227
+ },
2228
+ onLostPointerCapture: (event) => {
2229
+ if (press.current?.pointerId === event.pointerId) cancel();
2230
+ },
2231
+ onPointerCancel: (event) => {
2232
+ if (press.current?.pointerId === event.pointerId) cancel();
2233
+ },
2234
+ onPointerDown: (event) => {
2235
+ suppressClick.current = false;
2236
+ if (event.button !== 0 || event.isPrimary === false) return;
2237
+ start("pointer", event.timeStamp, event.pointerId);
2238
+ if (press.current?.pointerId === event.pointerId) event.currentTarget.setPointerCapture?.(event.pointerId);
2239
+ },
2240
+ onPointerUp: (event) => {
2241
+ if (press.current?.source === "pointer" && press.current.pointerId === event.pointerId) end(event.timeStamp);
2242
+ },
2243
+ ref: setButton,
2244
+ title: state === "microphone" ? texts.microphoneHint : label,
2245
+ type: "button",
2246
+ children: [
2247
+ /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", className: "qsl:primary-icon", "data-active": state === "microphone" || state === "recording", children: /* @__PURE__ */ jsx16(MicIcon, {}) }),
2248
+ /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", className: "qsl:primary-icon", "data-active": state === "send", children: /* @__PURE__ */ jsx16(SendIcon, {}) }),
2249
+ /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", className: "qsl:primary-icon", "data-active": state === "stop", children: /* @__PURE__ */ jsx16(SquareIcon2, { fill: "currentColor" }) }),
2250
+ /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", className: "qsl:primary-icon", "data-active": busy, children: /* @__PURE__ */ jsx16(LoaderCircleIcon, { className: "qsl:motion-safe:animate-spin" }) }),
2251
+ state === "recording" && /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", className: "qsl:recording-dot" })
2252
+ ]
2253
+ }
2254
+ )
2255
+ ] });
2256
+ }
2257
+
2258
+ // packages/quassel/src/ChatInputToolbar.tsx
2259
+ import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
2051
2260
  var COMPACT_WIDTH_PX = 480;
2052
- var errorClasses = "qsl:px-3 qsl:py-2 qsl:text-sm qsl:text-destructive";
2053
- var attachmentItemClasses = "qsl:relative qsl:flex qsl:w-[190px] qsl:max-w-full qsl:min-w-0 qsl:flex-col qsl:overflow-hidden qsl:rounded-lg qsl:border qsl:border-border qsl:text-sm";
2261
+ var errorClasses = "qsl:px-3 qsl:py-2 qsl:text-small qsl:text-destructive";
2262
+ var attachmentItemClasses = "qsl:relative qsl:flex qsl:w-[190px] qsl:max-w-full qsl:min-w-0 qsl:flex-col qsl:overflow-hidden qsl:rounded-lg qsl:border qsl:border-border qsl:text-small";
2054
2263
  var attachmentMediaClasses = "qsl:block qsl:h-[112px] qsl:w-full qsl:bg-secondary qsl:object-contain";
2055
2264
  function ChatInputToolbar({
2056
2265
  onSend,
@@ -2067,6 +2276,8 @@ function ChatInputToolbar({
2067
2276
  actions,
2068
2277
  toolbarLeft,
2069
2278
  toolbarRight,
2279
+ microphone,
2280
+ replaceInput,
2070
2281
  texts,
2071
2282
  handleRef,
2072
2283
  attachments: attachmentsEnabled = true,
@@ -2082,21 +2293,21 @@ function ChatInputToolbar({
2082
2293
  const sendScope = useContext4(ChatSendContext);
2083
2294
  const allTexts = { ...defaultTexts, ...texts };
2084
2295
  const { Button: Button2, StopButton: StopButton2, Toggle: Toggle2 } = useQuasselComponents();
2085
- const [draft, setDraft] = useState7(initialValue);
2086
- const [sending, setSending] = useState7(false);
2087
- const [sendError, setSendError] = useState7();
2088
- const [failedSubmissions, setFailedSubmissions] = useState7([]);
2089
- const nextFailedId = useRef6(0);
2090
- const pendingSend = useRef6(false);
2091
- const generation = useRef6(0);
2092
- useLayoutEffect3(() => () => {
2296
+ const [draft, setDraft] = useState8(initialValue);
2297
+ const [sending, setSending] = useState8(false);
2298
+ const [sendError, setSendError] = useState8();
2299
+ const [failedSubmissions, setFailedSubmissions] = useState8([]);
2300
+ const nextFailedId = useRef8(0);
2301
+ const pendingSend = useRef8(false);
2302
+ const generation = useRef8(0);
2303
+ useLayoutEffect5(() => () => {
2093
2304
  generation.current += 1;
2094
2305
  }, []);
2095
- const draftRevision = useRef6(0);
2096
- const [attachments, setAttachments] = useState7([]);
2097
- const attachmentsRef = useRef6([]);
2098
- const nextAttachmentId = useRef6(0);
2099
- const [attachmentError, setAttachmentError] = useState7();
2306
+ const draftRevision = useRef8(0);
2307
+ const [attachments, setAttachments] = useState8([]);
2308
+ const attachmentsRef = useRef8([]);
2309
+ const nextAttachmentId = useRef8(0);
2310
+ const [attachmentError, setAttachmentError] = useState8();
2100
2311
  const updateAttachments = (update) => {
2101
2312
  attachmentsRef.current = update(attachmentsRef.current);
2102
2313
  setAttachments(attachmentsRef.current);
@@ -2127,10 +2338,11 @@ function ChatInputToolbar({
2127
2338
  );
2128
2339
  }
2129
2340
  };
2130
- const rootRef = useRef6(null);
2131
- const textareaRef = useRef6(null);
2132
- const autoFocusSettled = useRef6(false);
2133
- useEffect5(() => {
2341
+ const rootRef = useRef8(null);
2342
+ const textareaRef = useRef8(null);
2343
+ const composing = useRef8(false);
2344
+ const autoFocusSettled = useRef8(false);
2345
+ useEffect6(() => {
2134
2346
  if (!autoFocus || autoFocusSettled.current) return;
2135
2347
  const input2 = textareaRef.current;
2136
2348
  if (!input2) return;
@@ -2175,8 +2387,8 @@ function ChatInputToolbar({
2175
2387
  window.removeEventListener("blur", settle);
2176
2388
  };
2177
2389
  }, [autoFocus, disabled, onAutoFocusSettled]);
2178
- const [compact, setCompact] = useState7(false);
2179
- const pendingFocus = useRef6(false);
2390
+ const [compact, setCompact] = useState8(false);
2391
+ const pendingFocus = useRef8(false);
2180
2392
  const fileInput = useFileInput({
2181
2393
  disabled: disabled || sending,
2182
2394
  onFiles: addFiles,
@@ -2192,17 +2404,21 @@ function ChatInputToolbar({
2192
2404
  });
2193
2405
  const toolbar = layout === "toolbar";
2194
2406
  const inline = layout === "inline";
2195
- const inputRows = inline || toolbar ? 1 : rows;
2196
- const inputMaxRows = toolbar ? 1 : inline ? maxRows ?? 1 : maxRows;
2407
+ const pill = layout === "pill";
2408
+ const inputRows = inline || toolbar || pill ? 1 : rows;
2409
+ const inputMaxRows = toolbar ? 1 : pill ? maxRows ?? 6 : inline ? maxRows ?? 1 : maxRows;
2410
+ const microphoneBusy = pill && !!microphone && microphone.state !== "idle";
2411
+ const replacingInput = pill && replaceInput !== void 0 && replaceInput !== null && typeof replaceInput !== "boolean";
2197
2412
  const rootClasses = cn(
2198
- "qsl:flex qsl:shrink-0 qsl:flex-col qsl:overflow-hidden qsl:rounded-[var(--qsl-input-card-radius,--theme(--radius-xl))] qsl:border qsl:border-border-strong qsl:bg-background qsl:shadow-bar qsl:transition-colors qsl:focus-within:border-primary",
2199
- "qsl:in-data-[tone=material]:rounded-xl qsl:in-data-[tone=material]:border-glass-edge/50 qsl:in-data-[tone=material]:bg-background",
2413
+ "qsl:flex qsl:shrink-0 qsl:flex-col",
2414
+ pill ? "qsl:min-w-0" : "qsl:overflow-hidden qsl:rounded-[var(--qsl-input-card-radius,--theme(--radius-xl))] qsl:border qsl:border-border-strong qsl:bg-background qsl:shadow-bar qsl:transition-colors qsl:focus-within:border-primary qsl:in-data-[tone=material]:rounded-xl qsl:in-data-[tone=material]:border-glass-edge/50 qsl:in-data-[tone=material]:bg-background",
2200
2415
  toolbar && "qsl:h-full qsl:min-w-0 qsl:flex-row qsl:items-center qsl:rounded-lg qsl:data-[working=true]:animate-working-pulse qsl:data-[working=true]:border-primary qsl:motion-reduce:data-[working=true]:animate-none",
2201
2416
  fileInput.dragging && "qsl:outline-2 qsl:outline-offset-[3px] qsl:outline-dashed qsl:outline-primary"
2202
2417
  );
2203
2418
  const textareaClasses = cn(
2204
- "qsl:resize-none qsl:overscroll-contain qsl:border-0 qsl:bg-transparent qsl:text-chat qsl:leading-[var(--qsl-chat-line-height,1.65)] qsl:text-foreground qsl:placeholder:text-muted-foreground qsl:focus:outline-none",
2205
- inline ? "qsl:min-w-0 qsl:flex-1 qsl:px-2.5 qsl:py-2" : toolbar ? "qsl:w-full qsl:min-w-0 qsl:flex-auto qsl:px-2 qsl:py-[3px] qsl:leading-[1.3]" : "qsl:px-3 qsl:pt-3 qsl:pb-2"
2419
+ "qsl:resize-none qsl:overscroll-contain qsl:border-0 qsl:bg-transparent qsl:text-foreground qsl:placeholder:text-muted-foreground",
2420
+ !pill && "qsl:text-body qsl:focus:outline-none",
2421
+ pill ? "qsl:pill-input" : inline ? "qsl:min-w-0 qsl:flex-1 qsl:px-2.5 qsl:py-2" : toolbar ? "qsl:w-full qsl:min-w-0 qsl:flex-auto qsl:px-2 qsl:py-[3px]" : "qsl:px-3 qsl:pt-3 qsl:pb-2"
2206
2422
  );
2207
2423
  const toolbarRowClasses = cn(
2208
2424
  "qsl:flex qsl:items-center qsl:gap-2 qsl:border-t qsl:border-border-soft qsl:px-2 qsl:py-1.5 qsl:in-data-[tone=overseer-details]:flex-nowrap",
@@ -2237,10 +2453,10 @@ function ChatInputToolbar({
2237
2453
  fileInput.clearDragging();
2238
2454
  }
2239
2455
  }));
2240
- useEffect5(() => {
2456
+ useEffect6(() => {
2241
2457
  onDraftChange?.(draft);
2242
2458
  }, [draft, onDraftChange]);
2243
- useEffect5(() => {
2459
+ useEffect6(() => {
2244
2460
  if (!pendingFocus.current) {
2245
2461
  return;
2246
2462
  }
@@ -2257,7 +2473,7 @@ function ChatInputToolbar({
2257
2473
  return;
2258
2474
  }
2259
2475
  const style = getComputedStyle(element);
2260
- const lineHeight = Number.parseFloat(style.lineHeight) || Number.parseFloat(style.fontSize) * 1.4;
2476
+ const lineHeight = Number.parseFloat(style.lineHeight) || Number.parseFloat(style.fontSize) * 1.5;
2261
2477
  const padding = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom);
2262
2478
  const maximum = lineHeight * Math.max(inputMaxRows, inputRows) + padding;
2263
2479
  element.style.height = "auto";
@@ -2265,10 +2481,10 @@ function ChatInputToolbar({
2265
2481
  element.style.height = `${Math.min(contentHeight, maximum)}px`;
2266
2482
  element.style.overflowY = contentHeight > maximum ? "auto" : "hidden";
2267
2483
  }, [inputMaxRows, inputRows]);
2268
- useEffect5(() => {
2484
+ useEffect6(() => {
2269
2485
  applyAutoGrow();
2270
- }, [draft, applyAutoGrow]);
2271
- useEffect5(() => {
2486
+ }, [draft, replacingInput, applyAutoGrow]);
2487
+ useEffect6(() => {
2272
2488
  const element = textareaRef.current;
2273
2489
  if (!element || inputMaxRows === void 0) {
2274
2490
  return;
@@ -2282,8 +2498,8 @@ function ChatInputToolbar({
2282
2498
  });
2283
2499
  observer.observe(element);
2284
2500
  return () => observer.disconnect();
2285
- }, [inputMaxRows, applyAutoGrow]);
2286
- useEffect5(() => {
2501
+ }, [inputMaxRows, replacingInput, applyAutoGrow]);
2502
+ useEffect6(() => {
2287
2503
  const element = rootRef.current;
2288
2504
  if (!element) {
2289
2505
  return;
@@ -2295,7 +2511,7 @@ function ChatInputToolbar({
2295
2511
  return () => observer.disconnect();
2296
2512
  }, []);
2297
2513
  const hasAttachments = attachments.length > 0;
2298
- useEffect5(() => {
2514
+ useEffect6(() => {
2299
2515
  onAttachmentsChange?.(hasAttachments);
2300
2516
  }, [hasAttachments, onAttachmentsChange]);
2301
2517
  const capabilityError = hasAttachments && attachmentCapabilitiesError ? attachmentCapabilitiesError : attachmentCapabilities ? attachmentCapabilityError(attachments.map((entry) => ({
@@ -2306,13 +2522,13 @@ function ChatInputToolbar({
2306
2522
  const preparing = attachments.some((entry) => entry.status === "reading");
2307
2523
  const attachmentBlocked = attachments.some((entry) => entry.status !== "ready");
2308
2524
  const currentError = sendError ?? attachmentError ?? capabilityError ?? attachments.find((entry) => entry.status === "error")?.error;
2309
- useEffect5(() => {
2525
+ useEffect6(() => {
2310
2526
  onErrorChange?.(currentError);
2311
2527
  }, [currentError, onErrorChange]);
2312
2528
  const send = async (onSubmit = onSend, allowEmpty = false) => {
2313
2529
  const text = draft.trim();
2314
2530
  const snapshot = attachmentsRef.current;
2315
- if (!allowEmpty && !text && !snapshot.length || disabled || sendDisabled || pendingSend.current || capabilityError || snapshot.some((entry) => entry.status !== "ready")) {
2531
+ if (!allowEmpty && !text && !snapshot.length || disabled || sendDisabled || microphoneBusy || replacingInput || pendingSend.current || capabilityError || snapshot.some((entry) => entry.status !== "ready")) {
2316
2532
  return;
2317
2533
  }
2318
2534
  pendingSend.current = true;
@@ -2357,21 +2573,21 @@ function ChatInputToolbar({
2357
2573
  }
2358
2574
  };
2359
2575
  const onKeyDown = (event) => {
2360
- if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing && event.nativeEvent.keyCode !== 229 && (sendShortcut === "enter" || event.metaKey || event.ctrlKey)) {
2576
+ if (event.key === "Enter" && !event.shiftKey && !composing.current && !event.nativeEvent.isComposing && event.nativeEvent.keyCode !== 229 && (sendShortcut === "enter" || event.metaKey || event.ctrlKey)) {
2361
2577
  event.preventDefault();
2362
2578
  if (!event.repeat) void send();
2363
2579
  }
2364
2580
  };
2365
- const attachmentPreviews = attachments.length > 0 && /* @__PURE__ */ jsx15("ul", { className: "qsl:flex qsl:max-h-[240px] qsl:flex-wrap qsl:gap-2 qsl:overflow-y-auto qsl:overscroll-contain qsl:px-3 qsl:pt-2.5 qsl:in-data-[tone=overseer-details]:max-h-40 qsl:in-data-[tone=overseer-details]:overflow-y-auto", "aria-label": allTexts.attachments, children: attachments.map((entry) => /* @__PURE__ */ jsxs8("li", { className: attachmentItemClasses, children: [
2366
- entry.status === "ready" && entry.input.mediaType.startsWith("image/") && /* @__PURE__ */ jsx15("img", { className: attachmentMediaClasses, src: `data:${entry.input.mediaType};base64,${entry.input.data}`, alt: entry.file.name }),
2367
- entry.status === "ready" && entry.input.mediaType.startsWith("video/") && /* @__PURE__ */ jsx15("video", { className: attachmentMediaClasses, controls: true, preload: "metadata", "aria-label": entry.file.name, src: `data:${entry.input.mediaType};base64,${entry.input.data}` }),
2368
- /* @__PURE__ */ jsxs8("div", { className: "qsl:flex qsl:flex-col qsl:gap-1 qsl:p-2 qsl:pr-8 qsl:[overflow-wrap:anywhere]", children: [
2369
- /* @__PURE__ */ jsx15("span", { className: "qsl:font-medium", title: entry.file.name, children: entry.file.name }),
2370
- /* @__PURE__ */ jsx15("small", { children: formatAttachmentSize(entry.file.size) }),
2371
- entry.status === "reading" && /* @__PURE__ */ jsx15("small", { role: "status", children: allTexts.attachmentPreparing }),
2372
- entry.status === "error" && /* @__PURE__ */ jsx15("small", { role: "alert", children: entry.error })
2581
+ const attachmentPreviews = attachments.length > 0 && /* @__PURE__ */ jsx17("ul", { className: "qsl:flex qsl:max-h-[240px] qsl:flex-wrap qsl:gap-2 qsl:overflow-y-auto qsl:overscroll-contain qsl:px-3 qsl:pt-2.5 qsl:in-data-[tone=overseer-details]:max-h-40 qsl:in-data-[tone=overseer-details]:overflow-y-auto", "aria-label": allTexts.attachments, children: attachments.map((entry) => /* @__PURE__ */ jsxs9("li", { className: attachmentItemClasses, children: [
2582
+ entry.status === "ready" && entry.input.mediaType.startsWith("image/") && /* @__PURE__ */ jsx17("img", { className: attachmentMediaClasses, src: `data:${entry.input.mediaType};base64,${entry.input.data}`, alt: entry.file.name }),
2583
+ entry.status === "ready" && entry.input.mediaType.startsWith("video/") && /* @__PURE__ */ jsx17("video", { className: attachmentMediaClasses, controls: true, preload: "metadata", "aria-label": entry.file.name, src: `data:${entry.input.mediaType};base64,${entry.input.data}` }),
2584
+ /* @__PURE__ */ jsxs9("div", { className: "qsl:flex qsl:flex-col qsl:gap-1 qsl:p-2 qsl:pr-8 qsl:[overflow-wrap:anywhere]", children: [
2585
+ /* @__PURE__ */ jsx17("span", { className: "qsl:font-medium", title: entry.file.name, children: entry.file.name }),
2586
+ /* @__PURE__ */ jsx17("small", { children: formatAttachmentSize(entry.file.size) }),
2587
+ entry.status === "reading" && /* @__PURE__ */ jsx17("small", { role: "status", children: allTexts.attachmentPreparing }),
2588
+ entry.status === "error" && /* @__PURE__ */ jsx17("small", { role: "alert", children: entry.error })
2373
2589
  ] }),
2374
- /* @__PURE__ */ jsx15(
2590
+ /* @__PURE__ */ jsx17(
2375
2591
  Button2,
2376
2592
  {
2377
2593
  "aria-label": fillText(allTexts.removeAttachment, { name: entry.file.name }),
@@ -2385,16 +2601,16 @@ function ChatInputToolbar({
2385
2601
  size: "icon-sm",
2386
2602
  title: fillText(allTexts.removeAttachment, { name: entry.file.name }),
2387
2603
  variant: "outline",
2388
- children: /* @__PURE__ */ jsx15(XIcon3, {})
2604
+ children: /* @__PURE__ */ jsx17(XIcon3, {})
2389
2605
  }
2390
2606
  )
2391
2607
  ] }, entry.id)) });
2392
- const errors = /* @__PURE__ */ jsxs8(Fragment3, { children: [
2393
- capabilityError && /* @__PURE__ */ jsx15("p", { className: errorClasses, role: "alert", children: capabilityError }),
2394
- attachmentError && /* @__PURE__ */ jsx15("p", { className: errorClasses, role: "alert", children: attachmentError }),
2395
- sendError && /* @__PURE__ */ jsx15("p", { className: errorClasses, role: "alert", children: sendError }),
2396
- failedSubmissions.map((failed) => /* @__PURE__ */ jsxs8("div", { className: cn(errorClasses, "qsl:flex qsl:flex-wrap qsl:items-center qsl:gap-2"), role: "status", children: [
2397
- /* @__PURE__ */ jsxs8("span", { children: [
2608
+ const errors = /* @__PURE__ */ jsxs9(Fragment4, { children: [
2609
+ capabilityError && /* @__PURE__ */ jsx17("p", { className: errorClasses, role: "alert", children: capabilityError }),
2610
+ attachmentError && /* @__PURE__ */ jsx17("p", { className: errorClasses, role: "alert", children: attachmentError }),
2611
+ sendError && /* @__PURE__ */ jsx17("p", { className: errorClasses, role: "alert", children: sendError }),
2612
+ failedSubmissions.map((failed) => /* @__PURE__ */ jsxs9("div", { className: cn(errorClasses, "qsl:flex qsl:flex-wrap qsl:items-center qsl:gap-2"), role: "status", children: [
2613
+ /* @__PURE__ */ jsxs9("span", { children: [
2398
2614
  allTexts.notSent,
2399
2615
  " ",
2400
2616
  failed.draft.slice(0, 100),
@@ -2402,7 +2618,7 @@ function ChatInputToolbar({
2402
2618
  failed.attachments.length > 0 ? ` ${fillText(allTexts.notSentAttachments, { count: failed.attachments.length })}` : "",
2403
2619
  " "
2404
2620
  ] }),
2405
- /* @__PURE__ */ jsx15(Button2, { disabled: disabled || sending, size: "sm", variant: "outline", onClick: () => {
2621
+ /* @__PURE__ */ jsx17(Button2, { disabled: disabled || sending, size: "sm", variant: "outline", onClick: () => {
2406
2622
  try {
2407
2623
  validateAttachmentSelection(attachmentsRef.current.map((entry) => entry.file), failed.attachments.map((entry) => entry.file), allTexts);
2408
2624
  } catch (error) {
@@ -2418,9 +2634,9 @@ function ChatInputToolbar({
2418
2634
  }, children: allTexts.insertUnsent })
2419
2635
  ] }, failed.id))
2420
2636
  ] });
2421
- const sendButtons = /* @__PURE__ */ jsxs8(Fragment3, { children: [
2422
- running2 && onStop && !hasContent && /* @__PURE__ */ jsx15(StopButton2, { className: "qsl:flex-none", label: allTexts.stop, onClick: onStop, size: "icon-sm" }),
2423
- (!running2 || !onStop || hasContent) && /* @__PURE__ */ jsx15(
2637
+ const sendButtons = /* @__PURE__ */ jsxs9(Fragment4, { children: [
2638
+ running2 && onStop && !hasContent && /* @__PURE__ */ jsx17(StopButton2, { className: "qsl:flex-none", label: allTexts.stop, onClick: onStop, size: "icon-sm" }),
2639
+ (!running2 || !onStop || hasContent) && /* @__PURE__ */ jsx17(
2424
2640
  Button2,
2425
2641
  {
2426
2642
  "aria-label": allTexts.send,
@@ -2431,53 +2647,62 @@ function ChatInputToolbar({
2431
2647
  },
2432
2648
  size: "icon-sm",
2433
2649
  title: running2 ? allTexts.sendIntoRun : allTexts.send,
2434
- children: /* @__PURE__ */ jsx15(SendIcon, { size: 14 })
2650
+ children: /* @__PURE__ */ jsx17(SendIcon2, { size: 14 })
2435
2651
  }
2436
2652
  )
2437
2653
  ] });
2438
- const detailToolbar = /* @__PURE__ */ jsxs8("div", { className: toolbarRowClasses, "data-input": "controls", children: [
2439
- /* @__PURE__ */ jsxs8("div", { className: cn(toolbarSideClasses, "qsl:in-data-[tone=overseer-details]:flex-none", inline || toolbar ? "qsl:flex-none" : "qsl:flex-auto"), children: [
2440
- attachmentsEnabled && /* @__PURE__ */ jsx15(
2441
- Button2,
2442
- {
2443
- "aria-label": allTexts.attachFiles,
2444
- disabled: disabled || sending,
2445
- onClick: () => fileInput.inputRef.current?.click(),
2446
- size: "icon-sm",
2447
- title: fillText(allTexts.attachFilesHint, { count: MAX_CHAT_ATTACHMENTS, size: maxAttachmentSizeLabel }),
2448
- variant: "ghost",
2449
- children: /* @__PURE__ */ jsx15(PaperclipIcon, {})
2450
- }
2451
- ),
2452
- preparing && /* @__PURE__ */ jsx15("span", { className: cn("qsl:text-xs qsl:text-muted-foreground", inline && "qsl:sr-only"), role: "status", children: allTexts.attachmentsPreparing }),
2453
- actions?.map((action, index) => action.active === void 0 ? /* @__PURE__ */ jsxs8(Button2, { disabled: action.disabled, onClick: action.onClick, size: "sm", title: action.title ?? action.label, variant: "ghost", children: [
2454
- action.icon,
2455
- action.label && /* @__PURE__ */ jsx15("span", { className: action.icon ? "qsl:in-data-[compact=true]:hidden" : void 0, children: action.label })
2456
- ] }, index) : /* @__PURE__ */ jsxs8(Toggle2, { disabled: action.disabled, onPressedChange: () => action.onClick(), pressed: action.active, size: "sm", title: action.title ?? action.label, children: [
2457
- action.icon,
2458
- action.label && /* @__PURE__ */ jsx15("span", { className: action.icon ? "qsl:in-data-[compact=true]:hidden" : void 0, children: action.label })
2459
- ] }, index)),
2654
+ const actionButtons = actions?.map((action, index) => action.active === void 0 ? /* @__PURE__ */ jsxs9(Button2, { "aria-label": action.label ?? action.title, disabled: action.disabled, onClick: action.onClick, size: "sm", title: action.title ?? action.label, variant: "ghost", children: [
2655
+ action.icon,
2656
+ action.label && /* @__PURE__ */ jsx17("span", { className: action.icon ? "qsl:in-data-[compact=true]:hidden" : void 0, children: action.label })
2657
+ ] }, index) : /* @__PURE__ */ jsxs9(Toggle2, { "aria-label": action.label ?? action.title, disabled: action.disabled, onPressedChange: () => action.onClick(), pressed: action.active, size: "sm", title: action.title ?? action.label, children: [
2658
+ action.icon,
2659
+ action.label && /* @__PURE__ */ jsx17("span", { className: action.icon ? "qsl:in-data-[compact=true]:hidden" : void 0, children: action.label })
2660
+ ] }, index));
2661
+ const attachButton = attachmentsEnabled && /* @__PURE__ */ jsx17(
2662
+ Button2,
2663
+ {
2664
+ "aria-label": allTexts.attachFiles,
2665
+ disabled: disabled || sending,
2666
+ onClick: () => fileInput.inputRef.current?.click(),
2667
+ size: "icon-sm",
2668
+ title: fillText(allTexts.attachFilesHint, { count: MAX_CHAT_ATTACHMENTS, size: maxAttachmentSizeLabel }),
2669
+ variant: "ghost",
2670
+ children: /* @__PURE__ */ jsx17(PaperclipIcon, {})
2671
+ }
2672
+ );
2673
+ const detailToolbar = /* @__PURE__ */ jsxs9("div", { className: toolbarRowClasses, "data-input": "controls", children: [
2674
+ /* @__PURE__ */ jsxs9("div", { className: cn(toolbarSideClasses, "qsl:in-data-[tone=overseer-details]:flex-none", inline || toolbar ? "qsl:flex-none" : "qsl:flex-auto"), children: [
2675
+ attachButton,
2676
+ preparing && /* @__PURE__ */ jsx17("span", { className: cn("qsl:text-small qsl:text-muted-foreground", inline && "qsl:sr-only"), role: "status", children: allTexts.attachmentsPreparing }),
2677
+ actionButtons,
2460
2678
  toolbarLeft
2461
2679
  ] }),
2462
- /* @__PURE__ */ jsxs8("div", { className: cn(toolbarSideClasses, "qsl:in-data-[tone=overseer-details]:flex-1", inline || toolbar ? "qsl:flex-none" : "qsl:flex-[0_1_auto]"), children: [
2680
+ /* @__PURE__ */ jsxs9("div", { className: cn(toolbarSideClasses, "qsl:in-data-[tone=overseer-details]:flex-1", inline || toolbar ? "qsl:flex-none" : "qsl:flex-[0_1_auto]"), children: [
2463
2681
  toolbarRight,
2464
2682
  !toolbar && sendButtons
2465
2683
  ] })
2466
2684
  ] });
2467
- const details = /* @__PURE__ */ jsxs8("div", { className: "qsl:min-w-0", "data-compact": compact, "data-quassel": "", children: [
2685
+ const details = /* @__PURE__ */ jsxs9("div", { className: "qsl:min-w-0", "data-compact": compact, "data-quassel": "", children: [
2468
2686
  attachmentPreviews,
2469
2687
  errors,
2470
2688
  detailToolbar
2471
2689
  ] });
2472
- const input = /* @__PURE__ */ jsx15(
2690
+ const input = /* @__PURE__ */ jsx17(
2473
2691
  "textarea",
2474
2692
  {
2475
2693
  "aria-controls": inputAriaControls,
2476
2694
  className: textareaClasses,
2477
2695
  "aria-label": allTexts.placeholder,
2478
- disabled: !toolbar && disabled,
2696
+ disabled: !toolbar && (disabled || microphoneBusy),
2697
+ hidden: replacingInput,
2479
2698
  readOnly: toolbar && disabled,
2480
2699
  onChange: (event) => changeDraft(event.target.value),
2700
+ onCompositionStart: () => {
2701
+ composing.current = true;
2702
+ },
2703
+ onCompositionEnd: () => {
2704
+ composing.current = false;
2705
+ },
2481
2706
  onKeyDown,
2482
2707
  placeholder: running2 ? allTexts.steeringPlaceholder : allTexts.placeholder,
2483
2708
  ref: textareaRef,
@@ -2485,7 +2710,7 @@ function ChatInputToolbar({
2485
2710
  value: draft
2486
2711
  }
2487
2712
  );
2488
- return /* @__PURE__ */ jsxs8(
2713
+ return /* @__PURE__ */ jsxs9(
2489
2714
  "div",
2490
2715
  {
2491
2716
  className: rootClasses,
@@ -2496,7 +2721,7 @@ function ChatInputToolbar({
2496
2721
  ref: rootRef,
2497
2722
  ...attachmentsEnabled ? fileInput.dropProps : {},
2498
2723
  children: [
2499
- attachmentsEnabled && /* @__PURE__ */ jsx15(
2724
+ attachmentsEnabled && /* @__PURE__ */ jsx17(
2500
2725
  "input",
2501
2726
  {
2502
2727
  "aria-label": allTexts.attachFiles,
@@ -2507,17 +2732,48 @@ function ChatInputToolbar({
2507
2732
  }
2508
2733
  ),
2509
2734
  !toolbar && attachmentPreviews,
2510
- inline ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
2735
+ pill ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
2511
2736
  errors,
2512
- /* @__PURE__ */ jsxs8("div", { className: "qsl:flex qsl:min-w-0 qsl:items-end", children: [
2737
+ preparing && /* @__PURE__ */ jsx17("span", { className: "qsl:sr-only", role: "status", children: allTexts.attachmentsPreparing }),
2738
+ /* @__PURE__ */ jsxs9("div", { className: "qsl:pill-row", "data-input": "pill", children: [
2739
+ (actionButtons?.length || toolbarLeft) && /* @__PURE__ */ jsxs9("div", { className: "qsl:pill-slot", "data-input": "left", children: [
2740
+ actionButtons,
2741
+ toolbarLeft
2742
+ ] }),
2743
+ input,
2744
+ replacingInput && /* @__PURE__ */ jsx17("div", { className: "qsl:pill-replacement", "data-input": "replacement", children: replaceInput }),
2745
+ (toolbarRight || attachmentsEnabled) && /* @__PURE__ */ jsxs9("div", { className: "qsl:pill-slot", "data-input": "right", children: [
2746
+ toolbarRight,
2747
+ attachButton
2748
+ ] }),
2749
+ /* @__PURE__ */ jsx17(
2750
+ ComposerPrimaryAction,
2751
+ {
2752
+ disabled,
2753
+ hasContent,
2754
+ microphone,
2755
+ onSend: () => {
2756
+ void send();
2757
+ },
2758
+ onStop,
2759
+ running: running2,
2760
+ sendDisabled: sendDisabled || attachmentBlocked || !!capabilityError || replacingInput,
2761
+ sending,
2762
+ texts: allTexts
2763
+ }
2764
+ )
2765
+ ] })
2766
+ ] }) : inline ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
2767
+ errors,
2768
+ /* @__PURE__ */ jsxs9("div", { className: "qsl:flex qsl:min-w-0 qsl:items-end", children: [
2513
2769
  input,
2514
2770
  detailToolbar
2515
2771
  ] })
2516
2772
  ] }) : input,
2517
- toolbar ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
2518
- /* @__PURE__ */ jsx15("div", { className: "qsl:flex qsl:flex-none qsl:items-center qsl:gap-1 qsl:p-1", children: sendButtons }),
2519
- detailsContainer && createPortal2(details, detailsContainer)
2520
- ] }) : !inline && /* @__PURE__ */ jsxs8(Fragment3, { children: [
2773
+ toolbar ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
2774
+ /* @__PURE__ */ jsx17("div", { className: "qsl:flex qsl:flex-none qsl:items-center qsl:gap-1 qsl:p-1", children: sendButtons }),
2775
+ detailsContainer && createPortal3(details, detailsContainer)
2776
+ ] }) : !inline && !pill && /* @__PURE__ */ jsxs9(Fragment4, { children: [
2521
2777
  errors,
2522
2778
  detailToolbar
2523
2779
  ] })
@@ -2528,7 +2784,7 @@ function ChatInputToolbar({
2528
2784
 
2529
2785
  // packages/quassel/src/DetailModeSwitch.tsx
2530
2786
  import { CircleDotIcon, EyeOffIcon, ListCollapseIcon, ListIcon, ShapesIcon, TagsIcon, TextAlignJustifyIcon } from "lucide-react";
2531
- import { jsx as jsx16 } from "react/jsx-runtime";
2787
+ import { jsx as jsx18 } from "react/jsx-runtime";
2532
2788
  var DETAIL_MODES = ["off", "current", "icons", "chips", "grouped", "compact", "full"];
2533
2789
  var TEXT_KEYS = {
2534
2790
  off: "detailModeOff",
@@ -2562,18 +2818,18 @@ function DetailModeSwitch({
2562
2818
  const next2 = modes[(current < 0 ? 0 : current + 1) % modes.length];
2563
2819
  const Icon = ICONS[mode];
2564
2820
  const title = `${allTexts.detailModeTitle}: ${detailModeLabel(mode, texts)}`;
2565
- return /* @__PURE__ */ jsx16(Button2, { "aria-label": title, className, onClick: () => onChange(next2), size: "icon-sm", title, variant: "ghost", children: /* @__PURE__ */ jsx16(Icon, {}) });
2821
+ return /* @__PURE__ */ jsx18(Button2, { "aria-label": title, className, onClick: () => onChange(next2), size: "icon-sm", title, variant: "ghost", children: /* @__PURE__ */ jsx18(Icon, {}) });
2566
2822
  }
2567
2823
 
2568
2824
  // packages/quassel/src/TranscriptModeSwitch.tsx
2569
2825
  import { MessagesSquareIcon, MessageSquareIcon } from "lucide-react";
2570
- import { jsx as jsx17 } from "react/jsx-runtime";
2826
+ import { jsx as jsx19 } from "react/jsx-runtime";
2571
2827
  function TranscriptModeSwitch({ mode, onChange, texts, className }) {
2572
2828
  const labels = { ...defaultTexts, ...texts };
2573
2829
  const { Button: Button2 } = useQuasselComponents();
2574
2830
  const latest = mode === "latest";
2575
2831
  const title = latest ? labels.showAllMessages : labels.showLatestExchange;
2576
- return /* @__PURE__ */ jsx17(
2832
+ return /* @__PURE__ */ jsx19(
2577
2833
  Button2,
2578
2834
  {
2579
2835
  "aria-label": title,
@@ -2583,19 +2839,19 @@ function TranscriptModeSwitch({ mode, onChange, texts, className }) {
2583
2839
  size: "icon-sm",
2584
2840
  title,
2585
2841
  variant: "ghost",
2586
- children: latest ? /* @__PURE__ */ jsx17(MessageSquareIcon, {}) : /* @__PURE__ */ jsx17(MessagesSquareIcon, {})
2842
+ children: latest ? /* @__PURE__ */ jsx19(MessageSquareIcon, {}) : /* @__PURE__ */ jsx19(MessagesSquareIcon, {})
2587
2843
  }
2588
2844
  );
2589
2845
  }
2590
2846
 
2591
2847
  // packages/quassel/src/TimestampSwitch.tsx
2592
2848
  import { ClockFadingIcon, ClockIcon } from "lucide-react";
2593
- import { jsx as jsx18 } from "react/jsx-runtime";
2849
+ import { jsx as jsx20 } from "react/jsx-runtime";
2594
2850
  function TimestampSwitch({ showTimestamps, onChange, texts, className }) {
2595
2851
  const labels = { ...defaultTexts, ...texts };
2596
2852
  const { Button: Button2 } = useQuasselComponents();
2597
2853
  const title = showTimestamps ? labels.hideTimestamps : labels.showTimestamps;
2598
- return /* @__PURE__ */ jsx18(
2854
+ return /* @__PURE__ */ jsx20(
2599
2855
  Button2,
2600
2856
  {
2601
2857
  "aria-label": title,
@@ -2605,7 +2861,7 @@ function TimestampSwitch({ showTimestamps, onChange, texts, className }) {
2605
2861
  size: "icon-sm",
2606
2862
  title,
2607
2863
  variant: "ghost",
2608
- children: showTimestamps ? /* @__PURE__ */ jsx18(ClockIcon, {}) : /* @__PURE__ */ jsx18(ClockFadingIcon, {})
2864
+ children: showTimestamps ? /* @__PURE__ */ jsx20(ClockIcon, {}) : /* @__PURE__ */ jsx20(ClockFadingIcon, {})
2609
2865
  }
2610
2866
  );
2611
2867
  }