quassel 0.2.2 → 0.4.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/ChatInputToolbar.d.ts +9 -9
- package/ChatMessages.d.ts +13 -11
- package/ChatPanel.d.ts +3 -3
- package/DetailModeSwitch.d.ts +4 -6
- package/Markdown.d.ts +1 -1
- package/PendingActionCard.d.ts +2 -2
- package/README.md +8 -4
- package/TimestampSwitch.d.ts +1 -2
- package/WorkingScenes.d.ts +4 -4
- package/announce.d.ts +1 -1
- package/chat.css +24 -0
- package/{chunk-IGCFQJZB.js → chunk-3VEL4HEO.js} +9 -9
- package/chunk-3VEL4HEO.js.map +7 -0
- package/elapsed.d.ts +16 -0
- package/events.d.ts +8 -8
- package/events.js +1 -1
- package/index.d.ts +1 -0
- package/index.js +547 -442
- package/index.js.map +4 -4
- package/package.json +1 -1
- package/texts.d.ts +1 -1
- package/types.d.ts +4 -4
- package/ui/stop-button.d.ts +2 -2
- package/chunk-IGCFQJZB.js.map +0 -7
package/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
chatAttachmentMediaType,
|
|
6
6
|
compactToolLine,
|
|
7
7
|
prettyJson
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-3VEL4HEO.js";
|
|
9
9
|
|
|
10
10
|
// packages/quassel/src/types.ts
|
|
11
11
|
function stepState(message) {
|
|
@@ -21,67 +21,67 @@ function stepState(message) {
|
|
|
21
21
|
// packages/quassel/src/texts.ts
|
|
22
22
|
var fillText = (text, values) => text.replace(/\{(\w+)\}/g, (match, name) => name in values ? String(values[name]) : match);
|
|
23
23
|
var defaultTexts = {
|
|
24
|
-
working: "
|
|
25
|
-
toolRunning: "
|
|
26
|
-
toolStillRunning: "
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
showTimestamps: "
|
|
55
|
-
hideTimestamps: "
|
|
56
|
-
copyMessage: "
|
|
57
|
-
editMessage: "
|
|
58
|
-
retryMessage: "
|
|
59
|
-
copyCode: "
|
|
60
|
-
copied: "
|
|
61
|
-
pendingAction: "
|
|
62
|
-
dismissAction: "
|
|
63
|
-
actionDismissed: "
|
|
64
|
-
actionFailed: "
|
|
65
|
-
clipboardUnavailable: "
|
|
66
|
-
transcript: "
|
|
67
|
-
attachments: "
|
|
68
|
-
attachFiles: "
|
|
69
|
-
attachFilesHint: "
|
|
70
|
-
removeAttachment: "{name}
|
|
71
|
-
attachmentPreparing: "
|
|
72
|
-
attachmentsPreparing: "
|
|
73
|
-
attachmentReadFailed: "
|
|
74
|
-
attachmentsWhileBusy: "
|
|
75
|
-
tooManyAttachments: "
|
|
76
|
-
attachmentsTooLarge: "
|
|
77
|
-
attachmentUnsupported: "
|
|
78
|
-
attachmentUnsupportedByModel: "
|
|
79
|
-
attachmentKindImage: "
|
|
80
|
-
attachmentKindVideo: "
|
|
24
|
+
working: "Working ...",
|
|
25
|
+
toolRunning: "running ...",
|
|
26
|
+
toolStillRunning: "Tool running ...",
|
|
27
|
+
toolElapsedSeconds: "{seconds} s",
|
|
28
|
+
currentTool: "Tool running",
|
|
29
|
+
thinkingChip: "Thinking",
|
|
30
|
+
toolChip: "Tool",
|
|
31
|
+
stepGroupOne: "1 step",
|
|
32
|
+
stepGroupMany: "{count} steps",
|
|
33
|
+
stepGroupCollapse: "Collapse",
|
|
34
|
+
thinkingTitle: "Thinking trace",
|
|
35
|
+
toolTitle: "Tool call",
|
|
36
|
+
argumentsLabel: "Arguments",
|
|
37
|
+
resultLabel: "Result",
|
|
38
|
+
close: "Close",
|
|
39
|
+
jumpToEnd: "Jump to end",
|
|
40
|
+
send: "Send",
|
|
41
|
+
sendIntoRun: "Feed into the running turn",
|
|
42
|
+
stop: "Stop work",
|
|
43
|
+
placeholder: "Write a message ...",
|
|
44
|
+
steeringPlaceholder: "Interject ...",
|
|
45
|
+
steered: "Fed into the running turn",
|
|
46
|
+
detailModeTitle: "Step detail level",
|
|
47
|
+
detailModeOff: "answers only",
|
|
48
|
+
detailModeCurrent: "current",
|
|
49
|
+
detailModeIcons: "icons",
|
|
50
|
+
detailModeChips: "compact",
|
|
51
|
+
detailModeGrouped: "grouped",
|
|
52
|
+
detailModeCompact: "single line",
|
|
53
|
+
detailModeFull: "everything",
|
|
54
|
+
showTimestamps: "Show timestamps",
|
|
55
|
+
hideTimestamps: "Hide timestamps",
|
|
56
|
+
copyMessage: "Copy message",
|
|
57
|
+
editMessage: "Edit message",
|
|
58
|
+
retryMessage: "Request answer again",
|
|
59
|
+
copyCode: "Copy code",
|
|
60
|
+
copied: "Copied",
|
|
61
|
+
pendingAction: "waiting for input",
|
|
62
|
+
dismissAction: "Dismiss",
|
|
63
|
+
actionDismissed: "dismissed",
|
|
64
|
+
actionFailed: "Action failed",
|
|
65
|
+
clipboardUnavailable: "The clipboard is not available",
|
|
66
|
+
transcript: "Chat history",
|
|
67
|
+
attachments: "Attachments",
|
|
68
|
+
attachFiles: "Attach files",
|
|
69
|
+
attachFilesHint: "Attach files (up to {count} files, {size} in total)",
|
|
70
|
+
removeAttachment: "Remove {name}",
|
|
71
|
+
attachmentPreparing: "Preparing ...",
|
|
72
|
+
attachmentsPreparing: "Preparing attachments ...",
|
|
73
|
+
attachmentReadFailed: "The file could not be read. Remove it and add it again.",
|
|
74
|
+
attachmentsWhileBusy: "Wait until the input is ready again, then add the files again.",
|
|
75
|
+
tooManyAttachments: "At most {count} attachments per message. Remove an attachment first.",
|
|
76
|
+
attachmentsTooLarge: "Attachments may be at most {size} in total. Choose smaller files.",
|
|
77
|
+
attachmentUnsupported: "This chat does not support {name} as {kind}. Remove the attachment.",
|
|
78
|
+
attachmentUnsupportedByModel: "The model {model} does not support {name} as {kind}. Choose a suitable model or remove the attachment.",
|
|
79
|
+
attachmentKindImage: "image",
|
|
80
|
+
attachmentKindVideo: "video",
|
|
81
81
|
attachmentKindPdf: "PDF",
|
|
82
|
-
notSent: "
|
|
83
|
-
notSentAttachments: "({count}
|
|
84
|
-
insertUnsent: "
|
|
82
|
+
notSent: "Not sent:",
|
|
83
|
+
notSentAttachments: "({count} attachments)",
|
|
84
|
+
insertUnsent: "Insert unsent input"
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
// packages/quassel/src/ui/cn.ts
|
|
@@ -90,6 +90,7 @@ var cn = createCn({ prefix: "qsl", extend: { theme: { text: ["meta", "trace", "l
|
|
|
90
90
|
|
|
91
91
|
// packages/quassel/src/ChatMessages.tsx
|
|
92
92
|
import { useCallback as useCallback2, useContext as useContext3, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
|
|
93
|
+
import { createPortal } from "react-dom";
|
|
93
94
|
|
|
94
95
|
// packages/quassel/src/chat-scroll.ts
|
|
95
96
|
var atEnd = (viewport) => viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop <= 24;
|
|
@@ -459,7 +460,7 @@ var components = {
|
|
|
459
460
|
a: ({ href, children }) => /* @__PURE__ */ jsx8(MarkdownLink, { href, children }),
|
|
460
461
|
pre: ({ children }) => /* @__PURE__ */ jsx8(CodeBlock, { children }),
|
|
461
462
|
code: ({ children, className }) => /* @__PURE__ */ jsx8("code", { className: cn(className, "qsl:rounded-sm qsl:bg-secondary qsl:px-1 qsl:py-0.5 qsl:font-mono qsl:text-sm qsl:[overflow-wrap:anywhere] qsl:in-data-[tone=on-color]:bg-white/20 qsl:in-data-[tone=on-color]:text-inherit", "qsl:[pre>&]:bg-transparent qsl:[pre>&]:p-0"), children }),
|
|
462
|
-
//
|
|
463
|
+
// Without max-content the table shrinks to the container and stacks letters.
|
|
463
464
|
table: ({ children }) => /* @__PURE__ */ jsx8("div", { className: cn(scrollbar, scrollCover, "qsl:my-2 qsl:overflow-x-auto"), children: /* @__PURE__ */ jsx8("table", { className: "qsl:w-max qsl:min-w-full qsl:border-collapse qsl:text-sm", children }) }),
|
|
464
465
|
thead: ({ children }) => /* @__PURE__ */ jsx8("thead", { children }),
|
|
465
466
|
tbody: ({ children }) => /* @__PURE__ */ jsx8("tbody", { children }),
|
|
@@ -493,7 +494,7 @@ function Markdown({ text, streaming = false }) {
|
|
|
493
494
|
);
|
|
494
495
|
}
|
|
495
496
|
function markdownPlainText(text) {
|
|
496
|
-
return text.split("\n").filter((line) => !/^\s*```\w*\s*$/.test(line) && !isTableDivider(line)).map((line) => (line.trim().startsWith("|") ? splitTableRow(line).join(", ") : line).replace(/^(#{1,6})\s+/, "").replace(/^\s*>\s?/, "").replace(/^\s*([-*+]|\d+[.)])\s+/, "").replace(/!?\[([^\]]*)\]\(([^)]+)\)|\*\*([^*]+)\*\*|__([^_]+)__|\*([^*]+)\*|`([^`]+)`/g, (_, link, _href,
|
|
497
|
+
return text.split("\n").filter((line) => !/^\s*```\w*\s*$/.test(line) && !isTableDivider(line)).map((line) => (line.trim().startsWith("|") ? splitTableRow(line).join(", ") : line).replace(/^(#{1,6})\s+/, "").replace(/^\s*>\s?/, "").replace(/^\s*([-*+]|\d+[.)])\s+/, "").replace(/!?\[([^\]]*)\]\(([^)]+)\)|\*\*([^*]+)\*\*|__([^_]+)__|\*([^*]+)\*|`([^`]+)`/g, (_, link, _href, bold, boldUnderscore, italic, code) => link ?? bold ?? boldUnderscore ?? italic ?? code).trim()).filter((line) => line !== "").join("\n");
|
|
497
498
|
}
|
|
498
499
|
function splitTableRow(line) {
|
|
499
500
|
let row = line.trim();
|
|
@@ -603,159 +604,159 @@ function PendingActionCard({
|
|
|
603
604
|
// packages/quassel/src/WorkingScenes.tsx
|
|
604
605
|
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
605
606
|
import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
606
|
-
var
|
|
607
|
-
var
|
|
608
|
-
var
|
|
609
|
-
function
|
|
610
|
-
return Array.from({ length:
|
|
607
|
+
var TRACK = 13;
|
|
608
|
+
var BASE_TICK_MS = 90;
|
|
609
|
+
var SCENE_CHANGE_MS = 2600;
|
|
610
|
+
function track(figures, width = TRACK) {
|
|
611
|
+
return Array.from({ length: width }, (_, x) => figures[x] ?? " ");
|
|
611
612
|
}
|
|
612
|
-
var
|
|
613
|
+
var SCENES = [
|
|
613
614
|
{
|
|
614
|
-
id: "
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
const
|
|
618
|
-
if (
|
|
619
|
-
return "\u{1F914}" + "\u{1F4AD}".repeat(Math.min(
|
|
615
|
+
id: "thinking",
|
|
616
|
+
tick: 2,
|
|
617
|
+
draw: (step) => {
|
|
618
|
+
const tick = step % 10;
|
|
619
|
+
if (tick >= 8) return "\u{1F929}\u{1F4A1}";
|
|
620
|
+
return "\u{1F914}" + "\u{1F4AD}".repeat(Math.min(tick, 3));
|
|
620
621
|
}
|
|
621
622
|
},
|
|
622
623
|
{
|
|
623
|
-
//
|
|
624
|
-
id: "
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
const x =
|
|
629
|
-
return
|
|
624
|
+
// Flies to the right, dragging its tail behind.
|
|
625
|
+
id: "rocket",
|
|
626
|
+
tick: 2,
|
|
627
|
+
moving: true,
|
|
628
|
+
draw: (step) => {
|
|
629
|
+
const x = step % TRACK;
|
|
630
|
+
return track({ [x - 2]: "\xB7", [x - 1]: "\u{1F4A8}", [x]: "\u{1F680}" });
|
|
630
631
|
}
|
|
631
632
|
},
|
|
632
633
|
{
|
|
633
|
-
//
|
|
634
|
+
// The dots stay put and are eaten one by one.
|
|
634
635
|
id: "pacman",
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const x =
|
|
639
|
-
const
|
|
640
|
-
for (let i = x + 1; i <
|
|
641
|
-
|
|
636
|
+
tick: 2,
|
|
637
|
+
moving: true,
|
|
638
|
+
draw: (step) => {
|
|
639
|
+
const x = step % TRACK;
|
|
640
|
+
const figures = {};
|
|
641
|
+
for (let i = x + 1; i < TRACK; i++) {
|
|
642
|
+
figures[i] = "\xB7";
|
|
642
643
|
}
|
|
643
|
-
|
|
644
|
-
return
|
|
644
|
+
figures[x] = step % 2 === 0 ? "\u{1F7E1}" : "\u{1F315}";
|
|
645
|
+
return track(figures);
|
|
645
646
|
}
|
|
646
647
|
},
|
|
647
648
|
{
|
|
648
|
-
//
|
|
649
|
-
id: "
|
|
649
|
+
// A seed becomes a tree, then start over.
|
|
650
|
+
id: "growing",
|
|
650
651
|
compact: true,
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
return
|
|
652
|
+
tick: 2,
|
|
653
|
+
draw: (step) => {
|
|
654
|
+
const stages = ["\u{1F331}", "\u{1F331}", "\u{1F33F}", "\u{1F33F}", "\u{1F343}", "\u{1F333}", "\u{1F333}", "\u{1F333}"];
|
|
655
|
+
return stages[step % stages.length];
|
|
655
656
|
}
|
|
656
657
|
},
|
|
657
658
|
{
|
|
658
|
-
//
|
|
659
|
-
id: "
|
|
659
|
+
// The egg cracks, the chick hatches.
|
|
660
|
+
id: "hatching",
|
|
660
661
|
compact: true,
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
const
|
|
664
|
-
if (
|
|
665
|
-
if (
|
|
666
|
-
if (
|
|
662
|
+
tick: 2,
|
|
663
|
+
draw: (step) => {
|
|
664
|
+
const tick = step % 12;
|
|
665
|
+
if (tick < 4) return "\u{1F95A}";
|
|
666
|
+
if (tick < 6) return "\u{1F95A}*";
|
|
667
|
+
if (tick < 9) return "\u{1F423}";
|
|
667
668
|
return "\u{1F425}";
|
|
668
669
|
}
|
|
669
670
|
},
|
|
670
671
|
{
|
|
671
|
-
// Morse-
|
|
672
|
+
// Morse-like signal that resolves into an OK.
|
|
672
673
|
id: "signal",
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const
|
|
676
|
-
if (
|
|
677
|
-
const
|
|
678
|
-
return "\u{1F4E1} " +
|
|
674
|
+
tick: 2,
|
|
675
|
+
draw: (step) => {
|
|
676
|
+
const tick = step % 14;
|
|
677
|
+
if (tick >= 11) return "\u{1F4E1} OK";
|
|
678
|
+
const pattern = ["\xB7", "\xB7\xB7", "\xB7-", "-\xB7", "\xB7\xB7-", "-\xB7\xB7"];
|
|
679
|
+
return "\u{1F4E1} " + pattern[tick % pattern.length];
|
|
679
680
|
}
|
|
680
681
|
},
|
|
681
682
|
{
|
|
682
|
-
//
|
|
683
|
-
id: "
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
const
|
|
687
|
-
const
|
|
688
|
-
return (
|
|
683
|
+
// Hourglass: the sand runs through, then it is flipped.
|
|
684
|
+
id: "hourglass",
|
|
685
|
+
tick: 2,
|
|
686
|
+
draw: (step) => {
|
|
687
|
+
const tick = step % 12;
|
|
688
|
+
const grains = "\xB7".repeat(Math.max(0, 5 - Math.floor(tick / 2)));
|
|
689
|
+
return (tick < 10 ? "\u23F3" : "\u231B") + " " + grains;
|
|
689
690
|
}
|
|
690
691
|
},
|
|
691
692
|
{
|
|
692
|
-
//
|
|
693
|
-
id: "
|
|
693
|
+
// Egg, chick, hen - and she lays the next egg.
|
|
694
|
+
id: "chicken-cycle",
|
|
694
695
|
compact: true,
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
const
|
|
698
|
-
if (
|
|
699
|
-
if (
|
|
700
|
-
if (
|
|
701
|
-
if (
|
|
702
|
-
if (
|
|
696
|
+
tick: 3,
|
|
697
|
+
draw: (step) => {
|
|
698
|
+
const tick = step % 16;
|
|
699
|
+
if (tick < 3) return "\u{1F95A}";
|
|
700
|
+
if (tick < 5) return "\u{1F95A}*";
|
|
701
|
+
if (tick < 8) return "\u{1F423}";
|
|
702
|
+
if (tick < 11) return "\u{1F424}";
|
|
703
|
+
if (tick < 14) return "\u{1F414}";
|
|
703
704
|
return "\u{1F414}\u{1F95A}";
|
|
704
705
|
}
|
|
705
706
|
},
|
|
706
707
|
{
|
|
707
|
-
//
|
|
708
|
-
id: "
|
|
708
|
+
// Hat, sparks - and the rabbit bolts.
|
|
709
|
+
id: "magic-hat",
|
|
709
710
|
compact: true,
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
const
|
|
713
|
-
if (
|
|
714
|
-
if (
|
|
715
|
-
if (
|
|
711
|
+
tick: 3,
|
|
712
|
+
draw: (step) => {
|
|
713
|
+
const tick = step % 14;
|
|
714
|
+
if (tick < 4) return "\u{1F3A9}";
|
|
715
|
+
if (tick < 7) return "\u{1F3A9}*";
|
|
716
|
+
if (tick < 11) return "\u{1F3A9}\u{1F407}";
|
|
716
717
|
return "\u{1F407}\u{1F4A8}";
|
|
717
718
|
}
|
|
718
719
|
},
|
|
719
720
|
{
|
|
720
|
-
//
|
|
721
|
-
id: "
|
|
721
|
+
// Red, yellow, green - and off.
|
|
722
|
+
id: "traffic-light",
|
|
722
723
|
compact: true,
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const
|
|
726
|
-
if (
|
|
727
|
-
if (
|
|
728
|
-
if (
|
|
724
|
+
tick: 3,
|
|
725
|
+
draw: (step) => {
|
|
726
|
+
const tick = step % 12;
|
|
727
|
+
if (tick < 4) return "\u{1F534}";
|
|
728
|
+
if (tick < 6) return "\u{1F7E1}";
|
|
729
|
+
if (tick < 8) return "\u{1F7E2}";
|
|
729
730
|
return "\u{1F697}\u{1F4A8}";
|
|
730
731
|
}
|
|
731
732
|
},
|
|
732
733
|
{
|
|
733
|
-
//
|
|
734
|
-
id: "
|
|
734
|
+
// Ringing, oversleeping, running off.
|
|
735
|
+
id: "alarm-clock",
|
|
735
736
|
compact: true,
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
const
|
|
739
|
-
if (
|
|
740
|
-
if (
|
|
741
|
-
if (
|
|
737
|
+
tick: 2,
|
|
738
|
+
draw: (step) => {
|
|
739
|
+
const tick = step % 14;
|
|
740
|
+
if (tick < 5) return tick % 2 === 0 ? "\u23F0" : "\u{1F514}";
|
|
741
|
+
if (tick < 8) return "\u{1F634}";
|
|
742
|
+
if (tick < 10) return "\u{1F633}";
|
|
742
743
|
return "\u{1F3C3}\u{1F4A8}";
|
|
743
744
|
}
|
|
744
745
|
},
|
|
745
746
|
{
|
|
746
|
-
//
|
|
747
|
-
id: "
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
const
|
|
751
|
-
if (
|
|
752
|
-
return
|
|
747
|
+
// A snail crawls to the flag - and celebrates.
|
|
748
|
+
id: "snail",
|
|
749
|
+
tick: 4,
|
|
750
|
+
draw: (step) => {
|
|
751
|
+
const tick = step % 8;
|
|
752
|
+
if (tick < 5) return track({ [tick]: "\u{1F40C}", 4: "\u{1F3C1}" }, 5);
|
|
753
|
+
return track({ 4: "\u{1F389}" }, 5);
|
|
753
754
|
}
|
|
754
755
|
}
|
|
755
756
|
];
|
|
756
|
-
var COMPACT_SCENES =
|
|
757
|
-
function WorkingScenes({ label = "
|
|
758
|
-
const scenes = compact ? COMPACT_SCENES :
|
|
757
|
+
var COMPACT_SCENES = SCENES.filter((scene) => scene.compact);
|
|
758
|
+
function WorkingScenes({ label = "Working ...", compact = false }) {
|
|
759
|
+
const scenes = compact ? COMPACT_SCENES : SCENES;
|
|
759
760
|
const [sceneIndex, setSceneIndex] = useState2(() => Math.floor(Math.random() * scenes.length));
|
|
760
761
|
const [offset, setOffset] = useState2(() => Math.floor(Math.random() * 20));
|
|
761
762
|
const [frameTick, setFrameTick] = useState2(0);
|
|
@@ -774,15 +775,15 @@ function WorkingScenes({ label = "Arbeitet ...", compact = false }) {
|
|
|
774
775
|
setSceneIndex((previous) => (previous + 1 + Math.floor(Math.random() * (scenes.length - 1))) % scenes.length);
|
|
775
776
|
setOffset(Math.floor(Math.random() * 20));
|
|
776
777
|
setFrameTick(0);
|
|
777
|
-
},
|
|
778
|
+
}, SCENE_CHANGE_MS);
|
|
778
779
|
return () => window.clearInterval(timer);
|
|
779
780
|
}, [reducedMotion, scenes]);
|
|
780
781
|
useEffect2(() => {
|
|
781
782
|
if (reducedMotion) return;
|
|
782
|
-
const timer = window.setInterval(() => setFrameTick((value) => value + 1),
|
|
783
|
+
const timer = window.setInterval(() => setFrameTick((value) => value + 1), BASE_TICK_MS);
|
|
783
784
|
return () => window.clearInterval(timer);
|
|
784
785
|
}, [reducedMotion]);
|
|
785
|
-
const frame = scene.
|
|
786
|
+
const frame = scene.draw(Math.floor(frameTick / scene.tick) + (scene.moving ? offset : 0));
|
|
786
787
|
return /* @__PURE__ */ jsxs4("span", { "aria-label": label, className: cn("qsl:flex qsl:items-center qsl:gap-2.5 qsl:text-[13px]", compact ? "qsl:text-inherit" : "qsl:mt-4 qsl:text-foreground"), "data-chat": "working", role: "status", children: [
|
|
787
788
|
!compact && /* @__PURE__ */ jsx11("span", { "aria-hidden": "true", className: "qsl:text-muted-foreground qsl:opacity-70", children: ">" }),
|
|
788
789
|
/* @__PURE__ */ jsx11("span", { "aria-hidden": "true", className: cn("qsl:inline-block qsl:flex-none qsl:font-mono qsl:[font-variant-ligatures:none]", compact ? "qsl:w-[2.4em] qsl:text-center" : "qsl:w-[15rem]"), children: /* @__PURE__ */ jsx11("span", { className: "qsl:tracking-[0.02em] qsl:whitespace-pre qsl:motion-reduce:opacity-60", children: typeof frame === "string" ? frame : frame.map((cell, index) => /* @__PURE__ */ jsx11("span", { className: "qsl:inline-block qsl:w-[1.35em] qsl:text-center", children: cell }, index)) }) }, scene.id)
|
|
@@ -887,6 +888,53 @@ function timestampDay(date, options = {}) {
|
|
|
887
888
|
return { key, label };
|
|
888
889
|
}
|
|
889
890
|
|
|
891
|
+
// packages/quassel/src/elapsed.ts
|
|
892
|
+
import { useSyncExternalStore } from "react";
|
|
893
|
+
var tickMs = 500;
|
|
894
|
+
function formatElapsed(ms, texts) {
|
|
895
|
+
const total = Math.max(0, Math.floor(ms / 1e3));
|
|
896
|
+
if (total < 60) return fillText(texts.toolElapsedSeconds, { seconds: total });
|
|
897
|
+
const hours = Math.floor(total / 3600);
|
|
898
|
+
const minutes = Math.floor(total / 60) % 60;
|
|
899
|
+
const seconds = String(total % 60).padStart(2, "0");
|
|
900
|
+
return hours > 0 ? `${hours}:${String(minutes).padStart(2, "0")}:${seconds}` : `${minutes}:${seconds}`;
|
|
901
|
+
}
|
|
902
|
+
function createTicker(intervalMs2 = tickMs) {
|
|
903
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
904
|
+
let now = Date.now();
|
|
905
|
+
let timer;
|
|
906
|
+
return {
|
|
907
|
+
subscribe(listener) {
|
|
908
|
+
listeners.add(listener);
|
|
909
|
+
if (timer === void 0) {
|
|
910
|
+
now = Date.now();
|
|
911
|
+
timer = setInterval(() => {
|
|
912
|
+
now = Date.now();
|
|
913
|
+
listeners.forEach((notify) => notify());
|
|
914
|
+
}, intervalMs2);
|
|
915
|
+
}
|
|
916
|
+
return () => {
|
|
917
|
+
listeners.delete(listener);
|
|
918
|
+
if (listeners.size === 0 && timer !== void 0) {
|
|
919
|
+
clearInterval(timer);
|
|
920
|
+
timer = void 0;
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
},
|
|
924
|
+
now() {
|
|
925
|
+
const current = Date.now();
|
|
926
|
+
if (timer === void 0 && (current - now >= intervalMs2 || current < now)) now = current;
|
|
927
|
+
return now;
|
|
928
|
+
},
|
|
929
|
+
active: () => timer !== void 0
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
var elapsedTicker = createTicker();
|
|
933
|
+
var nothingOnServer = () => void 0;
|
|
934
|
+
function useElapsed(start) {
|
|
935
|
+
return useSyncExternalStore(elapsedTicker.subscribe, () => Math.floor((elapsedTicker.now() - start) / 1e3) * 1e3, nothingOnServer);
|
|
936
|
+
}
|
|
937
|
+
|
|
890
938
|
// packages/quassel/src/MessageActions.tsx
|
|
891
939
|
import { CheckIcon as CheckIcon3, CopyIcon as CopyIcon2, PencilIcon, RotateCcwIcon } from "lucide-react";
|
|
892
940
|
import { useState as useState3 } from "react";
|
|
@@ -952,13 +1000,17 @@ function createChatSendScope(enabled) {
|
|
|
952
1000
|
};
|
|
953
1001
|
}
|
|
954
1002
|
|
|
1003
|
+
// packages/quassel/src/ChatActionDock.ts
|
|
1004
|
+
import { createContext as createContext4 } from "react";
|
|
1005
|
+
var ChatActionDock = createContext4(void 0);
|
|
1006
|
+
|
|
955
1007
|
// packages/quassel/src/announce.ts
|
|
956
|
-
var
|
|
957
|
-
var
|
|
1008
|
+
var intervalMs = 400;
|
|
1009
|
+
var maxQueued = 5;
|
|
958
1010
|
var region;
|
|
959
|
-
var
|
|
960
|
-
var
|
|
961
|
-
function
|
|
1011
|
+
var queued = [];
|
|
1012
|
+
var running = false;
|
|
1013
|
+
function liveRegion() {
|
|
962
1014
|
if (region?.isConnected) {
|
|
963
1015
|
return region;
|
|
964
1016
|
}
|
|
@@ -981,33 +1033,33 @@ function ansageRegion() {
|
|
|
981
1033
|
region = element;
|
|
982
1034
|
return element;
|
|
983
1035
|
}
|
|
984
|
-
function
|
|
985
|
-
if (
|
|
986
|
-
|
|
1036
|
+
function next() {
|
|
1037
|
+
if (queued.length === 0) {
|
|
1038
|
+
running = false;
|
|
987
1039
|
return;
|
|
988
1040
|
}
|
|
989
1041
|
if (document.visibilityState === "hidden") {
|
|
990
|
-
document.addEventListener("visibilitychange",
|
|
1042
|
+
document.addEventListener("visibilitychange", next, { once: true });
|
|
991
1043
|
return;
|
|
992
1044
|
}
|
|
993
|
-
const [text, ...rest] =
|
|
994
|
-
|
|
995
|
-
const
|
|
996
|
-
|
|
997
|
-
window.setTimeout(
|
|
1045
|
+
const [text, ...rest] = queued;
|
|
1046
|
+
queued = rest;
|
|
1047
|
+
const target = liveRegion();
|
|
1048
|
+
target.textContent = target.textContent === text ? `${text}\xA0` : text;
|
|
1049
|
+
window.setTimeout(next, intervalMs);
|
|
998
1050
|
}
|
|
999
1051
|
function announce(text) {
|
|
1000
|
-
const
|
|
1001
|
-
if (!
|
|
1052
|
+
const content = text.trim();
|
|
1053
|
+
if (!content || typeof document === "undefined") {
|
|
1002
1054
|
return;
|
|
1003
1055
|
}
|
|
1004
|
-
|
|
1005
|
-
if (
|
|
1056
|
+
queued = [...queued, content].slice(-maxQueued);
|
|
1057
|
+
if (running) {
|
|
1006
1058
|
return;
|
|
1007
1059
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
window.setTimeout(
|
|
1060
|
+
running = true;
|
|
1061
|
+
liveRegion();
|
|
1062
|
+
window.setTimeout(next, intervalMs);
|
|
1011
1063
|
}
|
|
1012
1064
|
|
|
1013
1065
|
// packages/quassel/src/ChatMessages.tsx
|
|
@@ -1030,11 +1082,14 @@ var chipClasses = cn(
|
|
|
1030
1082
|
);
|
|
1031
1083
|
var chipInteractiveClasses = "qsl:cursor-pointer qsl:hover:border-border qsl:hover:bg-secondary qsl:hover:text-foreground qsl:focus-visible:border-border qsl:focus-visible:bg-secondary qsl:focus-visible:text-foreground qsl:focus-visible:outline-none qsl:data-[state=done]:hover:opacity-100 qsl:data-[state=done]:focus-visible:opacity-100";
|
|
1032
1084
|
var collapseAtBottomFrom = 11;
|
|
1033
|
-
var
|
|
1085
|
+
var maxAnnouncementsAtOnce = 2;
|
|
1034
1086
|
var bubbleBodyClasses = "qsl:relative qsl:max-w-[86%] qsl:px-3 qsl:py-2 qsl:leading-[var(--qsl-chat-line-height,1.625)] qsl:[overflow-wrap:anywhere] qsl:@max-[620px]/chat:max-w-full";
|
|
1035
|
-
function
|
|
1087
|
+
function isStep(message) {
|
|
1036
1088
|
return !!message && (message.role === "thinking" || message.role === "tool");
|
|
1037
1089
|
}
|
|
1090
|
+
function isOpenAction(message) {
|
|
1091
|
+
return message.role === "action" && message.action !== void 0 && message.action.status === void 0;
|
|
1092
|
+
}
|
|
1038
1093
|
function defaultArgumentsText(tool) {
|
|
1039
1094
|
return prettyJson(tool.arguments);
|
|
1040
1095
|
}
|
|
@@ -1042,7 +1097,7 @@ function ChatMessages({
|
|
|
1042
1097
|
messages,
|
|
1043
1098
|
owner,
|
|
1044
1099
|
detailMode = "current",
|
|
1045
|
-
running = false,
|
|
1100
|
+
running: running2 = false,
|
|
1046
1101
|
working,
|
|
1047
1102
|
stepsExpandable = true,
|
|
1048
1103
|
texts,
|
|
@@ -1064,17 +1119,19 @@ function ChatMessages({
|
|
|
1064
1119
|
timestampOptions,
|
|
1065
1120
|
codeBlockOptions,
|
|
1066
1121
|
bubbleOptions,
|
|
1067
|
-
messageActions
|
|
1122
|
+
messageActions,
|
|
1123
|
+
toolElapsedThreshold = 3e3
|
|
1068
1124
|
}) {
|
|
1069
|
-
const
|
|
1125
|
+
const allTexts = { ...defaultTexts, ...texts };
|
|
1070
1126
|
const { Button: Button2 } = useQuasselComponents();
|
|
1071
|
-
const
|
|
1127
|
+
const scrollArea = useRef3(null);
|
|
1072
1128
|
const threadRef = useRef3(null);
|
|
1073
1129
|
const scrollbarDragging = useRef3(false);
|
|
1074
1130
|
const touchY = useRef3(void 0);
|
|
1075
1131
|
const [showJumpToEnd, setShowJumpToEnd] = useState4(false);
|
|
1076
1132
|
const [scroll] = useState4(() => createChatScroll());
|
|
1077
1133
|
const sendScope = useContext3(ChatSendContext);
|
|
1134
|
+
const dock = useContext3(ChatActionDock);
|
|
1078
1135
|
const [now, setNow] = useState4(() => Date.now());
|
|
1079
1136
|
useEffect3(() => {
|
|
1080
1137
|
if (!showTimestamps || timestampOptions?.format !== "relative") return;
|
|
@@ -1082,23 +1139,23 @@ function ChatMessages({
|
|
|
1082
1139
|
const timer = window.setInterval(() => setNow(Date.now()), 15e3);
|
|
1083
1140
|
return () => window.clearInterval(timer);
|
|
1084
1141
|
}, [showTimestamps, timestampOptions?.format]);
|
|
1085
|
-
const
|
|
1142
|
+
const announced = useRef3(new Set(messages.map((message) => message.key)));
|
|
1086
1143
|
useEffect3(() => {
|
|
1087
|
-
const
|
|
1088
|
-
if (
|
|
1144
|
+
const due = messages.filter((message, index) => !announced.current.has(message.key) && (message.role === "action" ? isOpenAction(message) : message.role === "assistant" && message.text.trim() !== "" && (message.closed === true || !running2 && index === messages.length - 1)));
|
|
1145
|
+
if (due.length === 0) {
|
|
1089
1146
|
return;
|
|
1090
1147
|
}
|
|
1091
|
-
|
|
1092
|
-
if (announce2 === false ||
|
|
1148
|
+
announced.current = /* @__PURE__ */ new Set([...announced.current, ...due.map((message) => message.key)]);
|
|
1149
|
+
if (announce2 === false || due.length > maxAnnouncementsAtOnce) {
|
|
1093
1150
|
return;
|
|
1094
1151
|
}
|
|
1095
|
-
|
|
1096
|
-
}, [messages,
|
|
1152
|
+
due.map((message) => announce2 ? announce2({ kind: message.role === "action" ? "action" : "reply", message }) : markdownPlainText(message.text)).forEach((text) => text && announce(text));
|
|
1153
|
+
}, [messages, running2, announce2]);
|
|
1097
1154
|
const updateJumpVisibility = useCallback2((viewport) => {
|
|
1098
1155
|
if (viewport.clientHeight > 0) setShowJumpToEnd(viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop > jumpToEndThreshold);
|
|
1099
1156
|
}, [jumpToEndThreshold]);
|
|
1100
1157
|
useLayoutEffect(() => sendScope?.subscribe(() => {
|
|
1101
|
-
const viewport =
|
|
1158
|
+
const viewport = scrollArea.current;
|
|
1102
1159
|
if (!viewport) return;
|
|
1103
1160
|
scroll.jump(viewport);
|
|
1104
1161
|
updateJumpVisibility(viewport);
|
|
@@ -1111,16 +1168,16 @@ function ChatMessages({
|
|
|
1111
1168
|
}
|
|
1112
1169
|
scroll.pause(viewport);
|
|
1113
1170
|
};
|
|
1114
|
-
const
|
|
1115
|
-
const
|
|
1116
|
-
if (
|
|
1117
|
-
scroll.scroll(
|
|
1118
|
-
updateJumpVisibility(
|
|
1171
|
+
const checkEnd = useCallback2(() => {
|
|
1172
|
+
const area = scrollArea.current;
|
|
1173
|
+
if (area) {
|
|
1174
|
+
scroll.scroll(area, scrollbarDragging.current);
|
|
1175
|
+
updateJumpVisibility(area);
|
|
1119
1176
|
}
|
|
1120
1177
|
}, [scroll, updateJumpVisibility]);
|
|
1121
1178
|
useEffect3(() => {
|
|
1122
1179
|
const finishDrag = () => {
|
|
1123
|
-
if (scrollbarDragging.current &&
|
|
1180
|
+
if (scrollbarDragging.current && scrollArea.current) scroll.scroll(scrollArea.current, true);
|
|
1124
1181
|
scrollbarDragging.current = false;
|
|
1125
1182
|
};
|
|
1126
1183
|
window.addEventListener("pointerup", finishDrag);
|
|
@@ -1131,24 +1188,27 @@ function ChatMessages({
|
|
|
1131
1188
|
};
|
|
1132
1189
|
}, [scroll]);
|
|
1133
1190
|
useLayoutEffect(() => {
|
|
1134
|
-
const
|
|
1135
|
-
if (!
|
|
1191
|
+
const area = scrollArea.current;
|
|
1192
|
+
if (!area) return;
|
|
1136
1193
|
const align = () => {
|
|
1137
|
-
scroll.layout(
|
|
1138
|
-
updateJumpVisibility(
|
|
1194
|
+
scroll.layout(area, scrollbarDragging.current);
|
|
1195
|
+
updateJumpVisibility(area);
|
|
1139
1196
|
};
|
|
1140
1197
|
align();
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
if (threadRef.current)
|
|
1144
|
-
return () =>
|
|
1198
|
+
const observer = new ResizeObserver(align);
|
|
1199
|
+
observer.observe(area);
|
|
1200
|
+
if (threadRef.current) observer.observe(threadRef.current, { box: "border-box" });
|
|
1201
|
+
return () => observer.disconnect();
|
|
1145
1202
|
});
|
|
1146
1203
|
const visible = useMemo3(
|
|
1147
1204
|
() => {
|
|
1148
|
-
const
|
|
1205
|
+
const lastNonUser = messages.reduce((last, message, index) => message.role === "user" ? last : index, -1);
|
|
1149
1206
|
return messages.filter((message, index) => {
|
|
1150
|
-
if (
|
|
1151
|
-
return
|
|
1207
|
+
if (dock !== void 0 && isOpenAction(message)) {
|
|
1208
|
+
return false;
|
|
1209
|
+
}
|
|
1210
|
+
if (detailMode === "current" && isStep(message)) {
|
|
1211
|
+
return running2 && index === lastNonUser && (message.role === "thinking" ? !message.closed : stepState(message) === "running");
|
|
1152
1212
|
}
|
|
1153
1213
|
if ((message.role === "thinking" || message.role === "assistant") && message.text.trim() === "" && !message.attachments?.length) {
|
|
1154
1214
|
return false;
|
|
@@ -1159,50 +1219,54 @@ function ChatMessages({
|
|
|
1159
1219
|
return true;
|
|
1160
1220
|
});
|
|
1161
1221
|
},
|
|
1162
|
-
[messages, detailMode,
|
|
1222
|
+
[messages, detailMode, running2, dock]
|
|
1163
1223
|
);
|
|
1164
|
-
const
|
|
1224
|
+
const openActions = dock ? messages.filter(isOpenAction) : [];
|
|
1225
|
+
const withTime = (content, key, at, dense = false) => showTimestamps ? /* @__PURE__ */ jsxs6("div", { className: cn("qsl:flex qsl:items-baseline qsl:gap-2", dense ? denseStepClasses : stepClasses), children: [
|
|
1165
1226
|
/* @__PURE__ */ jsx13("time", { dateTime: messageDate(at)?.toISOString(), className: cn(
|
|
1166
1227
|
"qsl:flex-none qsl:text-meta qsl:tabular-nums qsl:text-muted-foreground qsl:opacity-85",
|
|
1167
1228
|
timestampOptions?.format && timestampOptions.format !== "time" ? "qsl:w-[clamp(60px,20%,120px)]" : "qsl:w-[34px]"
|
|
1168
1229
|
), children: messageDate(at) ? timestampLabel(messageDate(at), timestampOptions, now) : "" }),
|
|
1169
|
-
/* @__PURE__ */ jsx13("div", { className: "qsl:min-w-0 qsl:flex-1 qsl:[&>*:first-child]:mt-0", children:
|
|
1170
|
-
] }, `
|
|
1171
|
-
const
|
|
1172
|
-
const
|
|
1173
|
-
const
|
|
1230
|
+
/* @__PURE__ */ jsx13("div", { className: "qsl:min-w-0 qsl:flex-1 qsl:[&>*:first-child]:mt-0", children: content })
|
|
1231
|
+
] }, `time-${key}`) : content;
|
|
1232
|
+
const chipMode = detailMode === "current" || detailMode === "chips" || detailMode === "icons";
|
|
1233
|
+
const elapsedAfter = running2 && toolElapsedThreshold !== false ? toolElapsedThreshold : void 0;
|
|
1234
|
+
const groupMode = detailMode === "grouped";
|
|
1235
|
+
const blocks = [];
|
|
1174
1236
|
let lastDay;
|
|
1175
|
-
let
|
|
1176
|
-
const
|
|
1177
|
-
if (
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1180
|
-
|
|
1237
|
+
let group = [];
|
|
1238
|
+
const closeGroup = () => {
|
|
1239
|
+
if (group.length > 0) {
|
|
1240
|
+
const runningStep = running2 ? [...group].reverse().find((message) => ["running", "thinking"].includes(stepState(message))) : void 0;
|
|
1241
|
+
blocks.push(withTime(
|
|
1242
|
+
groupMode ? /* @__PURE__ */ jsx13(
|
|
1181
1243
|
StepGroup,
|
|
1182
1244
|
{
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1245
|
+
elapsedAfter,
|
|
1246
|
+
expandable: stepsExpandable,
|
|
1247
|
+
runningStep,
|
|
1248
|
+
messages: group,
|
|
1249
|
+
texts: allTexts,
|
|
1187
1250
|
toolArgumentsText
|
|
1188
1251
|
},
|
|
1189
|
-
|
|
1252
|
+
group[0].key
|
|
1190
1253
|
) : /* @__PURE__ */ jsx13(
|
|
1191
1254
|
StepRow,
|
|
1192
1255
|
{
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1256
|
+
elapsedAfter,
|
|
1257
|
+
expandable: stepsExpandable,
|
|
1258
|
+
messages: group,
|
|
1259
|
+
withText: detailMode !== "icons",
|
|
1260
|
+
texts: allTexts,
|
|
1197
1261
|
toolArgumentsText,
|
|
1198
|
-
toolLabel: detailMode === "current" && !stepsExpandable ?
|
|
1262
|
+
toolLabel: detailMode === "current" && !stepsExpandable ? allTexts.currentTool : void 0
|
|
1199
1263
|
},
|
|
1200
|
-
|
|
1264
|
+
group[0].key
|
|
1201
1265
|
),
|
|
1202
|
-
|
|
1203
|
-
|
|
1266
|
+
group[0].key,
|
|
1267
|
+
group[0].at
|
|
1204
1268
|
));
|
|
1205
|
-
|
|
1269
|
+
group = [];
|
|
1206
1270
|
}
|
|
1207
1271
|
};
|
|
1208
1272
|
visible.forEach((message, index) => {
|
|
@@ -1210,37 +1274,38 @@ function ChatMessages({
|
|
|
1210
1274
|
if (date) {
|
|
1211
1275
|
const day = timestampDay(date, timestampOptions);
|
|
1212
1276
|
if (day.key !== lastDay) {
|
|
1213
|
-
|
|
1214
|
-
|
|
1277
|
+
closeGroup();
|
|
1278
|
+
blocks.push(/* @__PURE__ */ jsx13("div", { className: cn(stepClasses, "qsl:text-center qsl:text-xs qsl:text-muted-foreground"), "data-chat": "day-separator", "data-day": day.key, children: day.label }, `day-${message.key}`));
|
|
1215
1279
|
lastDay = day.key;
|
|
1216
1280
|
}
|
|
1217
1281
|
}
|
|
1218
|
-
const
|
|
1219
|
-
if (
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
/* @__PURE__ */ jsx13("div", { className: stepClasses, children:
|
|
1282
|
+
const customRendering = message.role === "tool" && message.tool && detailMode !== "current" ? renderTool?.(message.tool) : void 0;
|
|
1283
|
+
if (customRendering !== void 0) {
|
|
1284
|
+
closeGroup();
|
|
1285
|
+
blocks.push(withTime(
|
|
1286
|
+
/* @__PURE__ */ jsx13("div", { className: stepClasses, children: customRendering }, message.key),
|
|
1223
1287
|
message.key,
|
|
1224
1288
|
message.at
|
|
1225
1289
|
));
|
|
1226
1290
|
return;
|
|
1227
1291
|
}
|
|
1228
|
-
if ((
|
|
1229
|
-
|
|
1292
|
+
if ((chipMode || groupMode) && isStep(message)) {
|
|
1293
|
+
group = [...group, message];
|
|
1230
1294
|
return;
|
|
1231
1295
|
}
|
|
1232
|
-
|
|
1233
|
-
const
|
|
1234
|
-
|
|
1296
|
+
closeGroup();
|
|
1297
|
+
const dense = isStep(message) && isStep(visible[index - 1]);
|
|
1298
|
+
blocks.push(withTime(
|
|
1235
1299
|
/* @__PURE__ */ jsx13(
|
|
1236
1300
|
Bubble,
|
|
1237
1301
|
{
|
|
1238
1302
|
detailMode,
|
|
1239
|
-
|
|
1240
|
-
|
|
1303
|
+
dense,
|
|
1304
|
+
elapsedAfter,
|
|
1305
|
+
expandable: stepsExpandable,
|
|
1241
1306
|
message,
|
|
1242
1307
|
plain: owner != null && message.sender === owner,
|
|
1243
|
-
texts:
|
|
1308
|
+
texts: allTexts,
|
|
1244
1309
|
toolArgumentsText,
|
|
1245
1310
|
renderAction,
|
|
1246
1311
|
onDismissAction,
|
|
@@ -1251,11 +1316,11 @@ function ChatMessages({
|
|
|
1251
1316
|
),
|
|
1252
1317
|
message.key,
|
|
1253
1318
|
message.at,
|
|
1254
|
-
|
|
1319
|
+
dense
|
|
1255
1320
|
));
|
|
1256
1321
|
});
|
|
1257
|
-
|
|
1258
|
-
const
|
|
1322
|
+
closeGroup();
|
|
1323
|
+
const transcript = /* @__PURE__ */ jsxs6("div", { className: cn("qsl:@container/chat qsl:relative qsl:flex qsl:min-h-0 qsl:flex-1 qsl:flex-col", className), "data-quassel": "", style: {
|
|
1259
1324
|
...appearanceStyle(appearance),
|
|
1260
1325
|
"--qsl-chat-content-max-width": typeof maxWidth === "number" ? `${maxWidth}px` : maxWidth,
|
|
1261
1326
|
"--qsl-chat-horizontal-padding": typeof horizontalPadding === "number" ? `${horizontalPadding}px` : horizontalPadding
|
|
@@ -1263,7 +1328,7 @@ function ChatMessages({
|
|
|
1263
1328
|
/* @__PURE__ */ jsx13(
|
|
1264
1329
|
"div",
|
|
1265
1330
|
{
|
|
1266
|
-
"aria-label":
|
|
1331
|
+
"aria-label": allTexts.transcript,
|
|
1267
1332
|
className: cn(
|
|
1268
1333
|
"qsl:min-h-0 qsl:flex-1 qsl:cursor-default qsl:overflow-y-auto qsl:text-[length:var(--qsl-chat-font-size,13px)] qsl:text-foreground",
|
|
1269
1334
|
"qsl:[--qsl-fade-edge:calc(100%_-_var(--qsl-composer-height,0px))]",
|
|
@@ -1273,7 +1338,7 @@ function ChatMessages({
|
|
|
1273
1338
|
scrollerClassName
|
|
1274
1339
|
),
|
|
1275
1340
|
"data-quassel-transcript": "",
|
|
1276
|
-
onScroll:
|
|
1341
|
+
onScroll: checkEnd,
|
|
1277
1342
|
onWheel: (event) => {
|
|
1278
1343
|
if (!event.ctrlKey && event.deltaY < 0) pauseUp(event.currentTarget, event.target);
|
|
1279
1344
|
},
|
|
@@ -1289,20 +1354,20 @@ function ChatMessages({
|
|
|
1289
1354
|
touchY.current = event.touches[0]?.clientY;
|
|
1290
1355
|
},
|
|
1291
1356
|
onTouchMove: (event) => {
|
|
1292
|
-
const
|
|
1293
|
-
if (
|
|
1294
|
-
touchY.current =
|
|
1357
|
+
const next2 = event.touches[0]?.clientY;
|
|
1358
|
+
if (next2 !== void 0 && touchY.current !== void 0 && next2 > touchY.current) pauseUp(event.currentTarget, event.target);
|
|
1359
|
+
touchY.current = next2;
|
|
1295
1360
|
},
|
|
1296
1361
|
role: "region",
|
|
1297
1362
|
tabIndex: 0,
|
|
1298
1363
|
ref: (element) => {
|
|
1299
|
-
|
|
1364
|
+
scrollArea.current = element;
|
|
1300
1365
|
scrollerRef?.(element);
|
|
1301
1366
|
},
|
|
1302
|
-
children: visible.length === 0 && !
|
|
1367
|
+
children: visible.length === 0 && !running2 ? emptyState ?? null : /* @__PURE__ */ jsxs6(
|
|
1303
1368
|
"div",
|
|
1304
1369
|
{
|
|
1305
|
-
"aria-busy":
|
|
1370
|
+
"aria-busy": running2 || void 0,
|
|
1306
1371
|
className: cn(
|
|
1307
1372
|
"qsl:mx-auto qsl:w-[calc(100%_-_2_*_var(--qsl-chat-horizontal-padding,24px))] qsl:max-w-[var(--qsl-chat-content-max-width,none)] qsl:pt-2",
|
|
1308
1373
|
"qsl:pb-[calc(var(--qsl-composer-height,0px)_+_max(3.25em,40px))]",
|
|
@@ -1310,46 +1375,52 @@ function ChatMessages({
|
|
|
1310
1375
|
),
|
|
1311
1376
|
ref: threadRef,
|
|
1312
1377
|
children: [
|
|
1313
|
-
|
|
1314
|
-
|
|
1378
|
+
blocks,
|
|
1379
|
+
running2 && (working ?? /* @__PURE__ */ jsx13(WorkingScenes, { label: allTexts.working }))
|
|
1315
1380
|
]
|
|
1316
1381
|
}
|
|
1317
1382
|
)
|
|
1318
1383
|
}
|
|
1319
1384
|
),
|
|
1320
|
-
showJumpToEnd && /* @__PURE__ */ jsx13("div", { className: "qsl:pointer-events-none qsl:absolute qsl:inset-x-0 qsl:bottom-[calc(var(--qsl-composer-height,0px)_+_12px)] qsl:z-10 qsl:flex qsl:justify-center", children: /* @__PURE__ */ jsx13(Button2, { "aria-label":
|
|
1321
|
-
if (
|
|
1322
|
-
scroll.jump(
|
|
1323
|
-
updateJumpVisibility(
|
|
1385
|
+
showJumpToEnd && /* @__PURE__ */ jsx13("div", { className: "qsl:pointer-events-none qsl:absolute qsl:inset-x-0 qsl:bottom-[calc(var(--qsl-composer-height,0px)_+_12px)] qsl:z-10 qsl:flex qsl:justify-center", children: /* @__PURE__ */ jsx13(Button2, { "aria-label": allTexts.jumpToEnd, className: "qsl:pointer-events-auto qsl:rounded-full qsl:border qsl:border-primary-foreground/20 qsl:shadow-md", size: "icon", title: allTexts.jumpToEnd, variant: "default", onClick: () => {
|
|
1386
|
+
if (scrollArea.current) {
|
|
1387
|
+
scroll.jump(scrollArea.current);
|
|
1388
|
+
updateJumpVisibility(scrollArea.current);
|
|
1324
1389
|
}
|
|
1325
|
-
}, children: /* @__PURE__ */ jsx13(ChevronDownIcon, {}) }) })
|
|
1390
|
+
}, children: /* @__PURE__ */ jsx13(ChevronDownIcon, {}) }) }),
|
|
1391
|
+
dock && openActions.length > 0 && createPortal(
|
|
1392
|
+
/* @__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: openActions.map((message) => /* @__PURE__ */ jsx13("div", { children: /* @__PURE__ */ jsx13(ActionContent, { action: message.action, onDismissAction, renderAction, text: message.text, texts: allTexts }) }, message.key)) }),
|
|
1393
|
+
dock
|
|
1394
|
+
)
|
|
1326
1395
|
] });
|
|
1327
|
-
return /* @__PURE__ */ jsx13(MarkdownLinks, { onLinkClick, children: /* @__PURE__ */ jsx13(MarkdownCodeBlocks, { options: codeBlockOptions, texts:
|
|
1396
|
+
return /* @__PURE__ */ jsx13(MarkdownLinks, { onLinkClick, children: /* @__PURE__ */ jsx13(MarkdownCodeBlocks, { options: codeBlockOptions, texts: allTexts, children: transcript }) });
|
|
1328
1397
|
}
|
|
1329
1398
|
function StepRow({
|
|
1330
1399
|
messages,
|
|
1331
|
-
|
|
1332
|
-
|
|
1400
|
+
withText,
|
|
1401
|
+
expandable,
|
|
1402
|
+
elapsedAfter,
|
|
1333
1403
|
texts,
|
|
1334
1404
|
toolArgumentsText,
|
|
1335
1405
|
toolLabel
|
|
1336
1406
|
}) {
|
|
1337
1407
|
const [detail, setDetail] = useState4();
|
|
1338
|
-
const
|
|
1408
|
+
const open = expandable && detail && messages.find((message) => message.key === detail.key);
|
|
1339
1409
|
return /* @__PURE__ */ jsxs6("div", { className: cn(stepClasses, "qsl:flex qsl:flex-wrap qsl:items-center qsl:gap-x-1.5 qsl:gap-y-1 qsl:pl-6"), "data-step": "row", children: [
|
|
1340
1410
|
messages.map((message) => {
|
|
1341
1411
|
const thinking = message.role === "thinking";
|
|
1342
1412
|
const tool = message.tool;
|
|
1343
1413
|
const state = stepState(message);
|
|
1344
|
-
const
|
|
1414
|
+
const running2 = state === "running" || state === "thinking";
|
|
1345
1415
|
const label = thinking ? texts.thinkingChip : toolLabel ?? (tool?.name || message.text || texts.toolChip);
|
|
1346
1416
|
const content = /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1347
1417
|
thinking ? /* @__PURE__ */ jsx13(SparklesIcon2, { size: 11 }) : /* @__PURE__ */ jsx13(WrenchIcon2, { className: tool?.isError ? "qsl:text-destructive" : void 0, size: 11 }),
|
|
1348
|
-
|
|
1349
|
-
|
|
1418
|
+
withText && /* @__PURE__ */ jsx13("span", { className: "qsl:min-w-0 qsl:self-baseline qsl:overflow-hidden qsl:text-ellipsis qsl:whitespace-nowrap", children: label }),
|
|
1419
|
+
withText && /* @__PURE__ */ jsx13(ToolElapsed, { after: elapsedAfter, className: "qsl:flex-none qsl:self-baseline", message, texts }),
|
|
1420
|
+
withText && /* @__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
1421
|
] });
|
|
1351
|
-
const classes = cn(chipClasses, !
|
|
1352
|
-
if (!
|
|
1422
|
+
const classes = cn(chipClasses, !withText && "qsl:p-1", running2 && "qsl:animate-fade-pulse qsl:motion-reduce:animate-none");
|
|
1423
|
+
if (!expandable) {
|
|
1353
1424
|
return /* @__PURE__ */ jsx13("span", { className: classes, "data-kind": thinking ? "thinking" : "tool", "data-state": state, "data-step": "chip", title: label, children: content }, message.key);
|
|
1354
1425
|
}
|
|
1355
1426
|
return /* @__PURE__ */ jsx13(
|
|
@@ -1368,10 +1439,10 @@ function StepRow({
|
|
|
1368
1439
|
message.key
|
|
1369
1440
|
);
|
|
1370
1441
|
}),
|
|
1371
|
-
|
|
1442
|
+
open && detail && /* @__PURE__ */ jsx13(
|
|
1372
1443
|
StepPopover,
|
|
1373
1444
|
{
|
|
1374
|
-
message:
|
|
1445
|
+
message: open,
|
|
1375
1446
|
position: detail.position,
|
|
1376
1447
|
texts,
|
|
1377
1448
|
toolArgumentsText,
|
|
@@ -1382,56 +1453,58 @@ function StepRow({
|
|
|
1382
1453
|
}
|
|
1383
1454
|
function StepGroup({
|
|
1384
1455
|
messages,
|
|
1385
|
-
|
|
1386
|
-
|
|
1456
|
+
runningStep,
|
|
1457
|
+
elapsedAfter,
|
|
1458
|
+
expandable,
|
|
1387
1459
|
texts,
|
|
1388
1460
|
toolArgumentsText
|
|
1389
1461
|
}) {
|
|
1390
|
-
const [
|
|
1391
|
-
const
|
|
1392
|
-
const
|
|
1462
|
+
const [open, setOpen] = useState4(false);
|
|
1463
|
+
const header = useRef3(null);
|
|
1464
|
+
const collapsedFromBottom = useRef3(false);
|
|
1393
1465
|
useLayoutEffect(() => {
|
|
1394
|
-
if (!
|
|
1395
|
-
|
|
1396
|
-
|
|
1466
|
+
if (!open && collapsedFromBottom.current) {
|
|
1467
|
+
collapsedFromBottom.current = false;
|
|
1468
|
+
header.current?.scrollIntoView({ block: "nearest" });
|
|
1397
1469
|
}
|
|
1398
|
-
}, [
|
|
1399
|
-
const
|
|
1400
|
-
const
|
|
1401
|
-
const
|
|
1402
|
-
const
|
|
1470
|
+
}, [open]);
|
|
1471
|
+
const active = runningStep !== void 0;
|
|
1472
|
+
const failed = messages.some((message) => message.tool?.isError);
|
|
1473
|
+
const count = messages.length === 1 ? texts.stepGroupOne : texts.stepGroupMany.replace("{count}", String(messages.length));
|
|
1474
|
+
const runningLabel = runningStep && (runningStep.role === "thinking" ? texts.thinkingChip : runningStep.tool?.name || runningStep.text || texts.toolChip);
|
|
1403
1475
|
return /* @__PURE__ */ jsxs6("div", { className: stepClasses, "data-step": "group", children: [
|
|
1404
1476
|
/* @__PURE__ */ jsxs6(
|
|
1405
1477
|
"button",
|
|
1406
1478
|
{
|
|
1407
|
-
"aria-expanded":
|
|
1408
|
-
className: cn(traceClasses, traceButtonClasses, "qsl:items-center qsl:pl-1",
|
|
1409
|
-
onClick: () =>
|
|
1410
|
-
ref:
|
|
1479
|
+
"aria-expanded": open,
|
|
1480
|
+
className: cn(traceClasses, traceButtonClasses, "qsl:items-center qsl:pl-1", active && "qsl:animate-fade-pulse qsl:motion-reduce:animate-none"),
|
|
1481
|
+
onClick: () => setOpen((value) => !value),
|
|
1482
|
+
ref: header,
|
|
1411
1483
|
type: "button",
|
|
1412
1484
|
children: [
|
|
1413
|
-
/* @__PURE__ */ jsx13(ChevronRightIcon, { className: cn("qsl:flex-none qsl:opacity-60 qsl:transition-transform",
|
|
1414
|
-
/* @__PURE__ */ jsx13(LayersIcon, { className:
|
|
1485
|
+
/* @__PURE__ */ jsx13(ChevronRightIcon, { className: cn("qsl:flex-none qsl:opacity-60 qsl:transition-transform", open && "qsl:rotate-90"), size: 12 }),
|
|
1486
|
+
/* @__PURE__ */ jsx13(LayersIcon, { className: failed ? traceErrorIconClasses : traceIconClasses, size: 12 }),
|
|
1415
1487
|
/* @__PURE__ */ jsxs6("span", { className: cn(traceLineClasses, "qsl:overflow-hidden qsl:text-ellipsis qsl:whitespace-nowrap"), children: [
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1488
|
+
count,
|
|
1489
|
+
runningLabel && /* @__PURE__ */ jsxs6("span", { className: traceRunningClasses, children: [
|
|
1490
|
+
runningLabel,
|
|
1419
1491
|
" ",
|
|
1420
1492
|
texts.toolRunning
|
|
1421
|
-
] })
|
|
1493
|
+
] }),
|
|
1494
|
+
runningStep && /* @__PURE__ */ jsx13(ToolElapsed, { after: elapsedAfter, message: runningStep, texts })
|
|
1422
1495
|
] })
|
|
1423
1496
|
]
|
|
1424
1497
|
}
|
|
1425
1498
|
),
|
|
1426
|
-
|
|
1427
|
-
|
|
1499
|
+
open && /* @__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, expandable, message, texts, toolArgumentsText }, message.key)) }),
|
|
1500
|
+
open && messages.length >= collapseAtBottomFrom && /* @__PURE__ */ jsxs6(
|
|
1428
1501
|
"button",
|
|
1429
1502
|
{
|
|
1430
1503
|
className: cn(traceClasses, traceButtonClasses, "qsl:mt-0.5 qsl:items-center qsl:pl-1"),
|
|
1431
1504
|
"data-step": "group-collapse",
|
|
1432
1505
|
onClick: () => {
|
|
1433
|
-
|
|
1434
|
-
|
|
1506
|
+
collapsedFromBottom.current = true;
|
|
1507
|
+
setOpen(false);
|
|
1435
1508
|
},
|
|
1436
1509
|
type: "button",
|
|
1437
1510
|
children: [
|
|
@@ -1445,7 +1518,8 @@ function StepGroup({
|
|
|
1445
1518
|
function TraceLine({
|
|
1446
1519
|
message,
|
|
1447
1520
|
className,
|
|
1448
|
-
|
|
1521
|
+
elapsedAfter,
|
|
1522
|
+
expandable,
|
|
1449
1523
|
texts,
|
|
1450
1524
|
toolArgumentsText
|
|
1451
1525
|
}) {
|
|
@@ -1453,17 +1527,18 @@ function TraceLine({
|
|
|
1453
1527
|
const thinking = message.role === "thinking";
|
|
1454
1528
|
const tool = message.tool;
|
|
1455
1529
|
const classes = cn(traceClasses, className);
|
|
1456
|
-
const
|
|
1457
|
-
const
|
|
1530
|
+
const line = cn(traceLineClasses, thinking && "qsl:font-sans qsl:italic qsl:whitespace-pre-wrap", "qsl:overflow-hidden qsl:text-ellipsis qsl:whitespace-nowrap");
|
|
1531
|
+
const content = /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1458
1532
|
thinking ? /* @__PURE__ */ jsx13(SparklesIcon2, { className: traceIconClasses, size: 12 }) : /* @__PURE__ */ jsx13(WrenchIcon2, { className: tool?.isError ? traceErrorIconClasses : traceIconClasses, size: 12 }),
|
|
1459
|
-
/* @__PURE__ */ jsxs6("span", { className:
|
|
1533
|
+
/* @__PURE__ */ jsxs6("span", { className: line, children: [
|
|
1460
1534
|
message.text,
|
|
1461
|
-
tool && tool.result === void 0 && /* @__PURE__ */ jsx13("span", { className: traceRunningClasses, children: texts.toolRunning })
|
|
1535
|
+
tool && tool.result === void 0 && /* @__PURE__ */ jsx13("span", { className: traceRunningClasses, children: texts.toolRunning }),
|
|
1536
|
+
/* @__PURE__ */ jsx13(ToolElapsed, { after: elapsedAfter, message, texts })
|
|
1462
1537
|
] })
|
|
1463
1538
|
] });
|
|
1464
1539
|
const marks = { "data-compact": "true", "data-kind": thinking ? "thinking" : "tool", "data-step": "line" };
|
|
1465
|
-
if (!
|
|
1466
|
-
return /* @__PURE__ */ jsx13("div", { className: classes, ...marks, children:
|
|
1540
|
+
if (!expandable) {
|
|
1541
|
+
return /* @__PURE__ */ jsx13("div", { className: classes, ...marks, children: content });
|
|
1467
1542
|
}
|
|
1468
1543
|
return /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1469
1544
|
/* @__PURE__ */ jsx13(
|
|
@@ -1475,7 +1550,7 @@ function TraceLine({
|
|
|
1475
1550
|
...marks,
|
|
1476
1551
|
onClick: (event) => setDetailPosition({ x: event.clientX, y: event.clientY }),
|
|
1477
1552
|
type: "button",
|
|
1478
|
-
children:
|
|
1553
|
+
children: content
|
|
1479
1554
|
}
|
|
1480
1555
|
),
|
|
1481
1556
|
detailPosition && /* @__PURE__ */ jsx13(
|
|
@@ -1490,12 +1565,22 @@ function TraceLine({
|
|
|
1490
1565
|
)
|
|
1491
1566
|
] });
|
|
1492
1567
|
}
|
|
1568
|
+
function ToolElapsed({ message, after, texts, className = traceRunningClasses }) {
|
|
1569
|
+
const start = stepState(message) === "running" ? messageDate(message.at)?.getTime() : void 0;
|
|
1570
|
+
return after === void 0 || start === void 0 ? null : /* @__PURE__ */ jsx13(Elapsed, { after, className, start, texts });
|
|
1571
|
+
}
|
|
1572
|
+
function Elapsed({ start, after, className, texts }) {
|
|
1573
|
+
const elapsed = useElapsed(start);
|
|
1574
|
+
if (elapsed === void 0 || elapsed < after) return null;
|
|
1575
|
+
return /* @__PURE__ */ jsx13("span", { "aria-hidden": "true", className: cn(className, "qsl:tabular-nums"), "data-step": "elapsed", children: formatElapsed(elapsed, texts) });
|
|
1576
|
+
}
|
|
1493
1577
|
function Bubble({
|
|
1494
1578
|
message,
|
|
1495
1579
|
plain,
|
|
1496
1580
|
detailMode,
|
|
1497
|
-
|
|
1498
|
-
|
|
1581
|
+
dense,
|
|
1582
|
+
elapsedAfter,
|
|
1583
|
+
expandable,
|
|
1499
1584
|
texts,
|
|
1500
1585
|
toolArgumentsText,
|
|
1501
1586
|
renderAction,
|
|
@@ -1503,22 +1588,23 @@ function Bubble({
|
|
|
1503
1588
|
bubbleOptions,
|
|
1504
1589
|
messageActions
|
|
1505
1590
|
}) {
|
|
1506
|
-
const
|
|
1591
|
+
const stepSpacing = dense ? denseStepClasses : stepClasses;
|
|
1507
1592
|
if (message.role === "thinking" || message.role === "tool" && message.tool) {
|
|
1508
1593
|
if (detailMode === "compact") {
|
|
1509
|
-
return /* @__PURE__ */ jsx13(TraceLine, { className:
|
|
1594
|
+
return /* @__PURE__ */ jsx13(TraceLine, { className: stepSpacing, elapsedAfter, expandable, message, texts, toolArgumentsText });
|
|
1510
1595
|
}
|
|
1511
1596
|
const thinking = message.role === "thinking";
|
|
1512
1597
|
const tool = message.tool;
|
|
1513
1598
|
const icon = thinking ? /* @__PURE__ */ jsx13(SparklesIcon2, { className: traceIconClasses, size: 12 }) : /* @__PURE__ */ jsx13(WrenchIcon2, { className: tool?.isError ? traceErrorIconClasses : traceIconClasses, size: 12 });
|
|
1514
|
-
const
|
|
1599
|
+
const line = /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1515
1600
|
message.text,
|
|
1516
|
-
tool && tool.result === void 0 && /* @__PURE__ */ jsx13("span", { className: traceRunningClasses, children: texts.toolRunning })
|
|
1601
|
+
tool && tool.result === void 0 && /* @__PURE__ */ jsx13("span", { className: traceRunningClasses, children: texts.toolRunning }),
|
|
1602
|
+
/* @__PURE__ */ jsx13(ToolElapsed, { after: elapsedAfter, message, texts })
|
|
1517
1603
|
] });
|
|
1518
|
-
return /* @__PURE__ */ jsxs6("div", { className: cn(traceClasses,
|
|
1604
|
+
return /* @__PURE__ */ jsxs6("div", { className: cn(traceClasses, stepSpacing), "data-kind": thinking ? "thinking" : "tool", "data-step": "detail", children: [
|
|
1519
1605
|
icon,
|
|
1520
1606
|
/* @__PURE__ */ jsxs6("div", { className: "qsl:min-w-0 qsl:flex-1", children: [
|
|
1521
|
-
/* @__PURE__ */ jsx13("div", { className: cn(traceLineClasses, thinking && "qsl:font-sans qsl:italic qsl:whitespace-pre-wrap"), children:
|
|
1607
|
+
/* @__PURE__ */ jsx13("div", { className: cn(traceLineClasses, thinking && "qsl:font-sans qsl:italic qsl:whitespace-pre-wrap"), children: line }),
|
|
1522
1608
|
tool && /* @__PURE__ */ jsxs6("div", { className: "qsl:mt-0.5 qsl:border-l-2 qsl:border-border-soft qsl:pl-2", children: [
|
|
1523
1609
|
/* @__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) }),
|
|
1524
1610
|
tool.result !== void 0 && /* @__PURE__ */ jsx13("pre", { className: cn(
|
|
@@ -1530,21 +1616,10 @@ function Bubble({
|
|
|
1530
1616
|
] });
|
|
1531
1617
|
}
|
|
1532
1618
|
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
|
-
) });
|
|
1619
|
+
return /* @__PURE__ */ jsx13("div", { className: stepSpacing, children: /* @__PURE__ */ jsx13(ActionContent, { action: message.action, onDismissAction, renderAction, text: message.text, texts }) });
|
|
1545
1620
|
}
|
|
1546
1621
|
if (message.role === "system") {
|
|
1547
|
-
return /* @__PURE__ */ jsxs6("div", { className: cn(
|
|
1622
|
+
return /* @__PURE__ */ jsxs6("div", { className: cn(stepSpacing, "qsl:border-l-2 qsl:border-destructive/50 qsl:pl-3 qsl:text-sm qsl:text-muted-foreground"), "data-message": "system", children: [
|
|
1548
1623
|
/* @__PURE__ */ jsx13(Markdown, { text: message.text }),
|
|
1549
1624
|
/* @__PURE__ */ jsx13(MessageAttachments, { label: texts.attachments, message })
|
|
1550
1625
|
] });
|
|
@@ -1566,12 +1641,12 @@ function Bubble({
|
|
|
1566
1641
|
/* @__PURE__ */ jsx13(MessageActions, { message, options: messageActions, texts })
|
|
1567
1642
|
] });
|
|
1568
1643
|
const cursor = message.role === "assistant" && message.textCursor ? JSON.stringify(message.textCursor) : void 0;
|
|
1569
|
-
if (!useBubble) return /* @__PURE__ */ jsx13("div", { "data-chat-text-cursor": cursor, className: cn(
|
|
1644
|
+
if (!useBubble) return /* @__PURE__ */ jsx13("div", { "data-chat-text-cursor": cursor, className: cn(stepSpacing, "qsl:group qsl:relative qsl:w-full qsl:leading-[var(--qsl-chat-line-height,1.625)] qsl:text-foreground"), "data-message": "answer", children: content });
|
|
1570
1645
|
return /* @__PURE__ */ jsx13(
|
|
1571
1646
|
"div",
|
|
1572
1647
|
{
|
|
1573
1648
|
"data-chat-text-cursor": cursor,
|
|
1574
|
-
className: cn(
|
|
1649
|
+
className: cn(stepSpacing, "qsl:flex", side === "end" ? "qsl:justify-end" : "qsl:justify-start"),
|
|
1575
1650
|
"data-message": message.role === "user" && !message.bubble && variant === "default" ? "user" : "bubble",
|
|
1576
1651
|
"data-side": side,
|
|
1577
1652
|
children: /* @__PURE__ */ jsx13(
|
|
@@ -1586,6 +1661,25 @@ function Bubble({
|
|
|
1586
1661
|
}
|
|
1587
1662
|
);
|
|
1588
1663
|
}
|
|
1664
|
+
function ActionContent({
|
|
1665
|
+
action,
|
|
1666
|
+
text,
|
|
1667
|
+
texts,
|
|
1668
|
+
renderAction,
|
|
1669
|
+
onDismissAction
|
|
1670
|
+
}) {
|
|
1671
|
+
return renderAction?.(action, text) ?? /* @__PURE__ */ jsx13(
|
|
1672
|
+
PendingActionCard,
|
|
1673
|
+
{
|
|
1674
|
+
action,
|
|
1675
|
+
dismissedLabel: texts.actionDismissed,
|
|
1676
|
+
dismissLabel: texts.dismissAction,
|
|
1677
|
+
onDismiss: onDismissAction ? () => onDismissAction(action.actionId) : void 0,
|
|
1678
|
+
text,
|
|
1679
|
+
waitingLabel: texts.pendingAction
|
|
1680
|
+
}
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1589
1683
|
function MessageAttachments({ message, label }) {
|
|
1590
1684
|
if (!message.attachments?.length) return null;
|
|
1591
1685
|
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: [
|
|
@@ -1611,8 +1705,9 @@ function ChatPanel({
|
|
|
1611
1705
|
appearance,
|
|
1612
1706
|
scrollOnSend = false
|
|
1613
1707
|
}) {
|
|
1614
|
-
const
|
|
1615
|
-
const
|
|
1708
|
+
const root = useRef4(null);
|
|
1709
|
+
const composerRef = useRef4(null);
|
|
1710
|
+
const [dock, setDock] = useState5(null);
|
|
1616
1711
|
const hasComposer = composer !== void 0;
|
|
1617
1712
|
const scrollOnSendRef = useRef4(scrollOnSend);
|
|
1618
1713
|
const [sendScope] = useState5(() => createChatSendScope(scrollOnSendRef));
|
|
@@ -1620,21 +1715,26 @@ function ChatPanel({
|
|
|
1620
1715
|
scrollOnSendRef.current = scrollOnSend;
|
|
1621
1716
|
}, [scrollOnSend]);
|
|
1622
1717
|
useEffect4(() => {
|
|
1623
|
-
const element =
|
|
1624
|
-
const
|
|
1625
|
-
if (!element || !
|
|
1718
|
+
const element = composerRef.current;
|
|
1719
|
+
const frame = root.current;
|
|
1720
|
+
if (!element || !frame) {
|
|
1626
1721
|
return;
|
|
1627
1722
|
}
|
|
1628
|
-
const
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1723
|
+
const measure = () => {
|
|
1724
|
+
frame.style.setProperty("--qsl-composer-height", `${element.offsetHeight}px`);
|
|
1725
|
+
frame.style.setProperty("--qsl-panel-height", `${frame.offsetHeight}px`);
|
|
1726
|
+
};
|
|
1727
|
+
const observer = new ResizeObserver(measure);
|
|
1728
|
+
observer.observe(element);
|
|
1729
|
+
observer.observe(frame);
|
|
1730
|
+
measure();
|
|
1632
1731
|
return () => {
|
|
1633
|
-
|
|
1634
|
-
|
|
1732
|
+
observer.disconnect();
|
|
1733
|
+
frame.style.removeProperty("--qsl-composer-height");
|
|
1734
|
+
frame.style.removeProperty("--qsl-panel-height");
|
|
1635
1735
|
};
|
|
1636
1736
|
}, [hasComposer]);
|
|
1637
|
-
return /* @__PURE__ */ jsx14(ChatSendContext.Provider, { value: sendScope, children: /* @__PURE__ */ jsxs7(
|
|
1737
|
+
return /* @__PURE__ */ jsx14(ChatSendContext.Provider, { value: sendScope, children: /* @__PURE__ */ jsx14(ChatActionDock.Provider, { value: hasComposer ? dock : void 0, children: /* @__PURE__ */ jsxs7(
|
|
1638
1738
|
"div",
|
|
1639
1739
|
{
|
|
1640
1740
|
className: cn("qsl:relative qsl:flex qsl:min-h-0 qsl:min-w-0 qsl:flex-col qsl:overflow-hidden", className),
|
|
@@ -1646,7 +1746,7 @@ function ChatPanel({
|
|
|
1646
1746
|
"--qsl-chat-horizontal-padding": typeof horizontalPadding === "number" ? `${horizontalPadding}px` : horizontalPadding
|
|
1647
1747
|
},
|
|
1648
1748
|
ref: (element) => {
|
|
1649
|
-
|
|
1749
|
+
root.current = element;
|
|
1650
1750
|
if (typeof nodeRef === "function") {
|
|
1651
1751
|
nodeRef(element);
|
|
1652
1752
|
} else if (nodeRef) {
|
|
@@ -1655,10 +1755,20 @@ function ChatPanel({
|
|
|
1655
1755
|
},
|
|
1656
1756
|
children: [
|
|
1657
1757
|
children,
|
|
1658
|
-
hasComposer && /* @__PURE__ */
|
|
1758
|
+
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: composerRef, children: [
|
|
1759
|
+
/* @__PURE__ */ jsx14("div", { children: /* @__PURE__ */ jsx14(
|
|
1760
|
+
"div",
|
|
1761
|
+
{
|
|
1762
|
+
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",
|
|
1763
|
+
"data-chat": "actions",
|
|
1764
|
+
ref: setDock
|
|
1765
|
+
}
|
|
1766
|
+
) }),
|
|
1767
|
+
composer
|
|
1768
|
+
] })
|
|
1659
1769
|
]
|
|
1660
1770
|
}
|
|
1661
|
-
) });
|
|
1771
|
+
) }) });
|
|
1662
1772
|
}
|
|
1663
1773
|
|
|
1664
1774
|
// packages/quassel/src/ChatInputToolbar.tsx
|
|
@@ -1714,7 +1824,7 @@ function useFileInput({ disabled, onFiles, onPasteText }) {
|
|
|
1714
1824
|
|
|
1715
1825
|
// packages/quassel/src/ChatInputToolbar.tsx
|
|
1716
1826
|
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";
|
|
1827
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
1718
1828
|
import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1719
1829
|
var COMPACT_WIDTH_PX = 480;
|
|
1720
1830
|
var errorClasses = "qsl:px-3 qsl:py-2 qsl:text-sm qsl:text-destructive";
|
|
@@ -1723,7 +1833,7 @@ var attachmentMediaClasses = "qsl:block qsl:h-[112px] qsl:w-full qsl:bg-secondar
|
|
|
1723
1833
|
function ChatInputToolbar({
|
|
1724
1834
|
onSend,
|
|
1725
1835
|
onStop,
|
|
1726
|
-
running = false,
|
|
1836
|
+
running: running2 = false,
|
|
1727
1837
|
disabled = false,
|
|
1728
1838
|
sendDisabled = false,
|
|
1729
1839
|
layout = "card",
|
|
@@ -1748,7 +1858,7 @@ function ChatInputToolbar({
|
|
|
1748
1858
|
onAutoFocusSettled
|
|
1749
1859
|
}) {
|
|
1750
1860
|
const sendScope = useContext4(ChatSendContext);
|
|
1751
|
-
const
|
|
1861
|
+
const allTexts = { ...defaultTexts, ...texts };
|
|
1752
1862
|
const { Button: Button2, StopButton: StopButton2, Toggle: Toggle2 } = useQuasselComponents();
|
|
1753
1863
|
const [draft, setDraft] = useState7(initialValue);
|
|
1754
1864
|
const [sending, setSending] = useState7(false);
|
|
@@ -1772,11 +1882,11 @@ function ChatInputToolbar({
|
|
|
1772
1882
|
const addFiles = (files) => {
|
|
1773
1883
|
if (!files.length) return;
|
|
1774
1884
|
if (disabled || pendingSend.current) {
|
|
1775
|
-
setAttachmentError(
|
|
1885
|
+
setAttachmentError(allTexts.attachmentsWhileBusy);
|
|
1776
1886
|
return;
|
|
1777
1887
|
}
|
|
1778
1888
|
try {
|
|
1779
|
-
validateAttachmentSelection(attachmentsRef.current.map((entry) => entry.file), files,
|
|
1889
|
+
validateAttachmentSelection(attachmentsRef.current.map((entry) => entry.file), files, allTexts);
|
|
1780
1890
|
} catch (error) {
|
|
1781
1891
|
setAttachmentError(error instanceof Error ? error.message : String(error));
|
|
1782
1892
|
return;
|
|
@@ -1790,17 +1900,17 @@ function ChatInputToolbar({
|
|
|
1790
1900
|
() => updateAttachments((current) => current.map((item) => item.id === entry.id ? {
|
|
1791
1901
|
...entry,
|
|
1792
1902
|
status: "error",
|
|
1793
|
-
error:
|
|
1903
|
+
error: allTexts.attachmentReadFailed
|
|
1794
1904
|
} : item))
|
|
1795
1905
|
);
|
|
1796
1906
|
}
|
|
1797
1907
|
};
|
|
1798
1908
|
const rootRef = useRef6(null);
|
|
1799
|
-
const
|
|
1909
|
+
const textareaRef = useRef6(null);
|
|
1800
1910
|
const autoFocusSettled = useRef6(false);
|
|
1801
1911
|
useEffect5(() => {
|
|
1802
1912
|
if (!autoFocus || autoFocusSettled.current) return;
|
|
1803
|
-
const input2 =
|
|
1913
|
+
const input2 = textareaRef.current;
|
|
1804
1914
|
if (!input2) return;
|
|
1805
1915
|
let frame = 0;
|
|
1806
1916
|
const settle = () => {
|
|
@@ -1849,7 +1959,7 @@ function ChatInputToolbar({
|
|
|
1849
1959
|
disabled: disabled || sending,
|
|
1850
1960
|
onFiles: addFiles,
|
|
1851
1961
|
onPasteText: (text) => {
|
|
1852
|
-
const input2 =
|
|
1962
|
+
const input2 = textareaRef.current;
|
|
1853
1963
|
if (!input2 || disabled || pendingSend.current) return;
|
|
1854
1964
|
const start = input2.selectionStart;
|
|
1855
1965
|
const end = input2.selectionEnd;
|
|
@@ -1886,11 +1996,11 @@ function ChatInputToolbar({
|
|
|
1886
1996
|
insert: (text) => {
|
|
1887
1997
|
pendingFocus.current = true;
|
|
1888
1998
|
changeDraft(text);
|
|
1889
|
-
const element =
|
|
1999
|
+
const element = textareaRef.current;
|
|
1890
2000
|
element?.focus();
|
|
1891
2001
|
if (element?.value === text) element.setSelectionRange(text.length, text.length);
|
|
1892
2002
|
},
|
|
1893
|
-
focus: () =>
|
|
2003
|
+
focus: () => textareaRef.current?.focus(),
|
|
1894
2004
|
submit: (onSubmit, options) => send(onSubmit, options?.allowEmpty),
|
|
1895
2005
|
reset: () => {
|
|
1896
2006
|
generation.current += 1;
|
|
@@ -1913,31 +2023,31 @@ function ChatInputToolbar({
|
|
|
1913
2023
|
return;
|
|
1914
2024
|
}
|
|
1915
2025
|
pendingFocus.current = false;
|
|
1916
|
-
const element =
|
|
2026
|
+
const element = textareaRef.current;
|
|
1917
2027
|
if (element) {
|
|
1918
2028
|
element.focus();
|
|
1919
2029
|
element.setSelectionRange(element.value.length, element.value.length);
|
|
1920
2030
|
}
|
|
1921
2031
|
}, [draft, sending]);
|
|
1922
2032
|
const applyAutoGrow = useCallback3(() => {
|
|
1923
|
-
const element =
|
|
2033
|
+
const element = textareaRef.current;
|
|
1924
2034
|
if (!element || inputMaxRows === void 0) {
|
|
1925
2035
|
return;
|
|
1926
2036
|
}
|
|
1927
|
-
const
|
|
1928
|
-
const
|
|
1929
|
-
const
|
|
1930
|
-
const maximum =
|
|
2037
|
+
const style = getComputedStyle(element);
|
|
2038
|
+
const lineHeight = Number.parseFloat(style.lineHeight) || Number.parseFloat(style.fontSize) * 1.4;
|
|
2039
|
+
const padding = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom);
|
|
2040
|
+
const maximum = lineHeight * Math.max(inputMaxRows, inputRows) + padding;
|
|
1931
2041
|
element.style.height = "auto";
|
|
1932
|
-
const
|
|
1933
|
-
element.style.height = `${Math.min(
|
|
1934
|
-
element.style.overflowY =
|
|
2042
|
+
const contentHeight = element.scrollHeight;
|
|
2043
|
+
element.style.height = `${Math.min(contentHeight, maximum)}px`;
|
|
2044
|
+
element.style.overflowY = contentHeight > maximum ? "auto" : "hidden";
|
|
1935
2045
|
}, [inputMaxRows, inputRows]);
|
|
1936
2046
|
useEffect5(() => {
|
|
1937
2047
|
applyAutoGrow();
|
|
1938
2048
|
}, [draft, applyAutoGrow]);
|
|
1939
2049
|
useEffect5(() => {
|
|
1940
|
-
const element =
|
|
2050
|
+
const element = textareaRef.current;
|
|
1941
2051
|
if (!element || inputMaxRows === void 0) {
|
|
1942
2052
|
return;
|
|
1943
2053
|
}
|
|
@@ -1958,9 +2068,9 @@ function ChatInputToolbar({
|
|
|
1958
2068
|
}
|
|
1959
2069
|
const update = () => setCompact(element.clientWidth < COMPACT_WIDTH_PX);
|
|
1960
2070
|
update();
|
|
1961
|
-
const
|
|
1962
|
-
|
|
1963
|
-
return () =>
|
|
2071
|
+
const observer = new ResizeObserver(update);
|
|
2072
|
+
observer.observe(element);
|
|
2073
|
+
return () => observer.disconnect();
|
|
1964
2074
|
}, []);
|
|
1965
2075
|
const hasAttachments = attachments.length > 0;
|
|
1966
2076
|
useEffect5(() => {
|
|
@@ -1969,7 +2079,7 @@ function ChatInputToolbar({
|
|
|
1969
2079
|
const capabilityError = hasAttachments && attachmentCapabilitiesError ? attachmentCapabilitiesError : attachmentCapabilities ? attachmentCapabilityError(attachments.map((entry) => ({
|
|
1970
2080
|
name: entry.file.name,
|
|
1971
2081
|
mediaType: attachmentMediaType(entry.file)
|
|
1972
|
-
})), attachmentCapabilities,
|
|
2082
|
+
})), attachmentCapabilities, allTexts) : void 0;
|
|
1973
2083
|
const hasContent = draft.trim().length > 0 || attachments.length > 0;
|
|
1974
2084
|
const preparing = attachments.some((entry) => entry.status === "reading");
|
|
1975
2085
|
const attachmentBlocked = attachments.some((entry) => entry.status !== "ready");
|
|
@@ -1988,7 +2098,7 @@ function ChatInputToolbar({
|
|
|
1988
2098
|
const sentRevision = draftRevision.current;
|
|
1989
2099
|
const sentDraft = draft;
|
|
1990
2100
|
const focusOrigin = document.activeElement;
|
|
1991
|
-
const startedFocused = focusOrigin ===
|
|
2101
|
+
const startedFocused = focusOrigin === textareaRef.current || rootRef.current?.contains(focusOrigin);
|
|
1992
2102
|
let leftComposer = false;
|
|
1993
2103
|
const observeFocus = (event) => {
|
|
1994
2104
|
const target = event.target;
|
|
@@ -2019,7 +2129,7 @@ function ChatInputToolbar({
|
|
|
2019
2129
|
document.removeEventListener("pointerdown", observeFocus);
|
|
2020
2130
|
if (generation.current === sentGeneration) {
|
|
2021
2131
|
pendingSend.current = false;
|
|
2022
|
-
pendingFocus.current = !!startedFocused && !leftComposer && document.hasFocus() && (!toolbar || focusOrigin !==
|
|
2132
|
+
pendingFocus.current = !!startedFocused && !leftComposer && document.hasFocus() && (!toolbar || focusOrigin !== textareaRef.current) && (document.activeElement === focusOrigin || !toolbar && document.activeElement === document.body);
|
|
2023
2133
|
setSending(false);
|
|
2024
2134
|
}
|
|
2025
2135
|
}
|
|
@@ -2030,28 +2140,28 @@ function ChatInputToolbar({
|
|
|
2030
2140
|
if (!event.repeat) void send();
|
|
2031
2141
|
}
|
|
2032
2142
|
};
|
|
2033
|
-
const attachmentPreviews = attachments.length > 0 && /* @__PURE__ */ jsx15("ul", { className: "qsl:flex qsl:max-h-[240px] qsl:flex-wrap qsl:gap-2 qsl:overflow-y-auto qsl:px-3 qsl:pt-2.5 qsl:in-data-[tone=overseer-details]:max-h-40 qsl:in-data-[tone=overseer-details]:overflow-y-auto", "aria-label":
|
|
2143
|
+
const attachmentPreviews = attachments.length > 0 && /* @__PURE__ */ jsx15("ul", { className: "qsl:flex qsl:max-h-[240px] qsl:flex-wrap qsl:gap-2 qsl:overflow-y-auto qsl:px-3 qsl:pt-2.5 qsl:in-data-[tone=overseer-details]:max-h-40 qsl:in-data-[tone=overseer-details]:overflow-y-auto", "aria-label": allTexts.attachments, children: attachments.map((entry) => /* @__PURE__ */ jsxs8("li", { className: attachmentItemClasses, children: [
|
|
2034
2144
|
entry.status === "ready" && entry.input.mediaType.startsWith("image/") && /* @__PURE__ */ jsx15("img", { className: attachmentMediaClasses, src: `data:${entry.input.mediaType};base64,${entry.input.data}`, alt: entry.file.name }),
|
|
2035
2145
|
entry.status === "ready" && entry.input.mediaType.startsWith("video/") && /* @__PURE__ */ jsx15("video", { className: attachmentMediaClasses, controls: true, preload: "metadata", "aria-label": entry.file.name, src: `data:${entry.input.mediaType};base64,${entry.input.data}` }),
|
|
2036
2146
|
/* @__PURE__ */ jsxs8("div", { className: "qsl:flex qsl:flex-col qsl:gap-1 qsl:p-2 qsl:pr-8 qsl:[overflow-wrap:anywhere]", children: [
|
|
2037
2147
|
/* @__PURE__ */ jsx15("span", { className: "qsl:font-medium", title: entry.file.name, children: entry.file.name }),
|
|
2038
2148
|
/* @__PURE__ */ jsx15("small", { children: formatAttachmentSize(entry.file.size) }),
|
|
2039
|
-
entry.status === "reading" && /* @__PURE__ */ jsx15("small", { role: "status", children:
|
|
2149
|
+
entry.status === "reading" && /* @__PURE__ */ jsx15("small", { role: "status", children: allTexts.attachmentPreparing }),
|
|
2040
2150
|
entry.status === "error" && /* @__PURE__ */ jsx15("small", { role: "alert", children: entry.error })
|
|
2041
2151
|
] }),
|
|
2042
2152
|
/* @__PURE__ */ jsx15(
|
|
2043
2153
|
Button2,
|
|
2044
2154
|
{
|
|
2045
|
-
"aria-label": fillText(
|
|
2155
|
+
"aria-label": fillText(allTexts.removeAttachment, { name: entry.file.name }),
|
|
2046
2156
|
className: "qsl:absolute qsl:top-1 qsl:right-1",
|
|
2047
2157
|
disabled: disabled || sending,
|
|
2048
2158
|
onClick: () => {
|
|
2049
2159
|
updateAttachments((current) => current.filter((item) => item.id !== entry.id));
|
|
2050
2160
|
setAttachmentError(void 0);
|
|
2051
|
-
|
|
2161
|
+
textareaRef.current?.focus();
|
|
2052
2162
|
},
|
|
2053
2163
|
size: "icon-sm",
|
|
2054
|
-
title: fillText(
|
|
2164
|
+
title: fillText(allTexts.removeAttachment, { name: entry.file.name }),
|
|
2055
2165
|
variant: "outline",
|
|
2056
2166
|
children: /* @__PURE__ */ jsx15(XIcon3, {})
|
|
2057
2167
|
}
|
|
@@ -2063,16 +2173,16 @@ function ChatInputToolbar({
|
|
|
2063
2173
|
sendError && /* @__PURE__ */ jsx15("p", { className: errorClasses, role: "alert", children: sendError }),
|
|
2064
2174
|
failedSubmissions.map((failed) => /* @__PURE__ */ jsxs8("div", { className: cn(errorClasses, "qsl:flex qsl:flex-wrap qsl:items-center qsl:gap-2"), role: "status", children: [
|
|
2065
2175
|
/* @__PURE__ */ jsxs8("span", { children: [
|
|
2066
|
-
|
|
2176
|
+
allTexts.notSent,
|
|
2067
2177
|
" ",
|
|
2068
2178
|
failed.draft.slice(0, 100),
|
|
2069
2179
|
failed.draft.length > 100 ? " ..." : "",
|
|
2070
|
-
failed.attachments.length > 0 ? ` ${fillText(
|
|
2180
|
+
failed.attachments.length > 0 ? ` ${fillText(allTexts.notSentAttachments, { count: failed.attachments.length })}` : "",
|
|
2071
2181
|
" "
|
|
2072
2182
|
] }),
|
|
2073
2183
|
/* @__PURE__ */ jsx15(Button2, { disabled: disabled || sending, size: "sm", variant: "outline", onClick: () => {
|
|
2074
2184
|
try {
|
|
2075
|
-
validateAttachmentSelection(attachmentsRef.current.map((entry) => entry.file), failed.attachments.map((entry) => entry.file),
|
|
2185
|
+
validateAttachmentSelection(attachmentsRef.current.map((entry) => entry.file), failed.attachments.map((entry) => entry.file), allTexts);
|
|
2076
2186
|
} catch (error) {
|
|
2077
2187
|
setAttachmentError(error instanceof Error ? error.message : String(error));
|
|
2078
2188
|
return;
|
|
@@ -2083,22 +2193,22 @@ function ChatInputToolbar({
|
|
|
2083
2193
|
setFailedSubmissions((current) => current.filter((entry) => entry.id !== failed.id));
|
|
2084
2194
|
setAttachmentError(void 0);
|
|
2085
2195
|
pendingFocus.current = true;
|
|
2086
|
-
}, children:
|
|
2196
|
+
}, children: allTexts.insertUnsent })
|
|
2087
2197
|
] }, failed.id))
|
|
2088
2198
|
] });
|
|
2089
2199
|
const sendButtons = /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2090
|
-
|
|
2091
|
-
(!
|
|
2200
|
+
running2 && onStop && !hasContent && /* @__PURE__ */ jsx15(StopButton2, { className: "qsl:flex-none", label: allTexts.stop, onClick: onStop, size: "icon-sm" }),
|
|
2201
|
+
(!running2 || !onStop || hasContent) && /* @__PURE__ */ jsx15(
|
|
2092
2202
|
Button2,
|
|
2093
2203
|
{
|
|
2094
|
-
"aria-label":
|
|
2204
|
+
"aria-label": allTexts.send,
|
|
2095
2205
|
className: "qsl:flex-none",
|
|
2096
2206
|
disabled: disabled || sendDisabled || sending || attachmentBlocked || !!capabilityError || !hasContent,
|
|
2097
2207
|
onClick: () => {
|
|
2098
2208
|
void send();
|
|
2099
2209
|
},
|
|
2100
2210
|
size: "icon-sm",
|
|
2101
|
-
title:
|
|
2211
|
+
title: running2 ? allTexts.sendIntoRun : allTexts.send,
|
|
2102
2212
|
children: /* @__PURE__ */ jsx15(SendIcon, { size: 14 })
|
|
2103
2213
|
}
|
|
2104
2214
|
)
|
|
@@ -2108,16 +2218,16 @@ function ChatInputToolbar({
|
|
|
2108
2218
|
attachmentsEnabled && /* @__PURE__ */ jsx15(
|
|
2109
2219
|
Button2,
|
|
2110
2220
|
{
|
|
2111
|
-
"aria-label":
|
|
2221
|
+
"aria-label": allTexts.attachFiles,
|
|
2112
2222
|
disabled: disabled || sending,
|
|
2113
2223
|
onClick: () => fileInput.inputRef.current?.click(),
|
|
2114
2224
|
size: "icon-sm",
|
|
2115
|
-
title: fillText(
|
|
2225
|
+
title: fillText(allTexts.attachFilesHint, { count: MAX_CHAT_ATTACHMENTS, size: maxAttachmentSizeLabel }),
|
|
2116
2226
|
variant: "ghost",
|
|
2117
2227
|
children: /* @__PURE__ */ jsx15(PaperclipIcon, {})
|
|
2118
2228
|
}
|
|
2119
2229
|
),
|
|
2120
|
-
preparing && /* @__PURE__ */ jsx15("span", { className: cn("qsl:text-xs qsl:text-muted-foreground", inline && "qsl:sr-only"), role: "status", children:
|
|
2230
|
+
preparing && /* @__PURE__ */ jsx15("span", { className: cn("qsl:text-xs qsl:text-muted-foreground", inline && "qsl:sr-only"), role: "status", children: allTexts.attachmentsPreparing }),
|
|
2121
2231
|
actions?.map((action, index) => action.active === void 0 ? /* @__PURE__ */ jsxs8(Button2, { disabled: action.disabled, onClick: action.onClick, size: "sm", title: action.title ?? action.label, variant: "ghost", children: [
|
|
2122
2232
|
action.icon,
|
|
2123
2233
|
action.label && /* @__PURE__ */ jsx15("span", { className: action.icon ? "qsl:in-data-[compact=true]:hidden" : void 0, children: action.label })
|
|
@@ -2142,13 +2252,13 @@ function ChatInputToolbar({
|
|
|
2142
2252
|
{
|
|
2143
2253
|
"aria-controls": inputAriaControls,
|
|
2144
2254
|
className: textareaClasses,
|
|
2145
|
-
"aria-label":
|
|
2255
|
+
"aria-label": allTexts.placeholder,
|
|
2146
2256
|
disabled: !toolbar && disabled,
|
|
2147
2257
|
readOnly: toolbar && disabled,
|
|
2148
2258
|
onChange: (event) => changeDraft(event.target.value),
|
|
2149
2259
|
onKeyDown,
|
|
2150
|
-
placeholder:
|
|
2151
|
-
ref:
|
|
2260
|
+
placeholder: running2 ? allTexts.steeringPlaceholder : allTexts.placeholder,
|
|
2261
|
+
ref: textareaRef,
|
|
2152
2262
|
rows: inputRows,
|
|
2153
2263
|
value: draft
|
|
2154
2264
|
}
|
|
@@ -2160,14 +2270,14 @@ function ChatInputToolbar({
|
|
|
2160
2270
|
"data-compact": compact,
|
|
2161
2271
|
"data-layout": layout,
|
|
2162
2272
|
"data-quassel": "",
|
|
2163
|
-
"data-working":
|
|
2273
|
+
"data-working": running2 || sending,
|
|
2164
2274
|
ref: rootRef,
|
|
2165
2275
|
...attachmentsEnabled ? fileInput.dropProps : {},
|
|
2166
2276
|
children: [
|
|
2167
2277
|
attachmentsEnabled && /* @__PURE__ */ jsx15(
|
|
2168
2278
|
"input",
|
|
2169
2279
|
{
|
|
2170
|
-
"aria-label":
|
|
2280
|
+
"aria-label": allTexts.attachFiles,
|
|
2171
2281
|
hidden: true,
|
|
2172
2282
|
multiple: true,
|
|
2173
2283
|
type: "file",
|
|
@@ -2184,7 +2294,7 @@ function ChatInputToolbar({
|
|
|
2184
2294
|
] }) : input,
|
|
2185
2295
|
toolbar ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2186
2296
|
/* @__PURE__ */ jsx15("div", { className: "qsl:flex qsl:flex-none qsl:items-center qsl:gap-1 qsl:p-1", children: sendButtons }),
|
|
2187
|
-
detailsContainer &&
|
|
2297
|
+
detailsContainer && createPortal2(details, detailsContainer)
|
|
2188
2298
|
] }) : !inline && /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2189
2299
|
errors,
|
|
2190
2300
|
detailToolbar
|
|
@@ -2195,8 +2305,8 @@ function ChatInputToolbar({
|
|
|
2195
2305
|
}
|
|
2196
2306
|
|
|
2197
2307
|
// packages/quassel/src/DetailModeSwitch.tsx
|
|
2198
|
-
import {
|
|
2199
|
-
import { jsx as jsx16
|
|
2308
|
+
import { CircleDotIcon, EyeOffIcon, ListCollapseIcon, ListIcon, ShapesIcon, TagsIcon, TextAlignJustifyIcon } from "lucide-react";
|
|
2309
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
2200
2310
|
var DETAIL_MODES = ["off", "current", "icons", "chips", "grouped", "compact", "full"];
|
|
2201
2311
|
var TEXT_KEYS = {
|
|
2202
2312
|
off: "detailModeOff",
|
|
@@ -2207,57 +2317,50 @@ var TEXT_KEYS = {
|
|
|
2207
2317
|
compact: "detailModeCompact",
|
|
2208
2318
|
full: "detailModeFull"
|
|
2209
2319
|
};
|
|
2320
|
+
var ICONS = {
|
|
2321
|
+
off: EyeOffIcon,
|
|
2322
|
+
current: CircleDotIcon,
|
|
2323
|
+
icons: ShapesIcon,
|
|
2324
|
+
chips: TagsIcon,
|
|
2325
|
+
grouped: ListCollapseIcon,
|
|
2326
|
+
compact: ListIcon,
|
|
2327
|
+
full: TextAlignJustifyIcon
|
|
2328
|
+
};
|
|
2210
2329
|
var detailModeLabel = (mode, texts) => ({ ...defaultTexts, ...texts })[TEXT_KEYS[mode]];
|
|
2211
2330
|
function DetailModeSwitch({
|
|
2212
2331
|
mode,
|
|
2213
2332
|
onChange,
|
|
2214
2333
|
modes = DETAIL_MODES,
|
|
2215
|
-
collapsible = true,
|
|
2216
2334
|
texts,
|
|
2217
2335
|
className
|
|
2218
2336
|
}) {
|
|
2219
|
-
const
|
|
2337
|
+
const allTexts = { ...defaultTexts, ...texts };
|
|
2220
2338
|
const { Button: Button2 } = useQuasselComponents();
|
|
2221
|
-
const
|
|
2222
|
-
const
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
"aria-label": `${alleTexte.detailModeTitle}: ${detailModeLabel(mode, texts)}`,
|
|
2227
|
-
className,
|
|
2228
|
-
onClick: () => onChange(naechster),
|
|
2229
|
-
size: "sm",
|
|
2230
|
-
title: `${alleTexte.detailModeTitle}: ${detailModeLabel(mode, texts)}`,
|
|
2231
|
-
variant: "ghost",
|
|
2232
|
-
children: [
|
|
2233
|
-
/* @__PURE__ */ jsx16(SparklesIcon3, {}),
|
|
2234
|
-
/* @__PURE__ */ jsx16("span", { className: collapsible ? "qsl:in-data-[compact=true]:hidden" : void 0, children: detailModeLabel(mode, texts) })
|
|
2235
|
-
]
|
|
2236
|
-
}
|
|
2237
|
-
);
|
|
2339
|
+
const current = modes.indexOf(mode);
|
|
2340
|
+
const next2 = modes[(current < 0 ? 0 : current + 1) % modes.length];
|
|
2341
|
+
const Icon = ICONS[mode];
|
|
2342
|
+
const title = `${allTexts.detailModeTitle}: ${detailModeLabel(mode, texts)}`;
|
|
2343
|
+
return /* @__PURE__ */ jsx16(Button2, { "aria-label": title, className, onClick: () => onChange(next2), size: "icon-sm", title, variant: "ghost", children: /* @__PURE__ */ jsx16(Icon, {}) });
|
|
2238
2344
|
}
|
|
2239
2345
|
|
|
2240
2346
|
// packages/quassel/src/TimestampSwitch.tsx
|
|
2241
|
-
import { ClockIcon } from "lucide-react";
|
|
2242
|
-
import { jsx as jsx17
|
|
2243
|
-
function TimestampSwitch({ showTimestamps, onChange,
|
|
2347
|
+
import { ClockFadingIcon, ClockIcon } from "lucide-react";
|
|
2348
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
2349
|
+
function TimestampSwitch({ showTimestamps, onChange, texts, className }) {
|
|
2244
2350
|
const labels = { ...defaultTexts, ...texts };
|
|
2245
2351
|
const { Button: Button2 } = useQuasselComponents();
|
|
2246
2352
|
const title = showTimestamps ? labels.hideTimestamps : labels.showTimestamps;
|
|
2247
|
-
return /* @__PURE__ */
|
|
2353
|
+
return /* @__PURE__ */ jsx17(
|
|
2248
2354
|
Button2,
|
|
2249
2355
|
{
|
|
2250
2356
|
"aria-label": title,
|
|
2251
2357
|
"aria-pressed": showTimestamps,
|
|
2252
2358
|
className,
|
|
2253
2359
|
onClick: () => onChange(!showTimestamps),
|
|
2254
|
-
size: "sm",
|
|
2360
|
+
size: "icon-sm",
|
|
2255
2361
|
title,
|
|
2256
2362
|
variant: "ghost",
|
|
2257
|
-
children:
|
|
2258
|
-
/* @__PURE__ */ jsx17(ClockIcon, {}),
|
|
2259
|
-
/* @__PURE__ */ jsx17("span", { className: collapsible ? "qsl:in-data-[compact=true]:hidden" : void 0, children: labels.timestamps })
|
|
2260
|
-
]
|
|
2363
|
+
children: showTimestamps ? /* @__PURE__ */ jsx17(ClockIcon, {}) : /* @__PURE__ */ jsx17(ClockFadingIcon, {})
|
|
2261
2364
|
}
|
|
2262
2365
|
);
|
|
2263
2366
|
}
|
|
@@ -2295,6 +2398,7 @@ export {
|
|
|
2295
2398
|
encodeAttachment,
|
|
2296
2399
|
fillText,
|
|
2297
2400
|
formatAttachmentSize,
|
|
2401
|
+
formatElapsed,
|
|
2298
2402
|
markdownPlainText,
|
|
2299
2403
|
messageDate,
|
|
2300
2404
|
prettyJson,
|
|
@@ -2302,6 +2406,7 @@ export {
|
|
|
2302
2406
|
timestampDay,
|
|
2303
2407
|
timestampLabel,
|
|
2304
2408
|
useChatAction,
|
|
2409
|
+
useElapsed,
|
|
2305
2410
|
useQuasselComponents,
|
|
2306
2411
|
useQuasselPortalContainer,
|
|
2307
2412
|
validateAttachmentSelection
|