@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.
- package/MessageActions.d.ts +5 -2
- package/chat-scroll.d.ts +2 -1
- package/chat.css +26 -12
- package/index.d.ts +1 -1
- package/index.js +82 -57
- package/index.js.map +2 -2
- package/options.d.ts +1 -0
- package/package.json +1 -1
- package/texts.d.ts +1 -0
- package/useChatAction.d.ts +1 -0
package/MessageActions.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
1
2
|
import type { MessageActionsOptions } from "./options";
|
|
2
|
-
import type
|
|
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
|
|
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-
|
|
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
|
|
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: "
|
|
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
|
|
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.
|
|
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)
|
|
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:
|
|
1048
|
-
"qsl:
|
|
1049
|
-
"qsl:
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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__ */
|
|
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
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
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,
|