@schlenkr/quassel 0.5.0 → 0.5.2

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
@@ -72,6 +72,8 @@ var englishTexts = {
72
72
  attachFiles: "Attach files",
73
73
  attachFilesHint: "Attach files (up to {count} files, {size} in total)",
74
74
  removeAttachment: "Remove {name}",
75
+ enlargeImage: "Enlarge {name}",
76
+ downloadAttachment: "Download",
75
77
  attachmentPreparing: "Preparing ...",
76
78
  attachmentsPreparing: "Preparing attachments ...",
77
79
  attachmentReadFailed: "The file could not be read. Remove it and add it again.",
@@ -139,6 +141,8 @@ var germanTexts = {
139
141
  attachFiles: "Dateien anh\xE4ngen",
140
142
  attachFilesHint: "Dateien anh\xE4ngen (bis zu {count} Dateien, insgesamt {size})",
141
143
  removeAttachment: "{name} entfernen",
144
+ enlargeImage: "{name} vergr\xF6\xDFern",
145
+ downloadAttachment: "Herunterladen",
142
146
  attachmentPreparing: "Wird vorbereitet ...",
143
147
  attachmentsPreparing: "Anh\xE4nge werden vorbereitet ...",
144
148
  attachmentReadFailed: "Die Datei konnte nicht gelesen werden. Den Anhang entfernen und erneut hinzuf\xFCgen.",
@@ -158,11 +162,12 @@ var defaultTexts = englishTexts;
158
162
 
159
163
  // packages/quassel/src/ui/cn.ts
160
164
  import { createCn } from "cn/config";
161
- var cn = createCn({ prefix: "qsl", extend: { theme: { text: ["meta", "trace", "label", "chat"] } } });
165
+ var cn = createCn({ prefix: "qsl", extend: { theme: { text: ["meta", "trace", "timestamp", "label", "chat"] } } });
162
166
 
163
167
  // packages/quassel/src/ChatMessages.tsx
164
168
  import { useCallback as useCallback2, useContext as useContext3, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
165
169
  import { createPortal } from "react-dom";
170
+ import { Dialog } from "@base-ui/react/dialog";
166
171
 
167
172
  // packages/quassel/src/chat-scroll.ts
168
173
  var atEnd = (viewport) => viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop <= 24;
@@ -933,10 +938,14 @@ import { CheckIcon as CheckIcon4, ChevronDownIcon, ChevronRightIcon, ChevronUpIc
933
938
  // packages/quassel/src/options.ts
934
939
  var cssLength = (value) => typeof value === "number" ? `${value}px` : value;
935
940
  var appearanceStyle = (appearance) => ({
941
+ "--qsl-font-size": cssLength(appearance?.fontSize),
936
942
  "--qsl-chat-font-size": cssLength(appearance?.fontSize),
937
943
  "--qsl-chat-line-height": appearance?.lineHeight,
938
944
  "--qsl-chat-message-gap": cssLength(appearance?.messageGap),
939
- "--qsl-chat-dense-message-gap": cssLength(appearance?.denseMessageGap)
945
+ "--qsl-chat-dense-message-gap": cssLength(appearance?.denseMessageGap),
946
+ "--qsl-tool-font-scale": appearance?.toolFontScale,
947
+ "--qsl-tool-group-gap": cssLength(appearance?.toolGroupGap),
948
+ "--qsl-tool-row-padding": cssLength(appearance?.toolRowPadding)
940
949
  });
941
950
 
942
951
  // packages/quassel/src/timestamps.ts
@@ -1159,10 +1168,10 @@ function announce(text) {
1159
1168
 
1160
1169
  // packages/quassel/src/ChatMessages.tsx
1161
1170
  import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
1162
- var stepClasses = "qsl:mt-[var(--qsl-chat-message-gap,16px)]";
1163
- var denseStepClasses = "qsl:mt-[var(--qsl-chat-dense-message-gap,8px)]";
1164
- var traceClasses = "qsl:flex qsl:w-full qsl:gap-2 qsl:pl-6 qsl:text-left qsl:text-trace qsl:leading-[1.375] qsl:text-muted-foreground";
1165
- var traceButtonClasses = "qsl:cursor-pointer qsl:rounded-md qsl:py-1 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";
1171
+ var stepClasses = "qsl:mt-[var(--qsl-chat-message-gap,10px)]";
1172
+ var denseStepClasses = "qsl:mt-[var(--qsl-chat-dense-message-gap,3px)]";
1173
+ 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";
1174
+ 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";
1166
1175
  var traceIconClasses = "qsl:mt-0.5 qsl:flex-none qsl:opacity-60";
1167
1176
  var traceErrorIconClasses = "qsl:mt-0.5 qsl:flex-none qsl:text-destructive qsl:opacity-100";
1168
1177
  var traceLineClasses = "qsl:min-w-0 qsl:self-baseline qsl:[overflow-wrap:anywhere]";
@@ -1181,7 +1190,7 @@ var chipClasses = cn(
1181
1190
  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";
1182
1191
  var collapseAtBottomFrom = 11;
1183
1192
  var maxAnnouncementsAtOnce = 2;
1184
- var bubbleBodyClasses = "qsl:relative qsl:max-w-[86%] qsl:px-3 qsl:py-2 qsl:leading-[var(--qsl-chat-line-height,1.625)] qsl:[overflow-wrap:anywhere] qsl:@max-[620px]/chat:max-w-full";
1193
+ var bubbleBodyClasses = "qsl:relative qsl:max-w-[86%] qsl:px-3 qsl:py-2 qsl:leading-[var(--qsl-chat-line-height,1.5)] qsl:[overflow-wrap:anywhere] qsl:@max-[620px]/chat:max-w-full";
1185
1194
  function isStep(message) {
1186
1195
  return !!message && (message.role === "thinking" || message.role === "tool");
1187
1196
  }
@@ -1337,13 +1346,22 @@ function ChatMessages({
1337
1346
  [selected, detailMode, running2, dock]
1338
1347
  );
1339
1348
  const openActions = dock ? messages.filter(isOpenAction) : [];
1340
- const withTime = (content, key, at, dense = false) => showTimestamps ? /* @__PURE__ */ jsxs6("div", { className: cn("qsl:flex qsl:items-baseline qsl:gap-2", dense ? denseStepClasses : stepClasses), children: [
1341
- /* @__PURE__ */ jsx13("time", { dateTime: messageDate(at)?.toISOString(), className: cn(
1342
- "qsl:flex-none qsl:text-meta qsl:tabular-nums qsl:text-muted-foreground qsl:opacity-85",
1343
- timestampOptions?.format && timestampOptions.format !== "time" ? "qsl:w-[clamp(60px,20%,120px)]" : "qsl:min-w-[5ch] qsl:whitespace-nowrap"
1344
- ), children: messageDate(at) ? timestampLabel(messageDate(at), timestampOptions, now) : "" }),
1345
- /* @__PURE__ */ jsx13("div", { className: "qsl:min-w-0 qsl:flex-1 qsl:[&>*:first-child]:mt-0", children: content })
1346
- ] }, `time-${key}`) : content;
1349
+ const withTime = (content, key, at, dense = false) => showTimestamps ? /* @__PURE__ */ jsxs6(
1350
+ "div",
1351
+ {
1352
+ className: cn("qsl:flex", timestampOptions?.position === "above" ? "qsl:flex-col qsl:items-stretch qsl:gap-1" : "qsl:items-baseline qsl:gap-2", dense ? denseStepClasses : stepClasses),
1353
+ "data-chat": "timestamped",
1354
+ "data-timestamp-position": timestampOptions?.position ?? "aside",
1355
+ children: [
1356
+ /* @__PURE__ */ jsx13("time", { dateTime: messageDate(at)?.toISOString(), className: cn(
1357
+ "qsl:flex-none qsl:text-timestamp qsl:leading-[1.375] qsl:tabular-nums qsl:text-muted-foreground qsl:opacity-85",
1358
+ timestampOptions?.position === "above" ? "qsl:self-start" : timestampOptions?.format && timestampOptions.format !== "time" ? "qsl:w-[clamp(60px,20%,120px)]" : "qsl:min-w-[5ch] qsl:whitespace-nowrap"
1359
+ ), children: messageDate(at) ? timestampLabel(messageDate(at), timestampOptions, now) : "" }),
1360
+ /* @__PURE__ */ jsx13("div", { className: "qsl:min-w-0 qsl:flex-1 qsl:[&>*:first-child]:mt-0", children: content })
1361
+ ]
1362
+ },
1363
+ `time-${key}`
1364
+ ) : content;
1347
1365
  const chipMode = detailMode === "current" || detailMode === "chips" || detailMode === "icons";
1348
1366
  const elapsedAfter = running2 && toolElapsedThreshold !== false ? toolElapsedThreshold : void 0;
1349
1367
  const groupMode = detailMode === "grouped";
@@ -1439,7 +1457,8 @@ function ChatMessages({
1439
1457
  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: {
1440
1458
  ...appearanceStyle(appearance),
1441
1459
  "--qsl-chat-content-max-width": typeof maxWidth === "number" ? `${maxWidth}px` : maxWidth,
1442
- "--qsl-chat-horizontal-padding": typeof horizontalPadding === "number" ? `${horizontalPadding}px` : horizontalPadding
1460
+ "--qsl-chat-horizontal-padding": typeof horizontalPadding === "number" ? `${horizontalPadding}px` : horizontalPadding,
1461
+ "--qsl-timestamp-font-size": timestampOptions?.fontScale === void 0 ? void 0 : `calc(var(--qsl-base) * ${timestampOptions.fontScale})`
1443
1462
  }, children: [
1444
1463
  /* @__PURE__ */ jsx13(
1445
1464
  "div",
@@ -1505,7 +1524,7 @@ function ChatMessages({
1505
1524
  }
1506
1525
  }, children: /* @__PURE__ */ jsx13(ChevronDownIcon, {}) }) }),
1507
1526
  dock && openActions.length > 0 && createPortal(
1508
- /* @__PURE__ */ jsx13("div", { className: "qsl:flex qsl:flex-col qsl:gap-[var(--qsl-chat-dense-message-gap,8px)] 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)) }),
1527
+ /* @__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)) }),
1509
1528
  dock
1510
1529
  )
1511
1530
  ] });
@@ -1604,7 +1623,7 @@ function StepGroup({
1604
1623
  ] })
1605
1624
  ] });
1606
1625
  if (!groupExpandable) {
1607
- return /* @__PURE__ */ jsx13("div", { className: stepClasses, "data-step": "group", children: /* @__PURE__ */ jsx13("div", { className: cn(traceClasses, "qsl:items-center qsl:py-1 qsl:pr-2 qsl:pl-1", active && "qsl:animate-fade-pulse qsl:motion-reduce:animate-none"), children: summary }) });
1626
+ return /* @__PURE__ */ jsx13("div", { className: stepClasses, "data-step": "group", children: /* @__PURE__ */ jsx13("div", { className: cn(traceClasses, "qsl:items-center qsl:pr-2 qsl:pl-1", active && "qsl:animate-fade-pulse qsl:motion-reduce:animate-none"), children: summary }) });
1608
1627
  }
1609
1628
  return /* @__PURE__ */ jsxs6("div", { className: stepClasses, "data-step": "group", children: [
1610
1629
  /* @__PURE__ */ jsxs6(
@@ -1621,7 +1640,7 @@ function StepGroup({
1621
1640
  ]
1622
1641
  }
1623
1642
  ),
1624
- open && /* @__PURE__ */ jsx13("div", { className: "qsl:mt-0.5 qsl:flex qsl:flex-col qsl:gap-0.5 qsl:pl-3", children: messages.map((message) => /* @__PURE__ */ jsx13(TraceLine, { className: "qsl:pl-3", elapsedAfter, expandable, message, texts, toolArgumentsText }, message.key)) }),
1643
+ open && /* @__PURE__ */ jsx13("div", { className: "qsl:mt-0.5 qsl:flex qsl:flex-col qsl:gap-[var(--qsl-tool-group-gap,1px)] qsl:pl-3", children: messages.map((message) => /* @__PURE__ */ jsx13(TraceLine, { className: "qsl:pl-3", elapsedAfter, expandable, message, texts, toolArgumentsText }, message.key)) }),
1625
1644
  open && messages.length >= collapseAtBottomFrom && /* @__PURE__ */ jsxs6(
1626
1645
  "button",
1627
1646
  {
@@ -1746,7 +1765,7 @@ function Bubble({
1746
1765
  if (message.role === "system") {
1747
1766
  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: [
1748
1767
  /* @__PURE__ */ jsx13(Markdown, { text: message.text }),
1749
- /* @__PURE__ */ jsx13(MessageAttachments, { label: texts.attachments, message })
1768
+ /* @__PURE__ */ jsx13(MessageAttachments, { message, texts })
1750
1769
  ] });
1751
1770
  }
1752
1771
  if (!message.text.trim() && !message.attachments?.length) return null;
@@ -1761,28 +1780,35 @@ function Bubble({
1761
1780
  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]"
1762
1781
  ), "data-chat": "sender", children: sender }),
1763
1782
  /* @__PURE__ */ jsx13(Markdown, { text: message.text, streaming: message.role === "assistant" && !message.closed }),
1764
- /* @__PURE__ */ jsx13(MessageAttachments, { label: texts.attachments, message }),
1783
+ /* @__PURE__ */ jsx13(MessageAttachments, { message, texts }),
1765
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 }),
1766
1785
  /* @__PURE__ */ jsx13(MessageActions, { message, options: messageActions, texts })
1767
1786
  ] });
1768
1787
  const cursor = message.role === "assistant" && message.textCursor ? JSON.stringify(message.textCursor) : void 0;
1769
- 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.625)] qsl:text-foreground"), "data-message": "answer", children: content });
1770
- return /* @__PURE__ */ jsx13(
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.5)] qsl:text-foreground"), "data-message": "answer", children: content });
1789
+ const fullBackground = message.role === "user" && bubbleOptions?.userBackgroundFullWidth !== false;
1790
+ 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(
1771
1792
  "div",
1772
1793
  {
1773
1794
  "data-chat-text-cursor": cursor,
1774
- className: cn(stepSpacing, "qsl:flex", side === "end" ? "qsl:justify-end" : "qsl:justify-start"),
1795
+ className: cn(stepSpacing, "qsl:relative qsl:flex", side === "end" ? "qsl:justify-end" : "qsl:justify-start"),
1775
1796
  "data-message": message.role === "user" && !message.bubble && variant === "default" ? "user" : "bubble",
1776
1797
  "data-side": side,
1777
- children: /* @__PURE__ */ jsx13(
1778
- "div",
1779
- {
1780
- className: cn(bubbleBodyClasses, "qsl:group", message.bubble ? "qsl:rounded-lg qsl:text-white qsl:[--qsl-scroll-cover:var(--qsl-primary)]" : "qsl:rounded-[var(--qsl-bubble-radius,10px_10px_4px_10px)] qsl:border qsl:border-border qsl:bg-secondary qsl:text-foreground qsl:[--qsl-scroll-cover:var(--qsl-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 qsl:in-data-[tone=material]:text-foreground"),
1781
- "data-tone": message.bubble ? "on-color" : void 0,
1782
- style: { background: message.bubble?.color, maxWidth: bubbleOptions?.maxWidth },
1783
- children: content
1784
- }
1785
- )
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
+ ]
1786
1812
  }
1787
1813
  );
1788
1814
  }
@@ -1805,16 +1831,56 @@ function ActionContent({
1805
1831
  }
1806
1832
  );
1807
1833
  }
1808
- function MessageAttachments({ message, label }) {
1834
+ function MessageAttachments({ message, texts }) {
1835
+ const [enlarged, setEnlarged] = useState4();
1809
1836
  if (!message.attachments?.length) return null;
1810
- return /* @__PURE__ */ jsx13("ul", { className: "qsl:mt-2.5 qsl:flex qsl:flex-wrap qsl:gap-2", "aria-label": label, children: message.attachments.map((attachment, index) => /* @__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-sm", children: [
1811
- attachment.mediaType.startsWith("image/") && /* @__PURE__ */ jsx13("a", { href: attachmentDownloadUrl(attachment.url), download: attachment.name, children: /* @__PURE__ */ jsx13("img", { className: "qsl:block qsl:h-[112px] qsl:w-full qsl:bg-secondary qsl:object-contain", src: attachment.url, alt: attachment.name, loading: "lazy" }) }),
1812
- 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 }),
1813
- /* @__PURE__ */ jsxs6("div", { className: "qsl:flex qsl:flex-col qsl:gap-1 qsl:p-2 qsl:[overflow-wrap:anywhere]", children: [
1814
- /* @__PURE__ */ jsx13("a", { className: "qsl:font-medium", href: attachmentDownloadUrl(attachment.url), download: attachment.name, children: attachment.name }),
1815
- /* @__PURE__ */ jsx13("small", { children: formatAttachmentSize(attachment.size) })
1816
- ] })
1817
- ] }, `${attachment.url}-${index}`)) });
1837
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1838
+ /* @__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) => {
1839
+ const isImage = attachment.mediaType.startsWith("image/");
1840
+ 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: [
1841
+ isImage && /* @__PURE__ */ jsx13(
1842
+ "button",
1843
+ {
1844
+ type: "button",
1845
+ className: "qsl:block qsl:w-full qsl:cursor-zoom-in qsl:bg-secondary",
1846
+ "aria-label": fillText(texts.enlargeImage ?? defaultTexts.enlargeImage, { name: attachment.name }),
1847
+ onClick: () => setEnlarged(attachment),
1848
+ "data-chat": "attachment-image",
1849
+ 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" })
1850
+ }
1851
+ ),
1852
+ 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 }),
1853
+ /* @__PURE__ */ jsxs6("div", { className: "qsl:flex qsl:flex-col qsl:gap-1 qsl:p-2 qsl:[overflow-wrap:anywhere]", children: [
1854
+ /* @__PURE__ */ jsx13("a", { className: "qsl:font-medium", href: attachmentDownloadUrl(attachment.url), download: attachment.name, children: attachment.name }),
1855
+ /* @__PURE__ */ jsx13("small", { children: formatAttachmentSize(attachment.size) })
1856
+ ] })
1857
+ ] }, `${attachment.url}-${index}`);
1858
+ }) }),
1859
+ /* @__PURE__ */ jsx13(ImageViewer, { attachment: enlarged, onClose: () => setEnlarged(void 0), texts })
1860
+ ] });
1861
+ }
1862
+ function ImageViewer({ attachment, onClose, texts }) {
1863
+ const portalContainer = useQuasselPortalContainer();
1864
+ return /* @__PURE__ */ jsx13(Dialog.Root, { open: attachment !== void 0, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs6(Dialog.Portal, { container: portalContainer, children: [
1865
+ /* @__PURE__ */ jsx13(Dialog.Backdrop, { className: "qsl:fixed qsl:inset-0 qsl:z-[120] qsl:bg-black/75", "data-quassel": "" }),
1866
+ /* @__PURE__ */ jsx13(
1867
+ Dialog.Popup,
1868
+ {
1869
+ className: "qsl:fixed qsl:top-1/2 qsl:left-1/2 qsl:z-[121] qsl:flex qsl:max-w-[94vw] qsl:-translate-x-1/2 qsl:-translate-y-1/2 qsl:flex-col qsl:items-center qsl:gap-2 qsl:outline-none",
1870
+ "aria-label": attachment?.name,
1871
+ "data-quassel": "",
1872
+ "data-chat": "image-viewer",
1873
+ children: attachment && /* @__PURE__ */ jsxs6(Fragment2, { children: [
1874
+ /* @__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 }),
1875
+ /* @__PURE__ */ jsxs6("div", { className: "qsl:flex qsl:items-center qsl:gap-4 qsl:text-sm qsl:text-white", children: [
1876
+ /* @__PURE__ */ jsx13("span", { className: "qsl:[overflow-wrap:anywhere]", children: attachment.name }),
1877
+ /* @__PURE__ */ jsx13("a", { className: "qsl:underline", href: attachmentDownloadUrl(attachment.url), download: attachment.name, children: texts.downloadAttachment ?? defaultTexts.downloadAttachment }),
1878
+ /* @__PURE__ */ jsx13(Dialog.Close, { className: "qsl:cursor-pointer qsl:underline", children: texts.close })
1879
+ ] })
1880
+ ] })
1881
+ }
1882
+ )
1883
+ ] }) });
1818
1884
  }
