@schlenkr/quassel 0.5.3 → 0.5.5

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.
@@ -1,8 +1,11 @@
1
+ import { type RefObject } from "react";
1
2
  import type { MessageActionsOptions } from "./options";
2
- import type { ChatTexts } from "./texts";
3
+ import { type ChatTexts } from "./texts";
3
4
  import type { Message } from "./types";
4
- export declare function MessageActions({ message, options, texts }: {
5
+ export declare function MessageActions({ message, options, texts, contentRef, side }: {
5
6
  message: Message;
6
7
  options?: MessageActionsOptions;
7
8
  texts: ChatTexts;
9
+ contentRef?: RefObject<HTMLElement | null>;
10
+ side?: "start" | "end";
8
11
  }): import("react").JSX.Element;
package/chat-scroll.d.ts CHANGED
@@ -3,7 +3,8 @@ interface ScrollViewport {
3
3
  readonly scrollHeight: number;
4
4
  readonly clientHeight: number;
5
5
  }
6
- export declare function createChatScroll(onFollowChange?: (following: boolean) => void): {
6
+ export declare const defaultJumpToEndThreshold = 120;
7
+ export declare function createChatScroll(onFollowChange?: (following: boolean) => void, getThreshold?: () => number): {
7
8
  layout: (viewport: ScrollViewport, dragging?: boolean) => void;
8
9
  pause(viewport: ScrollViewport): void;
9
10
  scroll(viewport: ScrollViewport, dragging?: boolean): void;
package/chat.css CHANGED
@@ -79,9 +79,6 @@
79
79
  .qsl\:z-10 {
80
80
  z-index: 10;
81
81
  }
82
- .qsl\:z-\[1\] {
83
- z-index: 1;
84
- }
85
82
  .qsl\:z-\[5\] {
86
83
  z-index: 5;
87
84
  }
@@ -170,6 +167,10 @@
170
167
  width: calc(var(--qsl-spacing) * 4);
171
168
  height: calc(var(--qsl-spacing) * 4);
172
169
  }
170
+ .qsl\:size-5 {
171
+ width: calc(var(--qsl-spacing) * 5);
172
+ height: calc(var(--qsl-spacing) * 5);
173
+ }
173
174
  .qsl\:size-6 {
174
175
  width: calc(var(--qsl-spacing) * 6);
175
176
  height: calc(var(--qsl-spacing) * 6);
@@ -186,6 +187,9 @@
186
187
  width: calc(var(--qsl-spacing) * 9);
187
188
  height: calc(var(--qsl-spacing) * 9);
188
189
  }
190
+ .qsl\:h-5 {
191
+ height: calc(var(--qsl-spacing) * 5);
192
+ }
189
193
  .qsl\:h-6 {
190
194
  height: calc(var(--qsl-spacing) * 6);
191
195
  }
@@ -395,6 +399,9 @@
395
399
  .qsl\:gap-0 {
396
400
  gap: 0px;
397
401
  }
402
+ .qsl\:gap-0\.5 {
403
+ gap: calc(var(--qsl-spacing) * 0.5);
404
+ }
398
405
  .qsl\:gap-1 {
399
406
  gap: var(--qsl-spacing);
400
407
  }
@@ -890,13 +897,6 @@
890
897
  .qsl\:opacity-100 {
891
898
  opacity: 100%;
892
899
  }
893
- .qsl\:shadow-\[0_2px_6px_color-mix\(in_srgb\,var\(--qsl-foreground\)_15\%\,transparent\)\] {
894
- --tw-shadow: 0 2px 6px var(--tw-shadow-color, var(--qsl-foreground));
895
- @supports (color: color-mix(in lab, red, red)) {
896
- --tw-shadow: 0 2px 6px var(--tw-shadow-color, color-mix(in srgb,var(--qsl-foreground) 15%,transparent));
897
- }
898
- box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
899
- }
900
900
  .qsl\:shadow-bar {
901
901
  --tw-shadow: var(--qsl-bar-shadow);
902
902
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -951,6 +951,11 @@
951
951
  transition-timing-function: var(--tw-ease, var(--qsl-default-transition-timing-function));
952
952
  transition-duration: var(--tw-duration, var(--qsl-default-transition-duration));
953
953
  }
954
+ .qsl\:transition-opacity {
955
+ transition-property: opacity;
956
+ transition-timing-function: var(--tw-ease, var(--qsl-default-transition-timing-function));
957
+ transition-duration: var(--tw-duration, var(--qsl-default-transition-duration));
958
+ }
954
959
  .qsl\:transition-transform {
955
960
  transition-property: transform, translate, scale, rotate;
956
961
  transition-timing-function: var(--tw-ease, var(--qsl-default-transition-timing-function));
@@ -1000,10 +1005,13 @@
1000
1005
  max-width: var(--qsl-chat-content-max-width,none);
1001
1006
  }
1002
1007
  @media (hover: hover) {
1003
- .qsl\:group-hover\:pointer-events-auto:is(:where(.qsl\:group):hover *) {
1008
+ .qsl\:group-hover\/message\:pointer-events-auto:is(:where(.qsl\:group\/message):hover *) {
1004
1009
  pointer-events: auto;
1005
1010
  }
1006
- .qsl\:group-hover\:opacity-100:is(:where(.qsl\:group):hover *) {
1011
+ .qsl\:group-hover\/message\:opacity-70:is(:where(.qsl\:group\/message):hover *) {
1012
+ opacity: 70%;
1013
+ }
1014
+ .qsl\:group-hover\/message\:opacity-100:is(:where(.qsl\:group\/message):hover *) {
1007
1015
  opacity: 100%;
1008
1016
  }
1009
1017
  }
@@ -1031,6 +1039,9 @@
1031
1039
  .qsl\:focus-within\:border-primary:focus-within {
1032
1040
  border-color: var(--qsl-primary);
1033
1041
  }
1042
+ .qsl\:focus-within\:opacity-70:focus-within {
1043
+ opacity: 70%;
1044
+ }
1034
1045
  .qsl\:focus-within\:opacity-100:focus-within {
1035
1046
  opacity: 100%;
1036
1047
  }
@@ -1604,6 +1615,9 @@
1604
1615
  .qsl\:\[\@media\(hover\:none\)\]\:pointer-events-auto {
1605
1616
  pointer-events: auto;
1606
1617
  }
1618
+ .qsl\:\[\@media\(hover\:none\)\]\:opacity-70 {
1619
+ opacity: 70%;
1620
+ }
1607
1621
  .qsl\:\[\@media\(hover\:none\)\]\:opacity-100 {
1608
1622
  opacity: 100%;
1609
1623
  }
package/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export { PendingActionCard } from "./PendingActionCard";
14
14
  export { StepPopover } from "./StepPopover";
15
15
  export { WorkingScenes, type WorkingScenesProps } from "./WorkingScenes";
16
16
  export { announce } from "./announce";
17
- export { useChatAction, copyChatText } from "./useChatAction";
17
+ export { useChatAction, copyChatText, copyChatHtml } from "./useChatAction";
18
18
  export { createChatScroll } from "./chat-scroll";
19
19
  export { ChatSendContext, createChatSendScope, type ChatSendScope } from "./ChatSendContext";
20
20
  export { messageDate, timestampDay, timestampLabel } from "./timestamps";
package/index.js CHANGED
@@ -57,7 +57,8 @@ var englishTexts = {
57
57
  showLatestExchange: "Show latest reply between inputs",
58
58
  showTimestamps: "Show timestamps",
59
59
  hideTimestamps: "Hide timestamps",
60
- copyMessage: "Copy message",
60
+ copyMessage: "Copy as Markdown",
61
+ copyFormatted: "Copy with formatting",
61
62
  editMessage: "Edit message",
62
63
  retryMessage: "Request answer again",
63
64
  copyCode: "Copy code",
@@ -126,7 +127,8 @@ var germanTexts = {
126
127
  showLatestExchange: "Letzte Antwort zwischen den Eingaben anzeigen",
127
128
  showTimestamps: "Zeitstempel anzeigen",
128
129
  hideTimestamps: "Zeitstempel ausblenden",
129
- copyMessage: "Nachricht kopieren",
130
+ copyMessage: "Als Markdown kopieren",
131
+ copyFormatted: "Mit Formatierung kopieren",
130
132
  editMessage: "Nachricht bearbeiten",
131
133
  retryMessage: "Antwort erneut anfordern",
132
134
  copyCode: "Code kopieren",
@@ -170,9 +172,10 @@ import { createPortal } from "react-dom";
170
172
  import { Dialog } from "@base-ui/react/dialog";
171
173
 
172
174
  // packages/quassel/src/chat-scroll.ts
173
- var atEnd = (viewport) => viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop <= 24;
175
+ var defaultJumpToEndThreshold = 120;
174
176
  function createChatScroll(onFollowChange = () => {
175
- }) {
177
+ }, getThreshold = () => defaultJumpToEndThreshold) {
178
+ const atEnd = (viewport) => viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop <= getThreshold();
176
179
  let following = true;
177
180
  let lastTop = 0;
178
181
  let lastHeight = 0;
@@ -191,18 +194,18 @@ function createChatScroll(onFollowChange = () => {
191
194
  const layout = (viewport, dragging = false) => {
192
195
  if (viewport.clientHeight <= 0) return;
193
196
  if (movedUp(viewport, dragging) && !atEnd(viewport)) follow(false);
197
+ if (atEnd(viewport)) follow(true);
194
198
  if (following) viewport.scrollTop = Math.max(0, viewport.scrollHeight - viewport.clientHeight);
195
- else if (viewport.scrollHeight <= viewport.clientHeight) follow(true);
196
199
  remember(viewport);
197
200
  };
198
201
  return {
199
202
  layout,
200
203
  pause(viewport) {
201
- if (viewport.scrollHeight > viewport.clientHeight) follow(false);
204
+ if (viewport.clientHeight > 0) follow(atEnd(viewport));
202
205
  },
203
206
  scroll(viewport, dragging = false) {
204
207
  if (viewport.clientHeight <= 0) return;
205
- if (atEnd(viewport) && viewport.scrollTop > lastTop) follow(true);
208
+ if (atEnd(viewport)) follow(true);
206
209
  else if (movedUp(viewport, dragging) && !atEnd(viewport)) follow(false);
207
210
  remember(viewport);
208
211
  },
@@ -455,6 +458,15 @@ async function copyChatText(text, unavailable) {
455
458
  if (!navigator.clipboard) throw new Error(unavailable);
456
459
  await navigator.clipboard.writeText(text);
457
460
  }
461
+ async function copyChatHtml(element, text, unavailable) {
462
+ if (!navigator.clipboard) throw new Error(unavailable);
463
+ if (typeof ClipboardItem === "undefined" || !navigator.clipboard.write) return navigator.clipboard.writeText(text);
464
+ const clone = element.cloneNode(true);
465
+ clone.querySelectorAll("button").forEach((button) => button.remove());
466
+ clone.querySelectorAll("[class]").forEach((node) => node.removeAttribute("class"));
467
+ const blob = (content, type) => new Blob([content], { type });
468
+ await navigator.clipboard.write([new ClipboardItem({ "text/html": blob(clone.innerHTML, "text/html"), "text/plain": blob(markdownPlainText(text), "text/plain") })]);
469
+ }
458
470
 
459
471
  // packages/quassel/src/Markdown.tsx
460
472
  import { jsx as jsx8, jsxs } from "react/jsx-runtime";
@@ -1040,37 +1052,46 @@ function useElapsed(start) {
1040
1052
  }
1041
1053
 
1042
1054
  // packages/quassel/src/MessageActions.tsx
1043
- import { CheckIcon as CheckIcon3, CopyIcon as CopyIcon2, PencilIcon, RotateCcwIcon } from "lucide-react";
1055
+ import { CheckIcon as CheckIcon3, ClipboardTypeIcon, CopyIcon as CopyIcon2, PencilIcon, RotateCcwIcon } from "lucide-react";
1044
1056
  import { useState as useState3 } from "react";
1045
1057
  import { Fragment, jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
1046
1058
  var actionClasses = cn(
1047
- "qsl:absolute qsl:top-1 qsl:right-1 qsl:z-[1] qsl:flex qsl:flex-wrap qsl:rounded-sm qsl:bg-secondary qsl:opacity-0",
1048
- "qsl:shadow-[0_2px_6px_color-mix(in_srgb,var(--qsl-foreground)_15%,transparent)]",
1049
- "qsl:pointer-events-none qsl:group-hover:pointer-events-auto qsl:group-hover:opacity-100 qsl:focus-within:pointer-events-auto qsl:focus-within:opacity-100",
1050
- "qsl:[@media(hover:none)]:pointer-events-auto qsl:[@media(hover:none)]:opacity-100"
1059
+ "qsl:mt-0.5 qsl:flex qsl:h-5 qsl:items-center qsl:gap-0.5 qsl:text-muted-foreground qsl:opacity-0 qsl:transition-opacity",
1060
+ "qsl:pointer-events-none qsl:group-hover/message:pointer-events-auto qsl:focus-within:pointer-events-auto",
1061
+ "qsl:[@media(hover:none)]:pointer-events-auto"
1051
1062
  );
1052
- function MessageActions({ message, options, texts }) {
1063
+ var visibleClasses = "qsl:group-hover/message:opacity-100 qsl:focus-within:opacity-100 qsl:[@media(hover:none)]:opacity-100";
1064
+ var lightClasses = "qsl:group-hover/message:opacity-70 qsl:focus-within:opacity-70 qsl:[@media(hover:none)]:opacity-70";
1065
+ function MessageActions({ message, options, texts, contentRef, side = message.role === "user" ? "end" : "start" }) {
1053
1066
  const action = useChatAction();
1054
1067
  const { Button: Button2 } = useQuasselComponents();
1055
1068
  const [lastAction, setLastAction] = useState3();
1056
- const copy = typeof options?.copy === "function" ? options.copy(message) : options?.copy ?? message.role === "user";
1069
+ const enabled = (option, fallback) => typeof option === "function" ? option(message) : option ?? fallback;
1070
+ const copy = enabled(options?.copy, message.role === "user" || message.role === "assistant") && !!message.text.trim();
1071
+ const copyFormatted = copy && !!contentRef && enabled(options?.copyFormatted, message.role === "assistant");
1057
1072
  const actions = [
1058
- ...copy && message.text.trim() ? [{ id: "copy", label: texts.copyMessage, icon: /* @__PURE__ */ jsx12(CopyIcon2, {}), onClick: () => copyChatText(message.text, texts.clipboardUnavailable) }] : [],
1073
+ ...copy ? [{ id: "copy", label: texts.copyMessage, icon: /* @__PURE__ */ jsx12(CopyIcon2, {}), onClick: () => copyChatText(message.text, texts.clipboardUnavailable) }] : [],
1074
+ ...copyFormatted ? [{
1075
+ id: "copy-formatted",
1076
+ label: texts.copyFormatted ?? defaultTexts.copyFormatted,
1077
+ icon: /* @__PURE__ */ jsx12(ClipboardTypeIcon, {}),
1078
+ onClick: () => contentRef.current ? copyChatHtml(contentRef.current, message.text, texts.clipboardUnavailable) : copyChatText(message.text, texts.clipboardUnavailable)
1079
+ }] : [],
1059
1080
  ...options?.edit && message.role === "user" ? [{ id: "edit", label: texts.editMessage, icon: /* @__PURE__ */ jsx12(PencilIcon, {}), onClick: options.edit }] : [],
1060
1081
  ...options?.retry && message.role === "assistant" ? [{ id: "retry", label: texts.retryMessage, icon: /* @__PURE__ */ jsx12(RotateCcwIcon, {}), onClick: options.retry }] : [],
1061
1082
  ...options?.custom?.(message) ?? []
1062
1083
  ];
1063
1084
  return /* @__PURE__ */ jsxs5(Fragment, { children: [
1064
- actions.length > 0 && /* @__PURE__ */ jsx12("div", { className: actionClasses, children: actions.map((item) => {
1065
- const copied = item.id === "copy" && lastAction === item.id && action.status === "done";
1085
+ actions.length > 0 && /* @__PURE__ */ jsx12("div", { className: cn(actionClasses, message.role === "user" ? visibleClasses : lightClasses, side === "end" && "qsl:justify-end"), "data-chat": "message-actions", children: actions.map((item) => {
1086
+ const copied = ["copy", "copy-formatted"].includes(item.id) && lastAction === item.id && action.status === "done";
1066
1087
  const label = copied ? texts.copied : item.label;
1067
1088
  return /* @__PURE__ */ jsx12(
1068
1089
  Button2,
1069
1090
  {
1070
1091
  "aria-label": label,
1071
- className: "qsl:text-inherit",
1092
+ className: cn("qsl:text-inherit", item.icon && "qsl:size-5"),
1072
1093
  disabled: item.disabled || action.status === "pending",
1073
- size: item.icon ? "icon-sm" : "sm",
1094
+ size: item.icon ? "icon-xs" : "xs",
1074
1095
  title: label,
1075
1096
  variant: "ghost",
1076
1097
  onClick: () => {
@@ -1223,7 +1244,7 @@ function ChatMessages({
1223
1244
  scrollerRef,
1224
1245
  maxWidth,
1225
1246
  horizontalPadding,
1226
- jumpToEndThreshold = 120,
1247
+ jumpToEndThreshold = defaultJumpToEndThreshold,
1227
1248
  appearance,
1228
1249
  timestampOptions,
1229
1250
  codeBlockOptions,
@@ -1253,7 +1274,11 @@ function ChatMessages({
1253
1274
  const scrollbarDragging = useRef3(false);
1254
1275
  const touchY = useRef3(void 0);
1255
1276
  const [showJumpToEnd, setShowJumpToEnd] = useState4(false);
1256
- const [scroll] = useState4(() => createChatScroll());
1277
+ const endThreshold = useRef3(jumpToEndThreshold);
1278
+ useLayoutEffect(() => {
1279
+ endThreshold.current = jumpToEndThreshold;
1280
+ }, [jumpToEndThreshold]);
1281
+ const [scroll] = useState4(() => createChatScroll((following) => setShowJumpToEnd(!following), () => endThreshold.current));
1257
1282
  const sendScope = useContext3(ChatSendContext);
1258
1283
  const dock = useContext3(ChatActionDock);
1259
1284
  const [now, setNow] = useState4(() => Date.now());
@@ -1275,15 +1300,11 @@ function ChatMessages({
1275
1300
  }
1276
1301
  due.filter((message) => selected.includes(message)).map((message) => announce2 ? announce2({ kind: message.role === "action" ? "action" : "reply", message }) : markdownPlainText(message.text)).forEach((text) => text && announce(text));
1277
1302
  }, [messages, selected, running2, announce2]);
1278
- const updateJumpVisibility = useCallback2((viewport) => {
1279
- if (viewport.clientHeight > 0) setShowJumpToEnd(viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop > jumpToEndThreshold);
1280
- }, [jumpToEndThreshold]);
1281
1303
  useLayoutEffect(() => sendScope?.subscribe(() => {
1282
1304
  const viewport = scrollArea.current;
1283
1305
  if (!viewport) return;
1284
1306
  scroll.jump(viewport);
1285
- updateJumpVisibility(viewport);
1286
- }), [sendScope, scroll, updateJumpVisibility]);
1307
+ }), [sendScope, scroll]);
1287
1308
  const pauseUp = (viewport, target) => {
1288
1309
  let element = target instanceof Element ? target : null;
1289
1310
  while (element && element !== viewport) {
@@ -1296,9 +1317,8 @@ function ChatMessages({
1296
1317
  const area = scrollArea.current;
1297
1318
  if (area) {
1298
1319
  scroll.scroll(area, scrollbarDragging.current);
1299
- updateJumpVisibility(area);
1300
1320
  }
1301
- }, [scroll, updateJumpVisibility]);
1321
+ }, [scroll]);
1302
1322
  useEffect3(() => {
1303
1323
  const finishDrag = () => {
1304
1324
  if (scrollbarDragging.current && scrollArea.current) scroll.scroll(scrollArea.current, true);
@@ -1316,7 +1336,6 @@ function ChatMessages({
1316
1336
  if (!area) return;
1317
1337
  const align = () => {
1318
1338
  scroll.layout(area, scrollbarDragging.current);
1319
- updateJumpVisibility(area);
1320
1339
  };
1321
1340
  align();
1322
1341
  const observer = new ResizeObserver(align);
@@ -1520,7 +1539,6 @@ function ChatMessages({
1520
1539
  showJumpToEnd && /* @__PURE__ */ jsx13("div", { className: "qsl:pointer-events-none qsl:absolute qsl:inset-x-0 qsl:bottom-[calc(var(--qsl-composer-height,0px)_+_12px)] qsl:z-10 qsl:flex qsl:justify-center", children: /* @__PURE__ */ jsx13(Button2, { "aria-label": allTexts.jumpToEnd, className: "qsl:pointer-events-auto qsl:rounded-full qsl:border qsl:border-primary-foreground/20 qsl:shadow-md", size: "icon", title: allTexts.jumpToEnd, variant: "default", onClick: () => {
1521
1540
  if (scrollArea.current) {
1522
1541
  scroll.jump(scrollArea.current);
1523
- updateJumpVisibility(scrollArea.current);
1524
1542
  }
1525
1543
  }, children: /* @__PURE__ */ jsx13(ChevronDownIcon, {}) }) }),
1526
1544
  dock && openActions.length > 0 && createPortal(
@@ -1733,6 +1751,7 @@ function Bubble({
1733
1751
  messageActions
1734
1752
  }) {
1735
1753
  const stepSpacing = dense ? denseStepClasses : stepClasses;
1754
+ const textRef = useRef3(null);
1736
1755
  if (message.role === "thinking" || message.role === "tool" && message.tool) {
1737
1756
  if (detailMode === "compact") {
1738
1757
  return /* @__PURE__ */ jsx13(TraceLine, { className: stepSpacing, elapsedAfter, expandable, message, texts, toolArgumentsText });
@@ -1779,38 +1798,43 @@ function Bubble({
1779
1798
  "qsl:mb-[5px] qsl:table qsl:text-label qsl:leading-[1.45] qsl:font-semibold",
1780
1799
  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]"
1781
1800
  ), "data-chat": "sender", children: sender }),
1782
- /* @__PURE__ */ jsx13(Markdown, { text: message.text, streaming: message.role === "assistant" && !message.closed }),
1801
+ /* @__PURE__ */ jsx13("div", { "data-chat": "message-text", ref: textRef, children: /* @__PURE__ */ jsx13(Markdown, { text: message.text, streaming: message.role === "assistant" && !message.closed }) }),
1783
1802
  /* @__PURE__ */ jsx13(MessageAttachments, { message, texts }),
1784
- 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 }),
1785
- /* @__PURE__ */ jsx13(MessageActions, { message, options: messageActions, texts })
1803
+ 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 })
1786
1804
  ] });
1787
1805
  const cursor = message.role === "assistant" && message.textCursor ? JSON.stringify(message.textCursor) : void 0;
1788
- if (!useBubble) return /* @__PURE__ */ jsx13("div", { "data-chat-text-cursor": cursor, className: cn(stepSpacing, "qsl:group qsl:relative qsl:w-full qsl:leading-[var(--qsl-chat-line-height,1.65)] qsl:text-foreground"), "data-message": "answer", children: content });
1806
+ 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: [
1807
+ content,
1808
+ /* @__PURE__ */ jsx13(MessageActions, { contentRef: textRef, message, options: messageActions, side, texts })
1809
+ ] });
1789
1810
  const fullBackground = message.role === "user" && bubbleOptions?.userBackgroundFullWidth !== false;
1790
1811
  const surfaceClasses = message.bubble ? "qsl:rounded-lg" : "qsl:rounded-[var(--qsl-bubble-radius,10px_10px_4px_10px)] qsl:border qsl:border-border qsl:bg-secondary qsl:in-data-[tone=material]:rounded-[var(--qsl-bubble-radius,9px)] qsl:in-data-[tone=material]:border-glass-edge/20 qsl:in-data-[tone=material]:bg-primary/15";
1791
- return /* @__PURE__ */ jsxs6(
1792
- "div",
1793
- {
1794
- "data-chat-text-cursor": cursor,
1795
- className: cn(stepSpacing, "qsl:relative qsl:flex", side === "end" ? "qsl:justify-end" : "qsl:justify-start"),
1796
- "data-message": message.role === "user" && !message.bubble && variant === "default" ? "user" : "bubble",
1797
- "data-side": side,
1798
- "data-background-width": fullBackground ? "full" : "content",
1799
- children: [
1800
- fullBackground && /* @__PURE__ */ jsx13("div", { "aria-hidden": "true", className: cn("qsl:pointer-events-none qsl:absolute qsl:inset-0", surfaceClasses), "data-chat": "bubble-background", style: { background: message.bubble?.color } }),
1801
- /* @__PURE__ */ jsx13(
1802
- "div",
1803
- {
1804
- className: cn(bubbleBodyClasses, "qsl:group", !fullBackground && surfaceClasses, fullBackground && !message.bubble && "qsl:border qsl:border-transparent", message.bubble ? "qsl:text-white qsl:[--qsl-scroll-cover:var(--qsl-primary)]" : "qsl:text-foreground qsl:[--qsl-scroll-cover:var(--qsl-secondary)]"),
1805
- "data-chat": "bubble-content",
1806
- "data-tone": message.bubble ? "on-color" : void 0,
1807
- style: { background: fullBackground ? void 0 : message.bubble?.color, maxWidth: bubbleOptions?.maxWidth },
1808
- children: content
1809
- }
1810
- )
1811
- ]
1812
- }
1813
- );
1812
+ return /* @__PURE__ */ jsxs6("div", { className: cn(stepSpacing, "qsl:group/message"), children: [
1813
+ /* @__PURE__ */ jsxs6(
1814
+ "div",
1815
+ {
1816
+ "data-chat-text-cursor": cursor,
1817
+ className: cn("qsl:relative qsl:flex", side === "end" ? "qsl:justify-end" : "qsl:justify-start"),
1818
+ "data-message": message.role === "user" && !message.bubble && variant === "default" ? "user" : "bubble",
1819
+ "data-side": side,
1820
+ "data-background-width": fullBackground ? "full" : "content",
1821
+ children: [
1822
+ fullBackground && /* @__PURE__ */ jsx13("div", { "aria-hidden": "true", className: cn("qsl:pointer-events-none qsl:absolute qsl:inset-0", surfaceClasses), "data-chat": "bubble-background", style: { background: message.bubble?.color } }),
1823
+ /* @__PURE__ */ jsx13(
1824
+ "div",
1825
+ {
1826
+ className: cn(bubbleBodyClasses, !fullBackground && surfaceClasses, fullBackground && !message.bubble && "qsl:border qsl:border-transparent", message.bubble ? "qsl:text-white qsl:[--qsl-scroll-cover:var(--qsl-primary)]" : "qsl:text-foreground qsl:[--qsl-scroll-cover:var(--qsl-secondary)]"),
1827
+ "data-chat": "bubble-content",
1828
+ "data-tone": message.bubble ? "on-color" : void 0,
1829
+ style: { background: fullBackground ? void 0 : message.bubble?.color, maxWidth: bubbleOptions?.maxWidth },
1830
+ children: content
1831
+ }
1832
+ )
1833
+ ]
1834
+ }
1835
+ ),
1836
+ /* @__PURE__ */ jsx13(MessageActions, { contentRef: textRef, message, options: messageActions, side, texts })
1837
+ ] });
1814
1838
  }
1815
1839
  function ActionContent({
1816
1840
  action,
@@ -2604,6 +2628,7 @@ export {
2604
2628
  attachmentDownloadUrl,
2605
2629
  attachmentMediaType,
2606
2630
  compactToolLine,
2631
+ copyChatHtml,
2607
2632
  copyChatText,
2608
2633
  createChatScroll,
2609
2634
  createChatSendScope,