quassel 0.1.1 → 0.2.1
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/ChatInputToolbar.d.ts +35 -6
- package/ChatMessages.d.ts +19 -15
- package/ChatPanel.d.ts +4 -2
- package/ChatSendContext.d.ts +7 -12
- package/DetailModeSwitch.d.ts +1 -1
- package/Markdown.d.ts +4 -11
- package/MessageActions.d.ts +1 -10
- package/PendingActionCard.d.ts +13 -0
- package/QuasselProvider.d.ts +76 -0
- package/README.md +39 -38
- package/StepPopover.d.ts +1 -1
- package/TimestampSwitch.d.ts +2 -2
- package/WorkingScenes.d.ts +3 -1
- package/attachments.d.ts +24 -0
- package/chat-attachments.d.ts +6 -0
- package/chat-scroll.d.ts +12 -0
- package/chat.css +2051 -0
- package/chunk-IGCFQJZB.js +183 -0
- package/chunk-IGCFQJZB.js.map +7 -0
- package/events.d.ts +74 -17
- package/events.js +11 -1
- package/index.d.ts +17 -10
- package/index.js +1615 -988
- package/index.js.map +4 -4
- package/options.d.ts +1 -1
- package/package.json +12 -10
- package/texts.d.ts +38 -11
- package/timestamps.d.ts +6 -4
- package/types.d.ts +6 -6
- package/ui/button.d.ts +8 -0
- package/ui/card.d.ts +5 -0
- package/ui/cn.d.ts +2 -0
- package/ui/popover.d.ts +4 -0
- package/ui/spinner.d.ts +2 -0
- package/ui/stop-button.d.ts +10 -0
- package/ui/toggle.d.ts +8 -0
- package/ui/useFileInput.d.ts +20 -0
- package/useChatAction.d.ts +6 -0
- package/QuestionCard.d.ts +0 -12
- package/chunk-DOLTANTM.js +0 -84
- package/chunk-DOLTANTM.js.map +0 -7
- package/css/chat.css +0 -927
- package/css/foundation/base.css +0 -24
- package/css/foundation/index.css +0 -3
- package/css/foundation/panel.css +0 -111
- package/css/foundation/tokens.css +0 -68
- package/css/foundation/wash.css +0 -26
- package/css/question.css +0 -149
- package/icons.d.ts +0 -15
- package/server/http.d.ts +0 -17
- package/server/index.d.ts +0 -6
- package/server/index.js +0 -478
- package/server/index.js.map +0 -7
- package/server/manager.d.ts +0 -12
- package/server/openai.d.ts +0 -16
- package/server/session.d.ts +0 -27
- package/server/store.d.ts +0 -18
- package/server/types.d.ts +0 -62
- package/useChat.d.ts +0 -14
package/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
+
MAX_CHAT_ATTACHMENTS,
|
|
3
|
+
MAX_CHAT_ATTACHMENT_BYTES,
|
|
2
4
|
applyEvent,
|
|
5
|
+
chatAttachmentMediaType,
|
|
3
6
|
compactToolLine,
|
|
4
7
|
prettyJson
|
|
5
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IGCFQJZB.js";
|
|
6
9
|
|
|
7
10
|
// packages/quassel/src/types.ts
|
|
8
11
|
function stepState(message) {
|
|
@@ -16,11 +19,14 @@ function stepState(message) {
|
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
// packages/quassel/src/texts.ts
|
|
22
|
+
var fillText = (text, values) => text.replace(/\{(\w+)\}/g, (match, name) => name in values ? String(values[name]) : match);
|
|
19
23
|
var defaultTexts = {
|
|
20
24
|
working: "Arbeitet ...",
|
|
21
25
|
toolRunning: "l\xE4uft ...",
|
|
22
26
|
toolStillRunning: "Werkzeug l\xE4uft ...",
|
|
27
|
+
currentTool: "Werkzeug l\xE4uft",
|
|
23
28
|
thinkingChip: "Denken",
|
|
29
|
+
toolChip: "Werkzeug",
|
|
24
30
|
stepGroupOne: "1 Schritt",
|
|
25
31
|
stepGroupMany: "{count} Schritte",
|
|
26
32
|
stepGroupCollapse: "Einklappen",
|
|
@@ -31,421 +37,456 @@ var defaultTexts = {
|
|
|
31
37
|
close: "Schlie\xDFen",
|
|
32
38
|
jumpToEnd: "Zum Ende springen",
|
|
33
39
|
send: "Senden",
|
|
34
|
-
sendIntoRun: "In den laufenden
|
|
35
|
-
stop: "
|
|
40
|
+
sendIntoRun: "In den laufenden Turn einspeisen",
|
|
41
|
+
stop: "Arbeit stoppen",
|
|
36
42
|
placeholder: "Nachricht schreiben ...",
|
|
37
43
|
steeringPlaceholder: "Dazwischenfunken ...",
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
questionSubmit: "Auswahl \xFCbernehmen",
|
|
41
|
-
questionPlaceholder: "... oder frei antworten",
|
|
42
|
-
questionAnswer: "Antworten",
|
|
43
|
-
copyMessage: "Nachricht kopieren",
|
|
44
|
-
copyCode: "Code kopieren",
|
|
45
|
-
copied: "Kopiert",
|
|
46
|
-
copyFailed: "Kopieren ist nicht verf\xFCgbar.",
|
|
47
|
-
editMessage: "Nachricht bearbeiten",
|
|
48
|
-
retryMessage: "Erneut senden",
|
|
44
|
+
steered: "In den laufenden Turn eingespeist",
|
|
45
|
+
inputHint: "Enter zum Senden - Shift + Enter f\xFCr eine neue Zeile",
|
|
49
46
|
detailModeTitle: "Detailgrad der Schritte",
|
|
50
47
|
detailModeOff: "nur Antworten",
|
|
48
|
+
detailModeCurrent: "aktuell",
|
|
51
49
|
detailModeIcons: "Symbole",
|
|
52
50
|
detailModeChips: "kompakt",
|
|
53
51
|
detailModeGrouped: "gruppiert",
|
|
54
52
|
detailModeCompact: "einzeilig",
|
|
55
|
-
detailModeFull: "alles"
|
|
53
|
+
detailModeFull: "alles",
|
|
54
|
+
timestamps: "Zeitstempel",
|
|
55
|
+
showTimestamps: "Zeitstempel anzeigen",
|
|
56
|
+
hideTimestamps: "Zeitstempel ausblenden",
|
|
57
|
+
copyMessage: "Nachricht kopieren",
|
|
58
|
+
editMessage: "Nachricht bearbeiten",
|
|
59
|
+
retryMessage: "Antwort erneut anfordern",
|
|
60
|
+
copyCode: "Code kopieren",
|
|
61
|
+
copied: "Kopiert",
|
|
62
|
+
pendingAction: "wartet auf Eingabe",
|
|
63
|
+
dismissAction: "Verwerfen",
|
|
64
|
+
actionDismissed: "verworfen",
|
|
65
|
+
actionFailed: "Aktion fehlgeschlagen",
|
|
66
|
+
clipboardUnavailable: "Die Zwischenablage ist nicht verf\xFCgbar",
|
|
67
|
+
transcript: "Chatverlauf",
|
|
68
|
+
attachments: "Anh\xE4nge",
|
|
69
|
+
attachFiles: "Dateien anh\xE4ngen",
|
|
70
|
+
attachFilesHint: "Dateien anh\xE4ngen (bis zu {count} Dateien, insgesamt {size})",
|
|
71
|
+
removeAttachment: "{name} entfernen",
|
|
72
|
+
attachmentPreparing: "Wird vorbereitet ...",
|
|
73
|
+
attachmentsPreparing: "Anh\xE4nge werden vorbereitet ...",
|
|
74
|
+
attachmentReadFailed: "Datei konnte nicht gelesen werden. Entferne sie und f\xFCge sie erneut hinzu.",
|
|
75
|
+
attachmentsWhileBusy: "Warte, bis die Eingabe wieder bereit ist, und f\xFCge die Dateien dann erneut hinzu.",
|
|
76
|
+
tooManyAttachments: "H\xF6chstens {count} Anh\xE4nge pro Nachricht. Entferne zuerst einen Anhang.",
|
|
77
|
+
attachmentsTooLarge: "Anh\xE4nge d\xFCrfen zusammen h\xF6chstens {size} gro\xDF sein. W\xE4hle kleinere Dateien.",
|
|
78
|
+
attachmentUnsupported: "Dieser Chat unterst\xFCtzt {name} nicht als {kind}. Entferne den Anhang.",
|
|
79
|
+
attachmentUnsupportedByModel: "Das Modell {model} unterst\xFCtzt {name} nicht als {kind}. W\xE4hle ein passendes Modell oder entferne den Anhang.",
|
|
80
|
+
attachmentKindImage: "Bild",
|
|
81
|
+
attachmentKindVideo: "Video",
|
|
82
|
+
attachmentKindPdf: "PDF",
|
|
83
|
+
notSent: "Nicht gesendet:",
|
|
84
|
+
notSentAttachments: "({count} Anh\xE4nge)",
|
|
85
|
+
insertUnsent: "Nicht gesendete Eingabe einf\xFCgen"
|
|
56
86
|
};
|
|
57
87
|
|
|
58
|
-
// packages/quassel/src/
|
|
59
|
-
import {
|
|
60
|
-
|
|
61
|
-
const [messages, dispatch] = useReducer(applyEvent, []);
|
|
62
|
-
const [running, setRunning] = useState(false);
|
|
63
|
-
const [connected, setConnected] = useState(false);
|
|
64
|
-
const url = useRef(baseUrl);
|
|
65
|
-
url.current = baseUrl;
|
|
66
|
-
const zusatz = useRef(headers);
|
|
67
|
-
zusatz.current = headers;
|
|
68
|
-
const observer = useRef(onEvent);
|
|
69
|
-
observer.current = onEvent;
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
const controller = new AbortController();
|
|
72
|
-
let active = true;
|
|
73
|
-
async function verbinden() {
|
|
74
|
-
while (active) {
|
|
75
|
-
try {
|
|
76
|
-
const response = await fetch(`${url.current}/stream`, {
|
|
77
|
-
headers: { Accept: "text/event-stream", ...zusatz.current },
|
|
78
|
-
signal: controller.signal
|
|
79
|
-
});
|
|
80
|
-
if (!response.ok || !response.body) {
|
|
81
|
-
throw new Error(`Stream fehlgeschlagen: ${response.status}`);
|
|
82
|
-
}
|
|
83
|
-
setConnected(true);
|
|
84
|
-
const reader = response.body.getReader();
|
|
85
|
-
const decoder = new TextDecoder();
|
|
86
|
-
let puffer = "";
|
|
87
|
-
for (; ; ) {
|
|
88
|
-
const { done, value } = await reader.read();
|
|
89
|
-
if (done) {
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
puffer += decoder.decode(value, { stream: true });
|
|
93
|
-
const teile = puffer.split("\n\n");
|
|
94
|
-
puffer = teile.pop() ?? "";
|
|
95
|
-
for (const teil of teile) {
|
|
96
|
-
const daten = teil.split("\n").filter((zeile) => zeile.startsWith("data:")).map((zeile) => zeile.slice(5).trim()).join("");
|
|
97
|
-
if (!daten) {
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
const event = JSON.parse(daten);
|
|
101
|
-
if (event.kind === "status") {
|
|
102
|
-
setRunning(event.running);
|
|
103
|
-
}
|
|
104
|
-
observer.current?.(event);
|
|
105
|
-
dispatch(event);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
} catch {
|
|
109
|
-
if (!active) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
setConnected(false);
|
|
114
|
-
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
void verbinden();
|
|
118
|
-
return () => {
|
|
119
|
-
active = false;
|
|
120
|
-
controller.abort();
|
|
121
|
-
};
|
|
122
|
-
}, []);
|
|
123
|
-
const send = useCallback(async (text) => {
|
|
124
|
-
await fetch(`${url.current}/send`, {
|
|
125
|
-
method: "POST",
|
|
126
|
-
headers: { "Content-Type": "application/json", ...zusatz.current },
|
|
127
|
-
body: JSON.stringify({ text })
|
|
128
|
-
});
|
|
129
|
-
}, []);
|
|
130
|
-
const stop = useCallback(async () => {
|
|
131
|
-
await fetch(`${url.current}/stop`, { method: "POST", headers: { ...zusatz.current } });
|
|
132
|
-
}, []);
|
|
133
|
-
return { messages, running, connected, send, stop };
|
|
134
|
-
}
|
|
88
|
+
// packages/quassel/src/ui/cn.ts
|
|
89
|
+
import { createCn } from "cn/config";
|
|
90
|
+
var cn = createCn({ prefix: "qsl" });
|
|
135
91
|
|
|
136
92
|
// packages/quassel/src/ChatMessages.tsx
|
|
137
|
-
import { useCallback as useCallback2, useContext as useContext3, useEffect as
|
|
93
|
+
import { useCallback as useCallback2, useContext as useContext3, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
|
|
138
94
|
|
|
139
|
-
// packages/quassel/src/
|
|
140
|
-
var
|
|
141
|
-
function
|
|
95
|
+
// packages/quassel/src/chat-scroll.ts
|
|
96
|
+
var atEnd = (viewport) => viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop <= 24;
|
|
97
|
+
function createChatScroll(onFollowChange = () => {
|
|
98
|
+
}) {
|
|
99
|
+
let following = true;
|
|
100
|
+
let lastTop = 0;
|
|
101
|
+
const follow = (value) => {
|
|
102
|
+
if (following === value) return;
|
|
103
|
+
following = value;
|
|
104
|
+
onFollowChange(value);
|
|
105
|
+
};
|
|
106
|
+
const layout = (viewport, dragging = false) => {
|
|
107
|
+
if (viewport.clientHeight <= 0) return;
|
|
108
|
+
if (dragging && viewport.scrollTop < lastTop && !atEnd(viewport)) follow(false);
|
|
109
|
+
if (following) viewport.scrollTop = Math.max(0, viewport.scrollHeight - viewport.clientHeight);
|
|
110
|
+
else if (viewport.scrollHeight <= viewport.clientHeight) follow(true);
|
|
111
|
+
lastTop = viewport.scrollTop;
|
|
112
|
+
};
|
|
142
113
|
return {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
114
|
+
layout,
|
|
115
|
+
pause(viewport) {
|
|
116
|
+
if (viewport.scrollTop > 0) follow(false);
|
|
117
|
+
},
|
|
118
|
+
scroll(viewport, dragging = false) {
|
|
119
|
+
if (viewport.clientHeight <= 0) return;
|
|
120
|
+
if (atEnd(viewport)) follow(true);
|
|
121
|
+
else if (dragging && viewport.scrollTop < lastTop) follow(false);
|
|
122
|
+
lastTop = viewport.scrollTop;
|
|
123
|
+
},
|
|
124
|
+
jump(viewport) {
|
|
125
|
+
follow(true);
|
|
126
|
+
layout(viewport);
|
|
127
|
+
}
|
|
147
128
|
};
|
|
148
129
|
}
|
|
149
130
|
|
|
150
|
-
// packages/quassel/src/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
131
|
+
// packages/quassel/src/Markdown.tsx
|
|
132
|
+
import { createContext as createContext2, useCallback, useContext as useContext2, useRef as useRef2 } from "react";
|
|
133
|
+
import { Streamdown } from "streamdown";
|
|
134
|
+
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
135
|
+
|
|
136
|
+
// packages/quassel/src/QuasselProvider.tsx
|
|
137
|
+
import { createContext, useContext, useMemo } from "react";
|
|
138
|
+
|
|
139
|
+
// packages/quassel/src/ui/button.tsx
|
|
140
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
141
|
+
import { cva } from "class-variance-authority";
|
|
142
|
+
import { jsx } from "react/jsx-runtime";
|
|
143
|
+
var buttonVariants = cva(
|
|
144
|
+
"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",
|
|
145
|
+
{
|
|
146
|
+
variants: {
|
|
147
|
+
variant: {
|
|
148
|
+
default: "qsl:bg-primary qsl:text-primary-foreground qsl:hover:bg-primary/80",
|
|
149
|
+
outline: "qsl:border-border qsl:bg-background qsl:hover:bg-muted qsl:hover:text-foreground qsl:aria-expanded:bg-muted qsl:aria-expanded:text-foreground qsl:dark:border-input qsl:dark:bg-input/30 qsl:dark:hover:bg-input/50",
|
|
150
|
+
secondary: "qsl:bg-secondary qsl:text-secondary-foreground qsl:hover:bg-[color-mix(in_oklch,var(--qsl-secondary),var(--qsl-foreground)_5%)] qsl:aria-expanded:bg-secondary qsl:aria-expanded:text-secondary-foreground",
|
|
151
|
+
ghost: "qsl:hover:bg-muted qsl:hover:text-foreground qsl:aria-expanded:bg-muted qsl:aria-expanded:text-foreground qsl:dark:hover:bg-muted/50",
|
|
152
|
+
destructive: "qsl:bg-destructive/10 qsl:text-destructive qsl:hover:bg-destructive/20 qsl:focus-visible:border-destructive/40 qsl:focus-visible:ring-destructive/20 qsl:dark:bg-destructive/20 qsl:dark:hover:bg-destructive/30 qsl:dark:focus-visible:ring-destructive/40",
|
|
153
|
+
link: "qsl:text-primary qsl:underline-offset-4 qsl:hover:underline"
|
|
154
|
+
},
|
|
155
|
+
size: {
|
|
156
|
+
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",
|
|
157
|
+
xs: "qsl:h-6 qsl:gap-1 qsl:rounded-[min(var(--qsl-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",
|
|
158
|
+
sm: "qsl:h-7 qsl:gap-1 qsl:rounded-[min(var(--qsl-radius-md),12px)] qsl:px-2.5 qsl:text-[0.8rem] 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",
|
|
159
|
+
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",
|
|
160
|
+
icon: "qsl:size-8",
|
|
161
|
+
"icon-xs": "qsl:size-6 qsl:rounded-[min(var(--qsl-radius-md),10px)] qsl:in-data-[slot=button-group]:rounded-lg qsl:[&_svg:not([class*='size-'])]:size-3",
|
|
162
|
+
"icon-sm": "qsl:size-7 qsl:rounded-[min(var(--qsl-radius-md),12px)] qsl:in-data-[slot=button-group]:rounded-lg",
|
|
163
|
+
"icon-lg": "qsl:size-9"
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
defaultVariants: {
|
|
167
|
+
variant: "default",
|
|
168
|
+
size: "default"
|
|
169
|
+
}
|
|
171
170
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
171
|
+
);
|
|
172
|
+
function Button({
|
|
173
|
+
className,
|
|
174
|
+
variant = "default",
|
|
175
|
+
size = "default",
|
|
176
|
+
...props
|
|
177
|
+
}) {
|
|
178
|
+
return /* @__PURE__ */ jsx(
|
|
179
|
+
ButtonPrimitive,
|
|
180
|
+
{
|
|
181
|
+
"data-quassel": "",
|
|
182
|
+
"data-slot": "button",
|
|
183
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
184
|
+
...props
|
|
185
|
+
}
|
|
186
|
+
);
|
|
178
187
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
|
|
189
|
+
// packages/quassel/src/ui/card.tsx
|
|
190
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
191
|
+
function Card({
|
|
192
|
+
className,
|
|
193
|
+
size = "default",
|
|
194
|
+
...props
|
|
195
|
+
}) {
|
|
196
|
+
return /* @__PURE__ */ jsx2(
|
|
197
|
+
"div",
|
|
198
|
+
{
|
|
199
|
+
"data-quassel": "",
|
|
200
|
+
"data-slot": "card",
|
|
201
|
+
"data-size": size,
|
|
202
|
+
className: cn(
|
|
203
|
+
"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",
|
|
204
|
+
className
|
|
205
|
+
),
|
|
206
|
+
...props
|
|
207
|
+
}
|
|
208
|
+
);
|
|
188
209
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
210
|
+
|
|
211
|
+
// packages/quassel/src/ui/popover.tsx
|
|
212
|
+
import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
|
|
213
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
214
|
+
function Popover({ ...props }) {
|
|
215
|
+
return /* @__PURE__ */ jsx3(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
|
|
216
|
+
}
|
|
217
|
+
function PopoverContent({
|
|
218
|
+
className,
|
|
219
|
+
align = "center",
|
|
220
|
+
alignOffset = 0,
|
|
221
|
+
side = "bottom",
|
|
222
|
+
sideOffset = 8,
|
|
223
|
+
anchor,
|
|
224
|
+
collisionPadding,
|
|
225
|
+
keepMounted,
|
|
226
|
+
...props
|
|
227
|
+
}) {
|
|
228
|
+
return /* @__PURE__ */ jsx3(PopoverPrimitive.Portal, { keepMounted, children: /* @__PURE__ */ jsx3(
|
|
229
|
+
PopoverPrimitive.Positioner,
|
|
230
|
+
{
|
|
231
|
+
align,
|
|
232
|
+
alignOffset,
|
|
233
|
+
anchor,
|
|
234
|
+
collisionPadding,
|
|
235
|
+
side,
|
|
236
|
+
sideOffset,
|
|
237
|
+
className: "qsl:isolate qsl:z-[110]",
|
|
238
|
+
children: /* @__PURE__ */ jsx3(
|
|
239
|
+
PopoverPrimitive.Popup,
|
|
240
|
+
{
|
|
241
|
+
"data-quassel": "",
|
|
242
|
+
"data-slot": "popover-content",
|
|
243
|
+
className: cn(
|
|
244
|
+
"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",
|
|
245
|
+
className
|
|
246
|
+
),
|
|
247
|
+
...props
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
) });
|
|
194
252
|
}
|
|
195
253
|
|
|
196
|
-
// packages/quassel/src/
|
|
197
|
-
import {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
254
|
+
// packages/quassel/src/ui/stop-button.tsx
|
|
255
|
+
import { SquareIcon } from "lucide-react";
|
|
256
|
+
|
|
257
|
+
// packages/quassel/src/ui/spinner.tsx
|
|
258
|
+
import { Loader2Icon } from "lucide-react";
|
|
259
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
260
|
+
function Spinner({ className, ...props }) {
|
|
261
|
+
return /* @__PURE__ */ jsx4(Loader2Icon, { "data-slot": "spinner", role: "status", "aria-label": "Loading", className: cn("qsl:size-4 qsl:animate-spin", className), ...props });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// packages/quassel/src/ui/stop-button.tsx
|
|
265
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
266
|
+
function StopGlyph({ className, ...props }) {
|
|
267
|
+
return /* @__PURE__ */ jsx5(SquareIcon, { "aria-hidden": true, className: cn("qsl:text-destructive", className), fill: "currentColor", ...props });
|
|
268
|
+
}
|
|
269
|
+
var stopButtonClass = "qsl:text-destructive qsl:hover:bg-destructive/10 qsl:hover:text-destructive qsl:dark:hover:bg-destructive/20";
|
|
270
|
+
function StopButton({ label, busy = false, className, title, ...props }) {
|
|
271
|
+
return /* @__PURE__ */ jsx5(Button, { "aria-busy": busy || void 0, "aria-label": label, className: cn(stopButtonClass, className), title: title ?? label, variant: "ghost", ...props, children: busy ? /* @__PURE__ */ jsx5(Spinner, {}) : /* @__PURE__ */ jsx5(StopGlyph, {}) });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// packages/quassel/src/ui/toggle.tsx
|
|
275
|
+
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
|
|
276
|
+
import { cva as cva2 } from "class-variance-authority";
|
|
277
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
278
|
+
var toggleVariants = cva2(
|
|
279
|
+
"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",
|
|
280
|
+
{
|
|
281
|
+
variants: {
|
|
282
|
+
variant: {
|
|
283
|
+
default: "qsl:bg-transparent",
|
|
284
|
+
outline: "qsl:border qsl:border-input qsl:bg-transparent qsl:hover:bg-muted"
|
|
285
|
+
},
|
|
286
|
+
size: {
|
|
287
|
+
default: "qsl:h-8 qsl:min-w-8 qsl:px-2.5 qsl:has-data-[icon=inline-end]:pr-2 qsl:has-data-[icon=inline-start]:pl-2",
|
|
288
|
+
sm: "qsl:h-7 qsl:min-w-7 qsl:rounded-[min(var(--qsl-radius-md),12px)] qsl:px-2.5 qsl:text-[0.8rem] 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",
|
|
289
|
+
lg: "qsl:h-9 qsl:min-w-9 qsl:px-2.5 qsl:has-data-[icon=inline-end]:pr-2 qsl:has-data-[icon=inline-start]:pl-2"
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
defaultVariants: {
|
|
293
|
+
variant: "default",
|
|
294
|
+
size: "default"
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
function Toggle({
|
|
299
|
+
className,
|
|
300
|
+
variant = "default",
|
|
301
|
+
size = "default",
|
|
302
|
+
...props
|
|
303
|
+
}) {
|
|
304
|
+
return /* @__PURE__ */ jsx6(
|
|
305
|
+
TogglePrimitive,
|
|
306
|
+
{
|
|
307
|
+
"data-quassel": "",
|
|
308
|
+
"data-slot": "toggle",
|
|
309
|
+
className: cn(toggleVariants({ variant, size, className })),
|
|
310
|
+
...props
|
|
311
|
+
}
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// packages/quassel/src/QuasselProvider.tsx
|
|
316
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
317
|
+
var defaultComponents = { Button, Toggle, Card, StopButton, Popover, PopoverContent };
|
|
318
|
+
var QuasselContext = createContext({ components: defaultComponents });
|
|
319
|
+
function QuasselProvider({ components: components2, allowUrl, children }) {
|
|
320
|
+
const outer = useContext(QuasselContext);
|
|
321
|
+
const value = useMemo(() => ({
|
|
322
|
+
components: { ...outer.components, ...components2 },
|
|
323
|
+
allowUrl: allowUrl ?? outer.allowUrl
|
|
324
|
+
}), [outer, components2, allowUrl]);
|
|
325
|
+
return /* @__PURE__ */ jsx7(QuasselContext.Provider, { value, children });
|
|
326
|
+
}
|
|
327
|
+
var useQuasselComponents = () => useContext(QuasselContext).components;
|
|
328
|
+
var useAllowUrl = () => useContext(QuasselContext).allowUrl;
|
|
329
|
+
|
|
330
|
+
// packages/quassel/src/useChatAction.ts
|
|
331
|
+
import { useEffect, useRef, useState } from "react";
|
|
332
|
+
function useChatAction() {
|
|
333
|
+
const pending = useRef(false);
|
|
334
|
+
const [status, setStatus] = useState("idle");
|
|
335
|
+
const [error, setError] = useState();
|
|
336
|
+
useEffect(() => {
|
|
204
337
|
if (status !== "done") return;
|
|
205
338
|
const timer = window.setTimeout(() => setStatus("idle"), 1800);
|
|
206
339
|
return () => window.clearTimeout(timer);
|
|
207
340
|
}, [status]);
|
|
208
|
-
const
|
|
341
|
+
const invoke = async (action, failureLabel) => {
|
|
209
342
|
if (pending.current) return;
|
|
210
343
|
pending.current = true;
|
|
211
|
-
setStatus("pending");
|
|
212
344
|
setError(void 0);
|
|
345
|
+
setStatus("pending");
|
|
213
346
|
try {
|
|
214
|
-
await
|
|
347
|
+
await action();
|
|
215
348
|
setStatus("done");
|
|
216
349
|
} catch (failure) {
|
|
350
|
+
const detail = failure instanceof Error ? failure.message : String(failure);
|
|
351
|
+
setError(`${failureLabel}: ${detail}`);
|
|
217
352
|
setStatus("idle");
|
|
218
|
-
setError(failure instanceof Error ? failure.message : String(failure));
|
|
219
353
|
} finally {
|
|
220
354
|
pending.current = false;
|
|
221
355
|
}
|
|
222
356
|
};
|
|
223
|
-
return
|
|
224
|
-
/* @__PURE__ */ jsxs("button", { className: "qsl-icon-button", type: "button", disabled: disabled || status === "pending", onClick: () => void run(), children: [
|
|
225
|
-
icon,
|
|
226
|
-
status === "done" && successLabel ? successLabel : label
|
|
227
|
-
] }),
|
|
228
|
-
error && /* @__PURE__ */ jsx("span", { className: "qsl-action-error", role: "alert", children: error })
|
|
229
|
-
] });
|
|
357
|
+
return { status, error, invoke };
|
|
230
358
|
}
|
|
231
|
-
async function
|
|
232
|
-
if (!navigator.clipboard) throw new Error(
|
|
359
|
+
async function copyChatText(text, unavailable) {
|
|
360
|
+
if (!navigator.clipboard) throw new Error(unavailable);
|
|
233
361
|
await navigator.clipboard.writeText(text);
|
|
234
362
|
}
|
|
235
|
-
function MessageActions({ message, options, texts }) {
|
|
236
|
-
if (!options) return null;
|
|
237
|
-
const copy = typeof options.copy === "function" ? options.copy(message) : options.copy;
|
|
238
|
-
const edit = message.role === "user" ? options.edit : void 0;
|
|
239
|
-
const retry = message.role === "assistant" ? options.retry : void 0;
|
|
240
|
-
const custom = options.custom?.(message) ?? [];
|
|
241
|
-
if (!copy && !edit && !retry && custom.length === 0) return null;
|
|
242
|
-
return /* @__PURE__ */ jsxs("div", { className: "qsl-message-actions", role: "group", "aria-label": texts.messageActions, children: [
|
|
243
|
-
copy && /* @__PURE__ */ jsx(ActionButton, { label: texts.copyMessage, successLabel: texts.copied, onClick: () => copyText(message.text, texts.copyFailed) }),
|
|
244
|
-
edit && /* @__PURE__ */ jsx(ActionButton, { label: texts.editMessage, onClick: () => edit(message) }),
|
|
245
|
-
retry && /* @__PURE__ */ jsx(ActionButton, { label: texts.retryMessage, onClick: () => retry(message) }),
|
|
246
|
-
custom.map((action) => /* @__PURE__ */ jsx(ActionButton, { label: action.label, icon: action.icon, disabled: action.disabled, onClick: () => action.onClick(message) }, action.id))
|
|
247
|
-
] });
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// packages/quassel/src/ChatSendContext.ts
|
|
251
|
-
import { createContext, useContext, useLayoutEffect, useRef as useRef3, useState as useState3 } from "react";
|
|
252
|
-
var ChatSendContext = createContext(void 0);
|
|
253
|
-
function isSendKey(event, shortcut) {
|
|
254
|
-
return event.key === "Enter" && !event.shiftKey && !event.altKey && !event.nativeEvent.isComposing && event.nativeEvent.keyCode !== 229 && (shortcut === "enter" || event.ctrlKey || event.metaKey);
|
|
255
|
-
}
|
|
256
|
-
function useChatSubmit(onSend) {
|
|
257
|
-
const panel = useContext(ChatSendContext);
|
|
258
|
-
const pending = useRef3(false);
|
|
259
|
-
const mounted = useRef3(true);
|
|
260
|
-
useLayoutEffect(() => {
|
|
261
|
-
mounted.current = true;
|
|
262
|
-
return () => {
|
|
263
|
-
mounted.current = false;
|
|
264
|
-
};
|
|
265
|
-
}, []);
|
|
266
|
-
const [sending, setSending] = useState3(false);
|
|
267
|
-
const [error, setError] = useState3();
|
|
268
|
-
const submit = async (text) => {
|
|
269
|
-
if (pending.current) return false;
|
|
270
|
-
pending.current = true;
|
|
271
|
-
setSending(true);
|
|
272
|
-
setError(void 0);
|
|
273
|
-
try {
|
|
274
|
-
await onSend(text);
|
|
275
|
-
if (!mounted.current) return false;
|
|
276
|
-
panel?.onSent();
|
|
277
|
-
return true;
|
|
278
|
-
} catch (failure) {
|
|
279
|
-
if (mounted.current) setError(failure instanceof Error ? failure.message : String(failure));
|
|
280
|
-
return false;
|
|
281
|
-
} finally {
|
|
282
|
-
pending.current = false;
|
|
283
|
-
if (mounted.current) setSending(false);
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
return { submit, sending, error };
|
|
287
|
-
}
|
|
288
363
|
|
|
289
364
|
// packages/quassel/src/Markdown.tsx
|
|
290
|
-
import {
|
|
291
|
-
|
|
292
|
-
var CodeBlockContext = createContext2({});
|
|
365
|
+
import { jsx as jsx8, jsxs } from "react/jsx-runtime";
|
|
366
|
+
var LinkClickContext = createContext2(void 0);
|
|
367
|
+
var CodeBlockContext = createContext2({ texts: defaultTexts });
|
|
293
368
|
function MarkdownCodeBlocks({ children, options, texts }) {
|
|
294
|
-
return /* @__PURE__ */
|
|
369
|
+
return /* @__PURE__ */ jsx8(CodeBlockContext.Provider, { value: { options, texts }, children });
|
|
295
370
|
}
|
|
296
|
-
function CodeBlock({
|
|
371
|
+
function CodeBlock({ children }) {
|
|
297
372
|
const { options, texts } = useContext2(CodeBlockContext);
|
|
298
|
-
const
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
373
|
+
const { Button: Button2 } = useQuasselComponents();
|
|
374
|
+
const code = useRef2(null);
|
|
375
|
+
const action = useChatAction();
|
|
376
|
+
const label = action.status === "done" ? texts.copied : texts.copyCode;
|
|
377
|
+
const block = /* @__PURE__ */ jsx8(
|
|
378
|
+
"pre",
|
|
379
|
+
{
|
|
380
|
+
ref: code,
|
|
381
|
+
"data-chat": "code-block",
|
|
382
|
+
className: cn(scrollbar, "qsl:my-2 qsl:overflow-auto qsl:rounded-lg qsl:bg-secondary qsl:px-3 qsl:py-2 qsl:font-mono qsl:text-sm qsl:leading-relaxed qsl:in-data-[tone=on-color]:bg-black/30 qsl:in-data-[tone=on-color]:text-inherit"),
|
|
383
|
+
style: { maxHeight: options?.maxHeight, whiteSpace: options?.wrap ? "pre-wrap" : void 0, overflowWrap: options?.wrap ? "anywhere" : void 0 },
|
|
384
|
+
children
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
if (!options?.showCopyButton) return block;
|
|
388
|
+
return /* @__PURE__ */ jsxs("div", { className: "qsl:relative", children: [
|
|
389
|
+
block,
|
|
390
|
+
/* @__PURE__ */ jsx8(
|
|
391
|
+
Button2,
|
|
392
|
+
{
|
|
393
|
+
"aria-label": label,
|
|
394
|
+
className: "qsl:absolute qsl:top-1 qsl:right-1 qsl:bg-secondary",
|
|
395
|
+
disabled: action.status === "pending",
|
|
396
|
+
size: "icon-sm",
|
|
397
|
+
title: label,
|
|
398
|
+
variant: "ghost",
|
|
399
|
+
onClick: () => {
|
|
400
|
+
void action.invoke(() => copyChatText(code.current?.textContent ?? "", texts.clipboardUnavailable), texts.actionFailed);
|
|
401
|
+
},
|
|
402
|
+
children: action.status === "done" ? /* @__PURE__ */ jsx8(CheckIcon, {}) : /* @__PURE__ */ jsx8(CopyIcon, {})
|
|
403
|
+
}
|
|
404
|
+
),
|
|
405
|
+
action.error && /* @__PURE__ */ jsx8("p", { className: "qsl:text-sm qsl:text-destructive", role: "alert", children: action.error })
|
|
406
|
+
] });
|
|
309
407
|
}
|
|
310
|
-
var LinkClickContext = createContext2(void 0);
|
|
311
408
|
function MarkdownLinks({ children, onLinkClick }) {
|
|
312
|
-
return /* @__PURE__ */
|
|
409
|
+
return /* @__PURE__ */ jsx8(LinkClickContext.Provider, { value: onLinkClick, children });
|
|
313
410
|
}
|
|
314
|
-
function MarkdownLink({ href,
|
|
411
|
+
function MarkdownLink({ href, children }) {
|
|
315
412
|
const onLinkClick = useContext2(LinkClickContext);
|
|
316
|
-
|
|
413
|
+
if (!href || href === "streamdown:incomplete-link") {
|
|
414
|
+
return /* @__PURE__ */ jsx8("span", { children });
|
|
415
|
+
}
|
|
416
|
+
return /* @__PURE__ */ jsx8(
|
|
317
417
|
"a",
|
|
318
418
|
{
|
|
419
|
+
className: "qsl:text-primary qsl:[text-underline-offset:2px] qsl:in-data-[tone=on-color]:text-inherit qsl:in-data-[tone=on-color]:underline",
|
|
319
420
|
href,
|
|
320
421
|
onClick: (event) => {
|
|
321
|
-
if (onLinkClick?.(href,
|
|
422
|
+
if (href && onLinkClick?.(href, event.currentTarget.textContent ?? "")) {
|
|
322
423
|
event.preventDefault();
|
|
323
424
|
}
|
|
324
425
|
},
|
|
325
426
|
rel: "noreferrer",
|
|
326
427
|
target: "_blank",
|
|
327
|
-
children
|
|
428
|
+
children
|
|
328
429
|
}
|
|
329
430
|
);
|
|
330
431
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
] }) }, key++)
|
|
367
|
-
);
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
const heading = line.match(/^(#{1,4})\s+(.*)$/);
|
|
371
|
-
if (heading) {
|
|
372
|
-
const level = heading[1].length;
|
|
373
|
-
blocks.push(
|
|
374
|
-
level === 1 ? /* @__PURE__ */ jsx2("h3", { children: renderInline(heading[2], `h${key}`) }, key++) : /* @__PURE__ */ jsx2("h4", { children: renderInline(heading[2], `h${key}`) }, key++)
|
|
375
|
-
);
|
|
376
|
-
i++;
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
const quote = line.match(/^\s*>\s?(.*)$/);
|
|
380
|
-
if (quote) {
|
|
381
|
-
blocks.push(/* @__PURE__ */ jsx2("blockquote", { children: renderInline(quote[1], `q${key}`) }, key++));
|
|
382
|
-
i++;
|
|
383
|
-
continue;
|
|
384
|
-
}
|
|
385
|
-
const bullet = line.match(/^\s*[-*]\s+(.*)$/);
|
|
386
|
-
if (bullet) {
|
|
387
|
-
blocks.push(
|
|
388
|
-
/* @__PURE__ */ jsxs2("div", { className: "qsl-md-bullet", children: [
|
|
389
|
-
/* @__PURE__ */ jsx2("span", { children: "\u2022" }),
|
|
390
|
-
/* @__PURE__ */ jsx2("span", { children: renderInline(bullet[1], `l${key}`) })
|
|
391
|
-
] }, key++)
|
|
392
|
-
);
|
|
393
|
-
i++;
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
const numbered = line.match(/^\s*(\d+)\.\s+(.*)$/);
|
|
397
|
-
if (numbered) {
|
|
398
|
-
blocks.push(
|
|
399
|
-
/* @__PURE__ */ jsxs2("div", { className: "qsl-md-bullet", children: [
|
|
400
|
-
/* @__PURE__ */ jsxs2("span", { children: [
|
|
401
|
-
numbered[1],
|
|
402
|
-
"."
|
|
403
|
-
] }),
|
|
404
|
-
/* @__PURE__ */ jsx2("span", { children: renderInline(numbered[2], `l${key}`) })
|
|
405
|
-
] }, key++)
|
|
406
|
-
);
|
|
407
|
-
i++;
|
|
408
|
-
continue;
|
|
409
|
-
}
|
|
410
|
-
if (line.trim() === "") {
|
|
411
|
-
blocks.push(/* @__PURE__ */ jsx2("div", { className: "qsl-md-gap" }, key++));
|
|
412
|
-
i++;
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
blocks.push(/* @__PURE__ */ jsx2("p", { children: renderInline(line, `l${key}`) }, key++));
|
|
416
|
-
i++;
|
|
417
|
-
}
|
|
418
|
-
return /* @__PURE__ */ jsx2("div", { className: "qsl-md", children: blocks });
|
|
432
|
+
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";
|
|
433
|
+
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]";
|
|
434
|
+
var headingClasses = "qsl:mt-3 qsl:mb-1 qsl:text-[15px] qsl:font-medium";
|
|
435
|
+
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";
|
|
436
|
+
var components = {
|
|
437
|
+
h1: ({ children }) => /* @__PURE__ */ jsx8("h1", { className: "qsl:mt-3 qsl:mb-1 qsl:text-lg qsl:font-semibold", children }),
|
|
438
|
+
h2: ({ children }) => /* @__PURE__ */ jsx8("h2", { className: headingClasses, children }),
|
|
439
|
+
h3: ({ children }) => /* @__PURE__ */ jsx8("h3", { className: headingClasses, children }),
|
|
440
|
+
h4: ({ children }) => /* @__PURE__ */ jsx8("h4", { className: "qsl:mt-2 qsl:mb-1 qsl:text-base qsl:font-medium", children }),
|
|
441
|
+
h5: ({ children }) => /* @__PURE__ */ jsx8("h5", { className: "qsl:mt-2 qsl:mb-1 qsl:text-base qsl:font-medium", children }),
|
|
442
|
+
h6: ({ children }) => /* @__PURE__ */ jsx8("h6", { className: "qsl:mt-2 qsl:mb-1 qsl:text-base qsl:font-medium", children }),
|
|
443
|
+
p: ({ children }) => /* @__PURE__ */ jsx8("p", { className: "qsl:[p+&]:mt-2", children }),
|
|
444
|
+
ul: ({ children }) => /* @__PURE__ */ jsx8("ul", { className: "qsl:my-2 qsl:list-disc qsl:pl-6 qsl:[li>&]:my-0", children }),
|
|
445
|
+
ol: ({ children }) => /* @__PURE__ */ jsx8("ol", { className: "qsl:my-2 qsl:list-decimal qsl:pl-6 qsl:[li>&]:my-0", children }),
|
|
446
|
+
li: ({ children }) => /* @__PURE__ */ jsx8("li", { className: "qsl:marker:text-muted-foreground qsl:in-data-[tone=on-color]:marker:text-inherit", children }),
|
|
447
|
+
hr: () => /* @__PURE__ */ jsx8("hr", { className: "qsl:my-3 qsl:border-t qsl:border-border" }),
|
|
448
|
+
img: ({ src, alt }) => /* @__PURE__ */ jsx8("img", { alt, className: "qsl:h-auto qsl:max-w-full", src: typeof src === "string" ? src : void 0 }),
|
|
449
|
+
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 }),
|
|
450
|
+
strong: ({ children }) => /* @__PURE__ */ jsx8("strong", { className: "qsl:font-medium", children }),
|
|
451
|
+
em: ({ children }) => /* @__PURE__ */ jsx8("em", { children }),
|
|
452
|
+
a: ({ href, children }) => /* @__PURE__ */ jsx8(MarkdownLink, { href, children }),
|
|
453
|
+
pre: ({ children }) => /* @__PURE__ */ jsx8(CodeBlock, { children }),
|
|
454
|
+
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-sm 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 }),
|
|
455
|
+
// Ohne max-content schrumpft die Tabelle auf den Container und stapelt Buchstaben.
|
|
456
|
+
table: ({ children }) => /* @__PURE__ */ jsx8("div", { className: cn(scrollbar, scrollCover, "qsl:my-2 qsl:overflow-x-auto"), children: /* @__PURE__ */ jsx8("table", { className: "qsl:w-max qsl:min-w-full qsl:border-collapse qsl:text-sm", children }) }),
|
|
457
|
+
thead: ({ children }) => /* @__PURE__ */ jsx8("thead", { children }),
|
|
458
|
+
tbody: ({ children }) => /* @__PURE__ */ jsx8("tbody", { children }),
|
|
459
|
+
tr: ({ children }) => /* @__PURE__ */ jsx8("tr", { children }),
|
|
460
|
+
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 }),
|
|
461
|
+
td: ({ children }) => /* @__PURE__ */ jsx8("td", { className: cellClasses, children })
|
|
462
|
+
};
|
|
463
|
+
function transformUrl(url, allowUrl) {
|
|
464
|
+
if (allowUrl?.(url)) return url;
|
|
465
|
+
const scheme = /^[^/?#]*:/.exec(url);
|
|
466
|
+
return !scheme || /^(https?|mailto|tel|ftp|irc|ircs|xmpp):$/i.test(scheme[0]) ? url : "";
|
|
419
467
|
}
|
|
420
|
-
function
|
|
421
|
-
const
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
out.push(/* @__PURE__ */ jsx2("code", { children: match[5] }, `${keyPrefix}c${i}`));
|
|
468
|
+
function Markdown({ text, streaming = false }) {
|
|
469
|
+
const allowUrl = useAllowUrl();
|
|
470
|
+
const urlTransform = useCallback((url) => transformUrl(url, allowUrl), [allowUrl]);
|
|
471
|
+
return /* @__PURE__ */ jsx8(
|
|
472
|
+
Streamdown,
|
|
473
|
+
{
|
|
474
|
+
className: "space-y-0 text-inherit",
|
|
475
|
+
prefix: "qsl",
|
|
476
|
+
mode: streaming ? "streaming" : "static",
|
|
477
|
+
isAnimating: streaming,
|
|
478
|
+
parseIncompleteMarkdown: streaming,
|
|
479
|
+
components,
|
|
480
|
+
controls: false,
|
|
481
|
+
rehypePlugins: [],
|
|
482
|
+
urlTransform,
|
|
483
|
+
skipHtml: true,
|
|
484
|
+
children: text
|
|
438
485
|
}
|
|
439
|
-
|
|
440
|
-
i++;
|
|
441
|
-
}
|
|
442
|
-
if (last < text.length) {
|
|
443
|
-
out.push(text.slice(last));
|
|
444
|
-
}
|
|
445
|
-
return out;
|
|
486
|
+
);
|
|
446
487
|
}
|
|
447
488
|
function markdownPlainText(text) {
|
|
448
|
-
return text.split("\n").filter((line) => !/^\s*```\w*\s*$/.test(line) && !isTableDivider(line)).map((line) => (line.trim().startsWith("|") ? splitTableRow(line).join(", ") : line).replace(/^(#{1,
|
|
489
|
+
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, fett, fettUnterstrich, kursiv, code) => link ?? fett ?? fettUnterstrich ?? kursiv ?? code).trim()).filter((line) => line !== "").join("\n");
|
|
449
490
|
}
|
|
450
491
|
function splitTableRow(line) {
|
|
451
492
|
let row = line.trim();
|
|
@@ -461,134 +502,11 @@ function isTableDivider(line) {
|
|
|
461
502
|
return /^\s*\|?\s*:?-{1,}:?\s*(\|\s*:?-{1,}:?\s*)*\|?\s*$/.test(line) && line.includes("-");
|
|
462
503
|
}
|
|
463
504
|
|
|
464
|
-
// packages/quassel/src/announce.ts
|
|
465
|
-
var abstandMs = 400;
|
|
466
|
-
var hoechstensWartend = 5;
|
|
467
|
-
var region;
|
|
468
|
-
var wartend = [];
|
|
469
|
-
var laeuft = false;
|
|
470
|
-
function ansageRegion() {
|
|
471
|
-
if (region?.isConnected) {
|
|
472
|
-
return region;
|
|
473
|
-
}
|
|
474
|
-
const element = document.createElement("div");
|
|
475
|
-
element.setAttribute("role", "status");
|
|
476
|
-
element.setAttribute("aria-live", "polite");
|
|
477
|
-
element.setAttribute("aria-atomic", "true");
|
|
478
|
-
Object.assign(element.style, {
|
|
479
|
-
position: "absolute",
|
|
480
|
-
width: "1px",
|
|
481
|
-
height: "1px",
|
|
482
|
-
margin: "-1px",
|
|
483
|
-
padding: "0",
|
|
484
|
-
border: "0",
|
|
485
|
-
overflow: "hidden",
|
|
486
|
-
clipPath: "inset(50%)",
|
|
487
|
-
whiteSpace: "nowrap"
|
|
488
|
-
});
|
|
489
|
-
document.body.append(element);
|
|
490
|
-
region = element;
|
|
491
|
-
return element;
|
|
492
|
-
}
|
|
493
|
-
function naechste() {
|
|
494
|
-
if (wartend.length === 0) {
|
|
495
|
-
laeuft = false;
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
if (document.visibilityState === "hidden") {
|
|
499
|
-
document.addEventListener("visibilitychange", naechste, { once: true });
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
const [text, ...rest] = wartend;
|
|
503
|
-
wartend = rest;
|
|
504
|
-
const ziel = ansageRegion();
|
|
505
|
-
ziel.textContent = ziel.textContent === text ? `${text}\xA0` : text;
|
|
506
|
-
window.setTimeout(naechste, abstandMs);
|
|
507
|
-
}
|
|
508
|
-
function announce(text) {
|
|
509
|
-
const inhalt = text.trim();
|
|
510
|
-
if (!inhalt || typeof document === "undefined") {
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
wartend = [...wartend, inhalt].slice(-hoechstensWartend);
|
|
514
|
-
if (laeuft) {
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
laeuft = true;
|
|
518
|
-
ansageRegion();
|
|
519
|
-
window.setTimeout(naechste, abstandMs);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// packages/quassel/src/StepPopover.tsx
|
|
523
|
-
import { useEffect as useEffect3, useLayoutEffect as useLayoutEffect2, useRef as useRef4, useState as useState4 } from "react";
|
|
524
|
-
import { createPortal } from "react-dom";
|
|
525
|
-
|
|
526
|
-
// packages/quassel/src/icons.tsx
|
|
527
|
-
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
528
|
-
function Svg({ size = 14, className, children }) {
|
|
529
|
-
return /* @__PURE__ */ jsx3(
|
|
530
|
-
"svg",
|
|
531
|
-
{
|
|
532
|
-
"aria-hidden": "true",
|
|
533
|
-
className,
|
|
534
|
-
fill: "none",
|
|
535
|
-
height: size,
|
|
536
|
-
stroke: "currentColor",
|
|
537
|
-
strokeLinecap: "round",
|
|
538
|
-
strokeLinejoin: "round",
|
|
539
|
-
strokeWidth: 2,
|
|
540
|
-
viewBox: "0 0 24 24",
|
|
541
|
-
width: size,
|
|
542
|
-
children
|
|
543
|
-
}
|
|
544
|
-
);
|
|
545
|
-
}
|
|
546
|
-
function IconSend(props) {
|
|
547
|
-
return /* @__PURE__ */ jsxs3(Svg, { ...props, children: [
|
|
548
|
-
/* @__PURE__ */ jsx3("path", { d: "M22 2 11 13" }),
|
|
549
|
-
/* @__PURE__ */ jsx3("path", { d: "M22 2 15 22 11 13 2 9Z" })
|
|
550
|
-
] });
|
|
551
|
-
}
|
|
552
|
-
function IconStop(props) {
|
|
553
|
-
return /* @__PURE__ */ jsx3(Svg, { ...props, children: /* @__PURE__ */ jsx3("rect", { fill: "currentColor", height: 12, rx: 2, stroke: "none", width: 12, x: 6, y: 6 }) });
|
|
554
|
-
}
|
|
555
|
-
function IconX(props) {
|
|
556
|
-
return /* @__PURE__ */ jsxs3(Svg, { ...props, children: [
|
|
557
|
-
/* @__PURE__ */ jsx3("path", { d: "M18 6 6 18" }),
|
|
558
|
-
/* @__PURE__ */ jsx3("path", { d: "m6 6 12 12" })
|
|
559
|
-
] });
|
|
560
|
-
}
|
|
561
|
-
function IconChevronDown(props) {
|
|
562
|
-
return /* @__PURE__ */ jsx3(Svg, { ...props, children: /* @__PURE__ */ jsx3("path", { d: "m6 9 6 6 6-6" }) });
|
|
563
|
-
}
|
|
564
|
-
function IconChevronRight(props) {
|
|
565
|
-
return /* @__PURE__ */ jsx3(Svg, { ...props, children: /* @__PURE__ */ jsx3("path", { d: "m9 18 6-6-6-6" }) });
|
|
566
|
-
}
|
|
567
|
-
function IconLayers(props) {
|
|
568
|
-
return /* @__PURE__ */ jsxs3(Svg, { ...props, children: [
|
|
569
|
-
/* @__PURE__ */ jsx3("path", { d: "m12 2 10 5-10 5L2 7Z" }),
|
|
570
|
-
/* @__PURE__ */ jsx3("path", { d: "m2 17 10 5 10-5" }),
|
|
571
|
-
/* @__PURE__ */ jsx3("path", { d: "m2 12 10 5 10-5" })
|
|
572
|
-
] });
|
|
573
|
-
}
|
|
574
|
-
function IconCheck(props) {
|
|
575
|
-
return /* @__PURE__ */ jsx3(Svg, { ...props, children: /* @__PURE__ */ jsx3("path", { d: "M20 6 9 17l-5-5" }) });
|
|
576
|
-
}
|
|
577
|
-
function IconSpark(props) {
|
|
578
|
-
return /* @__PURE__ */ jsx3(Svg, { ...props, children: /* @__PURE__ */ jsx3("path", { d: "M12 3l1.9 6.1L20 11l-6.1 1.9L12 19l-1.9-6.1L4 11l6.1-1.9Z" }) });
|
|
579
|
-
}
|
|
580
|
-
function IconClock(props) {
|
|
581
|
-
return /* @__PURE__ */ jsxs3(Svg, { ...props, children: [
|
|
582
|
-
/* @__PURE__ */ jsx3("circle", { cx: 12, cy: 12, r: 9 }),
|
|
583
|
-
/* @__PURE__ */ jsx3("path", { d: "M12 7v5l3 2" })
|
|
584
|
-
] });
|
|
585
|
-
}
|
|
586
|
-
function IconTool(props) {
|
|
587
|
-
return /* @__PURE__ */ jsx3(Svg, { ...props, children: /* @__PURE__ */ jsx3("path", { d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" }) });
|
|
588
|
-
}
|
|
589
|
-
|
|
590
505
|
// packages/quassel/src/StepPopover.tsx
|
|
591
|
-
import {
|
|
506
|
+
import { useMemo as useMemo2 } from "react";
|
|
507
|
+
import { SparklesIcon, WrenchIcon, XIcon } from "lucide-react";
|
|
508
|
+
import { jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
509
|
+
var preClasses = "qsl:rounded-lg qsl:bg-secondary qsl:p-3 qsl:font-mono qsl:text-[11px] qsl:leading-[1.625] qsl:break-all qsl:whitespace-pre-wrap";
|
|
592
510
|
function StepPopover({
|
|
593
511
|
message,
|
|
594
512
|
position,
|
|
@@ -596,168 +514,86 @@ function StepPopover({
|
|
|
596
514
|
toolArgumentsText,
|
|
597
515
|
onClose
|
|
598
516
|
}) {
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
const margin = 16;
|
|
607
|
-
const gap = 10;
|
|
608
|
-
const bounds = element.getBoundingClientRect();
|
|
609
|
-
const preferredLeft = position.x + gap;
|
|
610
|
-
const preferredTop = position.y + gap;
|
|
611
|
-
const left = preferredLeft + bounds.width <= window.innerWidth - margin ? preferredLeft : position.x - bounds.width - gap;
|
|
612
|
-
const top = preferredTop + bounds.height <= window.innerHeight - margin ? preferredTop : position.y - bounds.height - gap;
|
|
613
|
-
setPlacement({
|
|
614
|
-
left: Math.max(margin, Math.min(left, window.innerWidth - bounds.width - margin)),
|
|
615
|
-
top: Math.max(margin, Math.min(top, window.innerHeight - bounds.height - margin))
|
|
616
|
-
});
|
|
617
|
-
}, [message.text, message.tool?.result, position]);
|
|
618
|
-
useEffect3(() => {
|
|
619
|
-
const handleKeyDown = (event) => {
|
|
620
|
-
if (event.key === "Escape") {
|
|
621
|
-
onClose();
|
|
622
|
-
}
|
|
623
|
-
};
|
|
624
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
625
|
-
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
626
|
-
}, [onClose]);
|
|
627
|
-
useEffect3(() => {
|
|
628
|
-
const handlePointerDown = (event) => {
|
|
629
|
-
if (!popover.current?.contains(event.target)) {
|
|
630
|
-
onClose();
|
|
631
|
-
}
|
|
632
|
-
};
|
|
633
|
-
document.addEventListener("pointerdown", handlePointerDown, true);
|
|
634
|
-
return () => document.removeEventListener("pointerdown", handlePointerDown, true);
|
|
635
|
-
}, [onClose]);
|
|
517
|
+
const anchor = useMemo2(() => ({
|
|
518
|
+
getBoundingClientRect: () => new DOMRect(position.x, position.y, 0, 0),
|
|
519
|
+
contextElement: document.documentElement
|
|
520
|
+
}), [position]);
|
|
521
|
+
const { Button: Button2, Popover: Popover2, PopoverContent: PopoverContent2 } = useQuasselComponents();
|
|
636
522
|
const tool = message.tool;
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
document.body
|
|
669
|
-
);
|
|
523
|
+
const thinking = message.role === "thinking";
|
|
524
|
+
const title = thinking ? texts.thinkingTitle : texts.toolTitle;
|
|
525
|
+
return /* @__PURE__ */ jsx9(Popover2, { open: true, onOpenChange: (open) => {
|
|
526
|
+
if (!open) onClose();
|
|
527
|
+
}, children: /* @__PURE__ */ jsxs2(
|
|
528
|
+
PopoverContent2,
|
|
529
|
+
{
|
|
530
|
+
align: "start",
|
|
531
|
+
anchor,
|
|
532
|
+
"aria-label": title,
|
|
533
|
+
className: cn(
|
|
534
|
+
"qsl:w-[min(680px,calc(100vw-32px))] qsl:gap-0 qsl:overflow-hidden qsl:p-0 qsl:text-[13px] qsl:leading-normal qsl:shadow-pop",
|
|
535
|
+
thinking ? "qsl:max-h-none" : "qsl:max-h-[min(70vh,620px)]"
|
|
536
|
+
),
|
|
537
|
+
collisionPadding: 16,
|
|
538
|
+
side: "bottom",
|
|
539
|
+
sideOffset: 10,
|
|
540
|
+
children: [
|
|
541
|
+
/* @__PURE__ */ jsxs2("div", { className: "qsl:flex qsl:flex-none qsl:items-center qsl:gap-2 qsl:border-b qsl:border-border-soft qsl:px-4 qsl:py-2.5", children: [
|
|
542
|
+
thinking ? /* @__PURE__ */ jsx9(SparklesIcon, { className: "qsl:text-muted-foreground", size: 14 }) : /* @__PURE__ */ jsx9(WrenchIcon, { className: tool?.isError ? "qsl:text-destructive" : "qsl:text-muted-foreground", size: 14 }),
|
|
543
|
+
/* @__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 }),
|
|
544
|
+
/* @__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, {}) })
|
|
545
|
+
] }),
|
|
546
|
+
/* @__PURE__ */ jsx9("div", { className: cn("qsl:min-h-0 qsl:p-4", thinking ? "qsl:overflow-visible" : "qsl:overflow-auto"), 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: [
|
|
547
|
+
/* @__PURE__ */ jsx9("div", { className: "qsl:font-mono qsl:text-sm", children: message.text }),
|
|
548
|
+
/* @__PURE__ */ jsx9(PopoverSection, { label: texts.argumentsLabel, value: toolArgumentsText(tool) }),
|
|
549
|
+
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) })
|
|
550
|
+
] }) : null })
|
|
551
|
+
]
|
|
552
|
+
}
|
|
553
|
+
) });
|
|
670
554
|
}
|
|
671
555
|
function PopoverSection({ label, value, error = false }) {
|
|
672
|
-
return /* @__PURE__ */
|
|
673
|
-
/* @__PURE__ */
|
|
674
|
-
/* @__PURE__ */
|
|
556
|
+
return /* @__PURE__ */ jsxs2("section", { children: [
|
|
557
|
+
/* @__PURE__ */ jsx9("h3", { className: "qsl:mb-1.5 qsl:text-[11px] qsl:font-semibold qsl:tracking-[0.04em] qsl:text-muted-foreground qsl:uppercase", children: label }),
|
|
558
|
+
/* @__PURE__ */ jsx9("pre", { className: cn(preClasses, error && "qsl:text-destructive"), children: value })
|
|
675
559
|
] });
|
|
676
560
|
}
|
|
677
561
|
|
|
678
|
-
// packages/quassel/src/
|
|
679
|
-
import {
|
|
680
|
-
import { jsx as
|
|
681
|
-
|
|
562
|
+
// packages/quassel/src/PendingActionCard.tsx
|
|
563
|
+
import { CheckIcon as CheckIcon2 } from "lucide-react";
|
|
564
|
+
import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
565
|
+
var cardClasses = "qsl:gap-2.5 qsl:bg-background qsl:px-3.5 qsl:text-sm";
|
|
566
|
+
var resultText = (result) => typeof result === "string" ? result : result === null || result === void 0 ? "" : JSON.stringify(result);
|
|
567
|
+
function PendingActionCard({
|
|
682
568
|
text,
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
569
|
+
action,
|
|
570
|
+
waitingLabel,
|
|
571
|
+
dismissLabel,
|
|
572
|
+
dismissedLabel,
|
|
573
|
+
onDismiss
|
|
686
574
|
}) {
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
/* @__PURE__ */
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
question.answer
|
|
575
|
+
const { Button: Button2, Card: Card2 } = useQuasselComponents();
|
|
576
|
+
if (action.status !== void 0) {
|
|
577
|
+
return /* @__PURE__ */ jsxs3(Card2, { className: cardClasses, size: "sm", children: [
|
|
578
|
+
/* @__PURE__ */ jsx10("div", { className: "qsl:leading-normal", children: text }),
|
|
579
|
+
/* @__PURE__ */ jsxs3("div", { className: "qsl:flex qsl:items-center qsl:gap-1.5 qsl:text-sm qsl:text-muted-foreground", "data-action": "resolved", children: [
|
|
580
|
+
/* @__PURE__ */ jsx10(CheckIcon2, { className: "qsl:text-success", size: 12 }),
|
|
581
|
+
action.status === "approved" ? resultText(action.result) : dismissedLabel
|
|
695
582
|
] })
|
|
696
583
|
] });
|
|
697
584
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
return /* @__PURE__ */ jsxs5("div", { className: "qsl-question", children: [
|
|
704
|
-
/* @__PURE__ */ jsx5("div", { className: "qsl-question__text", children: text }),
|
|
705
|
-
/* @__PURE__ */ jsx5("div", { className: "qsl-question__options", children: question.options.map(
|
|
706
|
-
(option) => question.multi ? /* @__PURE__ */ jsxs5(
|
|
707
|
-
"button",
|
|
708
|
-
{
|
|
709
|
-
"aria-pressed": gewaehlt.includes(option),
|
|
710
|
-
className: gewaehlt.includes(option) ? "qsl-question__option qsl-question__option--gewaehlt" : "qsl-question__option",
|
|
711
|
-
onClick: () => umschalten(option),
|
|
712
|
-
type: "button",
|
|
713
|
-
children: [
|
|
714
|
-
/* @__PURE__ */ jsx5("span", { className: "qsl-question__kasten", children: gewaehlt.includes(option) && /* @__PURE__ */ jsx5(IconCheck, { size: 10 }) }),
|
|
715
|
-
option
|
|
716
|
-
]
|
|
717
|
-
},
|
|
718
|
-
option
|
|
719
|
-
) : /* @__PURE__ */ jsx5("button", { className: "qsl-question__option", onClick: () => onAnswer(option), type: "button", children: option }, option)
|
|
720
|
-
) }),
|
|
721
|
-
question.multi && /* @__PURE__ */ jsx5(
|
|
722
|
-
"button",
|
|
723
|
-
{
|
|
724
|
-
className: "qsl-question__uebernehmen",
|
|
725
|
-
disabled: gewaehlt.length === 0,
|
|
726
|
-
onClick: () => onAnswer(gewaehlt.join("; ")),
|
|
727
|
-
type: "button",
|
|
728
|
-
children: texts.questionSubmit
|
|
729
|
-
}
|
|
730
|
-
),
|
|
731
|
-
/* @__PURE__ */ jsxs5("div", { className: "qsl-question__frei", children: [
|
|
732
|
-
/* @__PURE__ */ jsx5(
|
|
733
|
-
"input",
|
|
734
|
-
{
|
|
735
|
-
className: "qsl-field qsl-question__frei-feld",
|
|
736
|
-
onChange: (event) => setFrei(event.target.value),
|
|
737
|
-
onKeyDown: (event) => {
|
|
738
|
-
if (event.key === "Enter" && !event.nativeEvent.isComposing) freiSenden();
|
|
739
|
-
},
|
|
740
|
-
placeholder: texts.questionPlaceholder,
|
|
741
|
-
value: frei
|
|
742
|
-
}
|
|
743
|
-
),
|
|
744
|
-
/* @__PURE__ */ jsx5(
|
|
745
|
-
"button",
|
|
746
|
-
{
|
|
747
|
-
className: "qsl-question__frei-senden",
|
|
748
|
-
disabled: frei.trim() === "",
|
|
749
|
-
onClick: freiSenden,
|
|
750
|
-
type: "button",
|
|
751
|
-
children: texts.questionAnswer
|
|
752
|
-
}
|
|
753
|
-
)
|
|
585
|
+
return /* @__PURE__ */ jsxs3(Card2, { className: cardClasses, size: "sm", children: [
|
|
586
|
+
/* @__PURE__ */ jsx10("div", { className: "qsl:leading-normal", children: text }),
|
|
587
|
+
/* @__PURE__ */ jsxs3("div", { className: "qsl:flex qsl:items-center qsl:justify-between qsl:gap-2", children: [
|
|
588
|
+
/* @__PURE__ */ jsx10("span", { className: "qsl:text-sm qsl:text-muted-foreground", "data-action": "waiting", children: waitingLabel }),
|
|
589
|
+
onDismiss && /* @__PURE__ */ jsx10(Button2, { onClick: onDismiss, size: "sm", variant: "outline", children: dismissLabel })
|
|
754
590
|
] })
|
|
755
591
|
] });
|
|
756
592
|
}
|
|
757
593
|
|
|
758
594
|
// packages/quassel/src/WorkingScenes.tsx
|
|
759
|
-
import { useEffect as
|
|
760
|
-
import { jsx as
|
|
595
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
596
|
+
import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
761
597
|
var SPUR = 13;
|
|
762
598
|
var GRUNDTAKT_MS = 90;
|
|
763
599
|
var SZENENWECHSEL_MS = 2600;
|
|
@@ -802,6 +638,7 @@ var SZENEN = [
|
|
|
802
638
|
{
|
|
803
639
|
// Aus einem Samen wird ein Baum, dann von vorn.
|
|
804
640
|
id: "wachsen",
|
|
641
|
+
compact: true,
|
|
805
642
|
takt: 2,
|
|
806
643
|
zeichne: (schritt) => {
|
|
807
644
|
const stufen = ["\u{1F331}", "\u{1F331}", "\u{1F33F}", "\u{1F33F}", "\u{1F343}", "\u{1F333}", "\u{1F333}", "\u{1F333}"];
|
|
@@ -811,11 +648,12 @@ var SZENEN = [
|
|
|
811
648
|
{
|
|
812
649
|
// Das Ei bekommt Risse, das Kueken schluepft.
|
|
813
650
|
id: "schluepfen",
|
|
651
|
+
compact: true,
|
|
814
652
|
takt: 2,
|
|
815
653
|
zeichne: (schritt) => {
|
|
816
654
|
const takt = schritt % 12;
|
|
817
655
|
if (takt < 4) return "\u{1F95A}";
|
|
818
|
-
if (takt < 6) return "\u{1F95A}
|
|
656
|
+
if (takt < 6) return "\u{1F95A}*";
|
|
819
657
|
if (takt < 9) return "\u{1F423}";
|
|
820
658
|
return "\u{1F425}";
|
|
821
659
|
}
|
|
@@ -844,11 +682,12 @@ var SZENEN = [
|
|
|
844
682
|
{
|
|
845
683
|
// Ei, Kueken, Henne - und die legt das naechste Ei.
|
|
846
684
|
id: "huhn-kreislauf",
|
|
685
|
+
compact: true,
|
|
847
686
|
takt: 3,
|
|
848
687
|
zeichne: (schritt) => {
|
|
849
688
|
const takt = schritt % 16;
|
|
850
689
|
if (takt < 3) return "\u{1F95A}";
|
|
851
|
-
if (takt < 5) return "\u{1F95A}
|
|
690
|
+
if (takt < 5) return "\u{1F95A}*";
|
|
852
691
|
if (takt < 8) return "\u{1F423}";
|
|
853
692
|
if (takt < 11) return "\u{1F424}";
|
|
854
693
|
if (takt < 14) return "\u{1F414}";
|
|
@@ -858,11 +697,12 @@ var SZENEN = [
|
|
|
858
697
|
{
|
|
859
698
|
// Hut, Funken - und das Kaninchen tuermt.
|
|
860
699
|
id: "zauberhut",
|
|
700
|
+
compact: true,
|
|
861
701
|
takt: 3,
|
|
862
702
|
zeichne: (schritt) => {
|
|
863
703
|
const takt = schritt % 14;
|
|
864
704
|
if (takt < 4) return "\u{1F3A9}";
|
|
865
|
-
if (takt < 7) return "\u{1F3A9}
|
|
705
|
+
if (takt < 7) return "\u{1F3A9}*";
|
|
866
706
|
if (takt < 11) return "\u{1F3A9}\u{1F407}";
|
|
867
707
|
return "\u{1F407}\u{1F4A8}";
|
|
868
708
|
}
|
|
@@ -870,6 +710,7 @@ var SZENEN = [
|
|
|
870
710
|
{
|
|
871
711
|
// Rot, Gelb, Gruen - und ab.
|
|
872
712
|
id: "ampel",
|
|
713
|
+
compact: true,
|
|
873
714
|
takt: 3,
|
|
874
715
|
zeichne: (schritt) => {
|
|
875
716
|
const takt = schritt % 12;
|
|
@@ -882,6 +723,7 @@ var SZENEN = [
|
|
|
882
723
|
{
|
|
883
724
|
// Klingeln, verschlafen, losrennen.
|
|
884
725
|
id: "wecker",
|
|
726
|
+
compact: true,
|
|
885
727
|
takt: 2,
|
|
886
728
|
zeichne: (schritt) => {
|
|
887
729
|
const takt = schritt % 14;
|
|
@@ -901,34 +743,286 @@ var SZENEN = [
|
|
|
901
743
|
return bahn({ 4: "\u{1F389}" }, 5);
|
|
902
744
|
}
|
|
903
745
|
}
|
|
904
|
-
];
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
const
|
|
908
|
-
const [
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
return () =>
|
|
919
|
-
}, []);
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
746
|
+
];
|
|
747
|
+
var COMPACT_SCENES = SZENEN.filter((scene) => scene.compact);
|
|
748
|
+
function WorkingScenes({ label = "Arbeitet ...", compact = false }) {
|
|
749
|
+
const scenes = compact ? COMPACT_SCENES : SZENEN;
|
|
750
|
+
const [sceneIndex, setSceneIndex] = useState2(() => Math.floor(Math.random() * scenes.length));
|
|
751
|
+
const [offset, setOffset] = useState2(() => Math.floor(Math.random() * 20));
|
|
752
|
+
const [frameTick, setFrameTick] = useState2(0);
|
|
753
|
+
const [reducedMotion, setReducedMotion] = useState2(() => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
|
|
754
|
+
const scene = scenes[sceneIndex % scenes.length];
|
|
755
|
+
useEffect2(() => {
|
|
756
|
+
const preference = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
757
|
+
const update = () => setReducedMotion(preference.matches);
|
|
758
|
+
preference.addEventListener("change", update);
|
|
759
|
+
update();
|
|
760
|
+
return () => preference.removeEventListener("change", update);
|
|
761
|
+
}, []);
|
|
762
|
+
useEffect2(() => {
|
|
763
|
+
if (reducedMotion) return;
|
|
764
|
+
const timer = window.setInterval(() => {
|
|
765
|
+
setSceneIndex((previous) => (previous + 1 + Math.floor(Math.random() * (scenes.length - 1))) % scenes.length);
|
|
766
|
+
setOffset(Math.floor(Math.random() * 20));
|
|
767
|
+
setFrameTick(0);
|
|
768
|
+
}, SZENENWECHSEL_MS);
|
|
769
|
+
return () => window.clearInterval(timer);
|
|
770
|
+
}, [reducedMotion, scenes]);
|
|
771
|
+
useEffect2(() => {
|
|
772
|
+
if (reducedMotion) return;
|
|
773
|
+
const timer = window.setInterval(() => setFrameTick((value) => value + 1), GRUNDTAKT_MS);
|
|
774
|
+
return () => window.clearInterval(timer);
|
|
775
|
+
}, [reducedMotion]);
|
|
776
|
+
const frame = scene.zeichne(Math.floor(frameTick / scene.takt) + (scene.laufend ? offset : 0));
|
|
777
|
+
return /* @__PURE__ */ jsxs4("span", { "aria-label": label, className: cn("qsl:flex qsl:items-center qsl:gap-2.5 qsl:text-[13px]", compact ? "qsl:text-inherit" : "qsl:mt-4 qsl:text-foreground"), "data-chat": "working", role: "status", children: [
|
|
778
|
+
!compact && /* @__PURE__ */ jsx11("span", { "aria-hidden": "true", className: "qsl:text-muted-foreground qsl:opacity-70", children: ">" }),
|
|
779
|
+
/* @__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)
|
|
780
|
+
] });
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// packages/quassel/src/attachments.ts
|
|
784
|
+
var maxAttachmentSizeLabel = `${MAX_CHAT_ATTACHMENT_BYTES / (1024 * 1024)} MiB`;
|
|
785
|
+
function attachmentMediaType(file) {
|
|
786
|
+
return chatAttachmentMediaType(file.name, file.type);
|
|
787
|
+
}
|
|
788
|
+
function attachmentCapabilityError(files, capabilities, texts = defaultTexts) {
|
|
789
|
+
for (const file of files) {
|
|
790
|
+
const required = file.mediaType.startsWith("image/") ? "image" : file.mediaType.startsWith("video/") ? "video" : file.mediaType === "application/pdf" ? "file" : void 0;
|
|
791
|
+
if (required && !capabilities.input.includes(required)) {
|
|
792
|
+
const kind = required === "image" ? texts.attachmentKindImage : required === "video" ? texts.attachmentKindVideo : texts.attachmentKindPdf;
|
|
793
|
+
if (!capabilities.model) return fillText(texts.attachmentUnsupported, { name: file.name, kind });
|
|
794
|
+
return fillText(texts.attachmentUnsupportedByModel, { model: capabilities.model, name: file.name, kind });
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
function validateAttachmentSelection(existing, incoming, texts = defaultTexts) {
|
|
799
|
+
if (existing.length + incoming.length > MAX_CHAT_ATTACHMENTS) {
|
|
800
|
+
throw new Error(fillText(texts.tooManyAttachments, { count: MAX_CHAT_ATTACHMENTS }));
|
|
801
|
+
}
|
|
802
|
+
if ([...existing, ...incoming].reduce((total, file) => total + file.size, 0) > MAX_CHAT_ATTACHMENT_BYTES) {
|
|
803
|
+
throw new Error(fillText(texts.attachmentsTooLarge, { size: maxAttachmentSizeLabel }));
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
async function encodeAttachment(file) {
|
|
807
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
808
|
+
const chunks = [];
|
|
809
|
+
for (let offset = 0; offset < bytes.length; offset += 8192) {
|
|
810
|
+
chunks.push(String.fromCharCode(...bytes.subarray(offset, offset + 8192)));
|
|
811
|
+
}
|
|
812
|
+
return { name: file.name, mediaType: attachmentMediaType(file), data: btoa(chunks.join("")) };
|
|
813
|
+
}
|
|
814
|
+
function formatAttachmentSize(bytes) {
|
|
815
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
816
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
817
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
818
|
+
}
|
|
819
|
+
function attachmentDownloadUrl(url) {
|
|
820
|
+
if (/^(data|blob):/i.test(url)) return url;
|
|
821
|
+
const [resource, fragment] = url.split(/#(.*)/s);
|
|
822
|
+
return `${resource}${resource.includes("?") ? "&" : "?"}download=1${fragment === void 0 ? "" : `#${fragment}`}`;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// packages/quassel/src/ChatMessages.tsx
|
|
826
|
+
import { CheckIcon as CheckIcon4, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, LayersIcon, SparklesIcon as SparklesIcon2, WrenchIcon as WrenchIcon2 } from "lucide-react";
|
|
827
|
+
|
|
828
|
+
// packages/quassel/src/options.ts
|
|
829
|
+
var cssLength = (value) => typeof value === "number" ? `${value}px` : value;
|
|
830
|
+
var appearanceStyle = (appearance) => ({
|
|
831
|
+
"--qsl-chat-font-size": cssLength(appearance?.fontSize),
|
|
832
|
+
"--qsl-chat-line-height": appearance?.lineHeight,
|
|
833
|
+
"--qsl-chat-message-gap": cssLength(appearance?.messageGap),
|
|
834
|
+
"--qsl-chat-dense-message-gap": cssLength(appearance?.denseMessageGap)
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
// packages/quassel/src/timestamps.ts
|
|
838
|
+
function messageDate(at) {
|
|
839
|
+
if (!at) return void 0;
|
|
840
|
+
const date = new Date(at);
|
|
841
|
+
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
842
|
+
}
|
|
843
|
+
function timestampLabel(date, options = {}, now = Date.now()) {
|
|
844
|
+
if ((!options.format || options.format === "time") && !options.locale && !options.timeZone) {
|
|
845
|
+
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
846
|
+
}
|
|
847
|
+
if (options.format === "relative") {
|
|
848
|
+
const seconds = (date.getTime() - now) / 1e3;
|
|
849
|
+
const units = [
|
|
850
|
+
["year", 365 * 86400],
|
|
851
|
+
["month", 30 * 86400],
|
|
852
|
+
["day", 86400],
|
|
853
|
+
["hour", 3600],
|
|
854
|
+
["minute", 60]
|
|
855
|
+
];
|
|
856
|
+
const [unit, divisor] = units.find(([, value]) => Math.abs(seconds) >= value) ?? ["second", 1];
|
|
857
|
+
return new Intl.RelativeTimeFormat(options.locale, { numeric: "auto" }).format(Math.round(seconds / divisor), unit);
|
|
858
|
+
}
|
|
859
|
+
return new Intl.DateTimeFormat(options.locale, {
|
|
860
|
+
timeZone: options.timeZone,
|
|
861
|
+
hour: "2-digit",
|
|
862
|
+
minute: "2-digit",
|
|
863
|
+
hourCycle: "h23",
|
|
864
|
+
...options.format === "date-time" ? { year: "numeric", month: "2-digit", day: "2-digit" } : {}
|
|
865
|
+
}).format(date);
|
|
866
|
+
}
|
|
867
|
+
function timestampDay(date, options = {}) {
|
|
868
|
+
const key = new Intl.DateTimeFormat("en-CA", {
|
|
869
|
+
timeZone: options.timeZone,
|
|
870
|
+
year: "numeric",
|
|
871
|
+
month: "2-digit",
|
|
872
|
+
day: "2-digit"
|
|
873
|
+
}).format(date);
|
|
874
|
+
const label = new Intl.DateTimeFormat(options.locale, {
|
|
875
|
+
timeZone: options.timeZone,
|
|
876
|
+
dateStyle: "long"
|
|
877
|
+
}).format(date);
|
|
878
|
+
return { key, label };
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// packages/quassel/src/MessageActions.tsx
|
|
882
|
+
import { CheckIcon as CheckIcon3, CopyIcon as CopyIcon2, PencilIcon, RotateCcwIcon } from "lucide-react";
|
|
883
|
+
import { useState as useState3 } from "react";
|
|
884
|
+
import { Fragment, jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
885
|
+
var actionClasses = cn(
|
|
886
|
+
"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",
|
|
887
|
+
"qsl:shadow-[0_2px_6px_color-mix(in_srgb,var(--qsl-foreground)_15%,transparent)]",
|
|
888
|
+
"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",
|
|
889
|
+
"qsl:[@media(hover:none)]:pointer-events-auto qsl:[@media(hover:none)]:opacity-100"
|
|
890
|
+
);
|
|
891
|
+
function MessageActions({ message, options, texts }) {
|
|
892
|
+
const action = useChatAction();
|
|
893
|
+
const { Button: Button2 } = useQuasselComponents();
|
|
894
|
+
const [lastAction, setLastAction] = useState3();
|
|
895
|
+
const copy = typeof options?.copy === "function" ? options.copy(message) : options?.copy ?? message.role === "user";
|
|
896
|
+
const actions = [
|
|
897
|
+
...copy && message.text.trim() ? [{ id: "copy", label: texts.copyMessage, icon: /* @__PURE__ */ jsx12(CopyIcon2, {}), onClick: () => copyChatText(message.text, texts.clipboardUnavailable) }] : [],
|
|
898
|
+
...options?.edit && message.role === "user" ? [{ id: "edit", label: texts.editMessage, icon: /* @__PURE__ */ jsx12(PencilIcon, {}), onClick: options.edit }] : [],
|
|
899
|
+
...options?.retry && message.role === "assistant" ? [{ id: "retry", label: texts.retryMessage, icon: /* @__PURE__ */ jsx12(RotateCcwIcon, {}), onClick: options.retry }] : [],
|
|
900
|
+
...options?.custom?.(message) ?? []
|
|
901
|
+
];
|
|
902
|
+
return /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
903
|
+
actions.length > 0 && /* @__PURE__ */ jsx12("div", { className: actionClasses, children: actions.map((item) => {
|
|
904
|
+
const copied = item.id === "copy" && lastAction === item.id && action.status === "done";
|
|
905
|
+
const label = copied ? texts.copied : item.label;
|
|
906
|
+
return /* @__PURE__ */ jsx12(
|
|
907
|
+
Button2,
|
|
908
|
+
{
|
|
909
|
+
"aria-label": label,
|
|
910
|
+
className: "qsl:text-inherit",
|
|
911
|
+
disabled: item.disabled || action.status === "pending",
|
|
912
|
+
size: item.icon ? "icon-sm" : "sm",
|
|
913
|
+
title: label,
|
|
914
|
+
variant: "ghost",
|
|
915
|
+
onClick: () => {
|
|
916
|
+
setLastAction(item.id);
|
|
917
|
+
void action.invoke(() => item.onClick(message), texts.actionFailed);
|
|
918
|
+
},
|
|
919
|
+
children: copied ? /* @__PURE__ */ jsx12(CheckIcon3, {}) : item.icon ?? item.label
|
|
920
|
+
},
|
|
921
|
+
item.id
|
|
922
|
+
);
|
|
923
|
+
}) }),
|
|
924
|
+
action.error && /* @__PURE__ */ jsx12("p", { className: "qsl:mt-2 qsl:text-sm qsl:text-destructive", role: "alert", children: action.error })
|
|
925
|
+
] });
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// packages/quassel/src/ChatSendContext.ts
|
|
929
|
+
import { createContext as createContext3 } from "react";
|
|
930
|
+
var ChatSendContext = createContext3(void 0);
|
|
931
|
+
function createChatSendScope(enabled) {
|
|
932
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
933
|
+
return {
|
|
934
|
+
notify: () => {
|
|
935
|
+
if (enabled.current) listeners.forEach((listener) => listener());
|
|
936
|
+
},
|
|
937
|
+
subscribe: (listener) => {
|
|
938
|
+
listeners.add(listener);
|
|
939
|
+
return () => {
|
|
940
|
+
listeners.delete(listener);
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// packages/quassel/src/announce.ts
|
|
947
|
+
var abstandMs = 400;
|
|
948
|
+
var hoechstensWartend = 5;
|
|
949
|
+
var region;
|
|
950
|
+
var wartend = [];
|
|
951
|
+
var laeuft = false;
|
|
952
|
+
function ansageRegion() {
|
|
953
|
+
if (region?.isConnected) {
|
|
954
|
+
return region;
|
|
955
|
+
}
|
|
956
|
+
const element = document.createElement("div");
|
|
957
|
+
element.setAttribute("role", "status");
|
|
958
|
+
element.setAttribute("aria-live", "polite");
|
|
959
|
+
element.setAttribute("aria-atomic", "true");
|
|
960
|
+
Object.assign(element.style, {
|
|
961
|
+
position: "absolute",
|
|
962
|
+
width: "1px",
|
|
963
|
+
height: "1px",
|
|
964
|
+
margin: "-1px",
|
|
965
|
+
padding: "0",
|
|
966
|
+
border: "0",
|
|
967
|
+
overflow: "hidden",
|
|
968
|
+
clipPath: "inset(50%)",
|
|
969
|
+
whiteSpace: "nowrap"
|
|
970
|
+
});
|
|
971
|
+
document.body.append(element);
|
|
972
|
+
region = element;
|
|
973
|
+
return element;
|
|
974
|
+
}
|
|
975
|
+
function naechste() {
|
|
976
|
+
if (wartend.length === 0) {
|
|
977
|
+
laeuft = false;
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
if (document.visibilityState === "hidden") {
|
|
981
|
+
document.addEventListener("visibilitychange", naechste, { once: true });
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
const [text, ...rest] = wartend;
|
|
985
|
+
wartend = rest;
|
|
986
|
+
const ziel = ansageRegion();
|
|
987
|
+
ziel.textContent = ziel.textContent === text ? `${text}\xA0` : text;
|
|
988
|
+
window.setTimeout(naechste, abstandMs);
|
|
989
|
+
}
|
|
990
|
+
function announce(text) {
|
|
991
|
+
const inhalt = text.trim();
|
|
992
|
+
if (!inhalt || typeof document === "undefined") {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
wartend = [...wartend, inhalt].slice(-hoechstensWartend);
|
|
996
|
+
if (laeuft) {
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
laeuft = true;
|
|
1000
|
+
ansageRegion();
|
|
1001
|
+
window.setTimeout(naechste, abstandMs);
|
|
926
1002
|
}
|
|
927
1003
|
|
|
928
1004
|
// packages/quassel/src/ChatMessages.tsx
|
|
929
|
-
import { Fragment as
|
|
1005
|
+
import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1006
|
+
var stepClasses = "qsl:mt-[var(--qsl-chat-message-gap,16px)]";
|
|
1007
|
+
var denseStepClasses = "qsl:mt-[var(--qsl-chat-dense-message-gap,8px)]";
|
|
1008
|
+
var traceClasses = "qsl:flex qsl:w-full qsl:gap-2 qsl:pl-6 qsl:text-left qsl:text-[11px] qsl:leading-[1.375] qsl:text-muted-foreground";
|
|
1009
|
+
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";
|
|
1010
|
+
var traceIconClasses = "qsl:mt-0.5 qsl:flex-none qsl:opacity-60";
|
|
1011
|
+
var traceErrorIconClasses = "qsl:mt-0.5 qsl:flex-none qsl:text-destructive qsl:opacity-100";
|
|
1012
|
+
var traceLineClasses = "qsl:min-w-0 qsl:self-baseline qsl:font-mono qsl:[overflow-wrap:anywhere]";
|
|
1013
|
+
var traceRunningClasses = "qsl:ml-1.5 qsl:opacity-60";
|
|
1014
|
+
var chipClasses = cn(
|
|
1015
|
+
"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",
|
|
1016
|
+
"qsl:font-mono qsl:text-[10.5px] qsl:leading-[1.6] qsl:text-muted-foreground qsl:transition-colors",
|
|
1017
|
+
"qsl:data-[state=running]:border-[color-mix(in_srgb,var(--qsl-primary)_40%,var(--qsl-border-soft))]",
|
|
1018
|
+
"qsl:data-[state=thinking]:border-[color-mix(in_srgb,var(--qsl-primary)_40%,var(--qsl-border-soft))]",
|
|
1019
|
+
"qsl:data-[state=error]:border-[color-mix(in_srgb,var(--qsl-destructive)_45%,var(--qsl-border-soft))]",
|
|
1020
|
+
"qsl:data-[state=done]:opacity-85"
|
|
1021
|
+
);
|
|
1022
|
+
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";
|
|
930
1023
|
var collapseAtBottomFrom = 11;
|
|
931
1024
|
var hoechstensAnsagenAufEinmal = 2;
|
|
1025
|
+
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";
|
|
932
1026
|
function istSchritt(message) {
|
|
933
1027
|
return !!message && (message.role === "thinking" || message.role === "tool");
|
|
934
1028
|
}
|
|
@@ -937,22 +1031,25 @@ function defaultArgumentsText(tool) {
|
|
|
937
1031
|
}
|
|
938
1032
|
function ChatMessages({
|
|
939
1033
|
messages,
|
|
940
|
-
|
|
1034
|
+
owner,
|
|
1035
|
+
detailMode = "current",
|
|
941
1036
|
running = false,
|
|
942
1037
|
working,
|
|
943
|
-
workingTimeoutMs = 2e4,
|
|
944
1038
|
stepsExpandable = true,
|
|
945
1039
|
texts,
|
|
946
1040
|
toolArgumentsText = defaultArgumentsText,
|
|
947
1041
|
renderTool,
|
|
948
|
-
|
|
1042
|
+
renderAction,
|
|
1043
|
+
onDismissAction,
|
|
949
1044
|
onLinkClick,
|
|
950
|
-
announce: announce2,
|
|
951
1045
|
emptyState,
|
|
952
1046
|
className,
|
|
953
1047
|
showTimestamps = false,
|
|
954
|
-
|
|
1048
|
+
announce: announce2,
|
|
955
1049
|
scrollerRef,
|
|
1050
|
+
maxWidth,
|
|
1051
|
+
horizontalPadding,
|
|
1052
|
+
jumpToEndThreshold = 120,
|
|
956
1053
|
appearance,
|
|
957
1054
|
timestampOptions,
|
|
958
1055
|
codeBlockOptions,
|
|
@@ -960,33 +1057,24 @@ function ChatMessages({
|
|
|
960
1057
|
messageActions
|
|
961
1058
|
}) {
|
|
962
1059
|
const alleTexte = { ...defaultTexts, ...texts };
|
|
963
|
-
const
|
|
964
|
-
const
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const relative = showTimestamps && timestampOptions?.format === "relative";
|
|
975
|
-
useEffect5(() => {
|
|
976
|
-
if (!relative) return;
|
|
1060
|
+
const { Button: Button2 } = useQuasselComponents();
|
|
1061
|
+
const scrollBereich = useRef3(null);
|
|
1062
|
+
const threadRef = useRef3(null);
|
|
1063
|
+
const scrollbarDragging = useRef3(false);
|
|
1064
|
+
const touchY = useRef3(void 0);
|
|
1065
|
+
const [showJumpToEnd, setShowJumpToEnd] = useState4(false);
|
|
1066
|
+
const [scroll] = useState4(() => createChatScroll());
|
|
1067
|
+
const sendScope = useContext3(ChatSendContext);
|
|
1068
|
+
const [now, setNow] = useState4(() => Date.now());
|
|
1069
|
+
useEffect3(() => {
|
|
1070
|
+
if (!showTimestamps || timestampOptions?.format !== "relative") return;
|
|
977
1071
|
setNow(Date.now());
|
|
978
|
-
const timer = window.setInterval(() => setNow(Date.now()),
|
|
1072
|
+
const timer = window.setInterval(() => setNow(Date.now()), 15e3);
|
|
979
1073
|
return () => window.clearInterval(timer);
|
|
980
|
-
}, [
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
const timer = window.setTimeout(() => setStromAktiv(false), workingTimeoutMs);
|
|
985
|
-
return () => window.clearTimeout(timer);
|
|
986
|
-
}, [messages, workingTimeoutMs]);
|
|
987
|
-
const angesagt = useRef5(new Set(messages.map((message) => message.key)));
|
|
988
|
-
useEffect5(() => {
|
|
989
|
-
const faellig = messages.filter((message, index) => !angesagt.current.has(message.key) && (message.role === "question" ? message.question !== void 0 && message.question.answer === void 0 : message.role === "assistant" && message.text.trim() !== "" && (message.closed === true || !running && index === messages.length - 1)));
|
|
1074
|
+
}, [showTimestamps, timestampOptions?.format]);
|
|
1075
|
+
const angesagt = useRef3(new Set(messages.map((message) => message.key)));
|
|
1076
|
+
useEffect3(() => {
|
|
1077
|
+
const faellig = messages.filter((message, index) => !angesagt.current.has(message.key) && (message.role === "action" ? message.action !== void 0 && message.action.status === void 0 : message.role === "assistant" && message.text.trim() !== "" && (message.closed === true || !running && index === messages.length - 1)));
|
|
990
1078
|
if (faellig.length === 0) {
|
|
991
1079
|
return;
|
|
992
1080
|
}
|
|
@@ -994,68 +1082,92 @@ function ChatMessages({
|
|
|
994
1082
|
if (announce2 === false || faellig.length > hoechstensAnsagenAufEinmal) {
|
|
995
1083
|
return;
|
|
996
1084
|
}
|
|
997
|
-
faellig.map((message) => announce2 ? announce2({ kind: message.role === "
|
|
1085
|
+
faellig.map((message) => announce2 ? announce2({ kind: message.role === "action" ? "action" : "reply", message }) : markdownPlainText(message.text)).forEach((text) => text && announce(text));
|
|
998
1086
|
}, [messages, running, announce2]);
|
|
1087
|
+
const updateJumpVisibility = useCallback2((viewport) => {
|
|
1088
|
+
if (viewport.clientHeight > 0) setShowJumpToEnd(viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop > jumpToEndThreshold);
|
|
1089
|
+
}, [jumpToEndThreshold]);
|
|
1090
|
+
useLayoutEffect(() => sendScope?.subscribe(() => {
|
|
1091
|
+
const viewport = scrollBereich.current;
|
|
1092
|
+
if (!viewport) return;
|
|
1093
|
+
scroll.jump(viewport);
|
|
1094
|
+
updateJumpVisibility(viewport);
|
|
1095
|
+
}), [sendScope, scroll, updateJumpVisibility]);
|
|
1096
|
+
const pauseUp = (viewport, target) => {
|
|
1097
|
+
let element = target instanceof Element ? target : null;
|
|
1098
|
+
while (element && element !== viewport) {
|
|
1099
|
+
if (element.scrollTop > 0 && /auto|scroll/.test(getComputedStyle(element).overflowY)) return;
|
|
1100
|
+
element = element.parentElement;
|
|
1101
|
+
}
|
|
1102
|
+
scroll.pause(viewport);
|
|
1103
|
+
};
|
|
999
1104
|
const pruefeEnde = useCallback2(() => {
|
|
1000
1105
|
const bereich = scrollBereich.current;
|
|
1001
1106
|
if (bereich) {
|
|
1002
|
-
|
|
1003
|
-
|
|
1107
|
+
scroll.scroll(bereich, scrollbarDragging.current);
|
|
1108
|
+
updateJumpVisibility(bereich);
|
|
1004
1109
|
}
|
|
1005
|
-
}, [
|
|
1006
|
-
|
|
1110
|
+
}, [scroll, updateJumpVisibility]);
|
|
1111
|
+
useEffect3(() => {
|
|
1112
|
+
const finishDrag = () => {
|
|
1113
|
+
if (scrollbarDragging.current && scrollBereich.current) scroll.scroll(scrollBereich.current, true);
|
|
1114
|
+
scrollbarDragging.current = false;
|
|
1115
|
+
};
|
|
1116
|
+
window.addEventListener("pointerup", finishDrag);
|
|
1117
|
+
window.addEventListener("pointercancel", finishDrag);
|
|
1118
|
+
return () => {
|
|
1119
|
+
window.removeEventListener("pointerup", finishDrag);
|
|
1120
|
+
window.removeEventListener("pointercancel", finishDrag);
|
|
1121
|
+
};
|
|
1122
|
+
}, [scroll]);
|
|
1123
|
+
useLayoutEffect(() => {
|
|
1007
1124
|
const bereich = scrollBereich.current;
|
|
1008
|
-
if (!bereich)
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
pruefeEnde();
|
|
1016
|
-
}
|
|
1017
|
-
});
|
|
1125
|
+
if (!bereich) return;
|
|
1126
|
+
const align = () => {
|
|
1127
|
+
scroll.layout(bereich, scrollbarDragging.current);
|
|
1128
|
+
updateJumpVisibility(bereich);
|
|
1129
|
+
};
|
|
1130
|
+
align();
|
|
1131
|
+
const beobachter = new ResizeObserver(align);
|
|
1018
1132
|
beobachter.observe(bereich);
|
|
1019
|
-
|
|
1020
|
-
beobachter.observe(kind);
|
|
1021
|
-
}
|
|
1133
|
+
if (threadRef.current) beobachter.observe(threadRef.current, { box: "border-box" });
|
|
1022
1134
|
return () => beobachter.disconnect();
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
}
|
|
1040
|
-
[messages, detailMode]
|
|
1135
|
+
});
|
|
1136
|
+
const visible = useMemo3(
|
|
1137
|
+
() => {
|
|
1138
|
+
const letzterFremder = messages.reduce((last, message, index) => message.role === "user" ? last : index, -1);
|
|
1139
|
+
return messages.filter((message, index) => {
|
|
1140
|
+
if (detailMode === "current" && istSchritt(message)) {
|
|
1141
|
+
return running && index === letzterFremder && (message.role === "thinking" ? !message.closed : stepState(message) === "running");
|
|
1142
|
+
}
|
|
1143
|
+
if ((message.role === "thinking" || message.role === "assistant") && message.text.trim() === "" && !message.attachments?.length) {
|
|
1144
|
+
return false;
|
|
1145
|
+
}
|
|
1146
|
+
if (message.role === "thinking" || message.role === "tool") {
|
|
1147
|
+
return detailMode !== "off";
|
|
1148
|
+
}
|
|
1149
|
+
return true;
|
|
1150
|
+
});
|
|
1151
|
+
},
|
|
1152
|
+
[messages, detailMode, running]
|
|
1041
1153
|
);
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
/* @__PURE__ */
|
|
1048
|
-
/* @__PURE__ */ jsx7("div", { className: "qsl-line-body", children: inhalt })
|
|
1154
|
+
const mitZeit = (inhalt, key, at, dicht = false) => showTimestamps ? /* @__PURE__ */ jsxs6("div", { className: cn("qsl:flex qsl:items-baseline qsl:gap-2", dicht ? denseStepClasses : stepClasses), children: [
|
|
1155
|
+
/* @__PURE__ */ jsx13("time", { dateTime: messageDate(at)?.toISOString(), className: cn(
|
|
1156
|
+
"qsl:flex-none qsl:text-[10.5px] qsl:tabular-nums qsl:text-muted-foreground qsl:opacity-85",
|
|
1157
|
+
timestampOptions?.format && timestampOptions.format !== "time" ? "qsl:w-[clamp(60px,20%,120px)]" : "qsl:w-[34px]"
|
|
1158
|
+
), children: messageDate(at) ? timestampLabel(messageDate(at), timestampOptions, now) : "" }),
|
|
1159
|
+
/* @__PURE__ */ jsx13("div", { className: "qsl:min-w-0 qsl:flex-1 qsl:[&>*:first-child]:mt-0", children: inhalt })
|
|
1049
1160
|
] }, `zeit-${key}`) : inhalt;
|
|
1050
|
-
const chipModus = detailMode === "chips" || detailMode === "icons";
|
|
1161
|
+
const chipModus = detailMode === "current" || detailMode === "chips" || detailMode === "icons";
|
|
1051
1162
|
const gruppenModus = detailMode === "grouped";
|
|
1052
1163
|
const bloecke = [];
|
|
1164
|
+
let lastDay;
|
|
1053
1165
|
let gruppe = [];
|
|
1054
1166
|
const schliesseGruppe = () => {
|
|
1055
1167
|
if (gruppe.length > 0) {
|
|
1056
1168
|
const letzterZustand = stepState(gruppe[gruppe.length - 1]);
|
|
1057
1169
|
bloecke.push(mitZeit(
|
|
1058
|
-
gruppenModus ? /* @__PURE__ */
|
|
1170
|
+
gruppenModus ? /* @__PURE__ */ jsx13(
|
|
1059
1171
|
StepGroup,
|
|
1060
1172
|
{
|
|
1061
1173
|
aktiv: running && (letzterZustand === "running" || letzterZustand === "thinking"),
|
|
@@ -1065,14 +1177,15 @@ function ChatMessages({
|
|
|
1065
1177
|
toolArgumentsText
|
|
1066
1178
|
},
|
|
1067
1179
|
gruppe[0].key
|
|
1068
|
-
) : /* @__PURE__ */
|
|
1180
|
+
) : /* @__PURE__ */ jsx13(
|
|
1069
1181
|
StepRow,
|
|
1070
1182
|
{
|
|
1071
1183
|
expandierbar: stepsExpandable,
|
|
1072
1184
|
messages: gruppe,
|
|
1073
|
-
mitText: detailMode
|
|
1185
|
+
mitText: detailMode !== "icons",
|
|
1074
1186
|
texts: alleTexte,
|
|
1075
|
-
toolArgumentsText
|
|
1187
|
+
toolArgumentsText,
|
|
1188
|
+
toolLabel: detailMode === "current" && !stepsExpandable ? alleTexte.currentTool : void 0
|
|
1076
1189
|
},
|
|
1077
1190
|
gruppe[0].key
|
|
1078
1191
|
),
|
|
@@ -1082,22 +1195,21 @@ function ChatMessages({
|
|
|
1082
1195
|
gruppe = [];
|
|
1083
1196
|
}
|
|
1084
1197
|
};
|
|
1085
|
-
let currentDay;
|
|
1086
1198
|
visible.forEach((message, index) => {
|
|
1087
|
-
const date = timestampOptions?.showDaySeparators ?
|
|
1199
|
+
const date = timestampOptions?.showDaySeparators ? messageDate(message.at) : void 0;
|
|
1088
1200
|
if (date) {
|
|
1089
|
-
const day = timestampDay(date, timestampOptions
|
|
1090
|
-
if (day !==
|
|
1201
|
+
const day = timestampDay(date, timestampOptions);
|
|
1202
|
+
if (day.key !== lastDay) {
|
|
1091
1203
|
schliesseGruppe();
|
|
1092
|
-
bloecke.push(/* @__PURE__ */
|
|
1093
|
-
|
|
1204
|
+
bloecke.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}`));
|
|
1205
|
+
lastDay = day.key;
|
|
1094
1206
|
}
|
|
1095
1207
|
}
|
|
1096
|
-
const eigeneDarstellung = message.role === "tool" && message.tool ? renderTool?.(message.tool) : void 0;
|
|
1208
|
+
const eigeneDarstellung = message.role === "tool" && message.tool && detailMode !== "current" ? renderTool?.(message.tool) : void 0;
|
|
1097
1209
|
if (eigeneDarstellung !== void 0) {
|
|
1098
1210
|
schliesseGruppe();
|
|
1099
1211
|
bloecke.push(mitZeit(
|
|
1100
|
-
/* @__PURE__ */
|
|
1212
|
+
/* @__PURE__ */ jsx13("div", { className: stepClasses, children: eigeneDarstellung }, message.key),
|
|
1101
1213
|
message.key,
|
|
1102
1214
|
message.at
|
|
1103
1215
|
));
|
|
@@ -1108,77 +1220,134 @@ function ChatMessages({
|
|
|
1108
1220
|
return;
|
|
1109
1221
|
}
|
|
1110
1222
|
schliesseGruppe();
|
|
1223
|
+
const dicht = istSchritt(message) && istSchritt(visible[index - 1]);
|
|
1111
1224
|
bloecke.push(mitZeit(
|
|
1112
|
-
/* @__PURE__ */
|
|
1225
|
+
/* @__PURE__ */ jsx13(
|
|
1113
1226
|
Bubble,
|
|
1114
1227
|
{
|
|
1115
1228
|
detailMode,
|
|
1116
|
-
dicht
|
|
1229
|
+
dicht,
|
|
1117
1230
|
expandierbar: stepsExpandable,
|
|
1118
1231
|
message,
|
|
1119
|
-
|
|
1120
|
-
messageActions,
|
|
1232
|
+
plain: owner != null && message.sender === owner,
|
|
1121
1233
|
texts: alleTexte,
|
|
1122
1234
|
toolArgumentsText,
|
|
1123
|
-
|
|
1235
|
+
renderAction,
|
|
1236
|
+
onDismissAction,
|
|
1237
|
+
bubbleOptions,
|
|
1238
|
+
messageActions
|
|
1124
1239
|
},
|
|
1125
1240
|
message.key
|
|
1126
1241
|
),
|
|
1127
1242
|
message.key,
|
|
1128
|
-
message.at
|
|
1243
|
+
message.at,
|
|
1244
|
+
dicht
|
|
1129
1245
|
));
|
|
1130
1246
|
});
|
|
1131
1247
|
schliesseGruppe();
|
|
1132
|
-
const verlauf = /* @__PURE__ */ jsxs6("div", { className:
|
|
1133
|
-
|
|
1248
|
+
const verlauf = /* @__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: {
|
|
1249
|
+
...appearanceStyle(appearance),
|
|
1250
|
+
"--qsl-chat-content-max-width": typeof maxWidth === "number" ? `${maxWidth}px` : maxWidth,
|
|
1251
|
+
"--qsl-chat-horizontal-padding": typeof horizontalPadding === "number" ? `${horizontalPadding}px` : horizontalPadding
|
|
1252
|
+
}, children: [
|
|
1253
|
+
/* @__PURE__ */ jsx13(
|
|
1134
1254
|
"div",
|
|
1135
1255
|
{
|
|
1136
|
-
|
|
1256
|
+
"aria-label": alleTexte.transcript,
|
|
1257
|
+
className: cn(
|
|
1258
|
+
"qsl:min-h-0 qsl:flex-1 qsl:cursor-default qsl:overflow-y-auto qsl:text-[length:var(--qsl-chat-font-size,13px)] qsl:text-foreground",
|
|
1259
|
+
"qsl:[--qsl-fade-edge:calc(100%_-_var(--qsl-composer-height,0px))]",
|
|
1260
|
+
"qsl:[-webkit-mask-image:linear-gradient(to_bottom,#000_calc(var(--qsl-fade-edge)_-_40px),transparent_calc(var(--qsl-fade-edge)_+_16px))]",
|
|
1261
|
+
"qsl:[mask-image:linear-gradient(to_bottom,#000_calc(var(--qsl-fade-edge)_-_40px),transparent_calc(var(--qsl-fade-edge)_+_16px))]",
|
|
1262
|
+
"qsl:in-data-[tone=material]:p-0"
|
|
1263
|
+
),
|
|
1137
1264
|
onScroll: pruefeEnde,
|
|
1265
|
+
onWheel: (event) => {
|
|
1266
|
+
if (!event.ctrlKey && event.deltaY < 0) pauseUp(event.currentTarget, event.target);
|
|
1267
|
+
},
|
|
1268
|
+
onKeyDown: (event) => {
|
|
1269
|
+
const editable = event.target instanceof Element && event.target.closest("input, textarea, select, [contenteditable=true]");
|
|
1270
|
+
if (!editable && (["ArrowUp", "PageUp", "Home"].includes(event.key) || event.target === event.currentTarget && event.key === " " && event.shiftKey)) pauseUp(event.currentTarget, event.target);
|
|
1271
|
+
},
|
|
1272
|
+
onPointerDown: (event) => {
|
|
1273
|
+
const viewport = event.currentTarget;
|
|
1274
|
+
scrollbarDragging.current = event.clientX >= viewport.getBoundingClientRect().right - Math.max(16, viewport.offsetWidth - viewport.clientWidth);
|
|
1275
|
+
},
|
|
1276
|
+
onTouchStart: (event) => {
|
|
1277
|
+
touchY.current = event.touches[0]?.clientY;
|
|
1278
|
+
},
|
|
1279
|
+
onTouchMove: (event) => {
|
|
1280
|
+
const next = event.touches[0]?.clientY;
|
|
1281
|
+
if (next !== void 0 && touchY.current !== void 0 && next > touchY.current) pauseUp(event.currentTarget, event.target);
|
|
1282
|
+
touchY.current = next;
|
|
1283
|
+
},
|
|
1284
|
+
role: "region",
|
|
1285
|
+
tabIndex: 0,
|
|
1138
1286
|
ref: (element) => {
|
|
1139
1287
|
scrollBereich.current = element;
|
|
1140
1288
|
scrollerRef?.(element);
|
|
1141
1289
|
},
|
|
1142
|
-
children: visible.length === 0 && !running ? emptyState ?? null : /* @__PURE__ */ jsxs6(
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1290
|
+
children: visible.length === 0 && !running ? emptyState ?? null : /* @__PURE__ */ jsxs6(
|
|
1291
|
+
"div",
|
|
1292
|
+
{
|
|
1293
|
+
"aria-busy": running || void 0,
|
|
1294
|
+
className: cn(
|
|
1295
|
+
"qsl:mx-auto qsl:w-[calc(100%_-_2_*_var(--qsl-chat-horizontal-padding,24px))] qsl:max-w-[var(--qsl-chat-content-max-width,none)] qsl:pt-2",
|
|
1296
|
+
"qsl:pb-[calc(var(--qsl-composer-height,0px)_+_max(3.25em,40px))]",
|
|
1297
|
+
"qsl:in-data-[tone=material]:[&>*:first-child]:mt-0"
|
|
1298
|
+
),
|
|
1299
|
+
ref: threadRef,
|
|
1300
|
+
children: [
|
|
1301
|
+
bloecke,
|
|
1302
|
+
running && (working ?? /* @__PURE__ */ jsx13(WorkingScenes, { label: alleTexte.working }))
|
|
1303
|
+
]
|
|
1304
|
+
}
|
|
1305
|
+
)
|
|
1146
1306
|
}
|
|
1147
1307
|
),
|
|
1148
|
-
|
|
1308
|
+
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": alleTexte.jumpToEnd, className: "qsl:pointer-events-auto qsl:rounded-full qsl:border qsl:border-primary-foreground/20 qsl:shadow-md", size: "icon", title: alleTexte.jumpToEnd, variant: "default", onClick: () => {
|
|
1309
|
+
if (scrollBereich.current) {
|
|
1310
|
+
scroll.jump(scrollBereich.current);
|
|
1311
|
+
updateJumpVisibility(scrollBereich.current);
|
|
1312
|
+
}
|
|
1313
|
+
}, children: /* @__PURE__ */ jsx13(ChevronDownIcon, {}) }) })
|
|
1149
1314
|
] });
|
|
1150
|
-
return /* @__PURE__ */
|
|
1315
|
+
return /* @__PURE__ */ jsx13(MarkdownLinks, { onLinkClick, children: /* @__PURE__ */ jsx13(MarkdownCodeBlocks, { options: codeBlockOptions, texts: alleTexte, children: verlauf }) });
|
|
1151
1316
|
}
|
|
1152
1317
|
function StepRow({
|
|
1153
1318
|
messages,
|
|
1154
1319
|
mitText,
|
|
1155
1320
|
expandierbar,
|
|
1156
1321
|
texts,
|
|
1157
|
-
toolArgumentsText
|
|
1322
|
+
toolArgumentsText,
|
|
1323
|
+
toolLabel
|
|
1158
1324
|
}) {
|
|
1159
|
-
const [detail, setDetail] =
|
|
1325
|
+
const [detail, setDetail] = useState4();
|
|
1160
1326
|
const offen = expandierbar && detail && messages.find((message) => message.key === detail.key);
|
|
1161
|
-
return /* @__PURE__ */ jsxs6("div", { className: "qsl-
|
|
1327
|
+
return /* @__PURE__ */ jsxs6("div", { className: cn(stepClasses, "qsl:flex qsl:flex-wrap qsl:items-center qsl:gap-x-1.5 qsl:gap-y-1 qsl:pl-6"), "data-step": "row", children: [
|
|
1162
1328
|
messages.map((message) => {
|
|
1163
1329
|
const thinking = message.role === "thinking";
|
|
1164
1330
|
const tool = message.tool;
|
|
1165
1331
|
const state = stepState(message);
|
|
1166
1332
|
const running = state === "running" || state === "thinking";
|
|
1167
|
-
const label = thinking ? texts.thinkingChip : tool?.name
|
|
1168
|
-
const content = /* @__PURE__ */ jsxs6(
|
|
1169
|
-
thinking ? /* @__PURE__ */
|
|
1170
|
-
mitText && /* @__PURE__ */
|
|
1171
|
-
mitText && /* @__PURE__ */
|
|
1333
|
+
const label = thinking ? texts.thinkingChip : toolLabel ?? (tool?.name || message.text || texts.toolChip);
|
|
1334
|
+
const content = /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1335
|
+
thinking ? /* @__PURE__ */ jsx13(SparklesIcon2, { size: 11 }) : /* @__PURE__ */ jsx13(WrenchIcon2, { className: tool?.isError ? "qsl:text-destructive" : void 0, size: 11 }),
|
|
1336
|
+
mitText && /* @__PURE__ */ jsx13("span", { className: "qsl:min-w-0 qsl:self-baseline qsl:overflow-hidden qsl:text-ellipsis qsl:whitespace-nowrap", children: label }),
|
|
1337
|
+
mitText && /* @__PURE__ */ jsx13("span", { className: "qsl:inline-flex qsl:w-2.5 qsl:flex-none qsl:items-center qsl:justify-center qsl:text-success", children: state === "done" && /* @__PURE__ */ jsx13(CheckIcon4, { size: 10 }) })
|
|
1172
1338
|
] });
|
|
1173
|
-
const classes =
|
|
1339
|
+
const classes = cn(chipClasses, !mitText && "qsl:p-1", running && "qsl:animate-fade-pulse qsl:motion-reduce:animate-none");
|
|
1174
1340
|
if (!expandierbar) {
|
|
1175
|
-
return /* @__PURE__ */
|
|
1341
|
+
return /* @__PURE__ */ jsx13("span", { className: classes, "data-kind": thinking ? "thinking" : "tool", "data-state": state, "data-step": "chip", title: label, children: content }, message.key);
|
|
1176
1342
|
}
|
|
1177
|
-
return /* @__PURE__ */
|
|
1343
|
+
return /* @__PURE__ */ jsx13(
|
|
1178
1344
|
"button",
|
|
1179
1345
|
{
|
|
1180
1346
|
"aria-haspopup": "dialog",
|
|
1181
|
-
className:
|
|
1347
|
+
className: cn(classes, chipInteractiveClasses),
|
|
1348
|
+
"data-kind": thinking ? "thinking" : "tool",
|
|
1349
|
+
"data-state": state,
|
|
1350
|
+
"data-step": "chip",
|
|
1182
1351
|
onClick: (event) => setDetail({ key: message.key, position: { x: event.clientX, y: event.clientY } }),
|
|
1183
1352
|
title: label,
|
|
1184
1353
|
type: "button",
|
|
@@ -1187,7 +1356,7 @@ function StepRow({
|
|
|
1187
1356
|
message.key
|
|
1188
1357
|
);
|
|
1189
1358
|
}),
|
|
1190
|
-
offen && detail && /* @__PURE__ */
|
|
1359
|
+
offen && detail && /* @__PURE__ */ jsx13(
|
|
1191
1360
|
StepPopover,
|
|
1192
1361
|
{
|
|
1193
1362
|
message: offen,
|
|
@@ -1206,10 +1375,10 @@ function StepGroup({
|
|
|
1206
1375
|
texts,
|
|
1207
1376
|
toolArgumentsText
|
|
1208
1377
|
}) {
|
|
1209
|
-
const [offen, setOffen] =
|
|
1210
|
-
const kopf =
|
|
1211
|
-
const vonUntenEingeklappt =
|
|
1212
|
-
|
|
1378
|
+
const [offen, setOffen] = useState4(false);
|
|
1379
|
+
const kopf = useRef3(null);
|
|
1380
|
+
const vonUntenEingeklappt = useRef3(false);
|
|
1381
|
+
useLayoutEffect(() => {
|
|
1213
1382
|
if (!offen && vonUntenEingeklappt.current) {
|
|
1214
1383
|
vonUntenEingeklappt.current = false;
|
|
1215
1384
|
kopf.current?.scrollIntoView({ block: "nearest" });
|
|
@@ -1218,22 +1387,22 @@ function StepGroup({
|
|
|
1218
1387
|
const letzte = messages[messages.length - 1];
|
|
1219
1388
|
const fehler = messages.some((message) => message.tool?.isError);
|
|
1220
1389
|
const anzahl = messages.length === 1 ? texts.stepGroupOne : texts.stepGroupMany.replace("{count}", String(messages.length));
|
|
1221
|
-
const laufend = aktiv ? letzte.role === "thinking" ? texts.thinkingChip : letzte.tool?.name
|
|
1222
|
-
return /* @__PURE__ */ jsxs6("div", { className: "
|
|
1390
|
+
const laufend = aktiv ? letzte.role === "thinking" ? texts.thinkingChip : letzte.tool?.name || letzte.text || texts.toolChip : void 0;
|
|
1391
|
+
return /* @__PURE__ */ jsxs6("div", { className: stepClasses, "data-step": "group", children: [
|
|
1223
1392
|
/* @__PURE__ */ jsxs6(
|
|
1224
1393
|
"button",
|
|
1225
1394
|
{
|
|
1226
1395
|
"aria-expanded": offen,
|
|
1227
|
-
className:
|
|
1396
|
+
className: cn(traceClasses, traceButtonClasses, "qsl:items-center qsl:pl-1", aktiv && "qsl:animate-fade-pulse qsl:motion-reduce:animate-none"),
|
|
1228
1397
|
onClick: () => setOffen((wert) => !wert),
|
|
1229
1398
|
ref: kopf,
|
|
1230
1399
|
type: "button",
|
|
1231
1400
|
children: [
|
|
1232
|
-
/* @__PURE__ */
|
|
1233
|
-
/* @__PURE__ */
|
|
1234
|
-
/* @__PURE__ */ jsxs6("span", { className: "qsl-
|
|
1401
|
+
/* @__PURE__ */ jsx13(ChevronRightIcon, { className: cn("qsl:flex-none qsl:opacity-60 qsl:transition-transform", offen && "qsl:rotate-90"), size: 12 }),
|
|
1402
|
+
/* @__PURE__ */ jsx13(LayersIcon, { className: fehler ? traceErrorIconClasses : traceIconClasses, size: 12 }),
|
|
1403
|
+
/* @__PURE__ */ jsxs6("span", { className: cn(traceLineClasses, "qsl:overflow-hidden qsl:text-ellipsis qsl:whitespace-nowrap"), children: [
|
|
1235
1404
|
anzahl,
|
|
1236
|
-
laufend && /* @__PURE__ */ jsxs6("span", { className:
|
|
1405
|
+
laufend && /* @__PURE__ */ jsxs6("span", { className: traceRunningClasses, children: [
|
|
1237
1406
|
laufend,
|
|
1238
1407
|
" ",
|
|
1239
1408
|
texts.toolRunning
|
|
@@ -1242,24 +1411,23 @@ function StepGroup({
|
|
|
1242
1411
|
]
|
|
1243
1412
|
}
|
|
1244
1413
|
),
|
|
1245
|
-
offen && /* @__PURE__ */
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
] })
|
|
1414
|
+
offen && /* @__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", expandierbar, message, texts, toolArgumentsText }, message.key)) }),
|
|
1415
|
+
offen && messages.length >= collapseAtBottomFrom && /* @__PURE__ */ jsxs6(
|
|
1416
|
+
"button",
|
|
1417
|
+
{
|
|
1418
|
+
className: cn(traceClasses, traceButtonClasses, "qsl:mt-0.5 qsl:items-center qsl:pl-1"),
|
|
1419
|
+
"data-step": "group-collapse",
|
|
1420
|
+
onClick: () => {
|
|
1421
|
+
vonUntenEingeklappt.current = true;
|
|
1422
|
+
setOffen(false);
|
|
1423
|
+
},
|
|
1424
|
+
type: "button",
|
|
1425
|
+
children: [
|
|
1426
|
+
/* @__PURE__ */ jsx13(ChevronUpIcon, { className: "qsl:flex-none qsl:opacity-60", size: 12 }),
|
|
1427
|
+
/* @__PURE__ */ jsx13("span", { className: traceLineClasses, children: texts.stepGroupCollapse })
|
|
1428
|
+
]
|
|
1429
|
+
}
|
|
1430
|
+
)
|
|
1263
1431
|
] });
|
|
1264
1432
|
}
|
|
1265
1433
|
function TraceLine({
|
|
@@ -1269,28 +1437,36 @@ function TraceLine({
|
|
|
1269
1437
|
texts,
|
|
1270
1438
|
toolArgumentsText
|
|
1271
1439
|
}) {
|
|
1272
|
-
const [detailPosition, setDetailPosition] =
|
|
1273
|
-
const
|
|
1274
|
-
const
|
|
1275
|
-
|
|
1276
|
-
|
|
1440
|
+
const [detailPosition, setDetailPosition] = useState4();
|
|
1441
|
+
const thinking = message.role === "thinking";
|
|
1442
|
+
const tool = message.tool;
|
|
1443
|
+
const classes = cn(traceClasses, className);
|
|
1444
|
+
const zeile = cn(traceLineClasses, thinking && "qsl:font-sans qsl:italic qsl:whitespace-pre-wrap", "qsl:overflow-hidden qsl:text-ellipsis qsl:whitespace-nowrap");
|
|
1445
|
+
const inhalt = /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1446
|
+
thinking ? /* @__PURE__ */ jsx13(SparklesIcon2, { className: traceIconClasses, size: 12 }) : /* @__PURE__ */ jsx13(WrenchIcon2, { className: tool?.isError ? traceErrorIconClasses : traceIconClasses, size: 12 }),
|
|
1447
|
+
/* @__PURE__ */ jsxs6("span", { className: zeile, children: [
|
|
1448
|
+
message.text,
|
|
1449
|
+
tool && tool.result === void 0 && /* @__PURE__ */ jsx13("span", { className: traceRunningClasses, children: texts.toolRunning })
|
|
1450
|
+
] })
|
|
1277
1451
|
] });
|
|
1452
|
+
const marks = { "data-compact": "true", "data-kind": thinking ? "thinking" : "tool", "data-step": "line" };
|
|
1278
1453
|
if (!expandierbar) {
|
|
1279
|
-
return /* @__PURE__ */
|
|
1454
|
+
return /* @__PURE__ */ jsx13("div", { className: classes, ...marks, children: inhalt });
|
|
1280
1455
|
}
|
|
1281
|
-
return /* @__PURE__ */ jsxs6(
|
|
1282
|
-
/* @__PURE__ */
|
|
1456
|
+
return /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1457
|
+
/* @__PURE__ */ jsx13(
|
|
1283
1458
|
"button",
|
|
1284
1459
|
{
|
|
1285
1460
|
"aria-expanded": detailPosition !== void 0,
|
|
1286
1461
|
"aria-haspopup": "dialog",
|
|
1287
|
-
className: classes,
|
|
1462
|
+
className: cn(classes, traceButtonClasses),
|
|
1463
|
+
...marks,
|
|
1288
1464
|
onClick: (event) => setDetailPosition({ x: event.clientX, y: event.clientY }),
|
|
1289
1465
|
type: "button",
|
|
1290
1466
|
children: inhalt
|
|
1291
1467
|
}
|
|
1292
1468
|
),
|
|
1293
|
-
detailPosition && /* @__PURE__ */
|
|
1469
|
+
detailPosition && /* @__PURE__ */ jsx13(
|
|
1294
1470
|
StepPopover,
|
|
1295
1471
|
{
|
|
1296
1472
|
message,
|
|
@@ -1302,126 +1478,161 @@ function TraceLine({
|
|
|
1302
1478
|
)
|
|
1303
1479
|
] });
|
|
1304
1480
|
}
|
|
1305
|
-
function TraceIcon({ message }) {
|
|
1306
|
-
return message.role === "thinking" ? /* @__PURE__ */ jsx7(IconSpark, { className: "qsl-trace__icon", size: 12 }) : /* @__PURE__ */ jsx7(IconTool, { className: message.tool?.isError ? "qsl-trace__icon qsl-trace__icon--error" : "qsl-trace__icon", size: 12 });
|
|
1307
|
-
}
|
|
1308
|
-
function TraceText({ message, texts }) {
|
|
1309
|
-
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
1310
|
-
message.text,
|
|
1311
|
-
message.tool && message.tool.result === void 0 && /* @__PURE__ */ jsx7("span", { className: "qsl-trace__running", children: texts.toolRunning })
|
|
1312
|
-
] });
|
|
1313
|
-
}
|
|
1314
1481
|
function Bubble({
|
|
1315
1482
|
message,
|
|
1316
|
-
|
|
1317
|
-
messageActions,
|
|
1483
|
+
plain,
|
|
1318
1484
|
detailMode,
|
|
1319
1485
|
dicht,
|
|
1320
1486
|
expandierbar,
|
|
1321
1487
|
texts,
|
|
1322
1488
|
toolArgumentsText,
|
|
1323
|
-
|
|
1489
|
+
renderAction,
|
|
1490
|
+
onDismissAction,
|
|
1491
|
+
bubbleOptions,
|
|
1492
|
+
messageActions
|
|
1324
1493
|
}) {
|
|
1325
|
-
const schritt = dicht ?
|
|
1494
|
+
const schritt = dicht ? denseStepClasses : stepClasses;
|
|
1326
1495
|
if (message.role === "thinking" || message.role === "tool" && message.tool) {
|
|
1327
1496
|
if (detailMode === "compact") {
|
|
1328
|
-
return /* @__PURE__ */
|
|
1497
|
+
return /* @__PURE__ */ jsx13(TraceLine, { className: schritt, expandierbar, message, texts, toolArgumentsText });
|
|
1329
1498
|
}
|
|
1499
|
+
const thinking = message.role === "thinking";
|
|
1330
1500
|
const tool = message.tool;
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1501
|
+
const icon = thinking ? /* @__PURE__ */ jsx13(SparklesIcon2, { className: traceIconClasses, size: 12 }) : /* @__PURE__ */ jsx13(WrenchIcon2, { className: tool?.isError ? traceErrorIconClasses : traceIconClasses, size: 12 });
|
|
1502
|
+
const zeile = /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1503
|
+
message.text,
|
|
1504
|
+
tool && tool.result === void 0 && /* @__PURE__ */ jsx13("span", { className: traceRunningClasses, children: texts.toolRunning })
|
|
1505
|
+
] });
|
|
1506
|
+
return /* @__PURE__ */ jsxs6("div", { className: cn(traceClasses, schritt), "data-kind": thinking ? "thinking" : "tool", "data-step": "detail", children: [
|
|
1507
|
+
icon,
|
|
1508
|
+
/* @__PURE__ */ jsxs6("div", { className: "qsl:min-w-0 qsl:flex-1", children: [
|
|
1509
|
+
/* @__PURE__ */ jsx13("div", { className: cn(traceLineClasses, thinking && "qsl:font-sans qsl:italic qsl:whitespace-pre-wrap"), children: zeile }),
|
|
1510
|
+
tool && /* @__PURE__ */ jsxs6("div", { className: "qsl:mt-0.5 qsl:border-l-2 qsl:border-border-soft qsl:pl-2", children: [
|
|
1511
|
+
/* @__PURE__ */ jsx13("pre", { className: "qsl:mb-0.5 qsl:max-h-[208px] qsl:overflow-auto qsl:font-mono qsl:text-[10.5px] qsl:leading-[1.375] qsl:break-all qsl:whitespace-pre-wrap qsl:opacity-80", children: toolArgumentsText(tool) }),
|
|
1512
|
+
tool.result !== void 0 && /* @__PURE__ */ jsx13("pre", { className: cn(
|
|
1513
|
+
"qsl:mb-0.5 qsl:max-h-[208px] qsl:overflow-auto qsl:font-mono qsl:text-[10.5px] qsl:leading-[1.375] qsl:break-all qsl:whitespace-pre-wrap qsl:opacity-80",
|
|
1514
|
+
tool.isError && "qsl:text-destructive qsl:opacity-100"
|
|
1515
|
+
), children: prettyJson(tool.result) })
|
|
1338
1516
|
] })
|
|
1339
1517
|
] })
|
|
1340
1518
|
] });
|
|
1341
1519
|
}
|
|
1342
|
-
if (message.role === "
|
|
1343
|
-
|
|
1344
|
-
|
|
1520
|
+
if (message.role === "action" && message.action) {
|
|
1521
|
+
const action = message.action;
|
|
1522
|
+
return /* @__PURE__ */ jsx13("div", { className: schritt, children: renderAction?.(action, message.text) ?? /* @__PURE__ */ jsx13(
|
|
1523
|
+
PendingActionCard,
|
|
1345
1524
|
{
|
|
1346
|
-
|
|
1525
|
+
action,
|
|
1526
|
+
dismissedLabel: texts.actionDismissed,
|
|
1527
|
+
dismissLabel: texts.dismissAction,
|
|
1528
|
+
onDismiss: onDismissAction ? () => onDismissAction(action.actionId) : void 0,
|
|
1347
1529
|
text: message.text,
|
|
1348
|
-
texts
|
|
1349
|
-
onAnswer: (text) => onAnswerQuestion?.(message.question.callId, text)
|
|
1530
|
+
waitingLabel: texts.pendingAction
|
|
1350
1531
|
}
|
|
1351
1532
|
) });
|
|
1352
1533
|
}
|
|
1353
1534
|
if (message.role === "system") {
|
|
1354
|
-
return /* @__PURE__ */
|
|
1535
|
+
return /* @__PURE__ */ jsxs6("div", { className: cn(schritt, "qsl:border-l-2 qsl:border-destructive/50 qsl:pl-3 qsl:text-sm qsl:text-muted-foreground"), "data-message": "system", children: [
|
|
1536
|
+
/* @__PURE__ */ jsx13(Markdown, { text: message.text }),
|
|
1537
|
+
/* @__PURE__ */ jsx13(MessageAttachments, { label: texts.attachments, message })
|
|
1538
|
+
] });
|
|
1355
1539
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
const
|
|
1359
|
-
const isBubble = !plain && (message.role === "user" || message.bubble || bubbleOptions?.variant === "bubbles");
|
|
1540
|
+
if (!message.text.trim() && !message.attachments?.length) return null;
|
|
1541
|
+
const variant = bubbleOptions?.variant ?? "default";
|
|
1542
|
+
const useBubble = !plain && variant !== "plain" && (variant === "bubbles" || message.role === "user" || !!message.bubble);
|
|
1360
1543
|
const side = (message.role === "user" ? bubbleOptions?.userSide : bubbleOptions?.assistantSide) ?? message.bubble?.side ?? (message.role === "user" ? "end" : "start");
|
|
1361
|
-
const
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
/* @__PURE__ */
|
|
1365
|
-
|
|
1544
|
+
const sender = bubbleOptions?.senderLabel?.(message) ?? message.bubble?.label ?? message.sender;
|
|
1545
|
+
const showSender = bubbleOptions?.showSender ?? (!!message.bubble?.label && !plain);
|
|
1546
|
+
const content = /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1547
|
+
showSender && sender && /* @__PURE__ */ jsx13("span", { className: cn(
|
|
1548
|
+
"qsl:mb-[5px] qsl:table qsl:text-[0.72rem] qsl:leading-[1.45] qsl:font-semibold",
|
|
1549
|
+
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]"
|
|
1550
|
+
), "data-chat": "sender", children: sender }),
|
|
1551
|
+
/* @__PURE__ */ jsx13(Markdown, { text: message.text, streaming: message.role === "assistant" && !message.closed }),
|
|
1552
|
+
/* @__PURE__ */ jsx13(MessageAttachments, { label: texts.attachments, message }),
|
|
1553
|
+
message.steered && /* @__PURE__ */ jsx13("small", { className: "qsl:mt-1 qsl:block qsl:text-[0.72rem] qsl:leading-[1.45] qsl:opacity-70", "data-chat": "steered", children: texts.steered }),
|
|
1554
|
+
/* @__PURE__ */ jsx13(MessageActions, { message, options: messageActions, texts })
|
|
1366
1555
|
] });
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1556
|
+
const cursor = message.role === "assistant" && message.textCursor ? JSON.stringify(message.textCursor) : void 0;
|
|
1557
|
+
if (!useBubble) return /* @__PURE__ */ jsx13("div", { "data-chat-text-cursor": cursor, className: cn(schritt, "qsl:group qsl:relative qsl:w-full qsl:leading-[var(--qsl-chat-line-height,1.625)] qsl:text-foreground"), "data-message": "answer", children: content });
|
|
1558
|
+
return /* @__PURE__ */ jsx13(
|
|
1559
|
+
"div",
|
|
1560
|
+
{
|
|
1561
|
+
"data-chat-text-cursor": cursor,
|
|
1562
|
+
className: cn(schritt, "qsl:flex", side === "end" ? "qsl:justify-end" : "qsl:justify-start"),
|
|
1563
|
+
"data-message": message.role === "user" && !message.bubble && variant === "default" ? "user" : "bubble",
|
|
1564
|
+
"data-side": side,
|
|
1565
|
+
children: /* @__PURE__ */ jsx13(
|
|
1566
|
+
"div",
|
|
1567
|
+
{
|
|
1568
|
+
className: cn(bubbleBodyClasses, "qsl:group", message.bubble ? "qsl:rounded-lg qsl:text-white qsl:[--qsl-scroll-cover:var(--qsl-primary)]" : "qsl:rounded-[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-[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"),
|
|
1569
|
+
"data-tone": message.bubble ? "on-color" : void 0,
|
|
1570
|
+
style: { background: message.bubble?.color, maxWidth: bubbleOptions?.maxWidth },
|
|
1571
|
+
children: content
|
|
1572
|
+
}
|
|
1573
|
+
)
|
|
1574
|
+
}
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
function MessageAttachments({ message, label }) {
|
|
1578
|
+
if (!message.attachments?.length) return null;
|
|
1579
|
+
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: [
|
|
1580
|
+
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" }) }),
|
|
1581
|
+
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 }),
|
|
1582
|
+
/* @__PURE__ */ jsxs6("div", { className: "qsl:flex qsl:flex-col qsl:gap-1 qsl:p-2 qsl:[overflow-wrap:anywhere]", children: [
|
|
1583
|
+
/* @__PURE__ */ jsx13("a", { className: "qsl:font-medium", href: attachmentDownloadUrl(attachment.url), download: attachment.name, children: attachment.name }),
|
|
1584
|
+
/* @__PURE__ */ jsx13("small", { children: formatAttachmentSize(attachment.size) })
|
|
1585
|
+
] })
|
|
1586
|
+
] }, `${attachment.url}-${index}`)) });
|
|
1372
1587
|
}
|
|
1373
1588
|
|
|
1374
1589
|
// packages/quassel/src/ChatPanel.tsx
|
|
1375
|
-
import {
|
|
1376
|
-
import { jsx as
|
|
1590
|
+
import { useEffect as useEffect4, useLayoutEffect as useLayoutEffect2, useRef as useRef4, useState as useState5 } from "react";
|
|
1591
|
+
import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1377
1592
|
function ChatPanel({
|
|
1378
1593
|
children,
|
|
1379
1594
|
composer,
|
|
1380
1595
|
className,
|
|
1381
1596
|
nodeRef,
|
|
1597
|
+
maxWidth,
|
|
1598
|
+
horizontalPadding,
|
|
1382
1599
|
appearance,
|
|
1383
1600
|
scrollOnSend = false
|
|
1384
1601
|
}) {
|
|
1385
|
-
const
|
|
1386
|
-
const
|
|
1387
|
-
|
|
1602
|
+
const wurzel = useRef4(null);
|
|
1603
|
+
const eingabe = useRef4(null);
|
|
1604
|
+
const hasComposer = composer !== void 0;
|
|
1605
|
+
const scrollOnSendRef = useRef4(scrollOnSend);
|
|
1606
|
+
const [sendScope] = useState5(() => createChatSendScope(scrollOnSendRef));
|
|
1607
|
+
useLayoutEffect2(() => {
|
|
1388
1608
|
scrollOnSendRef.current = scrollOnSend;
|
|
1389
1609
|
}, [scrollOnSend]);
|
|
1390
|
-
|
|
1391
|
-
onSent: () => {
|
|
1392
|
-
if (scrollOnSendRef.current) jump.current?.();
|
|
1393
|
-
},
|
|
1394
|
-
registerJump: (callback) => {
|
|
1395
|
-
jump.current = callback;
|
|
1396
|
-
return () => {
|
|
1397
|
-
if (jump.current === callback) jump.current = void 0;
|
|
1398
|
-
};
|
|
1399
|
-
}
|
|
1400
|
-
}), []);
|
|
1401
|
-
const wurzel = useRef6(null);
|
|
1402
|
-
const eingabe = useRef6(null);
|
|
1403
|
-
const mitEingabe = composer !== void 0;
|
|
1404
|
-
useLayoutEffect4(() => {
|
|
1610
|
+
useEffect4(() => {
|
|
1405
1611
|
const element = eingabe.current;
|
|
1406
1612
|
const rahmen = wurzel.current;
|
|
1407
|
-
if (!rahmen) {
|
|
1408
|
-
return;
|
|
1409
|
-
}
|
|
1410
|
-
if (!element) {
|
|
1411
|
-
rahmen.style.removeProperty("--qsl-composer-height");
|
|
1613
|
+
if (!element || !rahmen) {
|
|
1412
1614
|
return;
|
|
1413
1615
|
}
|
|
1414
1616
|
const messen = () => rahmen.style.setProperty("--qsl-composer-height", `${element.offsetHeight}px`);
|
|
1415
1617
|
const beobachter = new ResizeObserver(messen);
|
|
1416
1618
|
beobachter.observe(element);
|
|
1417
1619
|
messen();
|
|
1418
|
-
return () =>
|
|
1419
|
-
|
|
1420
|
-
|
|
1620
|
+
return () => {
|
|
1621
|
+
beobachter.disconnect();
|
|
1622
|
+
rahmen.style.removeProperty("--qsl-composer-height");
|
|
1623
|
+
};
|
|
1624
|
+
}, [hasComposer]);
|
|
1625
|
+
return /* @__PURE__ */ jsx14(ChatSendContext.Provider, { value: sendScope, children: /* @__PURE__ */ jsxs7(
|
|
1421
1626
|
"div",
|
|
1422
1627
|
{
|
|
1423
|
-
className:
|
|
1424
|
-
|
|
1628
|
+
className: cn("qsl:relative qsl:flex qsl:min-h-0 qsl:min-w-0 qsl:flex-col qsl:overflow-hidden", className),
|
|
1629
|
+
"data-chat": "panel",
|
|
1630
|
+
"data-quassel": "",
|
|
1631
|
+
style: {
|
|
1632
|
+
...appearanceStyle(appearance),
|
|
1633
|
+
"--qsl-chat-content-max-width": typeof maxWidth === "number" ? `${maxWidth}px` : maxWidth,
|
|
1634
|
+
"--qsl-chat-horizontal-padding": typeof horizontalPadding === "number" ? `${horizontalPadding}px` : horizontalPadding
|
|
1635
|
+
},
|
|
1425
1636
|
ref: (element) => {
|
|
1426
1637
|
wurzel.current = element;
|
|
1427
1638
|
if (typeof nodeRef === "function") {
|
|
@@ -1432,83 +1643,290 @@ function ChatPanel({
|
|
|
1432
1643
|
},
|
|
1433
1644
|
children: [
|
|
1434
1645
|
children,
|
|
1435
|
-
|
|
1646
|
+
hasComposer && /* @__PURE__ */ jsx14("div", { className: "qsl:absolute qsl:inset-x-0 qsl:bottom-0 qsl:z-[5] qsl:px-[var(--qsl-chat-horizontal-padding,24px)] qsl:pb-3.5 qsl:*:mx-auto qsl:*:max-w-[var(--qsl-chat-content-max-width,none)]", "data-chat": "composer", ref: eingabe, children: composer })
|
|
1436
1647
|
]
|
|
1437
1648
|
}
|
|
1438
1649
|
) });
|
|
1439
1650
|
}
|
|
1440
1651
|
|
|
1441
|
-
// packages/quassel/src/
|
|
1442
|
-
import {
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
return
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1652
|
+
// packages/quassel/src/ChatInputToolbar.tsx
|
|
1653
|
+
import { PaperclipIcon, SendIcon, XIcon as XIcon2 } from "lucide-react";
|
|
1654
|
+
|
|
1655
|
+
// packages/quassel/src/ui/useFileInput.ts
|
|
1656
|
+
import { useRef as useRef5, useState as useState6 } from "react";
|
|
1657
|
+
function useFileInput({ disabled, onFiles, onPasteText }) {
|
|
1658
|
+
const inputRef = useRef5(null);
|
|
1659
|
+
const [dragging, setDragging] = useState6(false);
|
|
1660
|
+
const receive = (files) => {
|
|
1661
|
+
if (files.length) void onFiles(files);
|
|
1662
|
+
};
|
|
1663
|
+
return {
|
|
1664
|
+
inputRef,
|
|
1665
|
+
dragging,
|
|
1666
|
+
clearDragging: () => setDragging(false),
|
|
1667
|
+
inputProps: {
|
|
1668
|
+
ref: inputRef,
|
|
1669
|
+
onChange: (event) => {
|
|
1670
|
+
const files = Array.from(event.currentTarget.files ?? []);
|
|
1671
|
+
event.currentTarget.value = "";
|
|
1672
|
+
receive(files);
|
|
1673
|
+
}
|
|
1674
|
+
},
|
|
1675
|
+
dropProps: {
|
|
1676
|
+
onDragOver: (event) => {
|
|
1677
|
+
if (!event.dataTransfer.types.includes("Files")) return;
|
|
1678
|
+
event.preventDefault();
|
|
1679
|
+
event.dataTransfer.dropEffect = disabled ? "none" : "copy";
|
|
1680
|
+
setDragging(!disabled);
|
|
1681
|
+
},
|
|
1682
|
+
onDragLeave: (event) => {
|
|
1683
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
|
|
1684
|
+
},
|
|
1685
|
+
onDrop: (event) => {
|
|
1686
|
+
if (!event.dataTransfer.types.includes("Files")) return;
|
|
1687
|
+
event.preventDefault();
|
|
1688
|
+
setDragging(false);
|
|
1689
|
+
receive(Array.from(event.dataTransfer.files));
|
|
1690
|
+
},
|
|
1691
|
+
onPaste: (event) => {
|
|
1692
|
+
const files = Array.from(event.clipboardData.files);
|
|
1693
|
+
if (!files.length) return;
|
|
1694
|
+
event.preventDefault();
|
|
1695
|
+
receive(files);
|
|
1696
|
+
const text = event.clipboardData.getData("text/plain");
|
|
1697
|
+
if (text) onPasteText?.(text);
|
|
1698
|
+
}
|
|
1465
1699
|
}
|
|
1466
|
-
|
|
1700
|
+
};
|
|
1467
1701
|
}
|
|
1468
1702
|
|
|
1469
1703
|
// packages/quassel/src/ChatInputToolbar.tsx
|
|
1470
|
-
import { useCallback as useCallback3, useEffect as
|
|
1471
|
-
import {
|
|
1704
|
+
import { useCallback as useCallback3, useContext as useContext4, useEffect as useEffect5, useImperativeHandle, useLayoutEffect as useLayoutEffect3, useRef as useRef6, useState as useState7 } from "react";
|
|
1705
|
+
import { createPortal } from "react-dom";
|
|
1706
|
+
import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1472
1707
|
var COMPACT_WIDTH_PX = 480;
|
|
1708
|
+
var errorClasses = "qsl:px-3 qsl:py-2 qsl:text-sm qsl:text-destructive";
|
|
1709
|
+
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";
|
|
1710
|
+
var attachmentMediaClasses = "qsl:block qsl:h-[112px] qsl:w-full qsl:bg-secondary qsl:object-contain";
|
|
1473
1711
|
function ChatInputToolbar({
|
|
1474
1712
|
onSend,
|
|
1475
1713
|
onStop,
|
|
1476
1714
|
running = false,
|
|
1477
1715
|
disabled = false,
|
|
1716
|
+
sendDisabled = false,
|
|
1717
|
+
layout = "card",
|
|
1718
|
+
detailsContainer,
|
|
1719
|
+
onErrorChange,
|
|
1720
|
+
inputAriaControls,
|
|
1478
1721
|
rows = 3,
|
|
1479
1722
|
maxRows,
|
|
1480
1723
|
actions,
|
|
1481
1724
|
toolbarLeft,
|
|
1482
1725
|
toolbarRight,
|
|
1483
1726
|
texts,
|
|
1484
|
-
|
|
1727
|
+
handleRef,
|
|
1728
|
+
attachments: attachmentsEnabled = true,
|
|
1729
|
+
attachmentCapabilities,
|
|
1730
|
+
attachmentCapabilitiesError,
|
|
1731
|
+
onAttachmentsChange,
|
|
1732
|
+
initialValue = "",
|
|
1733
|
+
onDraftChange,
|
|
1734
|
+
sendShortcut = "enter",
|
|
1735
|
+
autoFocus = false,
|
|
1736
|
+
onAutoFocusSettled
|
|
1485
1737
|
}) {
|
|
1738
|
+
const sendScope = useContext4(ChatSendContext);
|
|
1486
1739
|
const alleTexte = { ...defaultTexts, ...texts };
|
|
1487
|
-
const
|
|
1488
|
-
const
|
|
1489
|
-
const
|
|
1490
|
-
const
|
|
1491
|
-
const [
|
|
1740
|
+
const { Button: Button2, StopButton: StopButton2, Toggle: Toggle2 } = useQuasselComponents();
|
|
1741
|
+
const [draft, setDraft] = useState7(initialValue);
|
|
1742
|
+
const [sending, setSending] = useState7(false);
|
|
1743
|
+
const [sendError, setSendError] = useState7();
|
|
1744
|
+
const [failedSubmissions, setFailedSubmissions] = useState7([]);
|
|
1745
|
+
const nextFailedId = useRef6(0);
|
|
1746
|
+
const pendingSend = useRef6(false);
|
|
1747
|
+
const generation = useRef6(0);
|
|
1748
|
+
useLayoutEffect3(() => () => {
|
|
1749
|
+
generation.current += 1;
|
|
1750
|
+
}, []);
|
|
1751
|
+
const draftRevision = useRef6(0);
|
|
1752
|
+
const [attachments, setAttachments] = useState7([]);
|
|
1753
|
+
const attachmentsRef = useRef6([]);
|
|
1754
|
+
const nextAttachmentId = useRef6(0);
|
|
1755
|
+
const [attachmentError, setAttachmentError] = useState7();
|
|
1756
|
+
const updateAttachments = (update) => {
|
|
1757
|
+
attachmentsRef.current = update(attachmentsRef.current);
|
|
1758
|
+
setAttachments(attachmentsRef.current);
|
|
1759
|
+
};
|
|
1760
|
+
const addFiles = (files) => {
|
|
1761
|
+
if (!files.length) return;
|
|
1762
|
+
if (disabled || pendingSend.current) {
|
|
1763
|
+
setAttachmentError(alleTexte.attachmentsWhileBusy);
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
try {
|
|
1767
|
+
validateAttachmentSelection(attachmentsRef.current.map((entry) => entry.file), files, alleTexte);
|
|
1768
|
+
} catch (error) {
|
|
1769
|
+
setAttachmentError(error instanceof Error ? error.message : String(error));
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
setAttachmentError(void 0);
|
|
1773
|
+
const entries = files.map((file) => ({ id: ++nextAttachmentId.current, file, status: "reading" }));
|
|
1774
|
+
updateAttachments((current) => [...current, ...entries]);
|
|
1775
|
+
for (const entry of entries) {
|
|
1776
|
+
void encodeAttachment(entry.file).then(
|
|
1777
|
+
(input2) => updateAttachments((current) => current.map((item) => item.id === entry.id ? { ...entry, status: "ready", input: input2 } : item)),
|
|
1778
|
+
() => updateAttachments((current) => current.map((item) => item.id === entry.id ? {
|
|
1779
|
+
...entry,
|
|
1780
|
+
status: "error",
|
|
1781
|
+
error: alleTexte.attachmentReadFailed
|
|
1782
|
+
} : item))
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
const rootRef = useRef6(null);
|
|
1787
|
+
const eingabeRef = useRef6(null);
|
|
1788
|
+
const autoFocusSettled = useRef6(false);
|
|
1789
|
+
useEffect5(() => {
|
|
1790
|
+
if (!autoFocus || autoFocusSettled.current) return;
|
|
1791
|
+
const input2 = eingabeRef.current;
|
|
1792
|
+
if (!input2) return;
|
|
1793
|
+
let frame = 0;
|
|
1794
|
+
const settle = () => {
|
|
1795
|
+
if (autoFocusSettled.current) return;
|
|
1796
|
+
autoFocusSettled.current = true;
|
|
1797
|
+
window.cancelAnimationFrame(frame);
|
|
1798
|
+
onAutoFocusSettled?.();
|
|
1799
|
+
};
|
|
1800
|
+
const onFocus = (event) => {
|
|
1801
|
+
if (event.target !== input2) settle();
|
|
1802
|
+
};
|
|
1803
|
+
const focusWhenVisible = () => {
|
|
1804
|
+
if (autoFocusSettled.current) return;
|
|
1805
|
+
const dialog = [...document.querySelectorAll('[role="dialog"], dialog[open]')].find((element) => element.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }) && !element.contains(input2));
|
|
1806
|
+
if (dialog) {
|
|
1807
|
+
settle();
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
const rect = input2.getBoundingClientRect();
|
|
1811
|
+
const visible = input2.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }) && rect.width > 0 && rect.height > 0 && !input2.closest('[inert], [hidden], [aria-hidden="true"]') && document.elementFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2) === input2;
|
|
1812
|
+
if (visible && !input2.disabled && !input2.readOnly && document.visibilityState === "visible") {
|
|
1813
|
+
input2.focus({ preventScroll: true });
|
|
1814
|
+
if (document.activeElement === input2) {
|
|
1815
|
+
settle();
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
frame = window.requestAnimationFrame(focusWhenVisible);
|
|
1820
|
+
};
|
|
1821
|
+
window.addEventListener("pointerdown", settle, true);
|
|
1822
|
+
window.addEventListener("keydown", settle, true);
|
|
1823
|
+
window.addEventListener("focusin", onFocus);
|
|
1824
|
+
window.addEventListener("blur", settle);
|
|
1825
|
+
if (!disabled) frame = window.requestAnimationFrame(focusWhenVisible);
|
|
1826
|
+
return () => {
|
|
1827
|
+
window.cancelAnimationFrame(frame);
|
|
1828
|
+
window.removeEventListener("pointerdown", settle, true);
|
|
1829
|
+
window.removeEventListener("keydown", settle, true);
|
|
1830
|
+
window.removeEventListener("focusin", onFocus);
|
|
1831
|
+
window.removeEventListener("blur", settle);
|
|
1832
|
+
};
|
|
1833
|
+
}, [autoFocus, disabled, onAutoFocusSettled]);
|
|
1834
|
+
const [compact, setCompact] = useState7(false);
|
|
1835
|
+
const pendingFocus = useRef6(false);
|
|
1836
|
+
const fileInput = useFileInput({
|
|
1837
|
+
disabled: disabled || sending,
|
|
1838
|
+
onFiles: addFiles,
|
|
1839
|
+
onPasteText: (text) => {
|
|
1840
|
+
const input2 = eingabeRef.current;
|
|
1841
|
+
if (!input2 || disabled || pendingSend.current) return;
|
|
1842
|
+
const start = input2.selectionStart;
|
|
1843
|
+
const end = input2.selectionEnd;
|
|
1844
|
+
draftRevision.current += 1;
|
|
1845
|
+
setDraft((current) => current.slice(0, start) + text + current.slice(end));
|
|
1846
|
+
pendingFocus.current = true;
|
|
1847
|
+
}
|
|
1848
|
+
});
|
|
1849
|
+
const toolbar = layout === "toolbar";
|
|
1850
|
+
const inline = layout === "inline";
|
|
1851
|
+
const inputRows = inline || toolbar ? 1 : rows;
|
|
1852
|
+
const inputMaxRows = inline || toolbar ? 1 : maxRows;
|
|
1853
|
+
const rootClasses = cn(
|
|
1854
|
+
"qsl:flex qsl:shrink-0 qsl:flex-col qsl:overflow-hidden qsl:rounded-[var(--qsl-input-card-radius,var(--qsl-radius-xl))] qsl:border qsl:border-border-strong qsl:bg-background qsl:shadow-bar qsl:transition-colors qsl:focus-within:border-primary",
|
|
1855
|
+
"qsl:in-data-[tone=material]:rounded-xl qsl:in-data-[tone=material]:border-glass-edge/50 qsl:in-data-[tone=material]:bg-background",
|
|
1856
|
+
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",
|
|
1857
|
+
fileInput.dragging && "qsl:outline-2 qsl:outline-offset-[3px] qsl:outline-dashed qsl:outline-primary"
|
|
1858
|
+
);
|
|
1859
|
+
const textareaClasses = cn(
|
|
1860
|
+
"qsl:resize-none qsl:border-0 qsl:bg-transparent qsl:text-[length:var(--qsl-chat-font-size,13px)] qsl:leading-[var(--qsl-chat-line-height,1.5)] qsl:text-foreground qsl:placeholder:text-muted-foreground qsl:focus:outline-none",
|
|
1861
|
+
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"
|
|
1862
|
+
);
|
|
1863
|
+
const toolbarRowClasses = cn(
|
|
1864
|
+
"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",
|
|
1865
|
+
inline && "qsl:flex-none qsl:gap-1 qsl:border-t-0 qsl:p-1",
|
|
1866
|
+
toolbar && "qsl:flex-wrap qsl:border-t-0"
|
|
1867
|
+
);
|
|
1868
|
+
const toolbarSideClasses = "qsl:flex qsl:min-w-0 qsl:items-center qsl:gap-2 qsl:in-data-[tone=overseer-details]:flex-nowrap";
|
|
1869
|
+
const changeDraft = useCallback3((text) => {
|
|
1870
|
+
draftRevision.current += 1;
|
|
1871
|
+
setDraft(text);
|
|
1872
|
+
}, []);
|
|
1873
|
+
useImperativeHandle(handleRef, () => ({
|
|
1874
|
+
insert: (text) => {
|
|
1875
|
+
pendingFocus.current = true;
|
|
1876
|
+
changeDraft(text);
|
|
1877
|
+
const element = eingabeRef.current;
|
|
1878
|
+
element?.focus();
|
|
1879
|
+
if (element?.value === text) element.setSelectionRange(text.length, text.length);
|
|
1880
|
+
},
|
|
1881
|
+
focus: () => eingabeRef.current?.focus(),
|
|
1882
|
+
submit: (onSubmit, options) => send(onSubmit, options?.allowEmpty),
|
|
1883
|
+
reset: () => {
|
|
1884
|
+
generation.current += 1;
|
|
1885
|
+
pendingSend.current = false;
|
|
1886
|
+
pendingFocus.current = false;
|
|
1887
|
+
changeDraft("");
|
|
1888
|
+
updateAttachments(() => []);
|
|
1889
|
+
setSendError(void 0);
|
|
1890
|
+
setFailedSubmissions([]);
|
|
1891
|
+
setAttachmentError(void 0);
|
|
1892
|
+
setSending(false);
|
|
1893
|
+
fileInput.clearDragging();
|
|
1894
|
+
}
|
|
1895
|
+
}));
|
|
1896
|
+
useEffect5(() => {
|
|
1897
|
+
onDraftChange?.(draft);
|
|
1898
|
+
}, [draft, onDraftChange]);
|
|
1899
|
+
useEffect5(() => {
|
|
1900
|
+
if (!pendingFocus.current) {
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
pendingFocus.current = false;
|
|
1904
|
+
const element = eingabeRef.current;
|
|
1905
|
+
if (element) {
|
|
1906
|
+
element.focus();
|
|
1907
|
+
element.setSelectionRange(element.value.length, element.value.length);
|
|
1908
|
+
}
|
|
1909
|
+
}, [draft, sending]);
|
|
1492
1910
|
const applyAutoGrow = useCallback3(() => {
|
|
1493
1911
|
const element = eingabeRef.current;
|
|
1494
|
-
if (!element ||
|
|
1912
|
+
if (!element || inputMaxRows === void 0) {
|
|
1495
1913
|
return;
|
|
1496
1914
|
}
|
|
1497
1915
|
const stil = getComputedStyle(element);
|
|
1498
1916
|
const zeilenhoehe = Number.parseFloat(stil.lineHeight) || Number.parseFloat(stil.fontSize) * 1.4;
|
|
1499
1917
|
const polster = Number.parseFloat(stil.paddingTop) + Number.parseFloat(stil.paddingBottom);
|
|
1500
|
-
const maximum = zeilenhoehe * Math.max(
|
|
1918
|
+
const maximum = zeilenhoehe * Math.max(inputMaxRows, inputRows) + polster;
|
|
1501
1919
|
element.style.height = "auto";
|
|
1502
1920
|
const inhalt = element.scrollHeight;
|
|
1503
1921
|
element.style.height = `${Math.min(inhalt, maximum)}px`;
|
|
1504
1922
|
element.style.overflowY = inhalt > maximum ? "auto" : "hidden";
|
|
1505
|
-
}, [
|
|
1506
|
-
|
|
1923
|
+
}, [inputMaxRows, inputRows]);
|
|
1924
|
+
useEffect5(() => {
|
|
1507
1925
|
applyAutoGrow();
|
|
1508
1926
|
}, [draft, applyAutoGrow]);
|
|
1509
|
-
|
|
1927
|
+
useEffect5(() => {
|
|
1510
1928
|
const element = eingabeRef.current;
|
|
1511
|
-
if (!element ||
|
|
1929
|
+
if (!element || inputMaxRows === void 0) {
|
|
1512
1930
|
return;
|
|
1513
1931
|
}
|
|
1514
1932
|
let lastWidth = element.clientWidth;
|
|
@@ -1520,8 +1938,8 @@ function ChatInputToolbar({
|
|
|
1520
1938
|
});
|
|
1521
1939
|
observer.observe(element);
|
|
1522
1940
|
return () => observer.disconnect();
|
|
1523
|
-
}, [
|
|
1524
|
-
|
|
1941
|
+
}, [inputMaxRows, applyAutoGrow]);
|
|
1942
|
+
useEffect5(() => {
|
|
1525
1943
|
const element = rootRef.current;
|
|
1526
1944
|
if (!element) {
|
|
1527
1945
|
return;
|
|
@@ -1532,79 +1950,245 @@ function ChatInputToolbar({
|
|
|
1532
1950
|
beobachter.observe(element);
|
|
1533
1951
|
return () => beobachter.disconnect();
|
|
1534
1952
|
}, []);
|
|
1535
|
-
const
|
|
1536
|
-
|
|
1953
|
+
const hasAttachments = attachments.length > 0;
|
|
1954
|
+
useEffect5(() => {
|
|
1955
|
+
onAttachmentsChange?.(hasAttachments);
|
|
1956
|
+
}, [hasAttachments, onAttachmentsChange]);
|
|
1957
|
+
const capabilityError = hasAttachments && attachmentCapabilitiesError ? attachmentCapabilitiesError : attachmentCapabilities ? attachmentCapabilityError(attachments.map((entry) => ({
|
|
1958
|
+
name: entry.file.name,
|
|
1959
|
+
mediaType: attachmentMediaType(entry.file)
|
|
1960
|
+
})), attachmentCapabilities, alleTexte) : void 0;
|
|
1961
|
+
const hasContent = draft.trim().length > 0 || attachments.length > 0;
|
|
1962
|
+
const preparing = attachments.some((entry) => entry.status === "reading");
|
|
1963
|
+
const attachmentBlocked = attachments.some((entry) => entry.status !== "ready");
|
|
1964
|
+
const currentError = sendError ?? attachmentError ?? capabilityError ?? attachments.find((entry) => entry.status === "error")?.error;
|
|
1965
|
+
useEffect5(() => {
|
|
1966
|
+
onErrorChange?.(currentError);
|
|
1967
|
+
}, [currentError, onErrorChange]);
|
|
1968
|
+
const send = async (onSubmit = onSend, allowEmpty = false) => {
|
|
1537
1969
|
const text = draft.trim();
|
|
1538
|
-
|
|
1970
|
+
const snapshot = attachmentsRef.current;
|
|
1971
|
+
if (!allowEmpty && !text && !snapshot.length || disabled || sendDisabled || pendingSend.current || capabilityError || snapshot.some((entry) => entry.status !== "ready")) {
|
|
1539
1972
|
return;
|
|
1540
1973
|
}
|
|
1541
|
-
|
|
1974
|
+
pendingSend.current = true;
|
|
1975
|
+
const sentGeneration = generation.current;
|
|
1976
|
+
const sentRevision = draftRevision.current;
|
|
1977
|
+
const sentDraft = draft;
|
|
1978
|
+
const focusOrigin = document.activeElement;
|
|
1979
|
+
const startedFocused = focusOrigin === eingabeRef.current || rootRef.current?.contains(focusOrigin);
|
|
1980
|
+
let leftComposer = false;
|
|
1981
|
+
const observeFocus = (event) => {
|
|
1982
|
+
const target = event.target;
|
|
1983
|
+
if (target instanceof Node && !rootRef.current?.contains(target) && !detailsContainer?.contains(target)) leftComposer = true;
|
|
1984
|
+
};
|
|
1985
|
+
document.addEventListener("focusin", observeFocus);
|
|
1986
|
+
document.addEventListener("pointerdown", observeFocus);
|
|
1987
|
+
setSending(true);
|
|
1988
|
+
setSendError(void 0);
|
|
1989
|
+
setDraft("");
|
|
1990
|
+
updateAttachments(() => []);
|
|
1991
|
+
try {
|
|
1992
|
+
const inputs = snapshot.flatMap((entry) => entry.status === "ready" ? [entry.input] : []);
|
|
1993
|
+
await onSubmit(text, inputs.length ? inputs : void 0);
|
|
1994
|
+
if (generation.current === sentGeneration && (text || inputs.length)) sendScope?.notify();
|
|
1995
|
+
} catch (error) {
|
|
1996
|
+
if (generation.current !== sentGeneration) return;
|
|
1997
|
+
if (draftRevision.current === sentRevision) {
|
|
1998
|
+
setDraft(sentDraft);
|
|
1999
|
+
updateAttachments((current) => [...snapshot, ...current]);
|
|
2000
|
+
} else if (sentDraft || snapshot.length) {
|
|
2001
|
+
const failed = { id: ++nextFailedId.current, draft: sentDraft, attachments: snapshot };
|
|
2002
|
+
setFailedSubmissions((current) => [...current, failed]);
|
|
2003
|
+
}
|
|
2004
|
+
setSendError(error instanceof Error ? error.message : String(error));
|
|
2005
|
+
} finally {
|
|
2006
|
+
document.removeEventListener("focusin", observeFocus);
|
|
2007
|
+
document.removeEventListener("pointerdown", observeFocus);
|
|
2008
|
+
if (generation.current === sentGeneration) {
|
|
2009
|
+
pendingSend.current = false;
|
|
2010
|
+
pendingFocus.current = !!startedFocused && !leftComposer && document.hasFocus() && (!toolbar || focusOrigin !== eingabeRef.current) && (document.activeElement === focusOrigin || !toolbar && document.activeElement === document.body);
|
|
2011
|
+
setSending(false);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
1542
2014
|
};
|
|
1543
2015
|
const onKeyDown = (event) => {
|
|
1544
|
-
if (
|
|
2016
|
+
if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing && event.nativeEvent.keyCode !== 229 && (sendShortcut === "enter" || event.metaKey || event.ctrlKey)) {
|
|
1545
2017
|
event.preventDefault();
|
|
1546
2018
|
if (!event.repeat) void send();
|
|
1547
2019
|
}
|
|
1548
2020
|
};
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
/* @__PURE__ */
|
|
1552
|
-
|
|
2021
|
+
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: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": alleTexte.attachments, children: attachments.map((entry) => /* @__PURE__ */ jsxs8("li", { className: attachmentItemClasses, children: [
|
|
2022
|
+
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 }),
|
|
2023
|
+
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}` }),
|
|
2024
|
+
/* @__PURE__ */ jsxs8("div", { className: "qsl:flex qsl:flex-col qsl:gap-1 qsl:p-2 qsl:pr-8 qsl:[overflow-wrap:anywhere]", children: [
|
|
2025
|
+
/* @__PURE__ */ jsx15("span", { className: "qsl:font-medium", title: entry.file.name, children: entry.file.name }),
|
|
2026
|
+
/* @__PURE__ */ jsx15("small", { children: formatAttachmentSize(entry.file.size) }),
|
|
2027
|
+
entry.status === "reading" && /* @__PURE__ */ jsx15("small", { role: "status", children: alleTexte.attachmentPreparing }),
|
|
2028
|
+
entry.status === "error" && /* @__PURE__ */ jsx15("small", { role: "alert", children: entry.error })
|
|
2029
|
+
] }),
|
|
2030
|
+
/* @__PURE__ */ jsx15(
|
|
2031
|
+
Button2,
|
|
1553
2032
|
{
|
|
1554
|
-
"aria-label": alleTexte.
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
2033
|
+
"aria-label": fillText(alleTexte.removeAttachment, { name: entry.file.name }),
|
|
2034
|
+
className: "qsl:absolute qsl:top-1 qsl:right-1",
|
|
2035
|
+
disabled: disabled || sending,
|
|
2036
|
+
onClick: () => {
|
|
2037
|
+
updateAttachments((current) => current.filter((item) => item.id !== entry.id));
|
|
2038
|
+
setAttachmentError(void 0);
|
|
2039
|
+
eingabeRef.current?.focus();
|
|
2040
|
+
},
|
|
2041
|
+
size: "icon-sm",
|
|
2042
|
+
title: fillText(alleTexte.removeAttachment, { name: entry.file.name }),
|
|
2043
|
+
variant: "outline",
|
|
2044
|
+
children: /* @__PURE__ */ jsx15(XIcon2, {})
|
|
1562
2045
|
}
|
|
1563
|
-
)
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
]
|
|
1578
|
-
},
|
|
1579
|
-
index
|
|
1580
|
-
)),
|
|
1581
|
-
toolbarLeft
|
|
2046
|
+
)
|
|
2047
|
+
] }, entry.id)) });
|
|
2048
|
+
const errors = /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2049
|
+
capabilityError && /* @__PURE__ */ jsx15("p", { className: errorClasses, role: "alert", children: capabilityError }),
|
|
2050
|
+
attachmentError && /* @__PURE__ */ jsx15("p", { className: errorClasses, role: "alert", children: attachmentError }),
|
|
2051
|
+
sendError && /* @__PURE__ */ jsx15("p", { className: errorClasses, role: "alert", children: sendError }),
|
|
2052
|
+
failedSubmissions.map((failed) => /* @__PURE__ */ jsxs8("div", { className: cn(errorClasses, "qsl:flex qsl:flex-wrap qsl:items-center qsl:gap-2"), role: "status", children: [
|
|
2053
|
+
/* @__PURE__ */ jsxs8("span", { children: [
|
|
2054
|
+
alleTexte.notSent,
|
|
2055
|
+
" ",
|
|
2056
|
+
failed.draft.slice(0, 100),
|
|
2057
|
+
failed.draft.length > 100 ? " ..." : "",
|
|
2058
|
+
failed.attachments.length > 0 ? ` ${fillText(alleTexte.notSentAttachments, { count: failed.attachments.length })}` : "",
|
|
2059
|
+
" "
|
|
1582
2060
|
] }),
|
|
1583
|
-
/* @__PURE__ */
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
2061
|
+
/* @__PURE__ */ jsx15(Button2, { disabled: disabled || sending, size: "sm", variant: "outline", onClick: () => {
|
|
2062
|
+
try {
|
|
2063
|
+
validateAttachmentSelection(attachmentsRef.current.map((entry) => entry.file), failed.attachments.map((entry) => entry.file), alleTexte);
|
|
2064
|
+
} catch (error) {
|
|
2065
|
+
setAttachmentError(error instanceof Error ? error.message : String(error));
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
draftRevision.current += 1;
|
|
2069
|
+
setDraft((current) => [current, failed.draft].filter(Boolean).join("\n\n"));
|
|
2070
|
+
updateAttachments((current) => [...current, ...failed.attachments]);
|
|
2071
|
+
setFailedSubmissions((current) => current.filter((entry) => entry.id !== failed.id));
|
|
2072
|
+
setAttachmentError(void 0);
|
|
2073
|
+
pendingFocus.current = true;
|
|
2074
|
+
}, children: alleTexte.insertUnsent })
|
|
2075
|
+
] }, failed.id))
|
|
2076
|
+
] });
|
|
2077
|
+
const sendButtons = /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2078
|
+
running && onStop && !hasContent && /* @__PURE__ */ jsx15(StopButton2, { className: "qsl:flex-none", label: alleTexte.stop, onClick: onStop, size: "icon-sm" }),
|
|
2079
|
+
(!running || !onStop || hasContent) && /* @__PURE__ */ jsx15(
|
|
2080
|
+
Button2,
|
|
2081
|
+
{
|
|
2082
|
+
"aria-label": alleTexte.send,
|
|
2083
|
+
className: "qsl:flex-none",
|
|
2084
|
+
disabled: disabled || sendDisabled || sending || attachmentBlocked || !!capabilityError || !hasContent,
|
|
2085
|
+
onClick: () => {
|
|
2086
|
+
void send();
|
|
2087
|
+
},
|
|
2088
|
+
size: "icon-sm",
|
|
2089
|
+
title: running ? alleTexte.sendIntoRun : alleTexte.send,
|
|
2090
|
+
children: /* @__PURE__ */ jsx15(SendIcon, { size: 14 })
|
|
2091
|
+
}
|
|
2092
|
+
)
|
|
2093
|
+
] });
|
|
2094
|
+
const detailToolbar = /* @__PURE__ */ jsxs8("div", { className: toolbarRowClasses, "data-input": "controls", children: [
|
|
2095
|
+
/* @__PURE__ */ jsxs8("div", { className: cn(toolbarSideClasses, "qsl:in-data-[tone=overseer-details]:flex-none", inline || toolbar ? "qsl:flex-none" : "qsl:flex-auto"), children: [
|
|
2096
|
+
attachmentsEnabled && /* @__PURE__ */ jsx15(
|
|
2097
|
+
Button2,
|
|
2098
|
+
{
|
|
2099
|
+
"aria-label": alleTexte.attachFiles,
|
|
2100
|
+
disabled: disabled || sending,
|
|
2101
|
+
onClick: () => fileInput.inputRef.current?.click(),
|
|
2102
|
+
size: "icon-sm",
|
|
2103
|
+
title: fillText(alleTexte.attachFilesHint, { count: MAX_CHAT_ATTACHMENTS, size: maxAttachmentSizeLabel }),
|
|
2104
|
+
variant: "ghost",
|
|
2105
|
+
children: /* @__PURE__ */ jsx15(PaperclipIcon, {})
|
|
2106
|
+
}
|
|
2107
|
+
),
|
|
2108
|
+
preparing && /* @__PURE__ */ jsx15("span", { className: cn("qsl:text-xs qsl:text-muted-foreground", inline && "qsl:sr-only"), role: "status", children: alleTexte.attachmentsPreparing }),
|
|
2109
|
+
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: [
|
|
2110
|
+
action.icon,
|
|
2111
|
+
action.label && /* @__PURE__ */ jsx15("span", { className: action.icon ? "qsl:in-data-[compact=true]:hidden" : void 0, children: action.label })
|
|
2112
|
+
] }, index) : /* @__PURE__ */ jsxs8(Toggle2, { disabled: action.disabled, onPressedChange: () => action.onClick(), pressed: action.active, size: "sm", title: action.title ?? action.label, children: [
|
|
2113
|
+
action.icon,
|
|
2114
|
+
action.label && /* @__PURE__ */ jsx15("span", { className: action.icon ? "qsl:in-data-[compact=true]:hidden" : void 0, children: action.label })
|
|
2115
|
+
] }, index)),
|
|
2116
|
+
toolbarLeft
|
|
2117
|
+
] }),
|
|
2118
|
+
/* @__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: [
|
|
2119
|
+
toolbarRight,
|
|
2120
|
+
!toolbar && sendButtons
|
|
1599
2121
|
] })
|
|
1600
2122
|
] });
|
|
2123
|
+
const details = /* @__PURE__ */ jsxs8("div", { className: "qsl:min-w-0", "data-compact": compact, "data-quassel": "", children: [
|
|
2124
|
+
attachmentPreviews,
|
|
2125
|
+
errors,
|
|
2126
|
+
detailToolbar
|
|
2127
|
+
] });
|
|
2128
|
+
const input = /* @__PURE__ */ jsx15(
|
|
2129
|
+
"textarea",
|
|
2130
|
+
{
|
|
2131
|
+
"aria-controls": inputAriaControls,
|
|
2132
|
+
className: textareaClasses,
|
|
2133
|
+
"aria-label": alleTexte.placeholder,
|
|
2134
|
+
disabled: !toolbar && disabled,
|
|
2135
|
+
readOnly: toolbar && disabled,
|
|
2136
|
+
onChange: (event) => changeDraft(event.target.value),
|
|
2137
|
+
onKeyDown,
|
|
2138
|
+
placeholder: running ? alleTexte.steeringPlaceholder : alleTexte.placeholder,
|
|
2139
|
+
ref: eingabeRef,
|
|
2140
|
+
rows: inputRows,
|
|
2141
|
+
value: draft
|
|
2142
|
+
}
|
|
2143
|
+
);
|
|
2144
|
+
return /* @__PURE__ */ jsxs8(
|
|
2145
|
+
"div",
|
|
2146
|
+
{
|
|
2147
|
+
className: rootClasses,
|
|
2148
|
+
"data-compact": compact,
|
|
2149
|
+
"data-layout": layout,
|
|
2150
|
+
"data-quassel": "",
|
|
2151
|
+
"data-working": running || sending,
|
|
2152
|
+
ref: rootRef,
|
|
2153
|
+
...attachmentsEnabled ? fileInput.dropProps : {},
|
|
2154
|
+
children: [
|
|
2155
|
+
attachmentsEnabled && /* @__PURE__ */ jsx15(
|
|
2156
|
+
"input",
|
|
2157
|
+
{
|
|
2158
|
+
"aria-label": alleTexte.attachFiles,
|
|
2159
|
+
hidden: true,
|
|
2160
|
+
multiple: true,
|
|
2161
|
+
type: "file",
|
|
2162
|
+
...fileInput.inputProps
|
|
2163
|
+
}
|
|
2164
|
+
),
|
|
2165
|
+
!toolbar && attachmentPreviews,
|
|
2166
|
+
inline ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2167
|
+
errors,
|
|
2168
|
+
/* @__PURE__ */ jsxs8("div", { className: "qsl:flex qsl:min-w-0 qsl:items-center", children: [
|
|
2169
|
+
input,
|
|
2170
|
+
detailToolbar
|
|
2171
|
+
] })
|
|
2172
|
+
] }) : input,
|
|
2173
|
+
toolbar ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2174
|
+
/* @__PURE__ */ jsx15("div", { className: "qsl:flex qsl:flex-none qsl:items-center qsl:gap-1 qsl:p-1", children: sendButtons }),
|
|
2175
|
+
detailsContainer && createPortal(details, detailsContainer)
|
|
2176
|
+
] }) : !inline && /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2177
|
+
errors,
|
|
2178
|
+
detailToolbar
|
|
2179
|
+
] })
|
|
2180
|
+
]
|
|
2181
|
+
}
|
|
2182
|
+
);
|
|
1601
2183
|
}
|
|
1602
2184
|
|
|
1603
2185
|
// packages/quassel/src/DetailModeSwitch.tsx
|
|
1604
|
-
import {
|
|
1605
|
-
|
|
2186
|
+
import { SparklesIcon as SparklesIcon3 } from "lucide-react";
|
|
2187
|
+
import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2188
|
+
var DETAIL_MODES = ["off", "current", "icons", "chips", "grouped", "compact", "full"];
|
|
1606
2189
|
var TEXT_KEYS = {
|
|
1607
2190
|
off: "detailModeOff",
|
|
2191
|
+
current: "detailModeCurrent",
|
|
1608
2192
|
icons: "detailModeIcons",
|
|
1609
2193
|
chips: "detailModeChips",
|
|
1610
2194
|
grouped: "detailModeGrouped",
|
|
@@ -1621,19 +2205,46 @@ function DetailModeSwitch({
|
|
|
1621
2205
|
className
|
|
1622
2206
|
}) {
|
|
1623
2207
|
const alleTexte = { ...defaultTexts, ...texts };
|
|
2208
|
+
const { Button: Button2 } = useQuasselComponents();
|
|
1624
2209
|
const aktuell = modes.indexOf(mode);
|
|
1625
2210
|
const naechster = modes[(aktuell < 0 ? 0 : aktuell + 1) % modes.length];
|
|
1626
|
-
return /* @__PURE__ */
|
|
1627
|
-
|
|
2211
|
+
return /* @__PURE__ */ jsxs9(
|
|
2212
|
+
Button2,
|
|
1628
2213
|
{
|
|
1629
2214
|
"aria-label": `${alleTexte.detailModeTitle}: ${detailModeLabel(mode, texts)}`,
|
|
1630
|
-
className
|
|
2215
|
+
className,
|
|
1631
2216
|
onClick: () => onChange(naechster),
|
|
2217
|
+
size: "sm",
|
|
1632
2218
|
title: `${alleTexte.detailModeTitle}: ${detailModeLabel(mode, texts)}`,
|
|
1633
|
-
|
|
2219
|
+
variant: "ghost",
|
|
2220
|
+
children: [
|
|
2221
|
+
/* @__PURE__ */ jsx16(SparklesIcon3, {}),
|
|
2222
|
+
/* @__PURE__ */ jsx16("span", { className: collapsible ? "qsl:in-data-[compact=true]:hidden" : void 0, children: detailModeLabel(mode, texts) })
|
|
2223
|
+
]
|
|
2224
|
+
}
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
// packages/quassel/src/TimestampSwitch.tsx
|
|
2229
|
+
import { ClockIcon } from "lucide-react";
|
|
2230
|
+
import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2231
|
+
function TimestampSwitch({ showTimestamps, onChange, collapsible = true, texts, className }) {
|
|
2232
|
+
const labels = { ...defaultTexts, ...texts };
|
|
2233
|
+
const { Button: Button2 } = useQuasselComponents();
|
|
2234
|
+
const title = showTimestamps ? labels.hideTimestamps : labels.showTimestamps;
|
|
2235
|
+
return /* @__PURE__ */ jsxs10(
|
|
2236
|
+
Button2,
|
|
2237
|
+
{
|
|
2238
|
+
"aria-label": title,
|
|
2239
|
+
"aria-pressed": showTimestamps,
|
|
2240
|
+
className,
|
|
2241
|
+
onClick: () => onChange(!showTimestamps),
|
|
2242
|
+
size: "sm",
|
|
2243
|
+
title,
|
|
2244
|
+
variant: "ghost",
|
|
1634
2245
|
children: [
|
|
1635
|
-
/* @__PURE__ */
|
|
1636
|
-
/* @__PURE__ */
|
|
2246
|
+
/* @__PURE__ */ jsx17(ClockIcon, {}),
|
|
2247
|
+
/* @__PURE__ */ jsx17("span", { className: collapsible ? "qsl:in-data-[compact=true]:hidden" : void 0, children: labels.timestamps })
|
|
1637
2248
|
]
|
|
1638
2249
|
}
|
|
1639
2250
|
);
|
|
@@ -1642,28 +2253,44 @@ export {
|
|
|
1642
2253
|
ChatInputToolbar,
|
|
1643
2254
|
ChatMessages,
|
|
1644
2255
|
ChatPanel,
|
|
2256
|
+
ChatSendContext,
|
|
1645
2257
|
DETAIL_MODES,
|
|
1646
2258
|
DetailModeSwitch,
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
IconChevronRight,
|
|
1650
|
-
IconClock,
|
|
1651
|
-
IconLayers,
|
|
1652
|
-
IconSend,
|
|
1653
|
-
IconSpark,
|
|
1654
|
-
IconStop,
|
|
1655
|
-
IconTool,
|
|
1656
|
-
IconX,
|
|
2259
|
+
MAX_CHAT_ATTACHMENTS,
|
|
2260
|
+
MAX_CHAT_ATTACHMENT_BYTES,
|
|
1657
2261
|
Markdown,
|
|
2262
|
+
MarkdownCodeBlocks,
|
|
2263
|
+
MarkdownLinks,
|
|
2264
|
+
MessageActions,
|
|
2265
|
+
PendingActionCard,
|
|
2266
|
+
QuasselProvider,
|
|
2267
|
+
StepPopover,
|
|
1658
2268
|
TimestampSwitch,
|
|
2269
|
+
WorkingScenes,
|
|
1659
2270
|
announce,
|
|
2271
|
+
appearanceStyle,
|
|
1660
2272
|
applyEvent,
|
|
2273
|
+
attachmentCapabilityError,
|
|
2274
|
+
attachmentDownloadUrl,
|
|
2275
|
+
attachmentMediaType,
|
|
1661
2276
|
compactToolLine,
|
|
2277
|
+
copyChatText,
|
|
2278
|
+
createChatScroll,
|
|
2279
|
+
createChatSendScope,
|
|
2280
|
+
defaultComponents,
|
|
1662
2281
|
defaultTexts,
|
|
1663
2282
|
detailModeLabel,
|
|
2283
|
+
encodeAttachment,
|
|
2284
|
+
fillText,
|
|
2285
|
+
formatAttachmentSize,
|
|
1664
2286
|
markdownPlainText,
|
|
2287
|
+
messageDate,
|
|
1665
2288
|
prettyJson,
|
|
1666
2289
|
stepState,
|
|
1667
|
-
|
|
2290
|
+
timestampDay,
|
|
2291
|
+
timestampLabel,
|
|
2292
|
+
useChatAction,
|
|
2293
|
+
useQuasselComponents,
|
|
2294
|
+
validateAttachmentSelection
|
|
1668
2295
|
};
|
|
1669
2296
|
//# sourceMappingURL=index.js.map
|