1819
1885
 
1820
1886
  // packages/quassel/src/ChatPanel.tsx
@@ -1884,7 +1950,7 @@ function ChatPanel({
1884
1950
  /* @__PURE__ */ jsx14("div", { children: /* @__PURE__ */ jsx14(
1885
1951
  "div",
1886
1952
  {
1887
- className: "qsl:-mx-[16px] qsl:max-h-[calc(var(--qsl-panel-height,100dvh)_/_2)] qsl:overflow-y-auto qsl:overscroll-contain qsl:px-[16px] qsl:pb-[var(--qsl-chat-message-gap,16px)] qsl:[-webkit-mask-image:linear-gradient(to_bottom,#000_calc(100%_-_var(--qsl-chat-message-gap,16px)),transparent)] qsl:[mask-image:linear-gradient(to_bottom,#000_calc(100%_-_var(--qsl-chat-message-gap,16px)),transparent)] qsl:empty:hidden",
1953
+ className: "qsl:-mx-[16px] qsl:max-h-[calc(var(--qsl-panel-height,100dvh)_/_2)] qsl:overflow-y-auto qsl:overscroll-contain qsl:px-[16px] qsl:pb-[var(--qsl-chat-message-gap,10px)] qsl:[-webkit-mask-image:linear-gradient(to_bottom,#000_calc(100%_-_var(--qsl-chat-message-gap,10px)),transparent)] qsl:[mask-image:linear-gradient(to_bottom,#000_calc(100%_-_var(--qsl-chat-message-gap,10px)),transparent)] qsl:empty:hidden",
1888
1954
  "data-chat": "actions",
1889
1955
  ref: setDock
1890
1956
  }