quassel 0.2.2 → 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/ChatActionDock.d.ts +2 -0
- package/ChatMessages.d.ts +3 -1
- package/README.md +6 -2
- package/chat.css +24 -0
- package/elapsed.d.ts +16 -0
- package/index.d.ts +1 -0
- package/index.js +145 -32
- package/index.js.map +4 -4
- package/package.json +1 -1
- package/texts.d.ts +1 -0
package/ChatMessages.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { type ChatAppearance, type TimestampOptions, type CodeBlockOptions, type
|
|
|
7
7
|
* Der Verlauf: eigener Scroll-Container mit Autoscroll, solange man unten ist,
|
|
8
8
|
* und einem Zum-Ende-Knopf, sobald man liest. Ohne Eingabe read-only nutzbar.
|
|
9
9
|
*/
|
|
10
|
-
export declare function ChatMessages({ messages, owner, detailMode, running, working, stepsExpandable, texts, toolArgumentsText, renderTool, renderAction, onDismissAction, onLinkClick, emptyState, className, scrollerClassName, showTimestamps, announce, scrollerRef, maxWidth, horizontalPadding, jumpToEndThreshold, appearance, timestampOptions, codeBlockOptions, bubbleOptions, messageActions, }: {
|
|
10
|
+
export declare function ChatMessages({ messages, owner, detailMode, running, working, stepsExpandable, texts, toolArgumentsText, renderTool, renderAction, onDismissAction, onLinkClick, emptyState, className, scrollerClassName, showTimestamps, announce, scrollerRef, maxWidth, horizontalPadding, jumpToEndThreshold, appearance, timestampOptions, codeBlockOptions, bubbleOptions, messageActions, toolElapsedThreshold, }: {
|
|
11
11
|
messages: Message[];
|
|
12
12
|
/** Beiträge mit diesem Message.sender erscheinen ohne Sprechblase; null behält die Nachrichtenvorgaben. */
|
|
13
13
|
owner?: string | null;
|
|
@@ -43,4 +43,6 @@ export declare function ChatMessages({ messages, owner, detailMode, running, wor
|
|
|
43
43
|
codeBlockOptions?: CodeBlockOptions;
|
|
44
44
|
bubbleOptions?: BubbleOptions;
|
|
45
45
|
messageActions?: MessageActionsOptions;
|
|
46
|
+
/** Milliseconds before a running tool call shows its elapsed time (from Message.at) while running; false = never. */
|
|
47
|
+
toolElapsedThreshold?: number | false;
|
|
46
48
|
}): import("react").JSX.Element;
|
package/README.md
CHANGED
|
@@ -12,12 +12,15 @@ color, radius, font and font size is a `--qsl-*` variable.
|
|
|
12
12
|
|
|
13
13
|
**Agent chats that show their work.** Thinking and tool calls appear as steps. Pick how much
|
|
14
14
|
of them the user sees - only the current step, icons, chips, a collapsible group, single lines
|
|
15
|
-
or everything - and let them switch at runtime.
|
|
15
|
+
or everything - and let them switch at runtime. A tool call that runs longer than a few seconds
|
|
16
|
+
shows how long it has been running, on its row and on the collapsed group header, so a slow step
|
|
17
|
+
does not look like a hung one.
|
|
16
18
|
|
|
17
19
|

|
|
18
20
|
|
|
19
21
|
**Pending actions.** The agent waits for input: a generic card with dismiss, or the host's own
|
|
20
|
-
rendering per action owner.
|
|
22
|
+
rendering per action owner. Open actions sit right above the input, like a question in a terminal
|
|
23
|
+
agent; resolved actions stay in the transcript as a record.
|
|
21
24
|
|
|
22
25
|
**Multi-party conversations.** Colored bubbles with labels for agents that talk to each other.
|
|
23
26
|
|
|
@@ -89,6 +92,7 @@ Any backend that emits the `quassel/events` stream works with the same component
|
|
|
89
92
|
pnpm install
|
|
90
93
|
pnpm dev # stylesheet in watch mode plus the gallery on http://localhost:3210
|
|
91
94
|
pnpm check # type check package and gallery
|
|
95
|
+
pnpm test # package tests (node:test with happy-dom)
|
|
92
96
|
pnpm build # stylesheet and gallery build
|
|
93
97
|
pnpm release --dry-run
|
|
94
98
|
```
|
package/chat.css
CHANGED
|
@@ -87,6 +87,9 @@
|
|
|
87
87
|
.qsl\:z-\[110\] {
|
|
88
88
|
z-index: 110;
|
|
89
89
|
}
|
|
90
|
+
.qsl\:-mx-\[16px\] {
|
|
91
|
+
margin-inline: calc(16px * -1);
|
|
92
|
+
}
|
|
90
93
|
.qsl\:mx-auto {
|
|
91
94
|
margin-inline: auto;
|
|
92
95
|
}
|
|
@@ -200,6 +203,9 @@
|
|
|
200
203
|
.qsl\:max-h-\[240px\] {
|
|
201
204
|
max-height: 240px;
|
|
202
205
|
}
|
|
206
|
+
.qsl\:max-h-\[calc\(var\(--qsl-panel-height\,100dvh\)_\/_2\)\] {
|
|
207
|
+
max-height: calc(var(--qsl-panel-height,100dvh) / 2);
|
|
208
|
+
}
|
|
203
209
|
.qsl\:max-h-\[min\(70vh\,620px\)\] {
|
|
204
210
|
max-height: min(70vh, 620px);
|
|
205
211
|
}
|
|
@@ -377,6 +383,9 @@
|
|
|
377
383
|
.qsl\:gap-\[5px\] {
|
|
378
384
|
gap: 5px;
|
|
379
385
|
}
|
|
386
|
+
.qsl\:gap-\[var\(--qsl-chat-dense-message-gap\,8px\)\] {
|
|
387
|
+
gap: var(--qsl-chat-dense-message-gap,8px);
|
|
388
|
+
}
|
|
380
389
|
:where(.qsl\:space-y-0 > :not(:last-child)) {
|
|
381
390
|
--tw-space-y-reverse: 0;
|
|
382
391
|
margin-block-start: 0;
|
|
@@ -523,6 +532,9 @@
|
|
|
523
532
|
.qsl\:bg-\[linear-gradient\(to_right\,var\(--qsl-scroll-cover\,var\(--qsl-background\)\)_50\%\,transparent\)_left\/20px_100\%_no-repeat_local\,linear-gradient\(to_left\,var\(--qsl-scroll-cover\,var\(--qsl-background\)\)_50\%\,transparent\)_right\/20px_100\%_no-repeat_local\,linear-gradient\(to_right\,\#00000047\,transparent\)_left\/14px_100\%_no-repeat_scroll\,linear-gradient\(to_left\,\#00000047\,transparent\)_right\/14px_100\%_no-repeat_scroll\] {
|
|
524
533
|
background-image: linear-gradient(to right,var(--qsl-scroll-cover,var(--qsl-background)) 50%,transparent) left/20px 100% no-repeat local,linear-gradient(to left,var(--qsl-scroll-cover,var(--qsl-background)) 50%,transparent) right/20px 100% no-repeat local,linear-gradient(to right,#00000047,transparent) left/14px 100% no-repeat scroll,linear-gradient(to left,#00000047,transparent) right/14px 100% no-repeat scroll;
|
|
525
534
|
}
|
|
535
|
+
.qsl\:\[mask-image\:linear-gradient\(to_bottom\,\#000_calc\(100\%_-_var\(--qsl-chat-message-gap\,16px\)\)\,transparent\)\] {
|
|
536
|
+
mask-image: linear-gradient(to bottom,#000 calc(100% - var(--qsl-chat-message-gap,16px)),transparent);
|
|
537
|
+
}
|
|
526
538
|
.qsl\:\[mask-image\:linear-gradient\(to_bottom\,\#000_calc\(var\(--qsl-fade-edge\)_-_40px\)\,transparent_calc\(var\(--qsl-fade-edge\)_\+_16px\)\)\] {
|
|
527
539
|
mask-image: linear-gradient(to bottom,#000 calc(var(--qsl-fade-edge) - 40px),transparent calc(var(--qsl-fade-edge) + 16px));
|
|
528
540
|
}
|
|
@@ -571,6 +583,9 @@
|
|
|
571
583
|
.qsl\:px-\[7px\] {
|
|
572
584
|
padding-inline: 7px;
|
|
573
585
|
}
|
|
586
|
+
.qsl\:px-\[16px\] {
|
|
587
|
+
padding-inline: 16px;
|
|
588
|
+
}
|
|
574
589
|
.qsl\:px-\[var\(--qsl-chat-horizontal-padding\,24px\)\] {
|
|
575
590
|
padding-inline: var(--qsl-chat-horizontal-padding,24px);
|
|
576
591
|
}
|
|
@@ -622,6 +637,9 @@
|
|
|
622
637
|
.qsl\:pb-\[calc\(var\(--qsl-composer-height\,0px\)_\+_max\(3\.25em\,40px\)\)\] {
|
|
623
638
|
padding-bottom: calc(var(--qsl-composer-height,0px) + max(3.25em, 40px));
|
|
624
639
|
}
|
|
640
|
+
.qsl\:pb-\[var\(--qsl-chat-message-gap\,16px\)\] {
|
|
641
|
+
padding-bottom: var(--qsl-chat-message-gap,16px);
|
|
642
|
+
}
|
|
625
643
|
.qsl\:pl-1 {
|
|
626
644
|
padding-left: var(--qsl-spacing);
|
|
627
645
|
}
|
|
@@ -916,6 +934,9 @@
|
|
|
916
934
|
.qsl\:\[--qsl-scroll-cover\:var\(--qsl-secondary\)\] {
|
|
917
935
|
--qsl-scroll-cover: var(--qsl-secondary);
|
|
918
936
|
}
|
|
937
|
+
.qsl\:\[-webkit-mask-image\:linear-gradient\(to_bottom\,\#000_calc\(100\%_-_var\(--qsl-chat-message-gap\,16px\)\)\,transparent\)\] {
|
|
938
|
+
-webkit-mask-image: linear-gradient(to bottom,#000 calc(100% - var(--qsl-chat-message-gap,16px)),transparent);
|
|
939
|
+
}
|
|
919
940
|
.qsl\:\[-webkit-mask-image\:linear-gradient\(to_bottom\,\#000_calc\(var\(--qsl-fade-edge\)_-_40px\)\,transparent_calc\(var\(--qsl-fade-edge\)_\+_16px\)\)\] {
|
|
920
941
|
-webkit-mask-image: linear-gradient(to bottom,#000 calc(var(--qsl-fade-edge) - 40px),transparent calc(var(--qsl-fade-edge) + 16px));
|
|
921
942
|
}
|
|
@@ -951,6 +972,9 @@
|
|
|
951
972
|
.qsl\:placeholder\:text-muted-foreground::placeholder {
|
|
952
973
|
color: var(--qsl-muted-foreground);
|
|
953
974
|
}
|
|
975
|
+
.qsl\:empty\:hidden:empty {
|
|
976
|
+
display: none;
|
|
977
|
+
}
|
|
954
978
|
.qsl\:focus-within\:pointer-events-auto:focus-within {
|
|
955
979
|
pointer-events: auto;
|
|
956
980
|
}
|
package/elapsed.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ChatTexts } from "./texts";
|
|
2
|
+
/** Under a minute "{seconds} s" from texts, then m:ss, from an hour on h:mm:ss. */
|
|
3
|
+
export declare function formatElapsed(ms: number, texts: Pick<ChatTexts, "toolElapsedSeconds">): string;
|
|
4
|
+
/** One interval for every subscriber; it runs only while someone listens. */
|
|
5
|
+
export declare function createTicker(intervalMs?: number): {
|
|
6
|
+
subscribe(listener: () => void): () => void;
|
|
7
|
+
now(): number;
|
|
8
|
+
active: () => boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare const elapsedTicker: {
|
|
11
|
+
subscribe(listener: () => void): () => void;
|
|
12
|
+
now(): number;
|
|
13
|
+
active: () => boolean;
|
|
14
|
+
};
|
|
15
|
+
/** Milliseconds since start in whole seconds, so a row renders only when its display changes; undefined on the server. */
|
|
16
|
+
export declare function useElapsed(start: number): number | undefined;
|
package/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export { useChatAction, copyChatText } from "./useChatAction";
|
|
|
17
17
|
export { createChatScroll } from "./chat-scroll";
|
|
18
18
|
export { ChatSendContext, createChatSendScope, type ChatSendScope } from "./ChatSendContext";
|
|
19
19
|
export { messageDate, timestampDay, timestampLabel } from "./timestamps";
|
|
20
|
+
export { formatElapsed, useElapsed } from "./elapsed";
|
|
20
21
|
export { attachmentCapabilityError, attachmentDownloadUrl, attachmentMediaType, encodeAttachment, formatAttachmentSize, validateAttachmentSelection, MAX_CHAT_ATTACHMENTS, MAX_CHAT_ATTACHMENT_BYTES, } from "./attachments";
|
|
21
22
|
export type { ChatAppearance, TimestampOptions, CodeBlockOptions, BubbleOptions, MessageAction, MessageActionsOptions, SendShortcut, } from "./options";
|
|
22
23
|
export { appearanceStyle } from "./options";
|
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",
|
|
@@ -90,6 +91,7 @@ var cn = createCn({ prefix: "qsl", extend: { theme: { text: ["meta", "trace", "l
|
|
|
90
91
|
|
|
91
92
|
// packages/quassel/src/ChatMessages.tsx
|
|
92
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";
|
|
93
95
|
|
|
94
96
|
// packages/quassel/src/chat-scroll.ts
|
|
95
97
|
var atEnd = (viewport) => viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop <= 24;
|
|
@@ -887,6 +889,53 @@ function timestampDay(date, options = {}) {
|
|
|
887
889
|
return { key, label };
|
|
888
890
|
}
|
|
889
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
|
+
|
|
890
939
|
// packages/quassel/src/MessageActions.tsx
|
|
891
940
|
import { CheckIcon as CheckIcon3, CopyIcon as CopyIcon2, PencilIcon, RotateCcwIcon } from "lucide-react";
|
|
892
941
|
import { useState as useState3 } from "react";
|
|
@@ -952,6 +1001,10 @@ function createChatSendScope(enabled) {
|
|
|
952
1001
|
};
|
|
953
1002
|
}
|
|
954
1003
|
|
|
1004
|
+
// packages/quassel/src/ChatActionDock.ts
|
|
1005
|
+
import { createContext as createContext4 } from "react";
|
|
1006
|
+
var ChatActionDock = createContext4(void 0);
|
|
1007
|
+
|
|
955
1008
|
// packages/quassel/src/announce.ts
|
|
956
1009
|
var abstandMs = 400;
|
|
957
1010
|
var hoechstensWartend = 5;
|
|
@@ -1035,6 +1088,9 @@ var bubbleBodyClasses = "qsl:relative qsl:max-w-[86%] qsl:px-3 qsl:py-2 qsl:lead
|
|
|
1035
1088
|
function istSchritt(message) {
|
|
1036
1089
|
return !!message && (message.role === "thinking" || message.role === "tool");
|
|
1037
1090
|
}
|
|
1091
|
+
function istOffeneAktion(message) {
|
|
1092
|
+
return message.role === "action" && message.action !== void 0 && message.action.status === void 0;
|
|
1093
|
+
}
|
|
1038
1094
|
function defaultArgumentsText(tool) {
|
|
1039
1095
|
return prettyJson(tool.arguments);
|
|
1040
1096
|
}
|
|
@@ -1064,7 +1120,8 @@ function ChatMessages({
|
|
|
1064
1120
|
timestampOptions,
|
|
1065
1121
|
codeBlockOptions,
|
|
1066
1122
|
bubbleOptions,
|
|
1067
|
-
messageActions
|
|
1123
|
+
messageActions,
|
|
1124
|
+
toolElapsedThreshold = 3e3
|
|
1068
1125
|
}) {
|
|
1069
1126
|
const alleTexte = { ...defaultTexts, ...texts };
|
|
1070
1127
|
const { Button: Button2 } = useQuasselComponents();
|
|
@@ -1075,6 +1132,7 @@ function ChatMessages({
|
|
|
1075
1132
|
const [showJumpToEnd, setShowJumpToEnd] = useState4(false);
|
|
1076
1133
|
const [scroll] = useState4(() => createChatScroll());
|
|
1077
1134
|
const sendScope = useContext3(ChatSendContext);
|
|
1135
|
+
const dock = useContext3(ChatActionDock);
|
|
1078
1136
|
const [now, setNow] = useState4(() => Date.now());
|
|
1079
1137
|
useEffect3(() => {
|
|
1080
1138
|
if (!showTimestamps || timestampOptions?.format !== "relative") return;
|
|
@@ -1084,7 +1142,7 @@ function ChatMessages({
|
|
|
1084
1142
|
}, [showTimestamps, timestampOptions?.format]);
|
|
1085
1143
|
const angesagt = useRef3(new Set(messages.map((message) => message.key)));
|
|
1086
1144
|
useEffect3(() => {
|
|
1087
|
-
const faellig = messages.filter((message, index) => !angesagt.current.has(message.key) && (message.role === "action" ? message
|
|
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)));
|
|
1088
1146
|
if (faellig.length === 0) {
|
|
1089
1147
|
return;
|
|
1090
1148
|
}
|
|
@@ -1147,6 +1205,9 @@ function ChatMessages({
|
|
|
1147
1205
|
() => {
|
|
1148
1206
|
const letzterFremder = messages.reduce((last, message, index) => message.role === "user" ? last : index, -1);
|
|
1149
1207
|
return messages.filter((message, index) => {
|
|
1208
|
+
if (dock !== void 0 && istOffeneAktion(message)) {
|
|
1209
|
+
return false;
|
|
1210
|
+
}
|
|
1150
1211
|
if (detailMode === "current" && istSchritt(message)) {
|
|
1151
1212
|
return running && index === letzterFremder && (message.role === "thinking" ? !message.closed : stepState(message) === "running");
|
|
1152
1213
|
}
|
|
@@ -1159,8 +1220,9 @@ function ChatMessages({
|
|
|
1159
1220
|
return true;
|
|
1160
1221
|
});
|
|
1161
1222
|
},
|
|
1162
|
-
[messages, detailMode, running]
|
|
1223
|
+
[messages, detailMode, running, dock]
|
|
1163
1224
|
);
|
|
1225
|
+
const offeneAktionen = dock ? messages.filter(istOffeneAktion) : [];
|
|
1164
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: [
|
|
1165
1227
|
/* @__PURE__ */ jsx13("time", { dateTime: messageDate(at)?.toISOString(), className: cn(
|
|
1166
1228
|
"qsl:flex-none qsl:text-meta qsl:tabular-nums qsl:text-muted-foreground qsl:opacity-85",
|
|
@@ -1169,19 +1231,21 @@ function ChatMessages({
|
|
|
1169
1231
|
/* @__PURE__ */ jsx13("div", { className: "qsl:min-w-0 qsl:flex-1 qsl:[&>*:first-child]:mt-0", children: inhalt })
|
|
1170
1232
|
] }, `zeit-${key}`) : inhalt;
|
|
1171
1233
|
const chipModus = detailMode === "current" || detailMode === "chips" || detailMode === "icons";
|
|
1234
|
+
const elapsedAfter = running && toolElapsedThreshold !== false ? toolElapsedThreshold : void 0;
|
|
1172
1235
|
const gruppenModus = detailMode === "grouped";
|
|
1173
1236
|
const bloecke = [];
|
|
1174
1237
|
let lastDay;
|
|
1175
1238
|
let gruppe = [];
|
|
1176
1239
|
const schliesseGruppe = () => {
|
|
1177
1240
|
if (gruppe.length > 0) {
|
|
1178
|
-
const
|
|
1241
|
+
const laufenderSchritt = running ? [...gruppe].reverse().find((message) => ["running", "thinking"].includes(stepState(message))) : void 0;
|
|
1179
1242
|
bloecke.push(mitZeit(
|
|
1180
1243
|
gruppenModus ? /* @__PURE__ */ jsx13(
|
|
1181
1244
|
StepGroup,
|
|
1182
1245
|
{
|
|
1183
|
-
|
|
1246
|
+
elapsedAfter,
|
|
1184
1247
|
expandierbar: stepsExpandable,
|
|
1248
|
+
laufenderSchritt,
|
|
1185
1249
|
messages: gruppe,
|
|
1186
1250
|
texts: alleTexte,
|
|
1187
1251
|
toolArgumentsText
|
|
@@ -1190,6 +1254,7 @@ function ChatMessages({
|
|
|
1190
1254
|
) : /* @__PURE__ */ jsx13(
|
|
1191
1255
|
StepRow,
|
|
1192
1256
|
{
|
|
1257
|
+
elapsedAfter,
|
|
1193
1258
|
expandierbar: stepsExpandable,
|
|
1194
1259
|
messages: gruppe,
|
|
1195
1260
|
mitText: detailMode !== "icons",
|
|
@@ -1237,6 +1302,7 @@ function ChatMessages({
|
|
|
1237
1302
|
{
|
|
1238
1303
|
detailMode,
|
|
1239
1304
|
dicht,
|
|
1305
|
+
elapsedAfter,
|
|
1240
1306
|
expandierbar: stepsExpandable,
|
|
1241
1307
|
message,
|
|
1242
1308
|
plain: owner != null && message.sender === owner,
|
|
@@ -1322,7 +1388,11 @@ function ChatMessages({
|
|
|
1322
1388
|
scroll.jump(scrollBereich.current);
|
|
1323
1389
|
updateJumpVisibility(scrollBereich.current);
|
|
1324
1390
|
}
|
|
1325
|
-
}, 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
|
+
)
|
|
1326
1396
|
] });
|
|
1327
1397
|
return /* @__PURE__ */ jsx13(MarkdownLinks, { onLinkClick, children: /* @__PURE__ */ jsx13(MarkdownCodeBlocks, { options: codeBlockOptions, texts: alleTexte, children: verlauf }) });
|
|
1328
1398
|
}
|
|
@@ -1330,6 +1400,7 @@ function StepRow({
|
|
|
1330
1400
|
messages,
|
|
1331
1401
|
mitText,
|
|
1332
1402
|
expandierbar,
|
|
1403
|
+
elapsedAfter,
|
|
1333
1404
|
texts,
|
|
1334
1405
|
toolArgumentsText,
|
|
1335
1406
|
toolLabel
|
|
@@ -1346,6 +1417,7 @@ function StepRow({
|
|
|
1346
1417
|
const content = /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1347
1418
|
thinking ? /* @__PURE__ */ jsx13(SparklesIcon2, { size: 11 }) : /* @__PURE__ */ jsx13(WrenchIcon2, { className: tool?.isError ? "qsl:text-destructive" : void 0, size: 11 }),
|
|
1348
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 }),
|
|
1349
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 }) })
|
|
1350
1422
|
] });
|
|
1351
1423
|
const classes = cn(chipClasses, !mitText && "qsl:p-1", running && "qsl:animate-fade-pulse qsl:motion-reduce:animate-none");
|
|
@@ -1382,7 +1454,8 @@ function StepRow({
|
|
|
1382
1454
|
}
|
|
1383
1455
|
function StepGroup({
|
|
1384
1456
|
messages,
|
|
1385
|
-
|
|
1457
|
+
laufenderSchritt,
|
|
1458
|
+
elapsedAfter,
|
|
1386
1459
|
expandierbar,
|
|
1387
1460
|
texts,
|
|
1388
1461
|
toolArgumentsText
|
|
@@ -1396,10 +1469,10 @@ function StepGroup({
|
|
|
1396
1469
|
kopf.current?.scrollIntoView({ block: "nearest" });
|
|
1397
1470
|
}
|
|
1398
1471
|
}, [offen]);
|
|
1399
|
-
const
|
|
1472
|
+
const aktiv = laufenderSchritt !== void 0;
|
|
1400
1473
|
const fehler = messages.some((message) => message.tool?.isError);
|
|
1401
1474
|
const anzahl = messages.length === 1 ? texts.stepGroupOne : texts.stepGroupMany.replace("{count}", String(messages.length));
|
|
1402
|
-
const laufend =
|
|
1475
|
+
const laufend = laufenderSchritt && (laufenderSchritt.role === "thinking" ? texts.thinkingChip : laufenderSchritt.tool?.name || laufenderSchritt.text || texts.toolChip);
|
|
1403
1476
|
return /* @__PURE__ */ jsxs6("div", { className: stepClasses, "data-step": "group", children: [
|
|
1404
1477
|
/* @__PURE__ */ jsxs6(
|
|
1405
1478
|
"button",
|
|
@@ -1418,12 +1491,13 @@ function StepGroup({
|
|
|
1418
1491
|
laufend,
|
|
1419
1492
|
" ",
|
|
1420
1493
|
texts.toolRunning
|
|
1421
|
-
] })
|
|
1494
|
+
] }),
|
|
1495
|
+
laufenderSchritt && /* @__PURE__ */ jsx13(ToolElapsed, { after: elapsedAfter, message: laufenderSchritt, texts })
|
|
1422
1496
|
] })
|
|
1423
1497
|
]
|
|
1424
1498
|
}
|
|
1425
1499
|
),
|
|
1426
|
-
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)) }),
|
|
1427
1501
|
offen && messages.length >= collapseAtBottomFrom && /* @__PURE__ */ jsxs6(
|
|
1428
1502
|
"button",
|
|
1429
1503
|
{
|
|
@@ -1445,6 +1519,7 @@ function StepGroup({
|
|
|
1445
1519
|
function TraceLine({
|
|
1446
1520
|
message,
|
|
1447
1521
|
className,
|
|
1522
|
+
elapsedAfter,
|
|
1448
1523
|
expandierbar,
|
|
1449
1524
|
texts,
|
|
1450
1525
|
toolArgumentsText
|
|
@@ -1458,7 +1533,8 @@ function TraceLine({
|
|
|
1458
1533
|
thinking ? /* @__PURE__ */ jsx13(SparklesIcon2, { className: traceIconClasses, size: 12 }) : /* @__PURE__ */ jsx13(WrenchIcon2, { className: tool?.isError ? traceErrorIconClasses : traceIconClasses, size: 12 }),
|
|
1459
1534
|
/* @__PURE__ */ jsxs6("span", { className: zeile, children: [
|
|
1460
1535
|
message.text,
|
|
1461
|
-
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 })
|
|
1462
1538
|
] })
|
|
1463
1539
|
] });
|
|
1464
1540
|
const marks = { "data-compact": "true", "data-kind": thinking ? "thinking" : "tool", "data-step": "line" };
|
|
@@ -1490,11 +1566,21 @@ function TraceLine({
|
|
|
1490
1566
|
)
|
|
1491
1567
|
] });
|
|
1492
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
|
+
}
|
|
1493
1578
|
function Bubble({
|
|
1494
1579
|
message,
|
|
1495
1580
|
plain,
|
|
1496
1581
|
detailMode,
|
|
1497
1582
|
dicht,
|
|
1583
|
+
elapsedAfter,
|
|
1498
1584
|
expandierbar,
|
|
1499
1585
|
texts,
|
|
1500
1586
|
toolArgumentsText,
|
|
@@ -1506,14 +1592,15 @@ function Bubble({
|
|
|
1506
1592
|
const schritt = dicht ? denseStepClasses : stepClasses;
|
|
1507
1593
|
if (message.role === "thinking" || message.role === "tool" && message.tool) {
|
|
1508
1594
|
if (detailMode === "compact") {
|
|
1509
|
-
return /* @__PURE__ */ jsx13(TraceLine, { className: schritt, expandierbar, message, texts, toolArgumentsText });
|
|
1595
|
+
return /* @__PURE__ */ jsx13(TraceLine, { className: schritt, elapsedAfter, expandierbar, message, texts, toolArgumentsText });
|
|
1510
1596
|
}
|
|
1511
1597
|
const thinking = message.role === "thinking";
|
|
1512
1598
|
const tool = message.tool;
|
|
1513
1599
|
const icon = thinking ? /* @__PURE__ */ jsx13(SparklesIcon2, { className: traceIconClasses, size: 12 }) : /* @__PURE__ */ jsx13(WrenchIcon2, { className: tool?.isError ? traceErrorIconClasses : traceIconClasses, size: 12 });
|
|
1514
1600
|
const zeile = /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1515
1601
|
message.text,
|
|
1516
|
-
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 })
|
|
1517
1604
|
] });
|
|
1518
1605
|
return /* @__PURE__ */ jsxs6("div", { className: cn(traceClasses, schritt), "data-kind": thinking ? "thinking" : "tool", "data-step": "detail", children: [
|
|
1519
1606
|
icon,
|
|
@@ -1530,18 +1617,7 @@ function Bubble({
|
|
|
1530
1617
|
] });
|
|
1531
1618
|
}
|
|
1532
1619
|
if (message.role === "action" && message.action) {
|
|
1533
|
-
|
|
1534
|
-
return /* @__PURE__ */ jsx13("div", { className: schritt, children: renderAction?.(action, message.text) ?? /* @__PURE__ */ jsx13(
|
|
1535
|
-
PendingActionCard,
|
|
1536
|
-
{
|
|
1537
|
-
action,
|
|
1538
|
-
dismissedLabel: texts.actionDismissed,
|
|
1539
|
-
dismissLabel: texts.dismissAction,
|
|
1540
|
-
onDismiss: onDismissAction ? () => onDismissAction(action.actionId) : void 0,
|
|
1541
|
-
text: message.text,
|
|
1542
|
-
waitingLabel: texts.pendingAction
|
|
1543
|
-
}
|
|
1544
|
-
) });
|
|
1620
|
+
return /* @__PURE__ */ jsx13("div", { className: schritt, children: /* @__PURE__ */ jsx13(ActionContent, { action: message.action, onDismissAction, renderAction, text: message.text, texts }) });
|
|
1545
1621
|
}
|
|
1546
1622
|
if (message.role === "system") {
|
|
1547
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: [
|
|
@@ -1586,6 +1662,25 @@ function Bubble({
|
|
|
1586
1662
|
}
|
|
1587
1663
|
);
|
|
1588
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
|
+
}
|
|
1589
1684
|
function MessageAttachments({ message, label }) {
|
|
1590
1685
|
if (!message.attachments?.length) return null;
|
|
1591
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: [
|
|
@@ -1613,6 +1708,7 @@ function ChatPanel({
|
|
|
1613
1708
|
}) {
|
|
1614
1709
|
const wurzel = useRef4(null);
|
|
1615
1710
|
const eingabe = useRef4(null);
|
|
1711
|
+
const [dock, setDock] = useState5(null);
|
|
1616
1712
|
const hasComposer = composer !== void 0;
|
|
1617
1713
|
const scrollOnSendRef = useRef4(scrollOnSend);
|
|
1618
1714
|
const [sendScope] = useState5(() => createChatSendScope(scrollOnSendRef));
|
|
@@ -1625,16 +1721,21 @@ function ChatPanel({
|
|
|
1625
1721
|
if (!element || !rahmen) {
|
|
1626
1722
|
return;
|
|
1627
1723
|
}
|
|
1628
|
-
const messen = () =>
|
|
1724
|
+
const messen = () => {
|
|
1725
|
+
rahmen.style.setProperty("--qsl-composer-height", `${element.offsetHeight}px`);
|
|
1726
|
+
rahmen.style.setProperty("--qsl-panel-height", `${rahmen.offsetHeight}px`);
|
|
1727
|
+
};
|
|
1629
1728
|
const beobachter = new ResizeObserver(messen);
|
|
1630
1729
|
beobachter.observe(element);
|
|
1730
|
+
beobachter.observe(rahmen);
|
|
1631
1731
|
messen();
|
|
1632
1732
|
return () => {
|
|
1633
1733
|
beobachter.disconnect();
|
|
1634
1734
|
rahmen.style.removeProperty("--qsl-composer-height");
|
|
1735
|
+
rahmen.style.removeProperty("--qsl-panel-height");
|
|
1635
1736
|
};
|
|
1636
1737
|
}, [hasComposer]);
|
|
1637
|
-
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(
|
|
1638
1739
|
"div",
|
|
1639
1740
|
{
|
|
1640
1741
|
className: cn("qsl:relative qsl:flex qsl:min-h-0 qsl:min-w-0 qsl:flex-col qsl:overflow-hidden", className),
|
|
@@ -1655,10 +1756,20 @@ function ChatPanel({
|
|
|
1655
1756
|
},
|
|
1656
1757
|
children: [
|
|
1657
1758
|
children,
|
|
1658
|
-
hasComposer && /* @__PURE__ */
|
|
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
|
+
] })
|
|
1659
1770
|
]
|
|
1660
1771
|
}
|
|
1661
|
-
) });
|
|
1772
|
+
) }) });
|
|
1662
1773
|
}
|
|
1663
1774
|
|
|
1664
1775
|
// packages/quassel/src/ChatInputToolbar.tsx
|
|
@@ -1714,7 +1825,7 @@ function useFileInput({ disabled, onFiles, onPasteText }) {
|
|
|
1714
1825
|
|
|
1715
1826
|
// packages/quassel/src/ChatInputToolbar.tsx
|
|
1716
1827
|
import { useCallback as useCallback3, useContext as useContext4, useEffect as useEffect5, useImperativeHandle, useLayoutEffect as useLayoutEffect3, useRef as useRef6, useState as useState7 } from "react";
|
|
1717
|
-
import { createPortal } from "react-dom";
|
|
1828
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
1718
1829
|
import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1719
1830
|
var COMPACT_WIDTH_PX = 480;
|
|
1720
1831
|
var errorClasses = "qsl:px-3 qsl:py-2 qsl:text-sm qsl:text-destructive";
|
|
@@ -2184,7 +2295,7 @@ function ChatInputToolbar({
|
|
|
2184
2295
|
] }) : input,
|
|
2185
2296
|
toolbar ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2186
2297
|
/* @__PURE__ */ jsx15("div", { className: "qsl:flex qsl:flex-none qsl:items-center qsl:gap-1 qsl:p-1", children: sendButtons }),
|
|
2187
|
-
detailsContainer &&
|
|
2298
|
+
detailsContainer && createPortal2(details, detailsContainer)
|
|
2188
2299
|
] }) : !inline && /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2189
2300
|
errors,
|
|
2190
2301
|
detailToolbar
|
|
@@ -2295,6 +2406,7 @@ export {
|
|
|
2295
2406
|
encodeAttachment,
|
|
2296
2407
|
fillText,
|
|
2297
2408
|
formatAttachmentSize,
|
|
2409
|
+
formatElapsed,
|
|
2298
2410
|
markdownPlainText,
|
|
2299
2411
|
messageDate,
|
|
2300
2412
|
prettyJson,
|
|
@@ -2302,6 +2414,7 @@ export {
|
|
|
2302
2414
|
timestampDay,
|
|
2303
2415
|
timestampLabel,
|
|
2304
2416
|
useChatAction,
|
|
2417
|
+
useElapsed,
|
|
2305
2418
|
useQuasselComponents,
|
|
2306
2419
|
useQuasselPortalContainer,
|
|
2307
2420
|
validateAttachmentSelection
|