quassel 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 -980
- 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: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,31 +1057,24 @@ function ChatMessages({
|
|
|
960
1057
|
messageActions
|
|
961
1058
|
}) {
|
|
962
1059
|
const alleTexte = { ...defaultTexts, ...texts };
|
|
963
|
-
const
|
|
964
|
-
const
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
const [now, setNow] =
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
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;
|
|
975
1071
|
setNow(Date.now());
|
|
976
|
-
const timer = window.setInterval(() => setNow(Date.now()),
|
|
1072
|
+
const timer = window.setInterval(() => setNow(Date.now()), 15e3);
|
|
977
1073
|
return () => window.clearInterval(timer);
|
|
978
|
-
}, [
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
const timer = window.setTimeout(() => setStromAktiv(false), workingTimeoutMs);
|
|
983
|
-
return () => window.clearTimeout(timer);
|
|
984
|
-
}, [messages, workingTimeoutMs]);
|
|
985
|
-
const angesagt = useRef5(new Set(messages.map((message) => message.key)));
|
|
986
|
-
useEffect5(() => {
|
|
987
|
-
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)));
|
|
988
1078
|
if (faellig.length === 0) {
|
|
989
1079
|
return;
|
|
990
1080
|
}
|
|
@@ -992,62 +1082,92 @@ function ChatMessages({
|
|
|
992
1082
|
if (announce2 === false || faellig.length > hoechstensAnsagenAufEinmal) {
|
|
993
1083
|
return;
|
|
994
1084
|
}
|
|
995
|
-
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));
|
|
996
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
|
+
};
|
|
997
1104
|
const pruefeEnde = useCallback2(() => {
|
|
998
1105
|
const bereich = scrollBereich.current;
|
|
999
1106
|
if (bereich) {
|
|
1000
|
-
|
|
1001
|
-
|
|
1107
|
+
scroll.scroll(bereich, scrollbarDragging.current);
|
|
1108
|
+
updateJumpVisibility(bereich);
|
|
1002
1109
|
}
|
|
1003
|
-
}, [
|
|
1004
|
-
|
|
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(() => {
|
|
1005
1124
|
const bereich = scrollBereich.current;
|
|
1006
|
-
if (!bereich)
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
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);
|
|
1010
1132
|
beobachter.observe(bereich);
|
|
1011
|
-
|
|
1012
|
-
beobachter.observe(kind);
|
|
1013
|
-
}
|
|
1133
|
+
if (threadRef.current) beobachter.observe(threadRef.current, { box: "border-box" });
|
|
1014
1134
|
return () => beobachter.disconnect();
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
}
|
|
1032
|
-
[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]
|
|
1033
1153
|
);
|
|
1034
|
-
const
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
/* @__PURE__ */
|
|
1040
|
-
/* @__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 })
|
|
1041
1160
|
] }, `zeit-${key}`) : inhalt;
|
|
1042
|
-
const chipModus = detailMode === "chips" || detailMode === "icons";
|
|
1161
|
+
const chipModus = detailMode === "current" || detailMode === "chips" || detailMode === "icons";
|
|
1043
1162
|
const gruppenModus = detailMode === "grouped";
|
|
1044
1163
|
const bloecke = [];
|
|
1164
|
+
let lastDay;
|
|
1045
1165
|
let gruppe = [];
|
|
1046
1166
|
const schliesseGruppe = () => {
|
|
1047
1167
|
if (gruppe.length > 0) {
|
|
1048
1168
|
const letzterZustand = stepState(gruppe[gruppe.length - 1]);
|
|
1049
1169
|
bloecke.push(mitZeit(
|
|
1050
|
-
gruppenModus ? /* @__PURE__ */
|
|
1170
|
+
gruppenModus ? /* @__PURE__ */ jsx13(
|
|
1051
1171
|
StepGroup,
|
|
1052
1172
|
{
|
|
1053
1173
|
aktiv: running && (letzterZustand === "running" || letzterZustand === "thinking"),
|
|
@@ -1057,14 +1177,15 @@ function ChatMessages({
|
|
|
1057
1177
|
toolArgumentsText
|
|
1058
1178
|
},
|
|
1059
1179
|
gruppe[0].key
|
|
1060
|
-
) : /* @__PURE__ */
|
|
1180
|
+
) : /* @__PURE__ */ jsx13(
|
|
1061
1181
|
StepRow,
|
|
1062
1182
|
{
|
|
1063
1183
|
expandierbar: stepsExpandable,
|
|
1064
1184
|
messages: gruppe,
|
|
1065
|
-
mitText: detailMode
|
|
1185
|
+
mitText: detailMode !== "icons",
|
|
1066
1186
|
texts: alleTexte,
|
|
1067
|
-
toolArgumentsText
|
|
1187
|
+
toolArgumentsText,
|
|
1188
|
+
toolLabel: detailMode === "current" && !stepsExpandable ? alleTexte.currentTool : void 0
|
|
1068
1189
|
},
|
|
1069
1190
|
gruppe[0].key
|
|
1070
1191
|
),
|
|
@@ -1074,22 +1195,21 @@ function ChatMessages({
|
|
|
1074
1195
|
gruppe = [];
|
|
1075
1196
|
}
|
|
1076
1197
|
};
|
|
1077
|
-
let currentDay;
|
|
1078
1198
|
visible.forEach((message, index) => {
|
|
1079
|
-
const date = timestampOptions?.showDaySeparators ?
|
|
1199
|
+
const date = timestampOptions?.showDaySeparators ? messageDate(message.at) : void 0;
|
|
1080
1200
|
if (date) {
|
|
1081
|
-
const day = timestampDay(date, timestampOptions
|
|
1082
|
-
if (day !==
|
|
1201
|
+
const day = timestampDay(date, timestampOptions);
|
|
1202
|
+
if (day.key !== lastDay) {
|
|
1083
1203
|
schliesseGruppe();
|
|
1084
|
-
bloecke.push(/* @__PURE__ */
|
|
1085
|
-
|
|
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;
|
|
1086
1206
|
}
|
|
1087
1207
|
}
|
|
1088
|
-
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;
|
|
1089
1209
|
if (eigeneDarstellung !== void 0) {
|
|
1090
1210
|
schliesseGruppe();
|
|
1091
1211
|
bloecke.push(mitZeit(
|
|
1092
|
-
/* @__PURE__ */
|
|
1212
|
+
/* @__PURE__ */ jsx13("div", { className: stepClasses, children: eigeneDarstellung }, message.key),
|
|
1093
1213
|
message.key,
|
|
1094
1214
|
message.at
|
|
1095
1215
|
));
|
|
@@ -1100,77 +1220,134 @@ function ChatMessages({
|
|
|
1100
1220
|
return;
|
|
1101
1221
|
}
|
|
1102
1222
|
schliesseGruppe();
|
|
1223
|
+
const dicht = istSchritt(message) && istSchritt(visible[index - 1]);
|
|
1103
1224
|
bloecke.push(mitZeit(
|
|
1104
|
-
/* @__PURE__ */
|
|
1225
|
+
/* @__PURE__ */ jsx13(
|
|
1105
1226
|
Bubble,
|
|
1106
1227
|
{
|
|
1107
1228
|
detailMode,
|
|
1108
|
-
dicht
|
|
1229
|
+
dicht,
|
|
1109
1230
|
expandierbar: stepsExpandable,
|
|
1110
1231
|
message,
|
|
1111
|
-
|
|
1112
|
-
messageActions,
|
|
1232
|
+
plain: owner != null && message.sender === owner,
|
|
1113
1233
|
texts: alleTexte,
|
|
1114
1234
|
toolArgumentsText,
|
|
1115
|
-
|
|
1235
|
+
renderAction,
|
|
1236
|
+
onDismissAction,
|
|
1237
|
+
bubbleOptions,
|
|
1238
|
+
messageActions
|
|
1116
1239
|
},
|
|
1117
1240
|
message.key
|
|
1118
1241
|
),
|
|
1119
1242
|
message.key,
|
|
1120
|
-
message.at
|
|
1243
|
+
message.at,
|
|
1244
|
+
dicht
|
|
1121
1245
|
));
|
|
1122
1246
|
});
|
|
1123
1247
|
schliesseGruppe();
|
|
1124
|
-
const verlauf = /* @__PURE__ */ jsxs6("div", { className:
|
|
1125
|
-
|
|
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(
|
|
1126
1254
|
"div",
|
|
1127
1255
|
{
|
|
1128
|
-
|
|
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
|
+
),
|
|
1129
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,
|
|
1130
1286
|
ref: (element) => {
|
|
1131
1287
|
scrollBereich.current = element;
|
|
1132
1288
|
scrollerRef?.(element);
|
|
1133
1289
|
},
|
|
1134
|
-
children: visible.length === 0 && !running ? emptyState ?? null : /* @__PURE__ */ jsxs6(
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
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
|
+
)
|
|
1138
1306
|
}
|
|
1139
1307
|
),
|
|
1140
|
-
|
|
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, {}) }) })
|
|
1141
1314
|
] });
|
|
1142
|
-
return /* @__PURE__ */
|
|
1315
|
+
return /* @__PURE__ */ jsx13(MarkdownLinks, { onLinkClick, children: /* @__PURE__ */ jsx13(MarkdownCodeBlocks, { options: codeBlockOptions, texts: alleTexte, children: verlauf }) });
|
|
1143
1316
|
}
|
|
1144
1317
|
function StepRow({
|
|
1145
1318
|
messages,
|
|
1146
1319
|
mitText,
|
|
1147
1320
|
expandierbar,
|
|
1148
1321
|
texts,
|
|
1149
|
-
toolArgumentsText
|
|
1322
|
+
toolArgumentsText,
|
|
1323
|
+
toolLabel
|
|
1150
1324
|
}) {
|
|
1151
|
-
const [detail, setDetail] =
|
|
1325
|
+
const [detail, setDetail] = useState4();
|
|
1152
1326
|
const offen = expandierbar && detail && messages.find((message) => message.key === detail.key);
|
|
1153
|
-
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: [
|
|
1154
1328
|
messages.map((message) => {
|
|
1155
1329
|
const thinking = message.role === "thinking";
|
|
1156
1330
|
const tool = message.tool;
|
|
1157
1331
|
const state = stepState(message);
|
|
1158
1332
|
const running = state === "running" || state === "thinking";
|
|
1159
|
-
const label = thinking ? texts.thinkingChip : tool?.name
|
|
1160
|
-
const content = /* @__PURE__ */ jsxs6(
|
|
1161
|
-
thinking ? /* @__PURE__ */
|
|
1162
|
-
mitText && /* @__PURE__ */
|
|
1163
|
-
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 }) })
|
|
1164
1338
|
] });
|
|
1165
|
-
const classes =
|
|
1339
|
+
const classes = cn(chipClasses, !mitText && "qsl:p-1", running && "qsl:animate-fade-pulse qsl:motion-reduce:animate-none");
|
|
1166
1340
|
if (!expandierbar) {
|
|
1167
|
-
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);
|
|
1168
1342
|
}
|
|
1169
|
-
return /* @__PURE__ */
|
|
1343
|
+
return /* @__PURE__ */ jsx13(
|
|
1170
1344
|
"button",
|
|
1171
1345
|
{
|
|
1172
1346
|
"aria-haspopup": "dialog",
|
|
1173
|
-
className:
|
|
1347
|
+
className: cn(classes, chipInteractiveClasses),
|
|
1348
|
+
"data-kind": thinking ? "thinking" : "tool",
|
|
1349
|
+
"data-state": state,
|
|
1350
|
+
"data-step": "chip",
|
|
1174
1351
|
onClick: (event) => setDetail({ key: message.key, position: { x: event.clientX, y: event.clientY } }),
|
|
1175
1352
|
title: label,
|
|
1176
1353
|
type: "button",
|
|
@@ -1179,7 +1356,7 @@ function StepRow({
|
|
|
1179
1356
|
message.key
|
|
1180
1357
|
);
|
|
1181
1358
|
}),
|
|
1182
|
-
offen && detail && /* @__PURE__ */
|
|
1359
|
+
offen && detail && /* @__PURE__ */ jsx13(
|
|
1183
1360
|
StepPopover,
|
|
1184
1361
|
{
|
|
1185
1362
|
message: offen,
|
|
@@ -1198,10 +1375,10 @@ function StepGroup({
|
|
|
1198
1375
|
texts,
|
|
1199
1376
|
toolArgumentsText
|
|
1200
1377
|
}) {
|
|
1201
|
-
const [offen, setOffen] =
|
|
1202
|
-
const kopf =
|
|
1203
|
-
const vonUntenEingeklappt =
|
|
1204
|
-
|
|
1378
|
+
const [offen, setOffen] = useState4(false);
|
|
1379
|
+
const kopf = useRef3(null);
|
|
1380
|
+
const vonUntenEingeklappt = useRef3(false);
|
|
1381
|
+
useLayoutEffect(() => {
|
|
1205
1382
|
if (!offen && vonUntenEingeklappt.current) {
|
|
1206
1383
|
vonUntenEingeklappt.current = false;
|
|
1207
1384
|
kopf.current?.scrollIntoView({ block: "nearest" });
|
|
@@ -1210,22 +1387,22 @@ function StepGroup({
|
|
|
1210
1387
|
const letzte = messages[messages.length - 1];
|
|
1211
1388
|
const fehler = messages.some((message) => message.tool?.isError);
|
|
1212
1389
|
const anzahl = messages.length === 1 ? texts.stepGroupOne : texts.stepGroupMany.replace("{count}", String(messages.length));
|
|
1213
|
-
const laufend = aktiv ? letzte.role === "thinking" ? texts.thinkingChip : letzte.tool?.name
|
|
1214
|
-
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: [
|
|
1215
1392
|
/* @__PURE__ */ jsxs6(
|
|
1216
1393
|
"button",
|
|
1217
1394
|
{
|
|
1218
1395
|
"aria-expanded": offen,
|
|
1219
|
-
className:
|
|
1396
|
+
className: cn(traceClasses, traceButtonClasses, "qsl:items-center qsl:pl-1", aktiv && "qsl:animate-fade-pulse qsl:motion-reduce:animate-none"),
|
|
1220
1397
|
onClick: () => setOffen((wert) => !wert),
|
|
1221
1398
|
ref: kopf,
|
|
1222
1399
|
type: "button",
|
|
1223
1400
|
children: [
|
|
1224
|
-
/* @__PURE__ */
|
|
1225
|
-
/* @__PURE__ */
|
|
1226
|
-
/* @__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: [
|
|
1227
1404
|
anzahl,
|
|
1228
|
-
laufend && /* @__PURE__ */ jsxs6("span", { className:
|
|
1405
|
+
laufend && /* @__PURE__ */ jsxs6("span", { className: traceRunningClasses, children: [
|
|
1229
1406
|
laufend,
|
|
1230
1407
|
" ",
|
|
1231
1408
|
texts.toolRunning
|
|
@@ -1234,24 +1411,23 @@ function StepGroup({
|
|
|
1234
1411
|
]
|
|
1235
1412
|
}
|
|
1236
1413
|
),
|
|
1237
|
-
offen && /* @__PURE__ */
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
] })
|
|
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
|
+
)
|
|
1255
1431
|
] });
|
|
1256
1432
|
}
|
|
1257
1433
|
function TraceLine({
|
|
@@ -1261,28 +1437,36 @@ function TraceLine({
|
|
|
1261
1437
|
texts,
|
|
1262
1438
|
toolArgumentsText
|
|
1263
1439
|
}) {
|
|
1264
|
-
const [detailPosition, setDetailPosition] =
|
|
1265
|
-
const
|
|
1266
|
-
const
|
|
1267
|
-
|
|
1268
|
-
|
|
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
|
+
] })
|
|
1269
1451
|
] });
|
|
1452
|
+
const marks = { "data-compact": "true", "data-kind": thinking ? "thinking" : "tool", "data-step": "line" };
|
|
1270
1453
|
if (!expandierbar) {
|
|
1271
|
-
return /* @__PURE__ */
|
|
1454
|
+
return /* @__PURE__ */ jsx13("div", { className: classes, ...marks, children: inhalt });
|
|
1272
1455
|
}
|
|
1273
|
-
return /* @__PURE__ */ jsxs6(
|
|
1274
|
-
/* @__PURE__ */
|
|
1456
|
+
return /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1457
|
+
/* @__PURE__ */ jsx13(
|
|
1275
1458
|
"button",
|
|
1276
1459
|
{
|
|
1277
1460
|
"aria-expanded": detailPosition !== void 0,
|
|
1278
1461
|
"aria-haspopup": "dialog",
|
|
1279
|
-
className: classes,
|
|
1462
|
+
className: cn(classes, traceButtonClasses),
|
|
1463
|
+
...marks,
|
|
1280
1464
|
onClick: (event) => setDetailPosition({ x: event.clientX, y: event.clientY }),
|
|
1281
1465
|
type: "button",
|
|
1282
1466
|
children: inhalt
|
|
1283
1467
|
}
|
|
1284
1468
|
),
|
|
1285
|
-
detailPosition && /* @__PURE__ */
|
|
1469
|
+
detailPosition && /* @__PURE__ */ jsx13(
|
|
1286
1470
|
StepPopover,
|
|
1287
1471
|
{
|
|
1288
1472
|
message,
|
|
@@ -1294,126 +1478,161 @@ function TraceLine({
|
|
|
1294
1478
|
)
|
|
1295
1479
|
] });
|
|
1296
1480
|
}
|
|
1297
|
-
function TraceIcon({ message }) {
|
|
1298
|
-
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 });
|
|
1299
|
-
}
|
|
1300
|
-
function TraceText({ message, texts }) {
|
|
1301
|
-
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
1302
|
-
message.text,
|
|
1303
|
-
message.tool && message.tool.result === void 0 && /* @__PURE__ */ jsx7("span", { className: "qsl-trace__running", children: texts.toolRunning })
|
|
1304
|
-
] });
|
|
1305
|
-
}
|
|
1306
1481
|
function Bubble({
|
|
1307
1482
|
message,
|
|
1308
|
-
|
|
1309
|
-
messageActions,
|
|
1483
|
+
plain,
|
|
1310
1484
|
detailMode,
|
|
1311
1485
|
dicht,
|
|
1312
1486
|
expandierbar,
|
|
1313
1487
|
texts,
|
|
1314
1488
|
toolArgumentsText,
|
|
1315
|
-
|
|
1489
|
+
renderAction,
|
|
1490
|
+
onDismissAction,
|
|
1491
|
+
bubbleOptions,
|
|
1492
|
+
messageActions
|
|
1316
1493
|
}) {
|
|
1317
|
-
const schritt = dicht ?
|
|
1494
|
+
const schritt = dicht ? denseStepClasses : stepClasses;
|
|
1318
1495
|
if (message.role === "thinking" || message.role === "tool" && message.tool) {
|
|
1319
1496
|
if (detailMode === "compact") {
|
|
1320
|
-
return /* @__PURE__ */
|
|
1497
|
+
return /* @__PURE__ */ jsx13(TraceLine, { className: schritt, expandierbar, message, texts, toolArgumentsText });
|
|
1321
1498
|
}
|
|
1499
|
+
const thinking = message.role === "thinking";
|
|
1322
1500
|
const tool = message.tool;
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
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) })
|
|
1330
1516
|
] })
|
|
1331
1517
|
] })
|
|
1332
1518
|
] });
|
|
1333
1519
|
}
|
|
1334
|
-
if (message.role === "
|
|
1335
|
-
|
|
1336
|
-
|
|
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,
|
|
1337
1524
|
{
|
|
1338
|
-
|
|
1525
|
+
action,
|
|
1526
|
+
dismissedLabel: texts.actionDismissed,
|
|
1527
|
+
dismissLabel: texts.dismissAction,
|
|
1528
|
+
onDismiss: onDismissAction ? () => onDismissAction(action.actionId) : void 0,
|
|
1339
1529
|
text: message.text,
|
|
1340
|
-
texts
|
|
1341
|
-
onAnswer: (text) => onAnswerQuestion?.(message.question.callId, text)
|
|
1530
|
+
waitingLabel: texts.pendingAction
|
|
1342
1531
|
}
|
|
1343
1532
|
) });
|
|
1344
1533
|
}
|
|
1345
1534
|
if (message.role === "system") {
|
|
1346
|
-
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
|
+
] });
|
|
1347
1539
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
const
|
|
1351
|
-
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);
|
|
1352
1543
|
const side = (message.role === "user" ? bubbleOptions?.userSide : bubbleOptions?.assistantSide) ?? message.bubble?.side ?? (message.role === "user" ? "end" : "start");
|
|
1353
|
-
const
|
|
1354
|
-
const
|
|
1355
|
-
|
|
1356
|
-
/* @__PURE__ */
|
|
1357
|
-
|
|
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 })
|
|
1358
1555
|
] });
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
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}`)) });
|
|
1364
1587
|
}
|
|
1365
1588
|
|
|
1366
1589
|
// packages/quassel/src/ChatPanel.tsx
|
|
1367
|
-
import { useEffect as
|
|
1368
|
-
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";
|
|
1369
1592
|
function ChatPanel({
|
|
1370
1593
|
children,
|
|
1371
1594
|
composer,
|
|
1372
1595
|
className,
|
|
1373
1596
|
nodeRef,
|
|
1597
|
+
maxWidth,
|
|
1598
|
+
horizontalPadding,
|
|
1374
1599
|
appearance,
|
|
1375
1600
|
scrollOnSend = false
|
|
1376
1601
|
}) {
|
|
1377
|
-
const
|
|
1378
|
-
const
|
|
1379
|
-
|
|
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(() => {
|
|
1380
1608
|
scrollOnSendRef.current = scrollOnSend;
|
|
1381
1609
|
}, [scrollOnSend]);
|
|
1382
|
-
|
|
1383
|
-
onSent: () => {
|
|
1384
|
-
if (scrollOnSendRef.current) jump.current?.();
|
|
1385
|
-
},
|
|
1386
|
-
registerJump: (callback) => {
|
|
1387
|
-
jump.current = callback;
|
|
1388
|
-
return () => {
|
|
1389
|
-
if (jump.current === callback) jump.current = void 0;
|
|
1390
|
-
};
|
|
1391
|
-
}
|
|
1392
|
-
}), []);
|
|
1393
|
-
const wurzel = useRef6(null);
|
|
1394
|
-
const eingabe = useRef6(null);
|
|
1395
|
-
const mitEingabe = composer !== void 0;
|
|
1396
|
-
useEffect6(() => {
|
|
1610
|
+
useEffect4(() => {
|
|
1397
1611
|
const element = eingabe.current;
|
|
1398
1612
|
const rahmen = wurzel.current;
|
|
1399
|
-
if (!rahmen) {
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1402
|
-
if (!element) {
|
|
1403
|
-
rahmen.style.removeProperty("--qsl-composer-height");
|
|
1613
|
+
if (!element || !rahmen) {
|
|
1404
1614
|
return;
|
|
1405
1615
|
}
|
|
1406
1616
|
const messen = () => rahmen.style.setProperty("--qsl-composer-height", `${element.offsetHeight}px`);
|
|
1407
1617
|
const beobachter = new ResizeObserver(messen);
|
|
1408
1618
|
beobachter.observe(element);
|
|
1409
1619
|
messen();
|
|
1410
|
-
return () =>
|
|
1411
|
-
|
|
1412
|
-
|
|
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(
|
|
1413
1626
|
"div",
|
|
1414
1627
|
{
|
|
1415
|
-
className:
|
|
1416
|
-
|
|
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
|
+
},
|
|
1417
1636
|
ref: (element) => {
|
|
1418
1637
|
wurzel.current = element;
|
|
1419
1638
|
if (typeof nodeRef === "function") {
|
|
@@ -1424,83 +1643,290 @@ function ChatPanel({
|
|
|
1424
1643
|
},
|
|
1425
1644
|
children: [
|
|
1426
1645
|
children,
|
|
1427
|
-
|
|
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 })
|
|
1428
1647
|
]
|
|
1429
1648
|
}
|
|
1430
1649
|
) });
|
|
1431
1650
|
}
|
|
1432
1651
|
|
|
1433
|
-
// packages/quassel/src/
|
|
1434
|
-
import {
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
return
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
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
|
+
}
|
|
1457
1699
|
}
|
|
1458
|
-
|
|
1700
|
+
};
|
|
1459
1701
|
}
|
|
1460
1702
|
|
|
1461
1703
|
// packages/quassel/src/ChatInputToolbar.tsx
|
|
1462
|
-
import { useCallback as useCallback3, useEffect as
|
|
1463
|
-
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";
|
|
1464
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";
|
|
1465
1711
|
function ChatInputToolbar({
|
|
1466
1712
|
onSend,
|
|
1467
1713
|
onStop,
|
|
1468
1714
|
running = false,
|
|
1469
1715
|
disabled = false,
|
|
1716
|
+
sendDisabled = false,
|
|
1717
|
+
layout = "card",
|
|
1718
|
+
detailsContainer,
|
|
1719
|
+
onErrorChange,
|
|
1720
|
+
inputAriaControls,
|
|
1470
1721
|
rows = 3,
|
|
1471
1722
|
maxRows,
|
|
1472
1723
|
actions,
|
|
1473
1724
|
toolbarLeft,
|
|
1474
1725
|
toolbarRight,
|
|
1475
1726
|
texts,
|
|
1476
|
-
|
|
1727
|
+
handleRef,
|
|
1728
|
+
attachments: attachmentsEnabled = true,
|
|
1729
|
+
attachmentCapabilities,
|
|
1730
|
+
attachmentCapabilitiesError,
|
|
1731
|
+
onAttachmentsChange,
|
|
1732
|
+
initialValue = "",
|
|
1733
|
+
onDraftChange,
|
|
1734
|
+
sendShortcut = "enter",
|
|
1735
|
+
autoFocus = false,
|
|
1736
|
+
onAutoFocusSettled
|
|
1477
1737
|
}) {
|
|
1738
|
+
const sendScope = useContext4(ChatSendContext);
|
|
1478
1739
|
const alleTexte = { ...defaultTexts, ...texts };
|
|
1479
|
-
const
|
|
1480
|
-
const
|
|
1481
|
-
const
|
|
1482
|
-
const
|
|
1483
|
-
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]);
|
|
1484
1910
|
const applyAutoGrow = useCallback3(() => {
|
|
1485
1911
|
const element = eingabeRef.current;
|
|
1486
|
-
if (!element ||
|
|
1912
|
+
if (!element || inputMaxRows === void 0) {
|
|
1487
1913
|
return;
|
|
1488
1914
|
}
|
|
1489
1915
|
const stil = getComputedStyle(element);
|
|
1490
1916
|
const zeilenhoehe = Number.parseFloat(stil.lineHeight) || Number.parseFloat(stil.fontSize) * 1.4;
|
|
1491
1917
|
const polster = Number.parseFloat(stil.paddingTop) + Number.parseFloat(stil.paddingBottom);
|
|
1492
|
-
const maximum = zeilenhoehe * Math.max(
|
|
1918
|
+
const maximum = zeilenhoehe * Math.max(inputMaxRows, inputRows) + polster;
|
|
1493
1919
|
element.style.height = "auto";
|
|
1494
1920
|
const inhalt = element.scrollHeight;
|
|
1495
1921
|
element.style.height = `${Math.min(inhalt, maximum)}px`;
|
|
1496
1922
|
element.style.overflowY = inhalt > maximum ? "auto" : "hidden";
|
|
1497
|
-
}, [
|
|
1498
|
-
|
|
1923
|
+
}, [inputMaxRows, inputRows]);
|
|
1924
|
+
useEffect5(() => {
|
|
1499
1925
|
applyAutoGrow();
|
|
1500
1926
|
}, [draft, applyAutoGrow]);
|
|
1501
|
-
|
|
1927
|
+
useEffect5(() => {
|
|
1502
1928
|
const element = eingabeRef.current;
|
|
1503
|
-
if (!element ||
|
|
1929
|
+
if (!element || inputMaxRows === void 0) {
|
|
1504
1930
|
return;
|
|
1505
1931
|
}
|
|
1506
1932
|
let lastWidth = element.clientWidth;
|
|
@@ -1512,8 +1938,8 @@ function ChatInputToolbar({
|
|
|
1512
1938
|
});
|
|
1513
1939
|
observer.observe(element);
|
|
1514
1940
|
return () => observer.disconnect();
|
|
1515
|
-
}, [
|
|
1516
|
-
|
|
1941
|
+
}, [inputMaxRows, applyAutoGrow]);
|
|
1942
|
+
useEffect5(() => {
|
|
1517
1943
|
const element = rootRef.current;
|
|
1518
1944
|
if (!element) {
|
|
1519
1945
|
return;
|
|
@@ -1524,79 +1950,245 @@ function ChatInputToolbar({
|
|
|
1524
1950
|
beobachter.observe(element);
|
|
1525
1951
|
return () => beobachter.disconnect();
|
|
1526
1952
|
}, []);
|
|
1527
|
-
const
|
|
1528
|
-
|
|
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) => {
|
|
1529
1969
|
const text = draft.trim();
|
|
1530
|
-
|
|
1970
|
+
const snapshot = attachmentsRef.current;
|
|
1971
|
+
if (!allowEmpty && !text && !snapshot.length || disabled || sendDisabled || pendingSend.current || capabilityError || snapshot.some((entry) => entry.status !== "ready")) {
|
|
1531
1972
|
return;
|
|
1532
1973
|
}
|
|
1533
|
-
|
|
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
|
+
}
|
|
1534
2014
|
};
|
|
1535
2015
|
const onKeyDown = (event) => {
|
|
1536
|
-
if (
|
|
2016
|
+
if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing && event.nativeEvent.keyCode !== 229 && (sendShortcut === "enter" || event.metaKey || event.ctrlKey)) {
|
|
1537
2017
|
event.preventDefault();
|
|
1538
2018
|
if (!event.repeat) void send();
|
|
1539
2019
|
}
|
|
1540
2020
|
};
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
/* @__PURE__ */
|
|
1544
|
-
|
|
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,
|
|
1545
2032
|
{
|
|
1546
|
-
"aria-label": alleTexte.
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
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, {})
|
|
1554
2045
|
}
|
|
1555
|
-
)
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
]
|
|
1570
|
-
},
|
|
1571
|
-
index
|
|
1572
|
-
)),
|
|
1573
|
-
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
|
+
" "
|
|
1574
2060
|
] }),
|
|
1575
|
-
/* @__PURE__ */
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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
|
|
1591
2121
|
] })
|
|
1592
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
|
+
);
|
|
1593
2183
|
}
|
|
1594
2184
|
|
|
1595
2185
|
// packages/quassel/src/DetailModeSwitch.tsx
|
|
1596
|
-
import {
|
|
1597
|
-
|
|
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"];
|
|
1598
2189
|
var TEXT_KEYS = {
|
|
1599
2190
|
off: "detailModeOff",
|
|
2191
|
+
current: "detailModeCurrent",
|
|
1600
2192
|
icons: "detailModeIcons",
|
|
1601
2193
|
chips: "detailModeChips",
|
|
1602
2194
|
grouped: "detailModeGrouped",
|
|
@@ -1613,19 +2205,46 @@ function DetailModeSwitch({
|
|
|
1613
2205
|
className
|
|
1614
2206
|
}) {
|
|
1615
2207
|
const alleTexte = { ...defaultTexts, ...texts };
|
|
2208
|
+
const { Button: Button2 } = useQuasselComponents();
|
|
1616
2209
|
const aktuell = modes.indexOf(mode);
|
|
1617
2210
|
const naechster = modes[(aktuell < 0 ? 0 : aktuell + 1) % modes.length];
|
|
1618
|
-
return /* @__PURE__ */
|
|
1619
|
-
|
|
2211
|
+
return /* @__PURE__ */ jsxs9(
|
|
2212
|
+
Button2,
|
|
1620
2213
|
{
|
|
1621
2214
|
"aria-label": `${alleTexte.detailModeTitle}: ${detailModeLabel(mode, texts)}`,
|
|
1622
|
-
className
|
|
2215
|
+
className,
|
|
1623
2216
|
onClick: () => onChange(naechster),
|
|
2217
|
+
size: "sm",
|
|
1624
2218
|
title: `${alleTexte.detailModeTitle}: ${detailModeLabel(mode, texts)}`,
|
|
1625
|
-
|
|
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",
|
|
1626
2245
|
children: [
|
|
1627
|
-
/* @__PURE__ */
|
|
1628
|
-
/* @__PURE__ */
|
|
2246
|
+
/* @__PURE__ */ jsx17(ClockIcon, {}),
|
|
2247
|
+
/* @__PURE__ */ jsx17("span", { className: collapsible ? "qsl:in-data-[compact=true]:hidden" : void 0, children: labels.timestamps })
|
|
1629
2248
|
]
|
|
1630
2249
|
}
|
|
1631
2250
|
);
|
|
@@ -1634,28 +2253,44 @@ export {
|
|
|
1634
2253
|
ChatInputToolbar,
|
|
1635
2254
|
ChatMessages,
|
|
1636
2255
|
ChatPanel,
|
|
2256
|
+
ChatSendContext,
|
|
1637
2257
|
DETAIL_MODES,
|
|
1638
2258
|
DetailModeSwitch,
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
IconChevronRight,
|
|
1642
|
-
IconClock,
|
|
1643
|
-
IconLayers,
|
|
1644
|
-
IconSend,
|
|
1645
|
-
IconSpark,
|
|
1646
|
-
IconStop,
|
|
1647
|
-
IconTool,
|
|
1648
|
-
IconX,
|
|
2259
|
+
MAX_CHAT_ATTACHMENTS,
|
|
2260
|
+
MAX_CHAT_ATTACHMENT_BYTES,
|
|
1649
2261
|
Markdown,
|
|
2262
|
+
MarkdownCodeBlocks,
|
|
2263
|
+
MarkdownLinks,
|
|
2264
|
+
MessageActions,
|
|
2265
|
+
PendingActionCard,
|
|
2266
|
+
QuasselProvider,
|
|
2267
|
+
StepPopover,
|
|
1650
2268
|
TimestampSwitch,
|
|
2269
|
+
WorkingScenes,
|
|
1651
2270
|
announce,
|
|
2271
|
+
appearanceStyle,
|
|
1652
2272
|
applyEvent,
|
|
2273
|
+
attachmentCapabilityError,
|
|
2274
|
+
attachmentDownloadUrl,
|
|
2275
|
+
attachmentMediaType,
|
|
1653
2276
|
compactToolLine,
|
|
2277
|
+
copyChatText,
|
|
2278
|
+
createChatScroll,
|
|
2279
|
+
createChatSendScope,
|
|
2280
|
+
defaultComponents,
|
|
1654
2281
|
defaultTexts,
|
|
1655
2282
|
detailModeLabel,
|
|
2283
|
+
encodeAttachment,
|
|
2284
|
+
fillText,
|
|
2285
|
+
formatAttachmentSize,
|
|
1656
2286
|
markdownPlainText,
|
|
2287
|
+
messageDate,
|
|
1657
2288
|
prettyJson,
|
|
1658
2289
|
stepState,
|
|
1659
|
-
|
|
2290
|
+
timestampDay,
|
|
2291
|
+
timestampLabel,
|
|
2292
|
+
useChatAction,
|
|
2293
|
+
useQuasselComponents,
|
|
2294
|
+
validateAttachmentSelection
|
|
1660
2295
|
};
|
|
1661
2296
|
//# sourceMappingURL=index.js.map
|