quassel 0.1.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/index.js ADDED
@@ -0,0 +1,1661 @@
1
+ import {
2
+ applyEvent,
3
+ compactToolLine,
4
+ prettyJson
5
+ } from "./chunk-DOLTANTM.js";
6
+
7
+ // packages/quassel/src/types.ts
8
+ function stepState(message) {
9
+ if (message.tool?.isError) {
10
+ return "error";
11
+ }
12
+ if (message.role === "thinking") {
13
+ return message.closed ? "done" : "thinking";
14
+ }
15
+ return message.tool !== void 0 && message.tool.result === void 0 ? "running" : "done";
16
+ }
17
+
18
+ // packages/quassel/src/texts.ts
19
+ var defaultTexts = {
20
+ working: "Arbeitet ...",
21
+ toolRunning: "l\xE4uft ...",
22
+ toolStillRunning: "Werkzeug l\xE4uft ...",
23
+ thinkingChip: "Denken",
24
+ stepGroupOne: "1 Schritt",
25
+ stepGroupMany: "{count} Schritte",
26
+ stepGroupCollapse: "Einklappen",
27
+ thinkingTitle: "Thinking-Trace",
28
+ toolTitle: "Tool-Call",
29
+ argumentsLabel: "Argumente",
30
+ resultLabel: "Ergebnis",
31
+ close: "Schlie\xDFen",
32
+ jumpToEnd: "Zum Ende springen",
33
+ send: "Senden",
34
+ sendIntoRun: "In den laufenden Lauf schicken",
35
+ stop: "Lauf abbrechen",
36
+ placeholder: "Nachricht schreiben ...",
37
+ steeringPlaceholder: "Dazwischenfunken ...",
38
+ timestamps: "Zeitstempel",
39
+ messageActions: "Nachrichtenaktionen",
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",
49
+ detailModeTitle: "Detailgrad der Schritte",
50
+ detailModeOff: "nur Antworten",
51
+ detailModeIcons: "Symbole",
52
+ detailModeChips: "kompakt",
53
+ detailModeGrouped: "gruppiert",
54
+ detailModeCompact: "einzeilig",
55
+ detailModeFull: "alles"
56
+ };
57
+
58
+ // packages/quassel/src/useChat.ts
59
+ import { useCallback, useEffect, useReducer, useRef, useState } from "react";
60
+ function useChat(baseUrl, headers = {}, onEvent) {
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
+ }
135
+
136
+ // packages/quassel/src/ChatMessages.tsx
137
+ import { useCallback as useCallback2, useContext as useContext3, useEffect as useEffect5, useLayoutEffect as useLayoutEffect3, useMemo, useRef as useRef5, useState as useState7 } from "react";
138
+
139
+ // packages/quassel/src/options.ts
140
+ var length = (value) => typeof value === "number" ? `${value}px` : value;
141
+ function appearanceStyle(appearance) {
142
+ return {
143
+ "--qsl-chat-font-size": length(appearance?.fontSize),
144
+ "--qsl-chat-line-height": appearance?.lineHeight,
145
+ "--qsl-message-gap": length(appearance?.messageGap),
146
+ "--qsl-dense-message-gap": length(appearance?.denseMessageGap)
147
+ };
148
+ }
149
+
150
+ // packages/quassel/src/timestamps.ts
151
+ function timestampDate(at) {
152
+ if (!at) return void 0;
153
+ const date = new Date(at);
154
+ return Number.isNaN(date.getTime()) ? void 0 : date;
155
+ }
156
+ function formatTimestamp(date, options = {}, now = Date.now()) {
157
+ if (options.format === "relative") {
158
+ const seconds = (date.getTime() - now) / 1e3;
159
+ const [unit, divisor] = Math.abs(seconds) < 60 ? ["second", 1] : Math.abs(seconds) < 3600 ? ["minute", 60] : Math.abs(seconds) < 86400 ? ["hour", 3600] : ["day", 86400];
160
+ return new Intl.RelativeTimeFormat(options.locale ?? "de", { numeric: "auto" }).format(Math.round(seconds / divisor), unit);
161
+ }
162
+ if (options.format === "date-time") {
163
+ return new Intl.DateTimeFormat(options.locale ?? "de", {
164
+ dateStyle: "short",
165
+ timeStyle: "short",
166
+ timeZone: options.timeZone
167
+ }).format(date);
168
+ }
169
+ if (!options.locale && !options.timeZone) {
170
+ return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
171
+ }
172
+ return new Intl.DateTimeFormat(options.locale ?? "de", {
173
+ hour: "2-digit",
174
+ minute: "2-digit",
175
+ hourCycle: "h23",
176
+ timeZone: options.timeZone
177
+ }).format(date);
178
+ }
179
+ function timestampDay(date, timeZone) {
180
+ return new Intl.DateTimeFormat("en-CA", {
181
+ year: "numeric",
182
+ month: "2-digit",
183
+ day: "2-digit",
184
+ calendar: "gregory",
185
+ numberingSystem: "latn",
186
+ timeZone
187
+ }).format(date);
188
+ }
189
+ function formatDay(date, options) {
190
+ return new Intl.DateTimeFormat(options?.locale ?? "de", {
191
+ dateStyle: "long",
192
+ timeZone: options?.timeZone
193
+ }).format(date);
194
+ }
195
+
196
+ // packages/quassel/src/MessageActions.tsx
197
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
198
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
199
+ function ActionButton({ label, icon, disabled, onClick, successLabel }) {
200
+ const pending = useRef2(false);
201
+ const [status, setStatus] = useState2("idle");
202
+ const [error, setError] = useState2();
203
+ useEffect2(() => {
204
+ if (status !== "done") return;
205
+ const timer = window.setTimeout(() => setStatus("idle"), 1800);
206
+ return () => window.clearTimeout(timer);
207
+ }, [status]);
208
+ const run = async () => {
209
+ if (pending.current) return;
210
+ pending.current = true;
211
+ setStatus("pending");
212
+ setError(void 0);
213
+ try {
214
+ await onClick();
215
+ setStatus("done");
216
+ } catch (failure) {
217
+ setStatus("idle");
218
+ setError(failure instanceof Error ? failure.message : String(failure));
219
+ } finally {
220
+ pending.current = false;
221
+ }
222
+ };
223
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
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
+ ] });
230
+ }
231
+ async function copyText(text, error) {
232
+ if (!navigator.clipboard) throw new Error(error);
233
+ await navigator.clipboard.writeText(text);
234
+ }
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
+
289
+ // packages/quassel/src/Markdown.tsx
290
+ import { createContext as createContext2, useContext as useContext2 } from "react";
291
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
292
+ var CodeBlockContext = createContext2({});
293
+ function MarkdownCodeBlocks({ children, options, texts }) {
294
+ return /* @__PURE__ */ jsx2(CodeBlockContext.Provider, { value: { options, texts }, children });
295
+ }
296
+ function CodeBlock({ text }) {
297
+ const { options, texts } = useContext2(CodeBlockContext);
298
+ const labels = { ...defaultTexts, ...texts };
299
+ const content = /* @__PURE__ */ jsx2("pre", { style: {
300
+ whiteSpace: options?.wrap ? "pre-wrap" : void 0,
301
+ overflowWrap: options?.wrap ? "anywhere" : void 0,
302
+ maxHeight: options?.maxHeight,
303
+ overflowY: options?.maxHeight === void 0 ? void 0 : "auto"
304
+ }, children: /* @__PURE__ */ jsx2("code", { children: text }) });
305
+ return options?.showCopyButton ? /* @__PURE__ */ jsxs2("div", { className: "qsl-code-block", children: [
306
+ /* @__PURE__ */ jsx2(ActionButton, { label: labels.copyCode, successLabel: labels.copied, onClick: () => copyText(text, labels.copyFailed) }),
307
+ content
308
+ ] }) : content;
309
+ }
310
+ var LinkClickContext = createContext2(void 0);
311
+ function MarkdownLinks({ children, onLinkClick }) {
312
+ return /* @__PURE__ */ jsx2(LinkClickContext.Provider, { value: onLinkClick, children });
313
+ }
314
+ function MarkdownLink({ href, label }) {
315
+ const onLinkClick = useContext2(LinkClickContext);
316
+ return /* @__PURE__ */ jsx2(
317
+ "a",
318
+ {
319
+ href,
320
+ onClick: (event) => {
321
+ if (onLinkClick?.(href, label)) {
322
+ event.preventDefault();
323
+ }
324
+ },
325
+ rel: "noreferrer",
326
+ target: "_blank",
327
+ children: label
328
+ }
329
+ );
330
+ }
331
+ function Markdown({ text }) {
332
+ const lines = text.split("\n");
333
+ const blocks = [];
334
+ let i = 0;
335
+ let key = 0;
336
+ while (i < lines.length) {
337
+ const line = lines[i];
338
+ const fence = line.match(/^\s*```(\w*)\s*$/);
339
+ if (fence) {
340
+ const body = [];
341
+ i++;
342
+ while (i < lines.length && !/^\s*```\s*$/.test(lines[i])) {
343
+ body.push(lines[i]);
344
+ i++;
345
+ }
346
+ if (i < lines.length) {
347
+ i++;
348
+ }
349
+ blocks.push(
350
+ /* @__PURE__ */ jsx2(CodeBlock, { text: body.join("\n") }, key++)
351
+ );
352
+ continue;
353
+ }
354
+ if (line.includes("|") && i + 1 < lines.length && isTableDivider(lines[i + 1])) {
355
+ const header = splitTableRow(line);
356
+ i += 2;
357
+ const rows = [];
358
+ while (i < lines.length && lines[i].includes("|") && lines[i].trim() !== "") {
359
+ rows.push(splitTableRow(lines[i]));
360
+ i++;
361
+ }
362
+ blocks.push(
363
+ /* @__PURE__ */ jsx2("div", { className: "qsl-md-scroll", children: /* @__PURE__ */ jsxs2("table", { children: [
364
+ /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsx2("tr", { children: header.map((cell, ci) => /* @__PURE__ */ jsx2("th", { children: renderInline(cell, `th${key}_${ci}`) }, ci)) }) }),
365
+ /* @__PURE__ */ jsx2("tbody", { children: rows.map((row, ri) => /* @__PURE__ */ jsx2("tr", { children: header.map((_, ci) => /* @__PURE__ */ jsx2("td", { children: renderInline(row[ci] ?? "", `td${key}_${ri}_${ci}`) }, ci)) }, ri)) })
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 });
419
+ }
420
+ function renderInline(text, keyPrefix) {
421
+ const out = [];
422
+ const pattern = /\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*|\*([^*]+)\*|`([^`]+)`/g;
423
+ let last = 0;
424
+ let match;
425
+ let i = 0;
426
+ while ((match = pattern.exec(text)) !== null) {
427
+ if (match.index > last) {
428
+ out.push(text.slice(last, match.index));
429
+ }
430
+ if (match[1] !== void 0) {
431
+ out.push(/* @__PURE__ */ jsx2(MarkdownLink, { href: match[2], label: match[1] }, `${keyPrefix}a${i}`));
432
+ } else if (match[3] !== void 0) {
433
+ out.push(/* @__PURE__ */ jsx2("strong", { children: match[3] }, `${keyPrefix}b${i}`));
434
+ } else if (match[4] !== void 0) {
435
+ out.push(/* @__PURE__ */ jsx2("em", { children: match[4] }, `${keyPrefix}i${i}`));
436
+ } else if (match[5] !== void 0) {
437
+ out.push(/* @__PURE__ */ jsx2("code", { children: match[5] }, `${keyPrefix}c${i}`));
438
+ }
439
+ last = match.index + match[0].length;
440
+ i++;
441
+ }
442
+ if (last < text.length) {
443
+ out.push(text.slice(last));
444
+ }
445
+ return out;
446
+ }
447
+ 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,4})\s+/, "").replace(/^\s*>\s?/, "").replace(/^\s*[-*]\s+/, "").replace(/\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*|\*([^*]+)\*|`([^`]+)`/g, (_, link, _href, fett, kursiv, code) => link ?? fett ?? kursiv ?? code).trim()).filter((line) => line !== "").join("\n");
449
+ }
450
+ function splitTableRow(line) {
451
+ let row = line.trim();
452
+ if (row.startsWith("|")) {
453
+ row = row.slice(1);
454
+ }
455
+ if (row.endsWith("|")) {
456
+ row = row.slice(0, -1);
457
+ }
458
+ return row.split("|").map((cell) => cell.trim());
459
+ }
460
+ function isTableDivider(line) {
461
+ return /^\s*\|?\s*:?-{1,}:?\s*(\|\s*:?-{1,}:?\s*)*\|?\s*$/.test(line) && line.includes("-");
462
+ }
463
+
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
+ // packages/quassel/src/StepPopover.tsx
591
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
592
+ function StepPopover({
593
+ message,
594
+ position,
595
+ texts,
596
+ toolArgumentsText,
597
+ onClose
598
+ }) {
599
+ const popover = useRef4(null);
600
+ const [placement, setPlacement] = useState4();
601
+ useLayoutEffect2(() => {
602
+ const element = popover.current;
603
+ if (!element) {
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]);
636
+ const tool = message.tool;
637
+ const title = message.role === "thinking" ? texts.thinkingTitle : texts.toolTitle;
638
+ return createPortal(
639
+ /* @__PURE__ */ jsxs4(Fragment2, { children: [
640
+ /* @__PURE__ */ jsx4("div", { className: "qsl-popover-backdrop", onClick: onClose }),
641
+ /* @__PURE__ */ jsxs4(
642
+ "div",
643
+ {
644
+ "aria-label": title,
645
+ className: "qsl-popover",
646
+ ref: popover,
647
+ role: "dialog",
648
+ style: {
649
+ left: placement?.left ?? position.x,
650
+ top: placement?.top ?? position.y,
651
+ visibility: placement ? "visible" : "hidden"
652
+ },
653
+ children: [
654
+ /* @__PURE__ */ jsxs4("div", { className: "qsl-popover__head", children: [
655
+ message.role === "thinking" ? /* @__PURE__ */ jsx4(IconSpark, { className: "qsl-popover__icon" }) : /* @__PURE__ */ jsx4(IconTool, { className: tool?.isError ? "qsl-popover__icon qsl-popover__icon--error" : "qsl-popover__icon" }),
656
+ /* @__PURE__ */ jsx4("span", { className: "qsl-popover__title", children: title }),
657
+ /* @__PURE__ */ jsx4("button", { "aria-label": texts.close, className: "qsl-popover__close", onClick: onClose, title: texts.close, type: "button", children: /* @__PURE__ */ jsx4(IconX, {}) })
658
+ ] }),
659
+ /* @__PURE__ */ jsx4("div", { className: "qsl-popover__body", children: message.role === "thinking" ? /* @__PURE__ */ jsx4("div", { className: "qsl-popover__thinking", children: message.text }) : tool ? /* @__PURE__ */ jsxs4("div", { className: "qsl-popover__sections", children: [
660
+ /* @__PURE__ */ jsx4("div", { className: "qsl-popover__toolname", children: message.text }),
661
+ /* @__PURE__ */ jsx4(PopoverSection, { label: texts.argumentsLabel, value: toolArgumentsText(tool) }),
662
+ tool.result === void 0 ? /* @__PURE__ */ jsx4("div", { className: "qsl-popover__running", children: texts.toolStillRunning }) : /* @__PURE__ */ jsx4(PopoverSection, { error: tool.isError, label: texts.resultLabel, value: prettyJson(tool.result) })
663
+ ] }) : null })
664
+ ]
665
+ }
666
+ )
667
+ ] }),
668
+ document.body
669
+ );
670
+ }
671
+ function PopoverSection({ label, value, error = false }) {
672
+ return /* @__PURE__ */ jsxs4("section", { children: [
673
+ /* @__PURE__ */ jsx4("h3", { children: label }),
674
+ /* @__PURE__ */ jsx4("pre", { className: error ? "qsl-popover__pre qsl-popover__pre--error" : "qsl-popover__pre", children: value })
675
+ ] });
676
+ }
677
+
678
+ // packages/quassel/src/QuestionCard.tsx
679
+ import { useState as useState5 } from "react";
680
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
681
+ function QuestionCard({
682
+ text,
683
+ question,
684
+ texts,
685
+ onAnswer
686
+ }) {
687
+ const [gewaehlt, setGewaehlt] = useState5([]);
688
+ const [frei, setFrei] = useState5("");
689
+ if (question.answer !== void 0) {
690
+ return /* @__PURE__ */ jsxs5("div", { className: "qsl-question", children: [
691
+ /* @__PURE__ */ jsx5("div", { className: "qsl-question__text", children: text }),
692
+ /* @__PURE__ */ jsxs5("div", { className: "qsl-question__answered", children: [
693
+ /* @__PURE__ */ jsx5(IconCheck, { className: "qsl-question__check", size: 12 }),
694
+ question.answer
695
+ ] })
696
+ ] });
697
+ }
698
+ const umschalten = (option) => setGewaehlt((bisher) => bisher.includes(option) ? bisher.filter((o) => o !== option) : [...bisher, option]);
699
+ const freiSenden = () => {
700
+ const wert = frei.trim();
701
+ if (wert) onAnswer(wert);
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
+ )
754
+ ] })
755
+ ] });
756
+ }
757
+
758
+ // packages/quassel/src/WorkingScenes.tsx
759
+ import { useEffect as useEffect4, useState as useState6 } from "react";
760
+ import { jsx as jsx6 } from "react/jsx-runtime";
761
+ var SPUR = 13;
762
+ var GRUNDTAKT_MS = 90;
763
+ var SZENENWECHSEL_MS = 2600;
764
+ function bahn(figuren, breite = SPUR) {
765
+ return Array.from({ length: breite }, (_, x) => figuren[x] ?? " ");
766
+ }
767
+ var SZENEN = [
768
+ {
769
+ id: "denken",
770
+ takt: 2,
771
+ zeichne: (schritt) => {
772
+ const takt = schritt % 10;
773
+ if (takt >= 8) return "\u{1F929}\u{1F4A1}";
774
+ return "\u{1F914}" + "\u{1F4AD}".repeat(Math.min(takt, 3));
775
+ }
776
+ },
777
+ {
778
+ // Fliegt nach rechts und zieht ihren Schweif hinterher.
779
+ id: "rakete",
780
+ takt: 2,
781
+ laufend: true,
782
+ zeichne: (schritt) => {
783
+ const x = schritt % SPUR;
784
+ return bahn({ [x - 2]: "\xB7", [x - 1]: "\u{1F4A8}", [x]: "\u{1F680}" });
785
+ }
786
+ },
787
+ {
788
+ // Die Punkte bleiben stehen und werden einzeln gefressen.
789
+ id: "pacman",
790
+ takt: 2,
791
+ laufend: true,
792
+ zeichne: (schritt) => {
793
+ const x = schritt % SPUR;
794
+ const figuren = {};
795
+ for (let i = x + 1; i < SPUR; i++) {
796
+ figuren[i] = "\xB7";
797
+ }
798
+ figuren[x] = schritt % 2 === 0 ? "\u{1F7E1}" : "\u{1F315}";
799
+ return bahn(figuren);
800
+ }
801
+ },
802
+ {
803
+ // Aus einem Samen wird ein Baum, dann von vorn.
804
+ id: "wachsen",
805
+ takt: 2,
806
+ zeichne: (schritt) => {
807
+ const stufen = ["\u{1F331}", "\u{1F331}", "\u{1F33F}", "\u{1F33F}", "\u{1F343}", "\u{1F333}", "\u{1F333}", "\u{1F333}"];
808
+ return stufen[schritt % stufen.length];
809
+ }
810
+ },
811
+ {
812
+ // Das Ei bekommt Risse, das Kueken schluepft.
813
+ id: "schluepfen",
814
+ takt: 2,
815
+ zeichne: (schritt) => {
816
+ const takt = schritt % 12;
817
+ if (takt < 4) return "\u{1F95A}";
818
+ if (takt < 6) return "\u{1F95A}\u2728";
819
+ if (takt < 9) return "\u{1F423}";
820
+ return "\u{1F425}";
821
+ }
822
+ },
823
+ {
824
+ // Morse-artiges Signal, das sich zu einem OK aufloest.
825
+ id: "signal",
826
+ takt: 2,
827
+ zeichne: (schritt) => {
828
+ const takt = schritt % 14;
829
+ if (takt >= 11) return "\u{1F4E1} OK";
830
+ const muster = ["\xB7", "\xB7\xB7", "\xB7-", "-\xB7", "\xB7\xB7-", "-\xB7\xB7"];
831
+ return "\u{1F4E1} " + muster[takt % muster.length];
832
+ }
833
+ },
834
+ {
835
+ // Sanduhr: der Sand laeuft durch, dann wird gedreht.
836
+ id: "sanduhr",
837
+ takt: 2,
838
+ zeichne: (schritt) => {
839
+ const takt = schritt % 12;
840
+ const koerner = "\xB7".repeat(Math.max(0, 5 - Math.floor(takt / 2)));
841
+ return (takt < 10 ? "\u23F3" : "\u231B") + " " + koerner;
842
+ }
843
+ },
844
+ {
845
+ // Ei, Kueken, Henne - und die legt das naechste Ei.
846
+ id: "huhn-kreislauf",
847
+ takt: 3,
848
+ zeichne: (schritt) => {
849
+ const takt = schritt % 16;
850
+ if (takt < 3) return "\u{1F95A}";
851
+ if (takt < 5) return "\u{1F95A}\u2728";
852
+ if (takt < 8) return "\u{1F423}";
853
+ if (takt < 11) return "\u{1F424}";
854
+ if (takt < 14) return "\u{1F414}";
855
+ return "\u{1F414}\u{1F95A}";
856
+ }
857
+ },
858
+ {
859
+ // Hut, Funken - und das Kaninchen tuermt.
860
+ id: "zauberhut",
861
+ takt: 3,
862
+ zeichne: (schritt) => {
863
+ const takt = schritt % 14;
864
+ if (takt < 4) return "\u{1F3A9}";
865
+ if (takt < 7) return "\u{1F3A9}\u2728";
866
+ if (takt < 11) return "\u{1F3A9}\u{1F407}";
867
+ return "\u{1F407}\u{1F4A8}";
868
+ }
869
+ },
870
+ {
871
+ // Rot, Gelb, Gruen - und ab.
872
+ id: "ampel",
873
+ takt: 3,
874
+ zeichne: (schritt) => {
875
+ const takt = schritt % 12;
876
+ if (takt < 4) return "\u{1F534}";
877
+ if (takt < 6) return "\u{1F7E1}";
878
+ if (takt < 8) return "\u{1F7E2}";
879
+ return "\u{1F697}\u{1F4A8}";
880
+ }
881
+ },
882
+ {
883
+ // Klingeln, verschlafen, losrennen.
884
+ id: "wecker",
885
+ takt: 2,
886
+ zeichne: (schritt) => {
887
+ const takt = schritt % 14;
888
+ if (takt < 5) return takt % 2 === 0 ? "\u23F0" : "\u{1F514}";
889
+ if (takt < 8) return "\u{1F634}";
890
+ if (takt < 10) return "\u{1F633}";
891
+ return "\u{1F3C3}\u{1F4A8}";
892
+ }
893
+ },
894
+ {
895
+ // Schnecke kriecht zur Flagge - und feiert.
896
+ id: "schnecke",
897
+ takt: 4,
898
+ zeichne: (schritt) => {
899
+ const takt = schritt % 8;
900
+ if (takt < 5) return bahn({ [takt]: "\u{1F40C}", 4: "\u{1F3C1}" }, 5);
901
+ return bahn({ 4: "\u{1F389}" }, 5);
902
+ }
903
+ }
904
+ ];
905
+ function WorkingScenes({ label = "Arbeitet ..." }) {
906
+ const [szene, setSzene] = useState6(() => SZENEN[Math.floor(Math.random() * SZENEN.length)]);
907
+ const [versatz, setVersatz] = useState6(() => Math.floor(Math.random() * 20));
908
+ const [frameTick, setFrameTick] = useState6(0);
909
+ useEffect4(() => {
910
+ const timer = window.setInterval(() => {
911
+ setSzene((bisher) => {
912
+ const andere = SZENEN.filter((s) => s.id !== bisher.id);
913
+ return andere[Math.floor(Math.random() * andere.length)];
914
+ });
915
+ setVersatz(Math.floor(Math.random() * 20));
916
+ setFrameTick(0);
917
+ }, SZENENWECHSEL_MS);
918
+ return () => window.clearInterval(timer);
919
+ }, []);
920
+ useEffect4(() => {
921
+ const timer = window.setInterval(() => setFrameTick((value) => value + 1), GRUNDTAKT_MS);
922
+ return () => window.clearInterval(timer);
923
+ }, []);
924
+ const bild = szene.zeichne(Math.floor(frameTick / szene.takt) + (szene.laufend ? versatz : 0));
925
+ return /* @__PURE__ */ jsx6("div", { "aria-label": label, className: "qsl-working", role: "img", children: /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", className: "qsl-working__stage", children: /* @__PURE__ */ jsx6("span", { className: "qsl-working__character", children: typeof bild === "string" ? bild : bild.map((zelle, i) => /* @__PURE__ */ jsx6("span", { className: "qsl-working__cell", children: zelle }, i)) }) }, szene.id) });
926
+ }
927
+
928
+ // packages/quassel/src/ChatMessages.tsx
929
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
930
+ var collapseAtBottomFrom = 11;
931
+ var hoechstensAnsagenAufEinmal = 2;
932
+ function istSchritt(message) {
933
+ return !!message && (message.role === "thinking" || message.role === "tool");
934
+ }
935
+ function defaultArgumentsText(tool) {
936
+ return prettyJson(tool.arguments);
937
+ }
938
+ function ChatMessages({
939
+ messages,
940
+ detailMode = "compact",
941
+ running = false,
942
+ working,
943
+ workingTimeoutMs = 2e4,
944
+ stepsExpandable = true,
945
+ texts,
946
+ toolArgumentsText = defaultArgumentsText,
947
+ renderTool,
948
+ onAnswerQuestion,
949
+ onLinkClick,
950
+ announce: announce2,
951
+ emptyState,
952
+ className,
953
+ showTimestamps = false,
954
+ bottomThreshold = 120,
955
+ scrollerRef,
956
+ appearance,
957
+ timestampOptions,
958
+ codeBlockOptions,
959
+ bubbleOptions,
960
+ messageActions
961
+ }) {
962
+ const alleTexte = { ...defaultTexts, ...texts };
963
+ const scrollBereich = useRef5(null);
964
+ const [amEnde, setAmEnde] = useState7(true);
965
+ const panel = useContext3(ChatSendContext);
966
+ useLayoutEffect3(() => panel?.registerJump(() => {
967
+ setAmEnde(true);
968
+ const viewport = scrollBereich.current;
969
+ if (viewport) viewport.scrollTop = viewport.scrollHeight;
970
+ }), [panel]);
971
+ const [now, setNow] = useState7(Date.now);
972
+ const relative = showTimestamps && timestampOptions?.format === "relative";
973
+ useEffect5(() => {
974
+ if (!relative) return;
975
+ setNow(Date.now());
976
+ const timer = window.setInterval(() => setNow(Date.now()), 1e3);
977
+ return () => window.clearInterval(timer);
978
+ }, [relative]);
979
+ const [stromAktiv, setStromAktiv] = useState7(true);
980
+ useEffect5(() => {
981
+ setStromAktiv(true);
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)));
988
+ if (faellig.length === 0) {
989
+ return;
990
+ }
991
+ angesagt.current = /* @__PURE__ */ new Set([...angesagt.current, ...faellig.map((message) => message.key)]);
992
+ if (announce2 === false || faellig.length > hoechstensAnsagenAufEinmal) {
993
+ return;
994
+ }
995
+ faellig.map((message) => announce2 ? announce2({ kind: message.role === "question" ? "question" : "reply", message }) : markdownPlainText(message.text)).forEach((text) => text && announce(text));
996
+ }, [messages, running, announce2]);
997
+ const pruefeEnde = useCallback2(() => {
998
+ const bereich = scrollBereich.current;
999
+ if (bereich) {
1000
+ const abstandZumEnde = bereich.scrollHeight - bereich.scrollTop - bereich.clientHeight;
1001
+ setAmEnde(abstandZumEnde <= bottomThreshold);
1002
+ }
1003
+ }, [bottomThreshold]);
1004
+ useEffect5(() => {
1005
+ const bereich = scrollBereich.current;
1006
+ if (!bereich) {
1007
+ return;
1008
+ }
1009
+ const beobachter = new ResizeObserver(pruefeEnde);
1010
+ beobachter.observe(bereich);
1011
+ for (const kind of bereich.children) {
1012
+ beobachter.observe(kind);
1013
+ }
1014
+ return () => beobachter.disconnect();
1015
+ }, [pruefeEnde, messages.length]);
1016
+ useEffect5(() => {
1017
+ if (!amEnde) {
1018
+ return;
1019
+ }
1020
+ scrollBereich.current?.scrollTo({ top: scrollBereich.current.scrollHeight, behavior: running ? "auto" : "smooth" });
1021
+ }, [messages, running, amEnde]);
1022
+ const visible = useMemo(
1023
+ () => messages.filter((message) => {
1024
+ if ((message.role === "thinking" || message.role === "assistant") && message.text.trim() === "") {
1025
+ return false;
1026
+ }
1027
+ if (message.role === "thinking" || message.role === "tool") {
1028
+ return detailMode !== "off";
1029
+ }
1030
+ return true;
1031
+ }),
1032
+ [messages, detailMode]
1033
+ );
1034
+ const zeitVon = (at) => {
1035
+ const date = timestampDate(at);
1036
+ return date ? formatTimestamp(date, timestampOptions, now) : "";
1037
+ };
1038
+ const mitZeit = (inhalt, key, at) => showTimestamps ? /* @__PURE__ */ jsxs6("div", { className: "qsl-line", children: [
1039
+ /* @__PURE__ */ jsx7("time", { className: "qsl-time", dateTime: timestampDate(at) ? at : void 0, style: timestampOptions?.format && timestampOptions.format !== "time" ? { width: "auto", maxWidth: "35%" } : void 0, children: zeitVon(at) }),
1040
+ /* @__PURE__ */ jsx7("div", { className: "qsl-line-body", children: inhalt })
1041
+ ] }, `zeit-${key}`) : inhalt;
1042
+ const chipModus = detailMode === "chips" || detailMode === "icons";
1043
+ const gruppenModus = detailMode === "grouped";
1044
+ const bloecke = [];
1045
+ let gruppe = [];
1046
+ const schliesseGruppe = () => {
1047
+ if (gruppe.length > 0) {
1048
+ const letzterZustand = stepState(gruppe[gruppe.length - 1]);
1049
+ bloecke.push(mitZeit(
1050
+ gruppenModus ? /* @__PURE__ */ jsx7(
1051
+ StepGroup,
1052
+ {
1053
+ aktiv: running && (letzterZustand === "running" || letzterZustand === "thinking"),
1054
+ expandierbar: stepsExpandable,
1055
+ messages: gruppe,
1056
+ texts: alleTexte,
1057
+ toolArgumentsText
1058
+ },
1059
+ gruppe[0].key
1060
+ ) : /* @__PURE__ */ jsx7(
1061
+ StepRow,
1062
+ {
1063
+ expandierbar: stepsExpandable,
1064
+ messages: gruppe,
1065
+ mitText: detailMode === "chips",
1066
+ texts: alleTexte,
1067
+ toolArgumentsText
1068
+ },
1069
+ gruppe[0].key
1070
+ ),
1071
+ gruppe[0].key,
1072
+ gruppe[0].at
1073
+ ));
1074
+ gruppe = [];
1075
+ }
1076
+ };
1077
+ let currentDay;
1078
+ visible.forEach((message, index) => {
1079
+ const date = timestampOptions?.showDaySeparators ? timestampDate(message.at) : void 0;
1080
+ if (date) {
1081
+ const day = timestampDay(date, timestampOptions?.timeZone);
1082
+ if (day !== currentDay) {
1083
+ schliesseGruppe();
1084
+ bloecke.push(/* @__PURE__ */ jsx7("div", { className: "qsl-day-separator", role: "separator", "aria-label": formatDay(date, timestampOptions), children: formatDay(date, timestampOptions) }, `day-${message.key}`));
1085
+ currentDay = day;
1086
+ }
1087
+ }
1088
+ const eigeneDarstellung = message.role === "tool" && message.tool ? renderTool?.(message.tool) : void 0;
1089
+ if (eigeneDarstellung !== void 0) {
1090
+ schliesseGruppe();
1091
+ bloecke.push(mitZeit(
1092
+ /* @__PURE__ */ jsx7("div", { className: "qsl-step", children: eigeneDarstellung }, message.key),
1093
+ message.key,
1094
+ message.at
1095
+ ));
1096
+ return;
1097
+ }
1098
+ if ((chipModus || gruppenModus) && istSchritt(message)) {
1099
+ gruppe = [...gruppe, message];
1100
+ return;
1101
+ }
1102
+ schliesseGruppe();
1103
+ bloecke.push(mitZeit(
1104
+ /* @__PURE__ */ jsx7(
1105
+ Bubble,
1106
+ {
1107
+ detailMode,
1108
+ dicht: istSchritt(message) && istSchritt(visible[index - 1]),
1109
+ expandierbar: stepsExpandable,
1110
+ message,
1111
+ bubbleOptions,
1112
+ messageActions,
1113
+ texts: alleTexte,
1114
+ toolArgumentsText,
1115
+ onAnswerQuestion
1116
+ },
1117
+ message.key
1118
+ ),
1119
+ message.key,
1120
+ message.at
1121
+ ));
1122
+ });
1123
+ schliesseGruppe();
1124
+ const verlauf = /* @__PURE__ */ jsxs6("div", { className: className ? `qsl-chat-wrap ${className}` : "qsl-chat-wrap", style: appearanceStyle(appearance), children: [
1125
+ /* @__PURE__ */ jsx7(
1126
+ "div",
1127
+ {
1128
+ className: "qsl-chat",
1129
+ onScroll: pruefeEnde,
1130
+ ref: (element) => {
1131
+ scrollBereich.current = element;
1132
+ scrollerRef?.(element);
1133
+ },
1134
+ children: visible.length === 0 && !running ? emptyState ?? null : /* @__PURE__ */ jsxs6("div", { "aria-busy": running || void 0, className: "qsl-thread", children: [
1135
+ bloecke,
1136
+ running && stromAktiv && (working ?? /* @__PURE__ */ jsx7(WorkingScenes, { label: alleTexte.working }))
1137
+ ] })
1138
+ }
1139
+ ),
1140
+ !amEnde && /* @__PURE__ */ jsx7("div", { className: "qsl-jump", children: /* @__PURE__ */ jsx7("button", { "aria-label": alleTexte.jumpToEnd, onClick: () => scrollBereich.current?.scrollTo({ top: scrollBereich.current.scrollHeight, behavior: "smooth" }), type: "button", children: /* @__PURE__ */ jsx7(IconChevronDown, {}) }) })
1141
+ ] });
1142
+ return /* @__PURE__ */ jsx7(MarkdownLinks, { onLinkClick, children: /* @__PURE__ */ jsx7(MarkdownCodeBlocks, { options: codeBlockOptions, texts, children: verlauf }) });
1143
+ }
1144
+ function StepRow({
1145
+ messages,
1146
+ mitText,
1147
+ expandierbar,
1148
+ texts,
1149
+ toolArgumentsText
1150
+ }) {
1151
+ const [detail, setDetail] = useState7();
1152
+ const offen = expandierbar && detail && messages.find((message) => message.key === detail.key);
1153
+ return /* @__PURE__ */ jsxs6("div", { className: "qsl-step qsl-steprow", children: [
1154
+ messages.map((message) => {
1155
+ const thinking = message.role === "thinking";
1156
+ const tool = message.tool;
1157
+ const state = stepState(message);
1158
+ const running = state === "running" || state === "thinking";
1159
+ const label = thinking ? texts.thinkingChip : tool?.name ?? message.text;
1160
+ const content = /* @__PURE__ */ jsxs6(Fragment3, { children: [
1161
+ thinking ? /* @__PURE__ */ jsx7(IconSpark, { size: 11 }) : /* @__PURE__ */ jsx7(IconTool, { className: tool?.isError ? "qsl-chip__icon--error" : void 0, size: 11 }),
1162
+ mitText && /* @__PURE__ */ jsx7("span", { className: "qsl-chip__label", children: label }),
1163
+ mitText && /* @__PURE__ */ jsx7("span", { className: "qsl-chip__state", children: state === "done" && /* @__PURE__ */ jsx7(IconCheck, { size: 10 }) })
1164
+ ] });
1165
+ const classes = `${mitText ? "" : " qsl-chip--icon"} qsl-chip--${state}${running ? " qsl-pulse" : ""}`;
1166
+ if (!expandierbar) {
1167
+ return /* @__PURE__ */ jsx7("span", { className: `qsl-chip qsl-chip--still${classes}`, title: label, children: content }, message.key);
1168
+ }
1169
+ return /* @__PURE__ */ jsx7(
1170
+ "button",
1171
+ {
1172
+ "aria-haspopup": "dialog",
1173
+ className: `qsl-chip${classes}`,
1174
+ onClick: (event) => setDetail({ key: message.key, position: { x: event.clientX, y: event.clientY } }),
1175
+ title: label,
1176
+ type: "button",
1177
+ children: content
1178
+ },
1179
+ message.key
1180
+ );
1181
+ }),
1182
+ offen && detail && /* @__PURE__ */ jsx7(
1183
+ StepPopover,
1184
+ {
1185
+ message: offen,
1186
+ position: detail.position,
1187
+ texts,
1188
+ toolArgumentsText,
1189
+ onClose: () => setDetail(void 0)
1190
+ }
1191
+ )
1192
+ ] });
1193
+ }
1194
+ function StepGroup({
1195
+ messages,
1196
+ aktiv,
1197
+ expandierbar,
1198
+ texts,
1199
+ toolArgumentsText
1200
+ }) {
1201
+ const [offen, setOffen] = useState7(false);
1202
+ const kopf = useRef5(null);
1203
+ const vonUntenEingeklappt = useRef5(false);
1204
+ useLayoutEffect3(() => {
1205
+ if (!offen && vonUntenEingeklappt.current) {
1206
+ vonUntenEingeklappt.current = false;
1207
+ kopf.current?.scrollIntoView({ block: "nearest" });
1208
+ }
1209
+ }, [offen]);
1210
+ const letzte = messages[messages.length - 1];
1211
+ const fehler = messages.some((message) => message.tool?.isError);
1212
+ 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 ?? letzte.text : void 0;
1214
+ return /* @__PURE__ */ jsxs6("div", { className: "qsl-step qsl-stepgroup", children: [
1215
+ /* @__PURE__ */ jsxs6(
1216
+ "button",
1217
+ {
1218
+ "aria-expanded": offen,
1219
+ className: `qsl-trace qsl-trace--compact qsl-stepgroup__toggle${aktiv ? " qsl-pulse" : ""}`,
1220
+ onClick: () => setOffen((wert) => !wert),
1221
+ ref: kopf,
1222
+ type: "button",
1223
+ children: [
1224
+ /* @__PURE__ */ jsx7(IconChevronRight, { className: "qsl-stepgroup__chevron", size: 12 }),
1225
+ /* @__PURE__ */ jsx7(IconLayers, { className: fehler ? "qsl-trace__icon qsl-trace__icon--error" : "qsl-trace__icon", size: 12 }),
1226
+ /* @__PURE__ */ jsxs6("span", { className: "qsl-trace__line", children: [
1227
+ anzahl,
1228
+ laufend && /* @__PURE__ */ jsxs6("span", { className: "qsl-trace__running", children: [
1229
+ laufend,
1230
+ " ",
1231
+ texts.toolRunning
1232
+ ] })
1233
+ ] })
1234
+ ]
1235
+ }
1236
+ ),
1237
+ offen && /* @__PURE__ */ jsxs6("div", { className: "qsl-stepgroup__lines", children: [
1238
+ messages.map((message) => /* @__PURE__ */ jsx7(TraceLine, { expandierbar, message, texts, toolArgumentsText }, message.key)),
1239
+ messages.length >= collapseAtBottomFrom && /* @__PURE__ */ jsxs6(
1240
+ "button",
1241
+ {
1242
+ className: "qsl-trace qsl-trace--compact qsl-stepgroup__toggle qsl-stepgroup__collapse",
1243
+ onClick: () => {
1244
+ vonUntenEingeklappt.current = true;
1245
+ setOffen(false);
1246
+ },
1247
+ type: "button",
1248
+ children: [
1249
+ /* @__PURE__ */ jsx7(IconChevronRight, { className: "qsl-stepgroup__chevron", size: 12 }),
1250
+ /* @__PURE__ */ jsx7("span", { className: "qsl-trace__line", children: texts.stepGroupCollapse })
1251
+ ]
1252
+ }
1253
+ )
1254
+ ] })
1255
+ ] });
1256
+ }
1257
+ function TraceLine({
1258
+ message,
1259
+ className,
1260
+ expandierbar,
1261
+ texts,
1262
+ toolArgumentsText
1263
+ }) {
1264
+ const [detailPosition, setDetailPosition] = useState7();
1265
+ const classes = `${className ? `${className} ` : ""}qsl-trace qsl-trace--compact${message.role === "thinking" ? " qsl-trace--thinking" : ""}`;
1266
+ const inhalt = /* @__PURE__ */ jsxs6(Fragment3, { children: [
1267
+ /* @__PURE__ */ jsx7(TraceIcon, { message }),
1268
+ /* @__PURE__ */ jsx7("span", { className: "qsl-trace__line", children: /* @__PURE__ */ jsx7(TraceText, { message, texts }) })
1269
+ ] });
1270
+ if (!expandierbar) {
1271
+ return /* @__PURE__ */ jsx7("div", { className: classes, children: inhalt });
1272
+ }
1273
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
1274
+ /* @__PURE__ */ jsx7(
1275
+ "button",
1276
+ {
1277
+ "aria-expanded": detailPosition !== void 0,
1278
+ "aria-haspopup": "dialog",
1279
+ className: classes,
1280
+ onClick: (event) => setDetailPosition({ x: event.clientX, y: event.clientY }),
1281
+ type: "button",
1282
+ children: inhalt
1283
+ }
1284
+ ),
1285
+ detailPosition && /* @__PURE__ */ jsx7(
1286
+ StepPopover,
1287
+ {
1288
+ message,
1289
+ position: detailPosition,
1290
+ texts,
1291
+ toolArgumentsText,
1292
+ onClose: () => setDetailPosition(void 0)
1293
+ }
1294
+ )
1295
+ ] });
1296
+ }
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
+ function Bubble({
1307
+ message,
1308
+ bubbleOptions,
1309
+ messageActions,
1310
+ detailMode,
1311
+ dicht,
1312
+ expandierbar,
1313
+ texts,
1314
+ toolArgumentsText,
1315
+ onAnswerQuestion
1316
+ }) {
1317
+ const schritt = dicht ? "qsl-step qsl-step--dense" : "qsl-step";
1318
+ if (message.role === "thinking" || message.role === "tool" && message.tool) {
1319
+ if (detailMode === "compact") {
1320
+ return /* @__PURE__ */ jsx7(TraceLine, { className: schritt, expandierbar, message, texts, toolArgumentsText });
1321
+ }
1322
+ const tool = message.tool;
1323
+ return /* @__PURE__ */ jsxs6("div", { className: `${schritt} qsl-trace${message.role === "thinking" ? " qsl-trace--thinking" : ""}`, children: [
1324
+ /* @__PURE__ */ jsx7(TraceIcon, { message }),
1325
+ /* @__PURE__ */ jsxs6("div", { className: "qsl-trace__body", children: [
1326
+ /* @__PURE__ */ jsx7("div", { className: "qsl-trace__line", children: /* @__PURE__ */ jsx7(TraceText, { message, texts }) }),
1327
+ tool && /* @__PURE__ */ jsxs6("div", { className: "qsl-details", children: [
1328
+ /* @__PURE__ */ jsx7("pre", { children: toolArgumentsText(tool) }),
1329
+ tool.result !== void 0 && /* @__PURE__ */ jsx7("pre", { className: tool.isError ? "qsl-details__pre--error" : void 0, children: prettyJson(tool.result) })
1330
+ ] })
1331
+ ] })
1332
+ ] });
1333
+ }
1334
+ if (message.role === "question" && message.question) {
1335
+ return /* @__PURE__ */ jsx7("div", { className: schritt, children: /* @__PURE__ */ jsx7(
1336
+ QuestionCard,
1337
+ {
1338
+ question: message.question,
1339
+ text: message.text,
1340
+ texts,
1341
+ onAnswer: (text) => onAnswerQuestion?.(message.question.callId, text)
1342
+ }
1343
+ ) });
1344
+ }
1345
+ if (message.role === "system") {
1346
+ return /* @__PURE__ */ jsx7("div", { className: `${schritt} qsl-system`, children: /* @__PURE__ */ jsx7(Markdown, { text: message.text }) });
1347
+ }
1348
+ const content = message.text.trim();
1349
+ if (!content && message.role !== "user") return null;
1350
+ const plain = bubbleOptions?.variant === "plain";
1351
+ const isBubble = !plain && (message.role === "user" || message.bubble || bubbleOptions?.variant === "bubbles");
1352
+ const side = (message.role === "user" ? bubbleOptions?.userSide : bubbleOptions?.assistantSide) ?? message.bubble?.side ?? (message.role === "user" ? "end" : "start");
1353
+ const label = bubbleOptions?.showSender === false ? void 0 : bubbleOptions?.senderLabel?.(message) ?? message.bubble?.label ?? (bubbleOptions?.showSender ? message.role : void 0);
1354
+ const body = /* @__PURE__ */ jsxs6(Fragment3, { children: [
1355
+ label && /* @__PURE__ */ jsx7("span", { className: "qsl-bubble__label", children: label }),
1356
+ /* @__PURE__ */ jsx7(Markdown, { text: message.role === "user" ? message.text : content }),
1357
+ /* @__PURE__ */ jsx7(MessageActions, { message, options: messageActions, texts })
1358
+ ] });
1359
+ if (isBubble) {
1360
+ const user = message.role === "user" && !message.bubble;
1361
+ return /* @__PURE__ */ jsx7("div", { className: `${schritt} ${user ? "qsl-user" : "qsl-bubble"}`, style: { justifyContent: side === "end" ? "flex-end" : "flex-start" }, children: /* @__PURE__ */ jsx7("div", { style: { background: message.bubble?.color ?? (user ? void 0 : "var(--qsl-accent)"), maxWidth: bubbleOptions?.maxWidth }, children: body }) });
1362
+ }
1363
+ return /* @__PURE__ */ jsx7("div", { className: `${schritt} qsl-answer`, children: body });
1364
+ }
1365
+
1366
+ // packages/quassel/src/ChatPanel.tsx
1367
+ import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect4, useMemo as useMemo2, useRef as useRef6 } from "react";
1368
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1369
+ function ChatPanel({
1370
+ children,
1371
+ composer,
1372
+ className,
1373
+ nodeRef,
1374
+ appearance,
1375
+ scrollOnSend = false
1376
+ }) {
1377
+ const jump = useRef6(void 0);
1378
+ const scrollOnSendRef = useRef6(scrollOnSend);
1379
+ useLayoutEffect4(() => {
1380
+ scrollOnSendRef.current = scrollOnSend;
1381
+ }, [scrollOnSend]);
1382
+ const sendContext = useMemo2(() => ({
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(() => {
1397
+ const element = eingabe.current;
1398
+ const rahmen = wurzel.current;
1399
+ if (!rahmen) {
1400
+ return;
1401
+ }
1402
+ if (!element) {
1403
+ rahmen.style.removeProperty("--qsl-composer-height");
1404
+ return;
1405
+ }
1406
+ const messen = () => rahmen.style.setProperty("--qsl-composer-height", `${element.offsetHeight}px`);
1407
+ const beobachter = new ResizeObserver(messen);
1408
+ beobachter.observe(element);
1409
+ messen();
1410
+ return () => beobachter.disconnect();
1411
+ }, [mitEingabe]);
1412
+ return /* @__PURE__ */ jsx8(ChatSendContext.Provider, { value: sendContext, children: /* @__PURE__ */ jsxs7(
1413
+ "div",
1414
+ {
1415
+ className: className ? `qsl-chat-panel ${className}` : "qsl-chat-panel",
1416
+ style: appearanceStyle(appearance),
1417
+ ref: (element) => {
1418
+ wurzel.current = element;
1419
+ if (typeof nodeRef === "function") {
1420
+ nodeRef(element);
1421
+ } else if (nodeRef) {
1422
+ nodeRef.current = element;
1423
+ }
1424
+ },
1425
+ children: [
1426
+ children,
1427
+ composer !== void 0 && /* @__PURE__ */ jsx8("div", { className: "qsl-chat-panel__composer", ref: eingabe, children: composer })
1428
+ ]
1429
+ }
1430
+ ) });
1431
+ }
1432
+
1433
+ // packages/quassel/src/TimestampSwitch.tsx
1434
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1435
+ function TimestampSwitch({
1436
+ showTimestamps,
1437
+ onChange,
1438
+ collapsible = true,
1439
+ texts,
1440
+ className
1441
+ }) {
1442
+ const label = texts?.timestamps ?? defaultTexts.timestamps;
1443
+ const classes = showTimestamps ? "qsl-icon-button qsl-icon-button--active" : "qsl-icon-button";
1444
+ return /* @__PURE__ */ jsxs8(
1445
+ "button",
1446
+ {
1447
+ "aria-label": label,
1448
+ "aria-pressed": showTimestamps,
1449
+ className: className ? `${classes} ${className}` : classes,
1450
+ onClick: () => onChange(!showTimestamps),
1451
+ title: label,
1452
+ type: "button",
1453
+ children: [
1454
+ /* @__PURE__ */ jsx9(IconClock, {}),
1455
+ /* @__PURE__ */ jsx9("span", { className: collapsible ? "qsl-collapsible" : void 0, children: label })
1456
+ ]
1457
+ }
1458
+ );
1459
+ }
1460
+
1461
+ // packages/quassel/src/ChatInputToolbar.tsx
1462
+ import { useCallback as useCallback3, useEffect as useEffect7, useRef as useRef7, useState as useState8 } from "react";
1463
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1464
+ var COMPACT_WIDTH_PX = 480;
1465
+ function ChatInputToolbar({
1466
+ onSend,
1467
+ onStop,
1468
+ running = false,
1469
+ disabled = false,
1470
+ rows = 3,
1471
+ maxRows,
1472
+ actions,
1473
+ toolbarLeft,
1474
+ toolbarRight,
1475
+ texts,
1476
+ sendShortcut = "enter"
1477
+ }) {
1478
+ const alleTexte = { ...defaultTexts, ...texts };
1479
+ const [draft, setDraft] = useState8("");
1480
+ const { submit, sending, error } = useChatSubmit(onSend);
1481
+ const rootRef = useRef7(null);
1482
+ const eingabeRef = useRef7(null);
1483
+ const [compact, setCompact] = useState8(false);
1484
+ const applyAutoGrow = useCallback3(() => {
1485
+ const element = eingabeRef.current;
1486
+ if (!element || maxRows === void 0) {
1487
+ return;
1488
+ }
1489
+ const stil = getComputedStyle(element);
1490
+ const zeilenhoehe = Number.parseFloat(stil.lineHeight) || Number.parseFloat(stil.fontSize) * 1.4;
1491
+ const polster = Number.parseFloat(stil.paddingTop) + Number.parseFloat(stil.paddingBottom);
1492
+ const maximum = zeilenhoehe * Math.max(maxRows, rows) + polster;
1493
+ element.style.height = "auto";
1494
+ const inhalt = element.scrollHeight;
1495
+ element.style.height = `${Math.min(inhalt, maximum)}px`;
1496
+ element.style.overflowY = inhalt > maximum ? "auto" : "hidden";
1497
+ }, [maxRows, rows]);
1498
+ useEffect7(() => {
1499
+ applyAutoGrow();
1500
+ }, [draft, applyAutoGrow]);
1501
+ useEffect7(() => {
1502
+ const element = eingabeRef.current;
1503
+ if (!element || maxRows === void 0) {
1504
+ return;
1505
+ }
1506
+ let lastWidth = element.clientWidth;
1507
+ const observer = new ResizeObserver(() => {
1508
+ if (element.clientWidth !== lastWidth) {
1509
+ lastWidth = element.clientWidth;
1510
+ applyAutoGrow();
1511
+ }
1512
+ });
1513
+ observer.observe(element);
1514
+ return () => observer.disconnect();
1515
+ }, [maxRows, applyAutoGrow]);
1516
+ useEffect7(() => {
1517
+ const element = rootRef.current;
1518
+ if (!element) {
1519
+ return;
1520
+ }
1521
+ const update = () => setCompact(element.clientWidth < COMPACT_WIDTH_PX);
1522
+ update();
1523
+ const beobachter = new ResizeObserver(update);
1524
+ beobachter.observe(element);
1525
+ return () => beobachter.disconnect();
1526
+ }, []);
1527
+ const hatText = draft.trim().length > 0;
1528
+ const send = async () => {
1529
+ const text = draft.trim();
1530
+ if (!text || disabled || sending) {
1531
+ return;
1532
+ }
1533
+ if (await submit(text)) setDraft((current) => current === draft ? "" : current);
1534
+ };
1535
+ const onKeyDown = (event) => {
1536
+ if (isSendKey(event, sendShortcut)) {
1537
+ event.preventDefault();
1538
+ if (!event.repeat) void send();
1539
+ }
1540
+ };
1541
+ return /* @__PURE__ */ jsxs9("div", { className: compact ? "qsl-input-card qsl-input-card--compact" : "qsl-input-card", ref: rootRef, children: [
1542
+ error && /* @__PURE__ */ jsx10("p", { className: "qsl-action-error", role: "alert", children: error }),
1543
+ /* @__PURE__ */ jsx10(
1544
+ "textarea",
1545
+ {
1546
+ "aria-label": alleTexte.placeholder,
1547
+ disabled,
1548
+ onChange: (event) => setDraft(event.target.value),
1549
+ onKeyDown,
1550
+ placeholder: running ? alleTexte.steeringPlaceholder : alleTexte.placeholder,
1551
+ ref: eingabeRef,
1552
+ rows,
1553
+ value: draft
1554
+ }
1555
+ ),
1556
+ /* @__PURE__ */ jsxs9("div", { className: "qsl-input-card__toolbar", children: [
1557
+ /* @__PURE__ */ jsxs9("div", { className: "qsl-input-card__left", children: [
1558
+ actions?.map((action, index) => /* @__PURE__ */ jsxs9(
1559
+ "button",
1560
+ {
1561
+ className: action.active ? "qsl-icon-button qsl-icon-button--active" : "qsl-icon-button",
1562
+ disabled: action.disabled,
1563
+ onClick: action.onClick,
1564
+ title: action.title ?? action.label,
1565
+ type: "button",
1566
+ children: [
1567
+ action.icon,
1568
+ action.label && /* @__PURE__ */ jsx10("span", { className: action.icon ? "qsl-collapsible" : void 0, children: action.label })
1569
+ ]
1570
+ },
1571
+ index
1572
+ )),
1573
+ toolbarLeft
1574
+ ] }),
1575
+ /* @__PURE__ */ jsxs9("div", { className: "qsl-input-card__right", children: [
1576
+ toolbarRight,
1577
+ running && onStop && /* @__PURE__ */ jsx10("button", { "aria-label": alleTexte.stop, className: "qsl-send qsl-send--stop", onClick: onStop, title: alleTexte.stop, type: "button", children: /* @__PURE__ */ jsx10(IconStop, {}) }),
1578
+ (!running || !onStop || hatText) && /* @__PURE__ */ jsx10(
1579
+ "button",
1580
+ {
1581
+ "aria-label": alleTexte.send,
1582
+ className: "qsl-send",
1583
+ disabled: disabled || sending || !hatText,
1584
+ onClick: () => void send(),
1585
+ title: running ? alleTexte.sendIntoRun : alleTexte.send,
1586
+ type: "button",
1587
+ children: /* @__PURE__ */ jsx10(IconSend, {})
1588
+ }
1589
+ )
1590
+ ] })
1591
+ ] })
1592
+ ] });
1593
+ }
1594
+
1595
+ // packages/quassel/src/DetailModeSwitch.tsx
1596
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1597
+ var DETAIL_MODES = ["off", "icons", "chips", "grouped", "compact", "full"];
1598
+ var TEXT_KEYS = {
1599
+ off: "detailModeOff",
1600
+ icons: "detailModeIcons",
1601
+ chips: "detailModeChips",
1602
+ grouped: "detailModeGrouped",
1603
+ compact: "detailModeCompact",
1604
+ full: "detailModeFull"
1605
+ };
1606
+ var detailModeLabel = (mode, texts) => ({ ...defaultTexts, ...texts })[TEXT_KEYS[mode]];
1607
+ function DetailModeSwitch({
1608
+ mode,
1609
+ onChange,
1610
+ modes = DETAIL_MODES,
1611
+ collapsible = true,
1612
+ texts,
1613
+ className
1614
+ }) {
1615
+ const alleTexte = { ...defaultTexts, ...texts };
1616
+ const aktuell = modes.indexOf(mode);
1617
+ const naechster = modes[(aktuell < 0 ? 0 : aktuell + 1) % modes.length];
1618
+ return /* @__PURE__ */ jsxs10(
1619
+ "button",
1620
+ {
1621
+ "aria-label": `${alleTexte.detailModeTitle}: ${detailModeLabel(mode, texts)}`,
1622
+ className: className ? `qsl-icon-button ${className}` : "qsl-icon-button",
1623
+ onClick: () => onChange(naechster),
1624
+ title: `${alleTexte.detailModeTitle}: ${detailModeLabel(mode, texts)}`,
1625
+ type: "button",
1626
+ children: [
1627
+ /* @__PURE__ */ jsx11(IconSpark, { size: 14 }),
1628
+ /* @__PURE__ */ jsx11("span", { className: collapsible ? "qsl-collapsible" : void 0, children: detailModeLabel(mode, texts) })
1629
+ ]
1630
+ }
1631
+ );
1632
+ }
1633
+ export {
1634
+ ChatInputToolbar,
1635
+ ChatMessages,
1636
+ ChatPanel,
1637
+ DETAIL_MODES,
1638
+ DetailModeSwitch,
1639
+ IconCheck,
1640
+ IconChevronDown,
1641
+ IconChevronRight,
1642
+ IconClock,
1643
+ IconLayers,
1644
+ IconSend,
1645
+ IconSpark,
1646
+ IconStop,
1647
+ IconTool,
1648
+ IconX,
1649
+ Markdown,
1650
+ TimestampSwitch,
1651
+ announce,
1652
+ applyEvent,
1653
+ compactToolLine,
1654
+ defaultTexts,
1655
+ detailModeLabel,
1656
+ markdownPlainText,
1657
+ prettyJson,
1658
+ stepState,
1659
+ useChat
1660
+ };
1661
+ //# sourceMappingURL=index.js.map