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/ChatInputToolbar.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { ReactNode, Ref } from "react";
|
|
|
2
2
|
import { ChatTexts } from "./texts";
|
|
3
3
|
import type { ChatAttachmentInput } from "./events";
|
|
4
4
|
import type { SendShortcut } from "./options";
|
|
5
|
-
/**
|
|
5
|
+
/** A toolbar button, declarative: icon, text or both - plus click handler. */
|
|
6
6
|
export interface ToolbarAction {
|
|
7
7
|
icon?: ReactNode;
|
|
8
8
|
label?: string;
|
|
@@ -11,9 +11,9 @@ export interface ToolbarAction {
|
|
|
11
11
|
active?: boolean;
|
|
12
12
|
onClick: () => void;
|
|
13
13
|
}
|
|
14
|
-
/** Hosts
|
|
14
|
+
/** Hosts control the draft from outside, e.g. to insert a template for editing. */
|
|
15
15
|
export interface ChatInputHandle {
|
|
16
|
-
/**
|
|
16
|
+
/** Replaces the draft, focuses the input and puts the caret at the end. */
|
|
17
17
|
insert: (text: string) => void;
|
|
18
18
|
focus: () => void;
|
|
19
19
|
reset: () => void;
|
|
@@ -22,11 +22,11 @@ export interface ChatInputHandle {
|
|
|
22
22
|
}) => Promise<void>;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
25
|
+
* The input card: borderless textarea on top, toolbar below a hairline. Custom buttons come
|
|
26
|
+
* declaratively through `actions` or freely through the left/right slots; while work is running
|
|
27
|
+
* the stop button stays visible, and typing shows the send button next to it, which then
|
|
28
|
+
* interjects. `rows` sets the initial height; with `maxRows` the textarea grows with its content
|
|
29
|
+
* up to that number of rows and scrolls after that.
|
|
30
30
|
*/
|
|
31
31
|
export declare function ChatInputToolbar({ onSend, onStop, running, disabled, sendDisabled, layout, detailsContainer, onErrorChange, inputAriaControls, rows, maxRows, actions, toolbarLeft, toolbarRight, texts, handleRef, attachments: attachmentsEnabled, attachmentCapabilities, attachmentCapabilitiesError, onAttachmentsChange, initialValue, onDraftChange, sendShortcut, autoFocus, onAutoFocusSettled, }: {
|
|
32
32
|
onSend: (text: string, attachments?: ChatAttachmentInput[]) => void | Promise<void>;
|
|
@@ -45,7 +45,7 @@ export declare function ChatInputToolbar({ onSend, onStop, running, disabled, se
|
|
|
45
45
|
toolbarRight?: ReactNode;
|
|
46
46
|
texts?: Partial<ChatTexts>;
|
|
47
47
|
handleRef?: Ref<ChatInputHandle>;
|
|
48
|
-
/** false =
|
|
48
|
+
/** false = no attaching: neither button nor dropping or pasting files. */
|
|
49
49
|
attachments?: boolean;
|
|
50
50
|
attachmentCapabilities?: {
|
|
51
51
|
input: readonly string[];
|
package/ChatMessages.d.ts
CHANGED
|
@@ -4,34 +4,34 @@ import { ChatTexts } from "./texts";
|
|
|
4
4
|
import { type LinkClickHandler } from "./Markdown";
|
|
5
5
|
import { type ChatAppearance, type TimestampOptions, type CodeBlockOptions, type BubbleOptions, type MessageActionsOptions } from "./options";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The transcript: its own scroll container with autoscroll while you are at the bottom, and a
|
|
8
|
+
* jump-to-end button as soon as you are reading. Usable read-only without a composer.
|
|
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
|
+
/** Contributions with this Message.sender appear without a speech bubble; null keeps the message defaults. */
|
|
13
13
|
owner?: string | null;
|
|
14
14
|
detailMode?: DetailMode;
|
|
15
15
|
running?: boolean;
|
|
16
16
|
working?: ReactNode;
|
|
17
|
-
/** false =
|
|
17
|
+
/** false = thinking and tool steps cannot be expanded (no popover). */
|
|
18
18
|
stepsExpandable?: boolean;
|
|
19
19
|
texts?: Partial<ChatTexts>;
|
|
20
20
|
toolArgumentsText?: (tool: ToolInfo) => string;
|
|
21
|
-
/**
|
|
21
|
+
/** Custom rendering for single tools; undefined = default rendering. */
|
|
22
22
|
renderTool?: (tool: ToolInfo) => ReactNode | undefined;
|
|
23
|
-
/**
|
|
23
|
+
/** Rendering of a pending action by its owner; undefined = generic card. */
|
|
24
24
|
renderAction?: (action: PendingAction, text: string) => ReactNode | undefined;
|
|
25
25
|
onDismissAction?: (actionId: string) => void;
|
|
26
|
-
/**
|
|
26
|
+
/** Intercepts clicks on Markdown links; true = handled, the browser does not follow. */
|
|
27
27
|
onLinkClick?: LinkClickHandler;
|
|
28
28
|
emptyState?: ReactNode;
|
|
29
29
|
className?: string;
|
|
30
|
-
/**
|
|
30
|
+
/** Host classes for the scroll container (data-quassel-transcript). */
|
|
31
31
|
scrollerClassName?: string;
|
|
32
|
-
/** true =
|
|
32
|
+
/** true = subtle HH:MM column to the left of each block (Message.at). */
|
|
33
33
|
showTimestamps?: boolean;
|
|
34
|
-
/**
|
|
34
|
+
/** Screen reader announcement of finished answers and new actions; false = the host announces itself. */
|
|
35
35
|
announce?: false | ((announcement: ChatAnnouncement) => string | undefined);
|
|
36
36
|
/** Exposes the scroll container so hosts can scroll without querying the DOM. */
|
|
37
37
|
scrollerRef?: (element: HTMLDivElement | null) => void;
|
|
@@ -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/ChatPanel.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { type CSSProperties, type ReactNode, type Ref } from "react";
|
|
2
2
|
import { type ChatAppearance } from "./options";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Transcript with an overlaid composer. The frame measures the composer itself and gives the
|
|
5
|
+
* transcript the footer space it needs, so no host has to set inner dimensions.
|
|
6
6
|
*/
|
|
7
7
|
export declare function ChatPanel({ children, composer, className, nodeRef, maxWidth, horizontalPadding, appearance, scrollOnSend, }: {
|
|
8
8
|
children: ReactNode;
|
|
9
|
-
/**
|
|
9
|
+
/** The composer; it lies over the transcript, not below it. */
|
|
10
10
|
composer?: ReactNode;
|
|
11
11
|
className?: string;
|
|
12
12
|
nodeRef?: Ref<HTMLDivElement>;
|
package/DetailModeSwitch.d.ts
CHANGED
|
@@ -3,16 +3,14 @@ import { ChatTexts } from "./texts";
|
|
|
3
3
|
export declare const DETAIL_MODES: readonly DetailMode[];
|
|
4
4
|
export declare const detailModeLabel: (mode: DetailMode, texts?: Partial<ChatTexts>) => string;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* An icon button that cycles the detail level of the thinking and tool steps. The order comes
|
|
7
|
+
* from `modes`, the current value shows in the icon and in the tooltip.
|
|
8
8
|
*/
|
|
9
|
-
export declare function DetailModeSwitch({ mode, onChange, modes,
|
|
9
|
+
export declare function DetailModeSwitch({ mode, onChange, modes, texts, className, }: {
|
|
10
10
|
mode: DetailMode;
|
|
11
11
|
onChange: (mode: DetailMode) => void;
|
|
12
|
-
/**
|
|
12
|
+
/** Selection and order of cycling; the default is all modes. */
|
|
13
13
|
modes?: readonly DetailMode[];
|
|
14
|
-
/** false = die Beschriftung bleibt auch in einer schmalen Eingabe-Karte stehen. */
|
|
15
|
-
collapsible?: boolean;
|
|
16
14
|
texts?: Partial<ChatTexts>;
|
|
17
15
|
className?: string;
|
|
18
16
|
}): import("react").JSX.Element;
|
package/Markdown.d.ts
CHANGED
|
@@ -15,5 +15,5 @@ export declare function Markdown({ text, streaming }: {
|
|
|
15
15
|
text: string;
|
|
16
16
|
streaming?: boolean;
|
|
17
17
|
}): import("react").JSX.Element;
|
|
18
|
-
/**
|
|
18
|
+
/** The same syntax as the renderer, as plain text without markup, e.g. for screen reader announcements. */
|
|
19
19
|
export declare function markdownPlainText(text: string): string;
|
package/PendingActionCard.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PendingAction } from "./types";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* The rendering of a pending action without a contribution from its owner: title, the hint
|
|
4
|
+
* that input is expected, and dismissing. Only the plugin knows the form.
|
|
5
5
|
*/
|
|
6
6
|
export declare function PendingActionCard({ text, action, waitingLabel, dismissLabel, dismissedLabel, onDismiss, }: {
|
|
7
7
|
text: string;
|
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
|
|
|
@@ -43,7 +46,7 @@ variables too.
|
|
|
43
46
|
- Timestamps, day separators, message actions, configurable bubbles and appearance.
|
|
44
47
|
- Screen reader friendly: the transcript is not a live region; finished replies and new
|
|
45
48
|
actions are announced once, with the host in control of the wording.
|
|
46
|
-
- All labels are replaceable through `texts` (defaults are
|
|
49
|
+
- All labels are replaceable through `texts` (defaults are English).
|
|
47
50
|
|
|
48
51
|
## Install
|
|
49
52
|
|
|
@@ -89,12 +92,13 @@ 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
|
```
|
|
95
99
|
|
|
96
100
|
The full guide - every prop, the event contract, theming, slots, dark mode and publishing -
|
|
97
|
-
is in [docs/guide.md](https://github.com/SchlenkR/quassel/blob/main/docs/guide.md)
|
|
101
|
+
is in [docs/guide.md](https://github.com/SchlenkR/quassel/blob/main/docs/guide.md). It is written so that an AI assistant can build
|
|
98
102
|
an application with quassel from it.
|
|
99
103
|
|
|
100
104
|
## License
|
package/TimestampSwitch.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { type ChatTexts } from "./texts";
|
|
2
|
-
export declare function TimestampSwitch({ showTimestamps, onChange,
|
|
2
|
+
export declare function TimestampSwitch({ showTimestamps, onChange, texts, className }: {
|
|
3
3
|
showTimestamps: boolean;
|
|
4
4
|
onChange: (showTimestamps: boolean) => void;
|
|
5
|
-
collapsible?: boolean;
|
|
6
5
|
texts?: Partial<ChatTexts>;
|
|
7
6
|
className?: string;
|
|
8
7
|
}): import("react").JSX.Element;
|
package/WorkingScenes.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export interface WorkingScenesProps {
|
|
2
2
|
/** Limits scenes to one or two visible characters for small status fields. */
|
|
3
3
|
compact?: boolean;
|
|
4
|
-
/** Text
|
|
4
|
+
/** Text for screen readers, default "Working ...". */
|
|
5
5
|
label?: string;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* The default working indicator: randomly rotating mini scenes. Random order and start phase,
|
|
9
|
+
* so the same scenes never feel like a loop. The working prop of ChatMessages replaces it
|
|
10
|
+
* completely.
|
|
11
11
|
*/
|
|
12
12
|
export declare function WorkingScenes({ label, compact }: WorkingScenesProps): import("react").JSX.Element;
|
package/announce.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Announces text through a shared, hidden live region: throttled, and paused while the tab is hidden. */
|
|
2
2
|
export declare function announce(text: string): void;
|
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
|
}
|
|
@@ -43,29 +43,29 @@ var chatAttachmentMediaType = (name, mediaType) => {
|
|
|
43
43
|
var parseChatAttachments = (value) => {
|
|
44
44
|
if (value === void 0) return [];
|
|
45
45
|
if (!Array.isArray(value) || value.length > MAX_CHAT_ATTACHMENTS) {
|
|
46
|
-
throw new Error(`
|
|
46
|
+
throw new Error(`Attachments must be a list of at most ${MAX_CHAT_ATTACHMENTS} files.`);
|
|
47
47
|
}
|
|
48
48
|
let total = 0;
|
|
49
49
|
return value.map((entry, index) => {
|
|
50
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw new Error(`
|
|
50
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw new Error(`Attachment ${index + 1} is invalid.`);
|
|
51
51
|
const { name, mediaType, data } = entry;
|
|
52
|
-
if (typeof name !== "string" || !name.trim() || name.length > 255 || /[/\\\u0000-\u001f\u007f]/.test(name) || name === "." || name === "..") throw new Error(`
|
|
52
|
+
if (typeof name !== "string" || !name.trim() || name.length > 255 || /[/\\\u0000-\u001f\u007f]/.test(name) || name === "." || name === "..") throw new Error(`Attachment ${index + 1} needs a valid file name without a path.`);
|
|
53
53
|
const normalizedType = typeof mediaType === "string" ? chatAttachmentMediaType(name, mediaType) : "";
|
|
54
54
|
if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/i.test(normalizedType)) {
|
|
55
|
-
throw new Error(`
|
|
55
|
+
throw new Error(`Attachment ${name} needs a valid MIME type.`);
|
|
56
56
|
}
|
|
57
57
|
if (typeof data === "string" && data.length > Math.ceil(MAX_CHAT_ATTACHMENT_BYTES / 3) * 4)
|
|
58
|
-
throw new Error("
|
|
58
|
+
throw new Error("Attachments may be at most 20 MiB in total.");
|
|
59
59
|
if (typeof data !== "string" || data.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(data)) {
|
|
60
|
-
throw new Error(`
|
|
60
|
+
throw new Error(`Attachment ${name} contains invalid Base64.`);
|
|
61
61
|
}
|
|
62
62
|
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
|
|
63
63
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
64
64
|
if (padding && (alphabet.indexOf(data[data.length - padding - 1]) & (padding === 2 ? 15 : 3)) !== 0) {
|
|
65
|
-
throw new Error(`
|
|
65
|
+
throw new Error(`Attachment ${name} contains non-canonical Base64.`);
|
|
66
66
|
}
|
|
67
67
|
total += data.length / 4 * 3 - padding;
|
|
68
|
-
if (total > MAX_CHAT_ATTACHMENT_BYTES) throw new Error("
|
|
68
|
+
if (total > MAX_CHAT_ATTACHMENT_BYTES) throw new Error("Attachments may be at most 20 MiB in total.");
|
|
69
69
|
return { name, mediaType: normalizedType, data };
|
|
70
70
|
});
|
|
71
71
|
};
|
|
@@ -180,4 +180,4 @@ export {
|
|
|
180
180
|
prettyJson,
|
|
181
181
|
compactToolLine
|
|
182
182
|
};
|
|
183
|
-
//# sourceMappingURL=chunk-
|
|
183
|
+
//# sourceMappingURL=chunk-3VEL4HEO.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/chat-attachments.ts", "../src/events.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ChatAttachmentInput } from \"./events\";\n\nexport const MAX_CHAT_ATTACHMENTS = 8;\nexport const MAX_CHAT_ATTACHMENT_BYTES = 20 * 1024 * 1024;\nexport const MAX_CHAT_REQUEST_BYTES = 29 * 1024 * 1024;\n\nexport const chatAttachmentMediaType = (name: string, mediaType: string): string => {\n const explicit = mediaType.trim().toLowerCase();\n if (explicit) return explicit === \"video/quicktime\" ? \"video/mov\" : explicit;\n const types: Record<string, string> = {\n png: \"image/png\", jpg: \"image/jpeg\", jpeg: \"image/jpeg\", gif: \"image/gif\", webp: \"image/webp\",\n avif: \"image/avif\", svg: \"image/svg+xml\", pdf: \"application/pdf\",\n mp4: \"video/mp4\", webm: \"video/webm\", mov: \"video/mov\", mpeg: \"video/mpeg\", mpg: \"video/mpeg\",\n txt: \"text/plain\", md: \"text/markdown\", csv: \"text/csv\", tsv: \"text/tab-separated-values\",\n json: \"application/json\", jsonl: \"application/x-ndjson\", xml: \"application/xml\",\n yaml: \"application/yaml\", yml: \"application/yaml\", ts: \"text/plain\", tsx: \"text/plain\",\n js: \"application/javascript\", py: \"text/plain\", cs: \"text/plain\", fs: \"text/plain\",\n html: \"text/html\", css: \"text/css\", log: \"text/plain\",\n };\n return types[name.split(\".\").at(-1)?.toLowerCase() ?? \"\"] ?? \"application/octet-stream\";\n};\n\nexport const parseChatAttachments = (value: unknown): ChatAttachmentInput[] => {\n if (value === undefined) return [];\n if (!Array.isArray(value) || value.length > MAX_CHAT_ATTACHMENTS) {\n throw new Error(`Attachments must be a list of at most ${MAX_CHAT_ATTACHMENTS} files.`);\n }\n let total = 0;\n return value.map((entry, index) => {\n if (!entry || typeof entry !== \"object\" || Array.isArray(entry)) throw new Error(`Attachment ${index + 1} is invalid.`);\n const { name, mediaType, data } = entry as Record<string, unknown>;\n if (typeof name !== \"string\" || !name.trim() || name.length > 255 || /[/\\\\\\u0000-\\u001f\\u007f]/.test(name)\n || name === \".\" || name === \"..\") throw new Error(`Attachment ${index + 1} needs a valid file name without a path.`);\n const normalizedType = typeof mediaType === \"string\" ? chatAttachmentMediaType(name, mediaType) : \"\";\n if (!/^[a-z0-9!#$&^_.+-]+\\/[a-z0-9!#$&^_.+-]+$/i.test(normalizedType)) {\n throw new Error(`Attachment ${name} needs a valid MIME type.`);\n }\n if (typeof data === \"string\" && data.length > Math.ceil(MAX_CHAT_ATTACHMENT_BYTES / 3) * 4)\n throw new Error(\"Attachments may be at most 20 MiB in total.\");\n if (typeof data !== \"string\" || data.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(data)) {\n throw new Error(`Attachment ${name} contains invalid Base64.`);\n }\n const padding = data.endsWith(\"==\") ? 2 : data.endsWith(\"=\") ? 1 : 0;\n const alphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n if (padding && (alphabet.indexOf(data[data.length - padding - 1]) & (padding === 2 ? 15 : 3)) !== 0) {\n throw new Error(`Attachment ${name} contains non-canonical Base64.`);\n }\n total += data.length / 4 * 3 - padding;\n if (total > MAX_CHAT_ATTACHMENT_BYTES) throw new Error(\"Attachments may be at most 20 MiB in total.\");\n return { name, mediaType: normalizedType, data };\n });\n};\n", "export type Role = \"user\" | \"assistant\" | \"thinking\" | \"tool\" | \"system\" | \"action\";\n\nexport interface ChatAttachmentInput {\n name: string;\n mediaType: string;\n data: string;\n}\n\nexport interface ChatAttachment {\n name: string;\n mediaType: string;\n size: number;\n url: string;\n}\n\nexport interface ChatAttachmentCapabilities {\n input: readonly string[];\n model: string;\n}\n\nexport interface ToolInfo {\n id: string;\n name: string;\n arguments: string;\n result?: string;\n isError?: boolean;\n}\n\nexport interface ChatTextCursor {\n conversationId: string;\n sequence: number;\n /** Accumulated non-whitespace UTF-16 units in the turn anchored by sequence. */\n offset: number;\n}\n\nexport interface ChatJournalCursor {\n conversationId: string;\n eventId: string;\n sequence: number;\n}\n\n/**\n * An action waiting for input from the user. Shape and content of `payload` and `result`\n * belong to the plugin in `owner`; a set `status` means: done, the card is a record.\n */\nexport interface PendingAction {\n actionId: string;\n owner: string | null;\n payload: unknown;\n status?: \"approved\" | \"dismissed\";\n result?: unknown;\n}\n\nexport interface Message {\n key: string;\n role: Role;\n /** Sender identifier for display; independent of role and bubble label. */\n sender?: string;\n text: string;\n textCursor?: ChatTextCursor;\n closed?: boolean;\n attachments?: ChatAttachment[];\n tool?: ToolInfo;\n action?: PendingAction;\n /** ISO timestamp of the message; display optional (ChatMessages showTimestamps). */\n at?: string;\n /** The ActorInput behind an incoming message; the steering mark hangs on it. */\n inputId?: string;\n /** The message was fed into an already running turn instead of starting its own. */\n steered?: boolean;\n /** Colored speech bubble instead of running text, e.g. for multi-party conversations. */\n bubble?: { color: string; side: \"start\" | \"end\"; label?: string };\n}\n\nexport interface ChatStartupStatus {\n status: \"preparing\" | \"failed\";\n message: string;\n}\n\nexport type ChatEvent =\n | { kind: \"reset\"; reason?: \"conversation-reset\"; conversationId: string | null }\n | { kind: \"replay-end\"; conversationId: string | null }\n | { kind: \"user\"; text: string; inputId?: string; at?: string; attachments?: ChatAttachment[] }\n | { kind: \"steered\"; inputId: string }\n | { kind: \"text\"; delta: string; at?: string; cursor: ChatTextCursor }\n | { kind: \"thinking\"; delta: string; at?: string }\n | { kind: \"tool\"; id: string; name: string; arguments: string; label?: string; at?: string }\n | { kind: \"tool-result\"; id: string; result: string; isError?: boolean }\n | { kind: \"action\"; actionId: string; owner: string | null; text: string; payload: unknown; at?: string }\n | { kind: \"action-resolved\"; actionId: string; status: \"approved\" | \"dismissed\"; result: unknown }\n | { kind: \"system\"; text: string; at?: string }\n | { kind: \"status\"; running: boolean; startup?: ChatStartupStatus }\n | { kind: \"turn-done\" }\n | { kind: \"plugin\"; pluginId: string; type: string; payload?: unknown; at?: string; journal?: ChatJournalCursor };\n\nlet keyCounter = 0;\n\n// crypto.randomUUID is missing outside secure contexts, e.g. for a host served over http.\nconst newKey = (): string => typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\"\n ? crypto.randomUUID()\n : `qsl-${Date.now().toString(36)}-${(keyCounter += 1).toString(36)}`;\n\nfunction openMessageIndex(messages: Message[]): number {\n for (let index = messages.length - 1; index >= 0; index--) {\n const message = messages[index];\n if (!message.closed && (message.role === \"assistant\" || message.role === \"thinking\")) return index;\n }\n return -1;\n}\n\nfunction closeOpen(messages: Message[]): Message[] {\n const index = openMessageIndex(messages);\n if (index === -1) {\n return messages;\n }\n return messages.map((message, position) => position === index ? { ...message, closed: true } : message);\n}\n\nfunction appendDelta(messages: Message[], role: Role, delta: string, at?: string, textCursor?: ChatTextCursor): Message[] {\n const index = openMessageIndex(messages);\n const current = messages[index];\n if (current && current.role === role\n && current.textCursor?.conversationId === textCursor?.conversationId\n && current.textCursor?.sequence === textCursor?.sequence) {\n return messages.map((message, position) => position === index\n ? { ...message, text: message.text + delta, ...(textCursor ? { textCursor } : {}) }\n : message);\n }\n return [...closeOpen(messages), { key: newKey(), role, text: delta, at, ...(textCursor ? { textCursor } : {}) }];\n}\n\n/** Incoming messages leave the running output block open. */\nexport function applyEvent(messages: Message[], event: ChatEvent): Message[] {\n switch (event.kind) {\n case \"reset\":\n return [];\n case \"user\":\n return [...messages, { key: newKey(), role: \"user\", text: event.text, closed: true, at: event.at,\n ...(event.inputId ? { inputId: event.inputId } : {}),\n ...(event.attachments?.length ? { attachments: event.attachments } : {}) }];\n case \"steered\":\n return messages.map((message) => message.inputId === event.inputId ? { ...message, steered: true } : message);\n case \"text\":\n return appendDelta(messages, \"assistant\", event.delta, event.at, event.cursor);\n case \"thinking\":\n return appendDelta(messages, \"thinking\", event.delta, event.at);\n case \"tool\":\n return [\n ...closeOpen(messages),\n {\n key: newKey(),\n role: \"tool\",\n text: event.label ?? event.name,\n closed: true,\n tool: { id: event.id, name: event.name, arguments: event.arguments },\n at: event.at,\n },\n ];\n case \"tool-result\": {\n const index = messages.map((message) => message.tool?.id).lastIndexOf(event.id);\n return messages.map((message, position) =>\n position === index && message.tool\n ? { ...message, tool: { ...message.tool, result: event.result, isError: event.isError } }\n : message,\n );\n }\n case \"action\":\n return [\n ...closeOpen(messages),\n {\n key: newKey(),\n role: \"action\",\n text: event.text,\n closed: true,\n action: { actionId: event.actionId, owner: event.owner, payload: event.payload },\n at: event.at,\n },\n ];\n case \"action-resolved\":\n return messages.map((message) =>\n message.action?.actionId === event.actionId\n ? { ...message, action: { ...message.action, status: event.status, result: event.result } }\n : message,\n );\n case \"system\":\n return [...closeOpen(messages), { key: newKey(), role: \"system\", text: event.text, closed: true, at: event.at }];\n case \"turn-done\":\n return closeOpen(messages);\n default:\n return messages;\n }\n}\n\nexport function prettyJson(value: string): string {\n try {\n return JSON.stringify(JSON.parse(value), null, 2);\n } catch {\n return value;\n }\n}\n\nexport function compactToolLine(tool: ToolInfo): string {\n const args = tool.arguments.replace(/\\s+/g, \" \").trim();\n const line = args && args !== \"{}\" ? `${tool.name} ${args}` : tool.name;\n return line.length > 160 ? `${line.slice(0, 160)} ...` : line;\n}\n\nexport { MAX_CHAT_ATTACHMENTS, MAX_CHAT_ATTACHMENT_BYTES, MAX_CHAT_REQUEST_BYTES, chatAttachmentMediaType, parseChatAttachments } from \"./chat-attachments\";\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
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/events.d.ts
CHANGED
|
@@ -33,8 +33,8 @@ export interface ChatJournalCursor {
|
|
|
33
33
|
sequence: number;
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* An action waiting for input from the user. Shape and content of `payload` and `result`
|
|
37
|
+
* belong to the plugin in `owner`; a set `status` means: done, the card is a record.
|
|
38
38
|
*/
|
|
39
39
|
export interface PendingAction {
|
|
40
40
|
actionId: string;
|
|
@@ -46,7 +46,7 @@ export interface PendingAction {
|
|
|
46
46
|
export interface Message {
|
|
47
47
|
key: string;
|
|
48
48
|
role: Role;
|
|
49
|
-
/**
|
|
49
|
+
/** Sender identifier for display; independent of role and bubble label. */
|
|
50
50
|
sender?: string;
|
|
51
51
|
text: string;
|
|
52
52
|
textCursor?: ChatTextCursor;
|
|
@@ -54,13 +54,13 @@ export interface Message {
|
|
|
54
54
|
attachments?: ChatAttachment[];
|
|
55
55
|
tool?: ToolInfo;
|
|
56
56
|
action?: PendingAction;
|
|
57
|
-
/** ISO
|
|
57
|
+
/** ISO timestamp of the message; display optional (ChatMessages showTimestamps). */
|
|
58
58
|
at?: string;
|
|
59
|
-
/**
|
|
59
|
+
/** The ActorInput behind an incoming message; the steering mark hangs on it. */
|
|
60
60
|
inputId?: string;
|
|
61
|
-
/**
|
|
61
|
+
/** The message was fed into an already running turn instead of starting its own. */
|
|
62
62
|
steered?: boolean;
|
|
63
|
-
/**
|
|
63
|
+
/** Colored speech bubble instead of running text, e.g. for multi-party conversations. */
|
|
64
64
|
bubble?: {
|
|
65
65
|
color: string;
|
|
66
66
|
side: "start" | "end";
|
|
@@ -138,7 +138,7 @@ export type ChatEvent = {
|
|
|
138
138
|
at?: string;
|
|
139
139
|
journal?: ChatJournalCursor;
|
|
140
140
|
};
|
|
141
|
-
/**
|
|
141
|
+
/** Incoming messages leave the running output block open. */
|
|
142
142
|
export declare function applyEvent(messages: Message[], event: ChatEvent): Message[];
|
|
143
143
|
export declare function prettyJson(value: string): string;
|
|
144
144
|
export declare function compactToolLine(tool: ToolInfo): string;
|
package/events.js
CHANGED
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";
|