quassel 0.3.0 → 0.4.1

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.
@@ -1,2 +1,2 @@
1
- /** Ziel offener Aktionen: undefined = im Verlauf, null = das Dock über der Eingabe ist noch nicht eingehängt. */
1
+ /** Target of open actions: undefined = in the transcript, null = the dock above the input is not mounted yet. */
2
2
  export declare const ChatActionDock: import("react").Context<HTMLElement | null | undefined>;
@@ -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
- /** Ein Toolbar-Knopf, deklarativ: Symbol, Text oder beides - plus Klick-Handler. */
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 steuern den Entwurf von aussen, z.B. um eine Vorlage zum Bearbeiten einzusetzen. */
14
+ /** Hosts control the draft from outside, e.g. to insert a template for editing. */
15
15
  export interface ChatInputHandle {
16
- /** Ersetzt den Entwurf, fokussiert die Eingabe und stellt die Schreibmarke ans Ende. */
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
- * Die Eingabe-Karte: rahmenlose Textarea oben, Toolbar unter einer Haarlinie. Eigene
26
- * Knöpfe kommen deklarativ über `actions` oder frei über die Slots links/rechts; bei
27
- * laufender Arbeit bleibt der Stop-Knopf sichtbar, und Tippen zeigt daneben den Senden-Knopf,
28
- * der zum Dazwischenfunken wird. `rows` bestimmt die Starthöhe; mit `maxRows` wächst die Textarea
29
- * mit dem Inhalt bis zu dieser Zeilenzahl und scrollt danach.
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 = kein Anhängen: weder Knopf noch Ablegen oder Einfügen von Dateien. */
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,36 @@ 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
- * Der Verlauf: eigener Scroll-Container mit Autoscroll, solange man unten ist,
8
- * und einem Zum-Ende-Knopf, sobald man liest. Ohne Eingabe read-only nutzbar.
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, toolElapsedThreshold, }: {
10
+ export declare function ChatMessages({ messages, owner, detailMode, running, working, stepsExpandable, groupsExpandable, 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
- /** Beiträge mit diesem Message.sender erscheinen ohne Sprechblase; null behält die Nachrichtenvorgaben. */
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 = Denk- und Werkzeug-Schritte lassen sich nicht aufklappen (kein Popover). */
17
+ /** false = thinking and tool steps cannot be expanded (no popover). */
18
18
  stepsExpandable?: boolean;
19
+ /** false = step groups in grouped mode stay collapsed (header without toggle). */
20
+ groupsExpandable?: boolean;
19
21
  texts?: Partial<ChatTexts>;
20
22
  toolArgumentsText?: (tool: ToolInfo) => string;
21
- /** Eigene Darstellung fuer einzelne Werkzeuge; undefined = Standarddarstellung. */
23
+ /** Custom rendering for single tools; undefined = default rendering. */
22
24
  renderTool?: (tool: ToolInfo) => ReactNode | undefined;
23
- /** Darstellung einer wartenden Aktion durch ihren Eigentümer; undefined = generische Karte. */
25
+ /** Rendering of a pending action by its owner; undefined = generic card. */
24
26
  renderAction?: (action: PendingAction, text: string) => ReactNode | undefined;
25
27
  onDismissAction?: (actionId: string) => void;
26
- /** Faengt Klicks auf Markdown-Links ab; true = behandelt, der Browser folgt nicht. */
28
+ /** Intercepts clicks on Markdown links; true = handled, the browser does not follow. */
27
29
  onLinkClick?: LinkClickHandler;
28
30
  emptyState?: ReactNode;
29
31
  className?: string;
30
- /** Klassen des Hosts für den Scroll-Container (data-quassel-transcript). */
32
+ /** Host classes for the scroll container (data-quassel-transcript). */
31
33
  scrollerClassName?: string;
32
- /** true = dezente HH:MM-Spalte links an jedem Block (Message.at). */
34
+ /** true = subtle HH:MM column to the left of each block (Message.at). */
33
35
  showTimestamps?: boolean;
34
- /** Screenreader-Ansage fertiger Antworten und neuer Aktionen; false = der Host sagt selbst an. */
36
+ /** Screen reader announcement of finished answers and new actions; false = the host announces itself. */
35
37
  announce?: false | ((announcement: ChatAnnouncement) => string | undefined);
36
38
  /** Exposes the scroll container so hosts can scroll without querying the DOM. */
37
39
  scrollerRef?: (element: HTMLDivElement | null) => void;
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
- * Verlauf mit ueberlagerter Eingabe. Der Rahmen misst die Eingabe selbst und
5
- * gibt dem Verlauf den noetigen Fussraum, damit kein Host Innenmasse setzen muss.
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
- /** Die Eingabe; sie liegt ueber dem Verlauf, nicht darunter. */
9
+ /** The composer; it lies over the transcript, not below it. */
10
10
  composer?: ReactNode;
11
11
  className?: string;
12
12
  nodeRef?: Ref<HTMLDivElement>;
@@ -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
- * Ein Knopf, der den Detailgrad der Denk- und Werkzeug-Schritte weiterschaltet. Die
7
- * Reihenfolge kommt aus `modes`, der aktuelle Wert steht als Beschriftung daneben.
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, collapsible, texts, className, }: {
9
+ export declare function DetailModeSwitch({ mode, onChange, modes, texts, className, }: {
10
10
  mode: DetailMode;
11
11
  onChange: (mode: DetailMode) => void;
12
- /** Auswahl und Reihenfolge des Weiterschaltens; Default sind alle Modi. */
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
- /** Dieselbe Syntax wie der Renderer, als Klartext ohne Markup, etwa für Screenreader-Ansagen. */
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;
@@ -1,7 +1,7 @@
1
1
  import type { PendingAction } from "./types";
2
2
  /**
3
- * Die Darstellung einer wartenden Aktion ohne Beitrag ihres Eigentümers: Titel, der Hinweis,
4
- * dass eine Eingabe erwartet wird, und das Verwerfen. Die Form kennt nur das Plugin.
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
@@ -46,7 +46,7 @@ variables too.
46
46
  - Timestamps, day separators, message actions, configurable bubbles and appearance.
47
47
  - Screen reader friendly: the transcript is not a live region; finished replies and new
48
48
  actions are announced once, with the host in control of the wording.
49
- - All labels are replaceable through `texts` (defaults are German).
49
+ - All labels are replaceable through `texts` (defaults are English).
50
50
 
51
51
  ## Install
52
52
 
@@ -98,7 +98,7 @@ pnpm release --dry-run
98
98
  ```
99
99
 
100
100
  The full guide - every prop, the event contract, theming, slots, dark mode and publishing -
101
- is in [docs/guide.md](https://github.com/SchlenkR/quassel/blob/main/docs/guide.md) (German). It is written so that an AI assistant can build
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
102
102
  an application with quassel from it.
103
103
 
104
104
  ## License
@@ -1,8 +1,7 @@
1
1
  import { type ChatTexts } from "./texts";
2
- export declare function TimestampSwitch({ showTimestamps, onChange, collapsible, texts, className }: {
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;
@@ -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 fuer Screenreader, Default "Arbeitet ...". */
4
+ /** Text for screen readers, default "Working ...". */
5
5
  label?: string;
6
6
  }
7
7
  /**
8
- * Der Default-Working-Indikator: zufaellig rotierende Mini-Szenen. Zufaellige Reihenfolge
9
- * und Startphase, damit dieselben Szenen nie wie eine Schleife wirken. Ueber die
10
- * working-Prop von ChatMessages laesst er sich komplett ersetzen.
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
- /** Sagt Text über eine gemeinsame, versteckte Live-Region an: gedrosselt, und bei verstecktem Tab angehalten. */
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;
@@ -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(`Anh\xE4nge m\xFCssen eine Liste mit h\xF6chstens ${MAX_CHAT_ATTACHMENTS} Dateien sein.`);
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(`Anhang ${index + 1} ist ung\xFCltig.`);
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(`Anhang ${index + 1} ben\xF6tigt einen g\xFCltigen Dateinamen ohne Pfad.`);
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(`Anhang ${name} ben\xF6tigt einen g\xFCltigen MIME-Typ.`);
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("Anh\xE4nge d\xFCrfen zusammen h\xF6chstens 20 MiB gro\xDF sein.");
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(`Anhang ${name} enth\xE4lt ung\xFCltiges Base64.`);
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(`Anhang ${name} enth\xE4lt nichtkanonisches Base64.`);
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("Anh\xE4nge d\xFCrfen zusammen h\xF6chstens 20 MiB gro\xDF sein.");
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-IGCFQJZB.js.map
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/events.d.ts CHANGED
@@ -33,8 +33,8 @@ export interface ChatJournalCursor {
33
33
  sequence: number;
34
34
  }
35
35
  /**
36
- * Eine Aktion, die auf eine Eingabe des Benutzers wartet. Form und Inhalt von `payload` und
37
- * `result` gehören dem Plugin in `owner`; `status` gesetzt heißt: erledigt, die Karte ist Beleg.
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
- /** Absenderkennung für die Darstellung; unabhängig von Rolle und Sprechblasenlabel. */
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-Zeitpunkt der Nachricht; Anzeige optional (ChatMessages showTimestamps). */
57
+ /** ISO timestamp of the message; display optional (ChatMessages showTimestamps). */
58
58
  at?: string;
59
- /** Der ActorInput hinter einer eingehenden Nachricht; an ihm hängt die Steering-Markierung. */
59
+ /** The ActorInput behind an incoming message; the steering mark hangs on it. */
60
60
  inputId?: string;
61
- /** Die Nachricht ist in einen schon laufenden Turn eingespeist worden, statt einen eigenen zu beginnen. */
61
+ /** The message was fed into an already running turn instead of starting its own. */
62
62
  steered?: boolean;
63
- /** Farbige Sprechblase statt Fliesstext, z.B. fuer Mehrparteien-Gespraeche. */
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
- /** Eingehende Nachrichten lassen den laufenden Ausgabeblock offen. */
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
@@ -7,7 +7,7 @@ import {
7
7
  compactToolLine,
8
8
  parseChatAttachments,
9
9
  prettyJson
10
- } from "./chunk-IGCFQJZB.js";
10
+ } from "./chunk-3VEL4HEO.js";
11
11
  export {
12
12
  MAX_CHAT_ATTACHMENTS,
13
13
  MAX_CHAT_ATTACHMENT_BYTES,