quassel 0.2.1 → 0.3.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 CHANGED
@@ -24,6 +24,7 @@ var defaultTexts = {
24
24
  working: "Arbeitet ...",
25
25
  toolRunning: "l\xE4uft ...",
26
26
  toolStillRunning: "Werkzeug l\xE4uft ...",
27
+ toolElapsedSeconds: "{seconds} s",
27
28
  currentTool: "Werkzeug l\xE4uft",
28
29
  thinkingChip: "Denken",
29
30
  toolChip: "Werkzeug",
@@ -42,7 +43,6 @@ var defaultTexts = {
42
43
  placeholder: "Nachricht schreiben ...",
43
44
  steeringPlaceholder: "Dazwischenfunken ...",
44
45
  steered: "In den laufenden Turn eingespeist",
45
- inputHint: "Enter zum Senden - Shift + Enter f\xFCr eine neue Zeile",
46
46
  detailModeTitle: "Detailgrad der Schritte",
47
47
  detailModeOff: "nur Antworten",
48
48
  detailModeCurrent: "aktuell",
@@ -87,10 +87,11 @@ var defaultTexts = {
87
87
 
88
88
  // packages/quassel/src/ui/cn.ts
89
89
  import { createCn } from "cn/config";
90
- var cn = createCn({ prefix: "qsl" });
90
+ var cn = createCn({ prefix: "qsl", extend: { theme: { text: ["meta", "trace", "label"] } } });
91
91
 
92
92
  // packages/quassel/src/ChatMessages.tsx
93
93
  import { useCallback as useCallback2, useContext as useContext3, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
94
+ import { createPortal } from "react-dom";
94
95
 
95
96
  // packages/quassel/src/chat-scroll.ts
96
97
  var atEnd = (viewport) => viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop <= 24;
@@ -154,12 +155,12 @@ var buttonVariants = cva(
154
155
  },
155
156
  size: {
156
157
  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",
158
+ xs: "qsl:h-6 qsl:gap-1 qsl:rounded-[min(--theme(--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",
159
+ sm: "qsl:h-7 qsl:gap-1 qsl:rounded-[min(--theme(--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
160
  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
161
  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",
162
+ "icon-xs": "qsl:size-6 qsl:rounded-[min(--theme(--radius-md),10px)] qsl:in-data-[slot=button-group]:rounded-lg qsl:[&_svg:not([class*='size-'])]:size-3",
163
+ "icon-sm": "qsl:size-7 qsl:rounded-[min(--theme(--radius-md),12px)] qsl:in-data-[slot=button-group]:rounded-lg",
163
164
  "icon-lg": "qsl:size-9"
164
165
  }
165
166
  },
@@ -223,9 +224,10 @@ function PopoverContent({
223
224
  anchor,
224
225
  collisionPadding,
225
226
  keepMounted,
227
+ portalContainer,
226
228
  ...props
227
229
  }) {
228
- return /* @__PURE__ */ jsx3(PopoverPrimitive.Portal, { keepMounted, children: /* @__PURE__ */ jsx3(
230
+ return /* @__PURE__ */ jsx3(PopoverPrimitive.Portal, { container: portalContainer, keepMounted, children: /* @__PURE__ */ jsx3(
229
231
  PopoverPrimitive.Positioner,
230
232
  {
231
233
  align,
@@ -239,6 +241,7 @@ function PopoverContent({
239
241
  PopoverPrimitive.Popup,
240
242
  {
241
243
  "data-quassel": "",
244
+ "data-quassel-popover": "",
242
245
  "data-slot": "popover-content",
243
246
  className: cn(
244
247
  "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",
@@ -285,7 +288,7 @@ var toggleVariants = cva2(
285
288
  },
286
289
  size: {
287
290
  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",
291
+ sm: "qsl:h-7 qsl:min-w-7 qsl:rounded-[min(--theme(--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
292
  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
293
  }
291
294
  },
@@ -316,16 +319,22 @@ function Toggle({
316
319
  import { jsx as jsx7 } from "react/jsx-runtime";
317
320
  var defaultComponents = { Button, Toggle, Card, StopButton, Popover, PopoverContent };
318
321
  var QuasselContext = createContext({ components: defaultComponents });
319
- function QuasselProvider({ components: components2, allowUrl, children }) {
322
+ function QuasselProvider({ components: components2, allowUrl, portalContainer, children }) {
320
323
  const outer = useContext(QuasselContext);
321
324
  const value = useMemo(() => ({
322
325
  components: { ...outer.components, ...components2 },
323
- allowUrl: allowUrl ?? outer.allowUrl
324
- }), [outer, components2, allowUrl]);
326
+ allowUrl: allowUrl ?? outer.allowUrl,
327
+ portalContainer: portalContainer ?? outer.portalContainer
328
+ }), [outer, components2, allowUrl, portalContainer]);
325
329
  return /* @__PURE__ */ jsx7(QuasselContext.Provider, { value, children });
326
330
  }
327
331
  var useQuasselComponents = () => useContext(QuasselContext).components;
328
332
  var useAllowUrl = () => useContext(QuasselContext).allowUrl;
333
+ function useQuasselPortalContainer() {
334
+ const container = useContext(QuasselContext).portalContainer;
335
+ const target = typeof container === "function" ? container() : container && "current" in container ? container.current : container;
336
+ return target ?? void 0;
337
+ }
329
338
 
330
339
  // packages/quassel/src/useChatAction.ts
331
340
  import { useEffect, useRef, useState } from "react";
@@ -506,7 +515,7 @@ function isTableDivider(line) {
506
515
  import { useMemo as useMemo2 } from "react";
507
516
  import { SparklesIcon, WrenchIcon, XIcon } from "lucide-react";
508
517
  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";
518
+ var preClasses = "qsl:rounded-lg qsl:bg-secondary qsl:p-3 qsl:font-mono qsl:text-trace qsl:leading-[1.625] qsl:break-all qsl:whitespace-pre-wrap";
510
519
  function StepPopover({
511
520
  message,
512
521
  position,
@@ -519,6 +528,7 @@ function StepPopover({
519
528
  contextElement: document.documentElement
520
529
  }), [position]);
521
530
  const { Button: Button2, Popover: Popover2, PopoverContent: PopoverContent2 } = useQuasselComponents();
531
+ const portalContainer = useQuasselPortalContainer();
522
532
  const tool = message.tool;
523
533
  const thinking = message.role === "thinking";
524
534
  const title = thinking ? texts.thinkingTitle : texts.toolTitle;
@@ -535,6 +545,7 @@ function StepPopover({
535
545
  thinking ? "qsl:max-h-none" : "qsl:max-h-[min(70vh,620px)]"
536
546
  ),
537
547
  collisionPadding: 16,
548
+ portalContainer,
538
549
  side: "bottom",
539
550
  sideOffset: 10,
540
551
  children: [
@@ -554,13 +565,13 @@ function StepPopover({
554
565
  }
555
566
  function PopoverSection({ label, value, error = false }) {
556
567
  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 }),
568
+ /* @__PURE__ */ jsx9("h3", { className: "qsl:mb-1.5 qsl:text-trace qsl:font-semibold qsl:tracking-[0.04em] qsl:text-muted-foreground qsl:uppercase", children: label }),
558
569
  /* @__PURE__ */ jsx9("pre", { className: cn(preClasses, error && "qsl:text-destructive"), children: value })
559
570
  ] });
560
571
  }
561
572
 
562
573
  // packages/quassel/src/PendingActionCard.tsx
563
- import { CheckIcon as CheckIcon2 } from "lucide-react";
574
+ import { CheckIcon as CheckIcon2, XIcon as XIcon2 } from "lucide-react";
564
575
  import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
565
576
  var cardClasses = "qsl:gap-2.5 qsl:bg-background qsl:px-3.5 qsl:text-sm";
566
577
  var resultText = (result) => typeof result === "string" ? result : result === null || result === void 0 ? "" : JSON.stringify(result);
@@ -577,7 +588,7 @@ function PendingActionCard({
577
588
  return /* @__PURE__ */ jsxs3(Card2, { className: cardClasses, size: "sm", children: [
578
589
  /* @__PURE__ */ jsx10("div", { className: "qsl:leading-normal", children: text }),
579
590
  /* @__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 }),
591
+ action.status === "approved" ? /* @__PURE__ */ jsx10(CheckIcon2, { className: "qsl:text-success", size: 12 }) : /* @__PURE__ */ jsx10(XIcon2, { size: 12 }),
581
592
  action.status === "approved" ? resultText(action.result) : dismissedLabel
582
593
  ] })
583
594
  ] });
@@ -878,6 +889,53 @@ function timestampDay(date, options = {}) {
878
889
  return { key, label };
879
890
  }
880
891
 
892
+ // packages/quassel/src/elapsed.ts
893
+ import { useSyncExternalStore } from "react";
894
+ var tickMs = 500;
895
+ function formatElapsed(ms, texts) {
896
+ const total = Math.max(0, Math.floor(ms / 1e3));
897
+ if (total < 60) return fillText(texts.toolElapsedSeconds, { seconds: total });
898
+ const hours = Math.floor(total / 3600);
899
+ const minutes = Math.floor(total / 60) % 60;
900
+ const seconds = String(total % 60).padStart(2, "0");
901
+ return hours > 0 ? `${hours}:${String(minutes).padStart(2, "0")}:${seconds}` : `${minutes}:${seconds}`;
902
+ }
903
+ function createTicker(intervalMs = tickMs) {
904
+ const listeners = /* @__PURE__ */ new Set();
905
+ let now = Date.now();
906
+ let timer;
907
+ return {
908
+ subscribe(listener) {
909
+ listeners.add(listener);
910
+ if (timer === void 0) {
911
+ now = Date.now();
912
+ timer = setInterval(() => {
913
+ now = Date.now();
914
+ listeners.forEach((notify) => notify());
915
+ }, intervalMs);
916
+ }
917
+ return () => {
918
+ listeners.delete(listener);
919
+ if (listeners.size === 0 && timer !== void 0) {
920
+ clearInterval(timer);
921
+ timer = void 0;
922
+ }
923
+ };
924
+ },
925
+ now() {
926
+ const current = Date.now();
927
+ if (timer === void 0 && (current - now >= intervalMs || current < now)) now = current;
928
+ return now;
929
+ },
930
+ active: () => timer !== void 0
931
+ };
932
+ }
933
+ var elapsedTicker = createTicker();
934
+ var nothingOnServer = () => void 0;
935
+ function useElapsed(start) {
936
+ return useSyncExternalStore(elapsedTicker.subscribe, () => Math.floor((elapsedTicker.now() - start) / 1e3) * 1e3, nothingOnServer);
937
+ }
938
+
881
939
  // packages/quassel/src/MessageActions.tsx
882
940
  import { CheckIcon as CheckIcon3, CopyIcon as CopyIcon2, PencilIcon, RotateCcwIcon } from "lucide-react";
883
941
  import { useState as useState3 } from "react";
@@ -943,6 +1001,10 @@ function createChatSendScope(enabled) {
943
1001
  };
944
1002
  }
945
1003
 
1004
+ // packages/quassel/src/ChatActionDock.ts
1005
+ import { createContext as createContext4 } from "react";
1006
+ var ChatActionDock = createContext4(void 0);
1007
+
946
1008
  // packages/quassel/src/announce.ts
947
1009
  var abstandMs = 400;
948
1010
  var hoechstensWartend = 5;
@@ -1005,7 +1067,7 @@ function announce(text) {
1005
1067
  import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
1006
1068
  var stepClasses = "qsl:mt-[var(--qsl-chat-message-gap,16px)]";
1007
1069
  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";
1070
+ var traceClasses = "qsl:flex qsl:w-full qsl:gap-2 qsl:pl-6 qsl:text-left qsl:text-trace qsl:leading-[1.375] qsl:text-muted-foreground";
1009
1071
  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
1072
  var traceIconClasses = "qsl:mt-0.5 qsl:flex-none qsl:opacity-60";
1011
1073
  var traceErrorIconClasses = "qsl:mt-0.5 qsl:flex-none qsl:text-destructive qsl:opacity-100";
@@ -1013,7 +1075,7 @@ var traceLineClasses = "qsl:min-w-0 qsl:self-baseline qsl:font-mono qsl:[overflo
1013
1075
  var traceRunningClasses = "qsl:ml-1.5 qsl:opacity-60";
1014
1076
  var chipClasses = cn(
1015
1077
  "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",
1078
+ "qsl:font-mono qsl:text-meta qsl:leading-[1.6] qsl:text-muted-foreground qsl:transition-colors",
1017
1079
  "qsl:data-[state=running]:border-[color-mix(in_srgb,var(--qsl-primary)_40%,var(--qsl-border-soft))]",
1018
1080
  "qsl:data-[state=thinking]:border-[color-mix(in_srgb,var(--qsl-primary)_40%,var(--qsl-border-soft))]",
1019
1081
  "qsl:data-[state=error]:border-[color-mix(in_srgb,var(--qsl-destructive)_45%,var(--qsl-border-soft))]",
@@ -1026,6 +1088,9 @@ var bubbleBodyClasses = "qsl:relative qsl:max-w-[86%] qsl:px-3 qsl:py-2 qsl:lead
1026
1088
  function istSchritt(message) {
1027
1089
  return !!message && (message.role === "thinking" || message.role === "tool");
1028
1090
  }
1091
+ function istOffeneAktion(message) {
1092
+ return message.role === "action" && message.action !== void 0 && message.action.status === void 0;
1093
+ }
1029
1094
  function defaultArgumentsText(tool) {
1030
1095
  return prettyJson(tool.arguments);
1031
1096
  }
@@ -1044,6 +1109,7 @@ function ChatMessages({
1044
1109
  onLinkClick,
1045
1110
  emptyState,
1046
1111
  className,
1112
+ scrollerClassName,
1047
1113
  showTimestamps = false,
1048
1114
  announce: announce2,
1049
1115
  scrollerRef,
@@ -1054,7 +1120,8 @@ function ChatMessages({
1054
1120
  timestampOptions,
1055
1121
  codeBlockOptions,
1056
1122
  bubbleOptions,
1057
- messageActions
1123
+ messageActions,
1124
+ toolElapsedThreshold = 3e3
1058
1125
  }) {
1059
1126
  const alleTexte = { ...defaultTexts, ...texts };
1060
1127
  const { Button: Button2 } = useQuasselComponents();
@@ -1065,6 +1132,7 @@ function ChatMessages({
1065
1132
  const [showJumpToEnd, setShowJumpToEnd] = useState4(false);
1066
1133
  const [scroll] = useState4(() => createChatScroll());
1067
1134
  const sendScope = useContext3(ChatSendContext);
1135
+ const dock = useContext3(ChatActionDock);
1068
1136
  const [now, setNow] = useState4(() => Date.now());
1069
1137
  useEffect3(() => {
1070
1138
  if (!showTimestamps || timestampOptions?.format !== "relative") return;
@@ -1074,7 +1142,7 @@ function ChatMessages({
1074
1142
  }, [showTimestamps, timestampOptions?.format]);
1075
1143
  const angesagt = useRef3(new Set(messages.map((message) => message.key)));
1076
1144
  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)));
1145
+ const faellig = messages.filter((message, index) => !angesagt.current.has(message.key) && (message.role === "action" ? istOffeneAktion(message) : message.role === "assistant" && message.text.trim() !== "" && (message.closed === true || !running && index === messages.length - 1)));
1078
1146
  if (faellig.length === 0) {
1079
1147
  return;
1080
1148
  }
@@ -1137,6 +1205,9 @@ function ChatMessages({
1137
1205
  () => {
1138
1206
  const letzterFremder = messages.reduce((last, message, index) => message.role === "user" ? last : index, -1);
1139
1207
  return messages.filter((message, index) => {
1208
+ if (dock !== void 0 && istOffeneAktion(message)) {
1209
+ return false;
1210
+ }
1140
1211
  if (detailMode === "current" && istSchritt(message)) {
1141
1212
  return running && index === letzterFremder && (message.role === "thinking" ? !message.closed : stepState(message) === "running");
1142
1213
  }
@@ -1149,29 +1220,32 @@ function ChatMessages({
1149
1220
  return true;
1150
1221
  });
1151
1222
  },
1152
- [messages, detailMode, running]
1223
+ [messages, detailMode, running, dock]
1153
1224
  );
1225
+ const offeneAktionen = dock ? messages.filter(istOffeneAktion) : [];
1154
1226
  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
1227
  /* @__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",
1228
+ "qsl:flex-none qsl:text-meta qsl:tabular-nums qsl:text-muted-foreground qsl:opacity-85",
1157
1229
  timestampOptions?.format && timestampOptions.format !== "time" ? "qsl:w-[clamp(60px,20%,120px)]" : "qsl:w-[34px]"
1158
1230
  ), children: messageDate(at) ? timestampLabel(messageDate(at), timestampOptions, now) : "" }),
1159
1231
  /* @__PURE__ */ jsx13("div", { className: "qsl:min-w-0 qsl:flex-1 qsl:[&>*:first-child]:mt-0", children: inhalt })
1160
1232
  ] }, `zeit-${key}`) : inhalt;
1161
1233
  const chipModus = detailMode === "current" || detailMode === "chips" || detailMode === "icons";
1234
+ const elapsedAfter = running && toolElapsedThreshold !== false ? toolElapsedThreshold : void 0;
1162
1235
  const gruppenModus = detailMode === "grouped";
1163
1236
  const bloecke = [];
1164
1237
  let lastDay;
1165
1238
  let gruppe = [];
1166
1239
  const schliesseGruppe = () => {
1167
1240
  if (gruppe.length > 0) {
1168
- const letzterZustand = stepState(gruppe[gruppe.length - 1]);
1241
+ const laufenderSchritt = running ? [...gruppe].reverse().find((message) => ["running", "thinking"].includes(stepState(message))) : void 0;
1169
1242
  bloecke.push(mitZeit(
1170
1243
  gruppenModus ? /* @__PURE__ */ jsx13(
1171
1244
  StepGroup,
1172
1245
  {
1173
- aktiv: running && (letzterZustand === "running" || letzterZustand === "thinking"),
1246
+ elapsedAfter,
1174
1247
  expandierbar: stepsExpandable,
1248
+ laufenderSchritt,
1175
1249
  messages: gruppe,
1176
1250
  texts: alleTexte,
1177
1251
  toolArgumentsText
@@ -1180,6 +1254,7 @@ function ChatMessages({
1180
1254
  ) : /* @__PURE__ */ jsx13(
1181
1255
  StepRow,
1182
1256
  {
1257
+ elapsedAfter,
1183
1258
  expandierbar: stepsExpandable,
1184
1259
  messages: gruppe,
1185
1260
  mitText: detailMode !== "icons",
@@ -1227,6 +1302,7 @@ function ChatMessages({
1227
1302
  {
1228
1303
  detailMode,
1229
1304
  dicht,
1305
+ elapsedAfter,
1230
1306
  expandierbar: stepsExpandable,
1231
1307
  message,
1232
1308
  plain: owner != null && message.sender === owner,
@@ -1259,8 +1335,10 @@ function ChatMessages({
1259
1335
  "qsl:[--qsl-fade-edge:calc(100%_-_var(--qsl-composer-height,0px))]",
1260
1336
  "qsl:[-webkit-mask-image:linear-gradient(to_bottom,#000_calc(var(--qsl-fade-edge)_-_40px),transparent_calc(var(--qsl-fade-edge)_+_16px))]",
1261
1337
  "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"
1338
+ "qsl:in-data-[tone=material]:p-0",
1339
+ scrollerClassName
1263
1340
  ),
1341
+ "data-quassel-transcript": "",
1264
1342
  onScroll: pruefeEnde,
1265
1343
  onWheel: (event) => {
1266
1344
  if (!event.ctrlKey && event.deltaY < 0) pauseUp(event.currentTarget, event.target);
@@ -1310,7 +1388,11 @@ function ChatMessages({
1310
1388
  scroll.jump(scrollBereich.current);
1311
1389
  updateJumpVisibility(scrollBereich.current);
1312
1390
  }
1313
- }, children: /* @__PURE__ */ jsx13(ChevronDownIcon, {}) }) })
1391
+ }, children: /* @__PURE__ */ jsx13(ChevronDownIcon, {}) }) }),
1392
+ dock && offeneAktionen.length > 0 && createPortal(
1393
+ /* @__PURE__ */ jsx13("div", { className: "qsl:flex qsl:flex-col qsl:gap-[var(--qsl-chat-dense-message-gap,8px)] qsl:text-[length:var(--qsl-chat-font-size,13px)] qsl:text-foreground", style: appearanceStyle(appearance), children: offeneAktionen.map((message) => /* @__PURE__ */ jsx13("div", { children: /* @__PURE__ */ jsx13(ActionContent, { action: message.action, onDismissAction, renderAction, text: message.text, texts: alleTexte }) }, message.key)) }),
1394
+ dock
1395
+ )
1314
1396
  ] });
1315
1397
  return /* @__PURE__ */ jsx13(MarkdownLinks, { onLinkClick, children: /* @__PURE__ */ jsx13(MarkdownCodeBlocks, { options: codeBlockOptions, texts: alleTexte, children: verlauf }) });
1316
1398
  }
@@ -1318,6 +1400,7 @@ function StepRow({
1318
1400
  messages,
1319
1401
  mitText,
1320
1402
  expandierbar,
1403
+ elapsedAfter,
1321
1404
  texts,
1322
1405
  toolArgumentsText,
1323
1406
  toolLabel
@@ -1334,6 +1417,7 @@ function StepRow({
1334
1417
  const content = /* @__PURE__ */ jsxs6(Fragment2, { children: [
1335
1418
  thinking ? /* @__PURE__ */ jsx13(SparklesIcon2, { size: 11 }) : /* @__PURE__ */ jsx13(WrenchIcon2, { className: tool?.isError ? "qsl:text-destructive" : void 0, size: 11 }),
1336
1419
  mitText && /* @__PURE__ */ jsx13("span", { className: "qsl:min-w-0 qsl:self-baseline qsl:overflow-hidden qsl:text-ellipsis qsl:whitespace-nowrap", children: label }),
1420
+ mitText && /* @__PURE__ */ jsx13(ToolElapsed, { after: elapsedAfter, className: "qsl:flex-none qsl:self-baseline", message, texts }),
1337
1421
  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 }) })
1338
1422
  ] });
1339
1423
  const classes = cn(chipClasses, !mitText && "qsl:p-1", running && "qsl:animate-fade-pulse qsl:motion-reduce:animate-none");
@@ -1370,7 +1454,8 @@ function StepRow({
1370
1454
  }
1371
1455
  function StepGroup({
1372
1456
  messages,
1373
- aktiv,
1457
+ laufenderSchritt,
1458
+ elapsedAfter,
1374
1459
  expandierbar,
1375
1460
  texts,
1376
1461
  toolArgumentsText
@@ -1384,10 +1469,10 @@ function StepGroup({
1384
1469
  kopf.current?.scrollIntoView({ block: "nearest" });
1385
1470
  }
1386
1471
  }, [offen]);
1387
- const letzte = messages[messages.length - 1];
1472
+ const aktiv = laufenderSchritt !== void 0;
1388
1473
  const fehler = messages.some((message) => message.tool?.isError);
1389
1474
  const anzahl = messages.length === 1 ? texts.stepGroupOne : texts.stepGroupMany.replace("{count}", String(messages.length));
1390
- const laufend = aktiv ? letzte.role === "thinking" ? texts.thinkingChip : letzte.tool?.name || letzte.text || texts.toolChip : void 0;
1475
+ const laufend = laufenderSchritt && (laufenderSchritt.role === "thinking" ? texts.thinkingChip : laufenderSchritt.tool?.name || laufenderSchritt.text || texts.toolChip);
1391
1476
  return /* @__PURE__ */ jsxs6("div", { className: stepClasses, "data-step": "group", children: [
1392
1477
  /* @__PURE__ */ jsxs6(
1393
1478
  "button",
@@ -1406,12 +1491,13 @@ function StepGroup({
1406
1491
  laufend,
1407
1492
  " ",
1408
1493
  texts.toolRunning
1409
- ] })
1494
+ ] }),
1495
+ laufenderSchritt && /* @__PURE__ */ jsx13(ToolElapsed, { after: elapsedAfter, message: laufenderSchritt, texts })
1410
1496
  ] })
1411
1497
  ]
1412
1498
  }
1413
1499
  ),
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)) }),
1500
+ 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", elapsedAfter, expandierbar, message, texts, toolArgumentsText }, message.key)) }),
1415
1501
  offen && messages.length >= collapseAtBottomFrom && /* @__PURE__ */ jsxs6(
1416
1502
  "button",
1417
1503
  {
@@ -1433,6 +1519,7 @@ function StepGroup({
1433
1519
  function TraceLine({
1434
1520
  message,
1435
1521
  className,
1522
+ elapsedAfter,
1436
1523
  expandierbar,
1437
1524
  texts,
1438
1525
  toolArgumentsText
@@ -1446,7 +1533,8 @@ function TraceLine({
1446
1533
  thinking ? /* @__PURE__ */ jsx13(SparklesIcon2, { className: traceIconClasses, size: 12 }) : /* @__PURE__ */ jsx13(WrenchIcon2, { className: tool?.isError ? traceErrorIconClasses : traceIconClasses, size: 12 }),
1447
1534
  /* @__PURE__ */ jsxs6("span", { className: zeile, children: [
1448
1535
  message.text,
1449
- tool && tool.result === void 0 && /* @__PURE__ */ jsx13("span", { className: traceRunningClasses, children: texts.toolRunning })
1536
+ tool && tool.result === void 0 && /* @__PURE__ */ jsx13("span", { className: traceRunningClasses, children: texts.toolRunning }),
1537
+ /* @__PURE__ */ jsx13(ToolElapsed, { after: elapsedAfter, message, texts })
1450
1538
  ] })
1451
1539
  ] });
1452
1540
  const marks = { "data-compact": "true", "data-kind": thinking ? "thinking" : "tool", "data-step": "line" };
@@ -1478,11 +1566,21 @@ function TraceLine({
1478
1566
  )
1479
1567
  ] });
1480
1568
  }
1569
+ function ToolElapsed({ message, after, texts, className = traceRunningClasses }) {
1570
+ const start = stepState(message) === "running" ? messageDate(message.at)?.getTime() : void 0;
1571
+ return after === void 0 || start === void 0 ? null : /* @__PURE__ */ jsx13(Elapsed, { after, className, start, texts });
1572
+ }
1573
+ function Elapsed({ start, after, className, texts }) {
1574
+ const elapsed = useElapsed(start);
1575
+ if (elapsed === void 0 || elapsed < after) return null;
1576
+ return /* @__PURE__ */ jsx13("span", { "aria-hidden": "true", className: cn(className, "qsl:tabular-nums"), "data-step": "elapsed", children: formatElapsed(elapsed, texts) });
1577
+ }
1481
1578
  function Bubble({
1482
1579
  message,
1483
1580
  plain,
1484
1581
  detailMode,
1485
1582
  dicht,
1583
+ elapsedAfter,
1486
1584
  expandierbar,
1487
1585
  texts,
1488
1586
  toolArgumentsText,
@@ -1494,23 +1592,24 @@ function Bubble({
1494
1592
  const schritt = dicht ? denseStepClasses : stepClasses;
1495
1593
  if (message.role === "thinking" || message.role === "tool" && message.tool) {
1496
1594
  if (detailMode === "compact") {
1497
- return /* @__PURE__ */ jsx13(TraceLine, { className: schritt, expandierbar, message, texts, toolArgumentsText });
1595
+ return /* @__PURE__ */ jsx13(TraceLine, { className: schritt, elapsedAfter, expandierbar, message, texts, toolArgumentsText });
1498
1596
  }
1499
1597
  const thinking = message.role === "thinking";
1500
1598
  const tool = message.tool;
1501
1599
  const icon = thinking ? /* @__PURE__ */ jsx13(SparklesIcon2, { className: traceIconClasses, size: 12 }) : /* @__PURE__ */ jsx13(WrenchIcon2, { className: tool?.isError ? traceErrorIconClasses : traceIconClasses, size: 12 });
1502
1600
  const zeile = /* @__PURE__ */ jsxs6(Fragment2, { children: [
1503
1601
  message.text,
1504
- tool && tool.result === void 0 && /* @__PURE__ */ jsx13("span", { className: traceRunningClasses, children: texts.toolRunning })
1602
+ tool && tool.result === void 0 && /* @__PURE__ */ jsx13("span", { className: traceRunningClasses, children: texts.toolRunning }),
1603
+ /* @__PURE__ */ jsx13(ToolElapsed, { after: elapsedAfter, message, texts })
1505
1604
  ] });
1506
1605
  return /* @__PURE__ */ jsxs6("div", { className: cn(traceClasses, schritt), "data-kind": thinking ? "thinking" : "tool", "data-step": "detail", children: [
1507
1606
  icon,
1508
1607
  /* @__PURE__ */ jsxs6("div", { className: "qsl:min-w-0 qsl:flex-1", children: [
1509
1608
  /* @__PURE__ */ jsx13("div", { className: cn(traceLineClasses, thinking && "qsl:font-sans qsl:italic qsl:whitespace-pre-wrap"), children: zeile }),
1510
1609
  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) }),
1610
+ /* @__PURE__ */ jsx13("pre", { className: "qsl:mb-0.5 qsl:max-h-[208px] qsl:overflow-auto qsl:font-mono qsl:text-meta qsl:leading-[1.375] qsl:break-all qsl:whitespace-pre-wrap qsl:opacity-80", children: toolArgumentsText(tool) }),
1512
1611
  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",
1612
+ "qsl:mb-0.5 qsl:max-h-[208px] qsl:overflow-auto qsl:font-mono qsl:text-meta qsl:leading-[1.375] qsl:break-all qsl:whitespace-pre-wrap qsl:opacity-80",
1514
1613
  tool.isError && "qsl:text-destructive qsl:opacity-100"
1515
1614
  ), children: prettyJson(tool.result) })
1516
1615
  ] })
@@ -1518,18 +1617,7 @@ function Bubble({
1518
1617
  ] });
1519
1618
  }
1520
1619
  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,
1524
- {
1525
- action,
1526
- dismissedLabel: texts.actionDismissed,
1527
- dismissLabel: texts.dismissAction,
1528
- onDismiss: onDismissAction ? () => onDismissAction(action.actionId) : void 0,
1529
- text: message.text,
1530
- waitingLabel: texts.pendingAction
1531
- }
1532
- ) });
1620
+ return /* @__PURE__ */ jsx13("div", { className: schritt, children: /* @__PURE__ */ jsx13(ActionContent, { action: message.action, onDismissAction, renderAction, text: message.text, texts }) });
1533
1621
  }
1534
1622
  if (message.role === "system") {
1535
1623
  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: [
@@ -1545,12 +1633,12 @@ function Bubble({
1545
1633
  const showSender = bubbleOptions?.showSender ?? (!!message.bubble?.label && !plain);
1546
1634
  const content = /* @__PURE__ */ jsxs6(Fragment2, { children: [
1547
1635
  showSender && sender && /* @__PURE__ */ jsx13("span", { className: cn(
1548
- "qsl:mb-[5px] qsl:table qsl:text-[0.72rem] qsl:leading-[1.45] qsl:font-semibold",
1636
+ "qsl:mb-[5px] qsl:table qsl:text-label qsl:leading-[1.45] qsl:font-semibold",
1549
1637
  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
1638
  ), "data-chat": "sender", children: sender }),
1551
1639
  /* @__PURE__ */ jsx13(Markdown, { text: message.text, streaming: message.role === "assistant" && !message.closed }),
1552
1640
  /* @__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 }),
1641
+ message.steered && /* @__PURE__ */ jsx13("small", { className: "qsl:mt-1 qsl:block qsl:text-label qsl:leading-[1.45] qsl:opacity-70", "data-chat": "steered", children: texts.steered }),
1554
1642
  /* @__PURE__ */ jsx13(MessageActions, { message, options: messageActions, texts })
1555
1643
  ] });
1556
1644
  const cursor = message.role === "assistant" && message.textCursor ? JSON.stringify(message.textCursor) : void 0;
@@ -1565,7 +1653,7 @@ function Bubble({
1565
1653
  children: /* @__PURE__ */ jsx13(
1566
1654
  "div",
1567
1655
  {
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"),
1656
+ className: cn(bubbleBodyClasses, "qsl:group", message.bubble ? "qsl:rounded-lg qsl:text-white qsl:[--qsl-scroll-cover:var(--qsl-primary)]" : "qsl:rounded-[var(--qsl-bubble-radius,10px_10px_4px_10px)] qsl:border qsl:border-border qsl:bg-secondary qsl:text-foreground qsl:[--qsl-scroll-cover:var(--qsl-secondary)] qsl:in-data-[tone=material]:rounded-[var(--qsl-bubble-radius,9px)] qsl:in-data-[tone=material]:border-glass-edge/20 qsl:in-data-[tone=material]:bg-primary/15 qsl:in-data-[tone=material]:text-foreground"),
1569
1657
  "data-tone": message.bubble ? "on-color" : void 0,
1570
1658
  style: { background: message.bubble?.color, maxWidth: bubbleOptions?.maxWidth },
1571
1659
  children: content
@@ -1574,6 +1662,25 @@ function Bubble({
1574
1662
  }
1575
1663
  );
1576
1664
  }
1665
+ function ActionContent({
1666
+ action,
1667
+ text,
1668
+ texts,
1669
+ renderAction,
1670
+ onDismissAction
1671
+ }) {
1672
+ return renderAction?.(action, text) ?? /* @__PURE__ */ jsx13(
1673
+ PendingActionCard,
1674
+ {
1675
+ action,
1676
+ dismissedLabel: texts.actionDismissed,
1677
+ dismissLabel: texts.dismissAction,
1678
+ onDismiss: onDismissAction ? () => onDismissAction(action.actionId) : void 0,
1679
+ text,
1680
+ waitingLabel: texts.pendingAction
1681
+ }
1682
+ );
1683
+ }
1577
1684
  function MessageAttachments({ message, label }) {
1578
1685
  if (!message.attachments?.length) return null;
1579
1686
  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: [
@@ -1601,6 +1708,7 @@ function ChatPanel({
1601
1708
  }) {
1602
1709
  const wurzel = useRef4(null);
1603
1710
  const eingabe = useRef4(null);
1711
+ const [dock, setDock] = useState5(null);
1604
1712
  const hasComposer = composer !== void 0;
1605
1713
  const scrollOnSendRef = useRef4(scrollOnSend);
1606
1714
  const [sendScope] = useState5(() => createChatSendScope(scrollOnSendRef));
@@ -1613,16 +1721,21 @@ function ChatPanel({
1613
1721
  if (!element || !rahmen) {
1614
1722
  return;
1615
1723
  }
1616
- const messen = () => rahmen.style.setProperty("--qsl-composer-height", `${element.offsetHeight}px`);
1724
+ const messen = () => {
1725
+ rahmen.style.setProperty("--qsl-composer-height", `${element.offsetHeight}px`);
1726
+ rahmen.style.setProperty("--qsl-panel-height", `${rahmen.offsetHeight}px`);
1727
+ };
1617
1728
  const beobachter = new ResizeObserver(messen);
1618
1729
  beobachter.observe(element);
1730
+ beobachter.observe(rahmen);
1619
1731
  messen();
1620
1732
  return () => {
1621
1733
  beobachter.disconnect();
1622
1734
  rahmen.style.removeProperty("--qsl-composer-height");
1735
+ rahmen.style.removeProperty("--qsl-panel-height");
1623
1736
  };
1624
1737
  }, [hasComposer]);
1625
- return /* @__PURE__ */ jsx14(ChatSendContext.Provider, { value: sendScope, children: /* @__PURE__ */ jsxs7(
1738
+ return /* @__PURE__ */ jsx14(ChatSendContext.Provider, { value: sendScope, children: /* @__PURE__ */ jsx14(ChatActionDock.Provider, { value: hasComposer ? dock : void 0, children: /* @__PURE__ */ jsxs7(
1626
1739
  "div",
1627
1740
  {
1628
1741
  className: cn("qsl:relative qsl:flex qsl:min-h-0 qsl:min-w-0 qsl:flex-col qsl:overflow-hidden", className),
@@ -1643,14 +1756,24 @@ function ChatPanel({
1643
1756
  },
1644
1757
  children: [
1645
1758
  children,
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 })
1759
+ hasComposer && /* @__PURE__ */ jsxs7("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: [
1760
+ /* @__PURE__ */ jsx14("div", { children: /* @__PURE__ */ jsx14(
1761
+ "div",
1762
+ {
1763
+ className: "qsl:-mx-[16px] qsl:max-h-[calc(var(--qsl-panel-height,100dvh)_/_2)] qsl:overflow-y-auto qsl:px-[16px] qsl:pb-[var(--qsl-chat-message-gap,16px)] qsl:[-webkit-mask-image:linear-gradient(to_bottom,#000_calc(100%_-_var(--qsl-chat-message-gap,16px)),transparent)] qsl:[mask-image:linear-gradient(to_bottom,#000_calc(100%_-_var(--qsl-chat-message-gap,16px)),transparent)] qsl:empty:hidden",
1764
+ "data-chat": "actions",
1765
+ ref: setDock
1766
+ }
1767
+ ) }),
1768
+ composer
1769
+ ] })
1647
1770
  ]
1648
1771
  }
1649
- ) });
1772
+ ) }) });
1650
1773
  }
1651
1774
 
1652
1775
  // packages/quassel/src/ChatInputToolbar.tsx
1653
- import { PaperclipIcon, SendIcon, XIcon as XIcon2 } from "lucide-react";
1776
+ import { PaperclipIcon, SendIcon, XIcon as XIcon3 } from "lucide-react";
1654
1777
 
1655
1778
  // packages/quassel/src/ui/useFileInput.ts
1656
1779
  import { useRef as useRef5, useState as useState6 } from "react";
@@ -1702,7 +1825,7 @@ function useFileInput({ disabled, onFiles, onPasteText }) {
1702
1825
 
1703
1826
  // packages/quassel/src/ChatInputToolbar.tsx
1704
1827
  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";
1828
+ import { createPortal as createPortal2 } from "react-dom";
1706
1829
  import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
1707
1830
  var COMPACT_WIDTH_PX = 480;
1708
1831
  var errorClasses = "qsl:px-3 qsl:py-2 qsl:text-sm qsl:text-destructive";
@@ -1851,7 +1974,7 @@ function ChatInputToolbar({
1851
1974
  const inputRows = inline || toolbar ? 1 : rows;
1852
1975
  const inputMaxRows = inline || toolbar ? 1 : maxRows;
1853
1976
  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",
1977
+ "qsl:flex qsl:shrink-0 qsl:flex-col qsl:overflow-hidden qsl:rounded-[var(--qsl-input-card-radius,--theme(--radius-xl))] qsl:border qsl:border-border-strong qsl:bg-background qsl:shadow-bar qsl:transition-colors qsl:focus-within:border-primary",
1855
1978
  "qsl:in-data-[tone=material]:rounded-xl qsl:in-data-[tone=material]:border-glass-edge/50 qsl:in-data-[tone=material]:bg-background",
1856
1979
  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
1980
  fileInput.dragging && "qsl:outline-2 qsl:outline-offset-[3px] qsl:outline-dashed qsl:outline-primary"
@@ -2041,7 +2164,7 @@ function ChatInputToolbar({
2041
2164
  size: "icon-sm",
2042
2165
  title: fillText(alleTexte.removeAttachment, { name: entry.file.name }),
2043
2166
  variant: "outline",
2044
- children: /* @__PURE__ */ jsx15(XIcon2, {})
2167
+ children: /* @__PURE__ */ jsx15(XIcon3, {})
2045
2168
  }
2046
2169
  )
2047
2170
  ] }, entry.id)) });
@@ -2172,7 +2295,7 @@ function ChatInputToolbar({
2172
2295
  ] }) : input,
2173
2296
  toolbar ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
2174
2297
  /* @__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)
2298
+ detailsContainer && createPortal2(details, detailsContainer)
2176
2299
  ] }) : !inline && /* @__PURE__ */ jsxs8(Fragment3, { children: [
2177
2300
  errors,
2178
2301
  detailToolbar
@@ -2283,6 +2406,7 @@ export {
2283
2406
  encodeAttachment,
2284
2407
  fillText,
2285
2408
  formatAttachmentSize,
2409
+ formatElapsed,
2286
2410
  markdownPlainText,
2287
2411
  messageDate,
2288
2412
  prettyJson,
@@ -2290,7 +2414,9 @@ export {
2290
2414
  timestampDay,
2291
2415
  timestampLabel,
2292
2416
  useChatAction,
2417
+ useElapsed,
2293
2418
  useQuasselComponents,
2419
+ useQuasselPortalContainer,
2294
2420
  validateAttachmentSelection
2295
2421
  };
2296
2422
  //# sourceMappingURL=index.js.map