@schlenkr/quassel 0.0.0-stage → 0.5.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.
- package/ChatActionDock.d.ts +2 -0
- package/ChatInputToolbar.d.ts +61 -0
- package/ChatMessages.d.ts +51 -0
- package/ChatPanel.d.ts +17 -0
- package/ChatSendContext.d.ts +7 -0
- package/DetailModeSwitch.d.ts +16 -0
- package/LICENSE +21 -0
- package/Markdown.d.ts +19 -0
- package/MessageActions.d.ts +8 -0
- package/PendingActionCard.d.ts +13 -0
- package/QuasselProvider.d.ts +89 -0
- package/README.md +135 -2
- package/StepPopover.d.ts +16 -0
- package/TimestampSwitch.d.ts +7 -0
- package/TranscriptModeSwitch.d.ts +8 -0
- package/WorkingScenes.d.ts +12 -0
- package/announce.d.ts +2 -0
- package/attachments.d.ts +24 -0
- package/chat-attachments.d.ts +6 -0
- package/chat-scroll.d.ts +12 -0
- package/chat.css +2080 -0
- package/chunk-3VEL4HEO.js +183 -0
- package/chunk-3VEL4HEO.js.map +7 -0
- package/elapsed.d.ts +16 -0
- package/events.d.ts +145 -0
- package/events.js +21 -0
- package/events.js.map +7 -0
- package/index.d.ts +25 -0
- package/index.js +2586 -0
- package/index.js.map +7 -0
- package/options.d.ts +48 -0
- package/package.json +53 -4
- package/texts.d.ts +71 -0
- package/timestamps.d.ts +7 -0
- package/types.d.ts +23 -0
- package/ui/button.d.ts +8 -0
- package/ui/card.d.ts +5 -0
- package/ui/cn.d.ts +2 -0
- package/ui/popover.d.ts +6 -0
- package/ui/spinner.d.ts +2 -0
- package/ui/stop-button.d.ts +10 -0
- package/ui/toggle.d.ts +8 -0
- package/ui/useFileInput.d.ts +20 -0
- package/useChatAction.d.ts +6 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// packages/quassel/src/chat-attachments.ts
|
|
2
|
+
var MAX_CHAT_ATTACHMENTS = 8;
|
|
3
|
+
var MAX_CHAT_ATTACHMENT_BYTES = 20 * 1024 * 1024;
|
|
4
|
+
var MAX_CHAT_REQUEST_BYTES = 29 * 1024 * 1024;
|
|
5
|
+
var chatAttachmentMediaType = (name, mediaType) => {
|
|
6
|
+
const explicit = mediaType.trim().toLowerCase();
|
|
7
|
+
if (explicit) return explicit === "video/quicktime" ? "video/mov" : explicit;
|
|
8
|
+
const types = {
|
|
9
|
+
png: "image/png",
|
|
10
|
+
jpg: "image/jpeg",
|
|
11
|
+
jpeg: "image/jpeg",
|
|
12
|
+
gif: "image/gif",
|
|
13
|
+
webp: "image/webp",
|
|
14
|
+
avif: "image/avif",
|
|
15
|
+
svg: "image/svg+xml",
|
|
16
|
+
pdf: "application/pdf",
|
|
17
|
+
mp4: "video/mp4",
|
|
18
|
+
webm: "video/webm",
|
|
19
|
+
mov: "video/mov",
|
|
20
|
+
mpeg: "video/mpeg",
|
|
21
|
+
mpg: "video/mpeg",
|
|
22
|
+
txt: "text/plain",
|
|
23
|
+
md: "text/markdown",
|
|
24
|
+
csv: "text/csv",
|
|
25
|
+
tsv: "text/tab-separated-values",
|
|
26
|
+
json: "application/json",
|
|
27
|
+
jsonl: "application/x-ndjson",
|
|
28
|
+
xml: "application/xml",
|
|
29
|
+
yaml: "application/yaml",
|
|
30
|
+
yml: "application/yaml",
|
|
31
|
+
ts: "text/plain",
|
|
32
|
+
tsx: "text/plain",
|
|
33
|
+
js: "application/javascript",
|
|
34
|
+
py: "text/plain",
|
|
35
|
+
cs: "text/plain",
|
|
36
|
+
fs: "text/plain",
|
|
37
|
+
html: "text/html",
|
|
38
|
+
css: "text/css",
|
|
39
|
+
log: "text/plain"
|
|
40
|
+
};
|
|
41
|
+
return types[name.split(".").at(-1)?.toLowerCase() ?? ""] ?? "application/octet-stream";
|
|
42
|
+
};
|
|
43
|
+
var parseChatAttachments = (value) => {
|
|
44
|
+
if (value === void 0) return [];
|
|
45
|
+
if (!Array.isArray(value) || value.length > MAX_CHAT_ATTACHMENTS) {
|
|
46
|
+
throw new Error(`Attachments must be a list of at most ${MAX_CHAT_ATTACHMENTS} files.`);
|
|
47
|
+
}
|
|
48
|
+
let total = 0;
|
|
49
|
+
return value.map((entry, index) => {
|
|
50
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw new Error(`Attachment ${index + 1} is invalid.`);
|
|
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(`Attachment ${index + 1} needs a valid file name without a path.`);
|
|
53
|
+
const normalizedType = typeof mediaType === "string" ? chatAttachmentMediaType(name, mediaType) : "";
|
|
54
|
+
if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/i.test(normalizedType)) {
|
|
55
|
+
throw new Error(`Attachment ${name} needs a valid MIME type.`);
|
|
56
|
+
}
|
|
57
|
+
if (typeof data === "string" && data.length > Math.ceil(MAX_CHAT_ATTACHMENT_BYTES / 3) * 4)
|
|
58
|
+
throw new Error("Attachments may be at most 20 MiB in total.");
|
|
59
|
+
if (typeof data !== "string" || data.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(data)) {
|
|
60
|
+
throw new Error(`Attachment ${name} contains invalid Base64.`);
|
|
61
|
+
}
|
|
62
|
+
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
|
|
63
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
64
|
+
if (padding && (alphabet.indexOf(data[data.length - padding - 1]) & (padding === 2 ? 15 : 3)) !== 0) {
|
|
65
|
+
throw new Error(`Attachment ${name} contains non-canonical Base64.`);
|
|
66
|
+
}
|
|
67
|
+
total += data.length / 4 * 3 - padding;
|
|
68
|
+
if (total > MAX_CHAT_ATTACHMENT_BYTES) throw new Error("Attachments may be at most 20 MiB in total.");
|
|
69
|
+
return { name, mediaType: normalizedType, data };
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// packages/quassel/src/events.ts
|
|
74
|
+
var keyCounter = 0;
|
|
75
|
+
var newKey = () => typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `qsl-${Date.now().toString(36)}-${(keyCounter += 1).toString(36)}`;
|
|
76
|
+
function openMessageIndex(messages) {
|
|
77
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
78
|
+
const message = messages[index];
|
|
79
|
+
if (!message.closed && (message.role === "assistant" || message.role === "thinking")) return index;
|
|
80
|
+
}
|
|
81
|
+
return -1;
|
|
82
|
+
}
|
|
83
|
+
function closeOpen(messages) {
|
|
84
|
+
const index = openMessageIndex(messages);
|
|
85
|
+
if (index === -1) {
|
|
86
|
+
return messages;
|
|
87
|
+
}
|
|
88
|
+
return messages.map((message, position) => position === index ? { ...message, closed: true } : message);
|
|
89
|
+
}
|
|
90
|
+
function appendDelta(messages, role, delta, at, textCursor) {
|
|
91
|
+
const index = openMessageIndex(messages);
|
|
92
|
+
const current = messages[index];
|
|
93
|
+
if (current && current.role === role && current.textCursor?.conversationId === textCursor?.conversationId && current.textCursor?.sequence === textCursor?.sequence) {
|
|
94
|
+
return messages.map((message, position) => position === index ? { ...message, text: message.text + delta, ...textCursor ? { textCursor } : {} } : message);
|
|
95
|
+
}
|
|
96
|
+
return [...closeOpen(messages), { key: newKey(), role, text: delta, at, ...textCursor ? { textCursor } : {} }];
|
|
97
|
+
}
|
|
98
|
+
function applyEvent(messages, event) {
|
|
99
|
+
switch (event.kind) {
|
|
100
|
+
case "reset":
|
|
101
|
+
return [];
|
|
102
|
+
case "user":
|
|
103
|
+
return [...messages, {
|
|
104
|
+
key: newKey(),
|
|
105
|
+
role: "user",
|
|
106
|
+
text: event.text,
|
|
107
|
+
closed: true,
|
|
108
|
+
at: event.at,
|
|
109
|
+
...event.inputId ? { inputId: event.inputId } : {},
|
|
110
|
+
...event.attachments?.length ? { attachments: event.attachments } : {}
|
|
111
|
+
}];
|
|
112
|
+
case "steered":
|
|
113
|
+
return messages.map((message) => message.inputId === event.inputId ? { ...message, steered: true } : message);
|
|
114
|
+
case "text":
|
|
115
|
+
return appendDelta(messages, "assistant", event.delta, event.at, event.cursor);
|
|
116
|
+
case "thinking":
|
|
117
|
+
return appendDelta(messages, "thinking", event.delta, event.at);
|
|
118
|
+
case "tool":
|
|
119
|
+
return [
|
|
120
|
+
...closeOpen(messages),
|
|
121
|
+
{
|
|
122
|
+
key: newKey(),
|
|
123
|
+
role: "tool",
|
|
124
|
+
text: event.label ?? event.name,
|
|
125
|
+
closed: true,
|
|
126
|
+
tool: { id: event.id, name: event.name, arguments: event.arguments },
|
|
127
|
+
at: event.at
|
|
128
|
+
}
|
|
129
|
+
];
|
|
130
|
+
case "tool-result": {
|
|
131
|
+
const index = messages.map((message) => message.tool?.id).lastIndexOf(event.id);
|
|
132
|
+
return messages.map(
|
|
133
|
+
(message, position) => position === index && message.tool ? { ...message, tool: { ...message.tool, result: event.result, isError: event.isError } } : message
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
case "action":
|
|
137
|
+
return [
|
|
138
|
+
...closeOpen(messages),
|
|
139
|
+
{
|
|
140
|
+
key: newKey(),
|
|
141
|
+
role: "action",
|
|
142
|
+
text: event.text,
|
|
143
|
+
closed: true,
|
|
144
|
+
action: { actionId: event.actionId, owner: event.owner, payload: event.payload },
|
|
145
|
+
at: event.at
|
|
146
|
+
}
|
|
147
|
+
];
|
|
148
|
+
case "action-resolved":
|
|
149
|
+
return messages.map(
|
|
150
|
+
(message) => message.action?.actionId === event.actionId ? { ...message, action: { ...message.action, status: event.status, result: event.result } } : message
|
|
151
|
+
);
|
|
152
|
+
case "system":
|
|
153
|
+
return [...closeOpen(messages), { key: newKey(), role: "system", text: event.text, closed: true, at: event.at }];
|
|
154
|
+
case "turn-done":
|
|
155
|
+
return closeOpen(messages);
|
|
156
|
+
default:
|
|
157
|
+
return messages;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function prettyJson(value) {
|
|
161
|
+
try {
|
|
162
|
+
return JSON.stringify(JSON.parse(value), null, 2);
|
|
163
|
+
} catch {
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function compactToolLine(tool) {
|
|
168
|
+
const args = tool.arguments.replace(/\s+/g, " ").trim();
|
|
169
|
+
const line = args && args !== "{}" ? `${tool.name} ${args}` : tool.name;
|
|
170
|
+
return line.length > 160 ? `${line.slice(0, 160)} ...` : line;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export {
|
|
174
|
+
MAX_CHAT_ATTACHMENTS,
|
|
175
|
+
MAX_CHAT_ATTACHMENT_BYTES,
|
|
176
|
+
MAX_CHAT_REQUEST_BYTES,
|
|
177
|
+
chatAttachmentMediaType,
|
|
178
|
+
parseChatAttachments,
|
|
179
|
+
applyEvent,
|
|
180
|
+
prettyJson,
|
|
181
|
+
compactToolLine
|
|
182
|
+
};
|
|
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
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
export type Role = "user" | "assistant" | "thinking" | "tool" | "system" | "action";
|
|
2
|
+
export interface ChatAttachmentInput {
|
|
3
|
+
name: string;
|
|
4
|
+
mediaType: string;
|
|
5
|
+
data: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ChatAttachment {
|
|
8
|
+
name: string;
|
|
9
|
+
mediaType: string;
|
|
10
|
+
size: number;
|
|
11
|
+
url: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ChatAttachmentCapabilities {
|
|
14
|
+
input: readonly string[];
|
|
15
|
+
model: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ToolInfo {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
arguments: string;
|
|
21
|
+
result?: string;
|
|
22
|
+
isError?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface ChatTextCursor {
|
|
25
|
+
conversationId: string;
|
|
26
|
+
sequence: number;
|
|
27
|
+
/** Accumulated non-whitespace UTF-16 units in the turn anchored by sequence. */
|
|
28
|
+
offset: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ChatJournalCursor {
|
|
31
|
+
conversationId: string;
|
|
32
|
+
eventId: string;
|
|
33
|
+
sequence: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
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
|
+
*/
|
|
39
|
+
export interface PendingAction {
|
|
40
|
+
actionId: string;
|
|
41
|
+
owner: string | null;
|
|
42
|
+
payload: unknown;
|
|
43
|
+
status?: "approved" | "dismissed";
|
|
44
|
+
result?: unknown;
|
|
45
|
+
}
|
|
46
|
+
export interface Message {
|
|
47
|
+
key: string;
|
|
48
|
+
role: Role;
|
|
49
|
+
/** Sender identifier for display; independent of role and bubble label. */
|
|
50
|
+
sender?: string;
|
|
51
|
+
text: string;
|
|
52
|
+
textCursor?: ChatTextCursor;
|
|
53
|
+
closed?: boolean;
|
|
54
|
+
attachments?: ChatAttachment[];
|
|
55
|
+
tool?: ToolInfo;
|
|
56
|
+
action?: PendingAction;
|
|
57
|
+
/** ISO timestamp of the message; display optional (ChatMessages showTimestamps). */
|
|
58
|
+
at?: string;
|
|
59
|
+
/** The ActorInput behind an incoming message; the steering mark hangs on it. */
|
|
60
|
+
inputId?: string;
|
|
61
|
+
/** The message was fed into an already running turn instead of starting its own. */
|
|
62
|
+
steered?: boolean;
|
|
63
|
+
/** Colored speech bubble instead of running text, e.g. for multi-party conversations. */
|
|
64
|
+
bubble?: {
|
|
65
|
+
color: string;
|
|
66
|
+
side: "start" | "end";
|
|
67
|
+
label?: string;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export interface ChatStartupStatus {
|
|
71
|
+
status: "preparing" | "failed";
|
|
72
|
+
message: string;
|
|
73
|
+
}
|
|
74
|
+
export type ChatEvent = {
|
|
75
|
+
kind: "reset";
|
|
76
|
+
reason?: "conversation-reset";
|
|
77
|
+
conversationId: string | null;
|
|
78
|
+
} | {
|
|
79
|
+
kind: "replay-end";
|
|
80
|
+
conversationId: string | null;
|
|
81
|
+
} | {
|
|
82
|
+
kind: "user";
|
|
83
|
+
text: string;
|
|
84
|
+
inputId?: string;
|
|
85
|
+
at?: string;
|
|
86
|
+
attachments?: ChatAttachment[];
|
|
87
|
+
} | {
|
|
88
|
+
kind: "steered";
|
|
89
|
+
inputId: string;
|
|
90
|
+
} | {
|
|
91
|
+
kind: "text";
|
|
92
|
+
delta: string;
|
|
93
|
+
at?: string;
|
|
94
|
+
cursor: ChatTextCursor;
|
|
95
|
+
} | {
|
|
96
|
+
kind: "thinking";
|
|
97
|
+
delta: string;
|
|
98
|
+
at?: string;
|
|
99
|
+
} | {
|
|
100
|
+
kind: "tool";
|
|
101
|
+
id: string;
|
|
102
|
+
name: string;
|
|
103
|
+
arguments: string;
|
|
104
|
+
label?: string;
|
|
105
|
+
at?: string;
|
|
106
|
+
} | {
|
|
107
|
+
kind: "tool-result";
|
|
108
|
+
id: string;
|
|
109
|
+
result: string;
|
|
110
|
+
isError?: boolean;
|
|
111
|
+
} | {
|
|
112
|
+
kind: "action";
|
|
113
|
+
actionId: string;
|
|
114
|
+
owner: string | null;
|
|
115
|
+
text: string;
|
|
116
|
+
payload: unknown;
|
|
117
|
+
at?: string;
|
|
118
|
+
} | {
|
|
119
|
+
kind: "action-resolved";
|
|
120
|
+
actionId: string;
|
|
121
|
+
status: "approved" | "dismissed";
|
|
122
|
+
result: unknown;
|
|
123
|
+
} | {
|
|
124
|
+
kind: "system";
|
|
125
|
+
text: string;
|
|
126
|
+
at?: string;
|
|
127
|
+
} | {
|
|
128
|
+
kind: "status";
|
|
129
|
+
running: boolean;
|
|
130
|
+
startup?: ChatStartupStatus;
|
|
131
|
+
} | {
|
|
132
|
+
kind: "turn-done";
|
|
133
|
+
} | {
|
|
134
|
+
kind: "plugin";
|
|
135
|
+
pluginId: string;
|
|
136
|
+
type: string;
|
|
137
|
+
payload?: unknown;
|
|
138
|
+
at?: string;
|
|
139
|
+
journal?: ChatJournalCursor;
|
|
140
|
+
};
|
|
141
|
+
/** Incoming messages leave the running output block open. */
|
|
142
|
+
export declare function applyEvent(messages: Message[], event: ChatEvent): Message[];
|
|
143
|
+
export declare function prettyJson(value: string): string;
|
|
144
|
+
export declare function compactToolLine(tool: ToolInfo): string;
|
|
145
|
+
export { MAX_CHAT_ATTACHMENTS, MAX_CHAT_ATTACHMENT_BYTES, MAX_CHAT_REQUEST_BYTES, chatAttachmentMediaType, parseChatAttachments } from "./chat-attachments";
|
package/events.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_CHAT_ATTACHMENTS,
|
|
3
|
+
MAX_CHAT_ATTACHMENT_BYTES,
|
|
4
|
+
MAX_CHAT_REQUEST_BYTES,
|
|
5
|
+
applyEvent,
|
|
6
|
+
chatAttachmentMediaType,
|
|
7
|
+
compactToolLine,
|
|
8
|
+
parseChatAttachments,
|
|
9
|
+
prettyJson
|
|
10
|
+
} from "./chunk-3VEL4HEO.js";
|
|
11
|
+
export {
|
|
12
|
+
MAX_CHAT_ATTACHMENTS,
|
|
13
|
+
MAX_CHAT_ATTACHMENT_BYTES,
|
|
14
|
+
MAX_CHAT_REQUEST_BYTES,
|
|
15
|
+
applyEvent,
|
|
16
|
+
chatAttachmentMediaType,
|
|
17
|
+
compactToolLine,
|
|
18
|
+
parseChatAttachments,
|
|
19
|
+
prettyJson
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=events.js.map
|
package/events.js.map
ADDED
package/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type { Role, ToolInfo, PendingAction, Message, ChatEvent, ChatTextCursor, ChatJournalCursor, ChatAttachment, ChatAttachmentInput, ChatAttachmentCapabilities, ChatStartupStatus, DetailMode, TranscriptMode, StepState, ChatAnnouncement, } from "./types";
|
|
2
|
+
export { applyEvent, prettyJson, compactToolLine, stepState } from "./types";
|
|
3
|
+
export type { ChatTexts } from "./texts";
|
|
4
|
+
export { defaultTexts, englishTexts, germanTexts, fillText } from "./texts";
|
|
5
|
+
export { ChatMessages } from "./ChatMessages";
|
|
6
|
+
export { ChatPanel } from "./ChatPanel";
|
|
7
|
+
export { ChatInputToolbar, type ChatInputHandle, type ToolbarAction } from "./ChatInputToolbar";
|
|
8
|
+
export { DetailModeSwitch, DETAIL_MODES, detailModeLabel } from "./DetailModeSwitch";
|
|
9
|
+
export { TranscriptModeSwitch } from "./TranscriptModeSwitch";
|
|
10
|
+
export { TimestampSwitch } from "./TimestampSwitch";
|
|
11
|
+
export { Markdown, MarkdownCodeBlocks, MarkdownLinks, markdownPlainText, type LinkClickHandler } from "./Markdown";
|
|
12
|
+
export { MessageActions } from "./MessageActions";
|
|
13
|
+
export { PendingActionCard } from "./PendingActionCard";
|
|
14
|
+
export { StepPopover } from "./StepPopover";
|
|
15
|
+
export { WorkingScenes, type WorkingScenesProps } from "./WorkingScenes";
|
|
16
|
+
export { announce } from "./announce";
|
|
17
|
+
export { useChatAction, copyChatText } from "./useChatAction";
|
|
18
|
+
export { createChatScroll } from "./chat-scroll";
|
|
19
|
+
export { ChatSendContext, createChatSendScope, type ChatSendScope } from "./ChatSendContext";
|
|
20
|
+
export { messageDate, timestampDay, timestampLabel } from "./timestamps";
|
|
21
|
+
export { formatElapsed, useElapsed } from "./elapsed";
|
|
22
|
+
export { attachmentCapabilityError, attachmentDownloadUrl, attachmentMediaType, encodeAttachment, formatAttachmentSize, validateAttachmentSelection, MAX_CHAT_ATTACHMENTS, MAX_CHAT_ATTACHMENT_BYTES, } from "./attachments";
|
|
23
|
+
export type { ChatAppearance, TimestampOptions, CodeBlockOptions, BubbleOptions, MessageAction, MessageActionsOptions, SendShortcut, } from "./options";
|
|
24
|
+
export { appearanceStyle } from "./options";
|
|
25
|
+
export { QuasselProvider, defaultComponents, useQuasselComponents, useQuasselPortalContainer, type QuasselComponents, type QuasselButtonProps, type QuasselButtonVariant, type QuasselButtonSize, type QuasselToggleProps, type QuasselCardProps, type QuasselStopButtonProps, type QuasselPopoverProps, type QuasselPopoverContentProps, type QuasselPopoverAnchor, type QuasselPortalContainer, type QuasselPortalTarget, } from "./QuasselProvider";
|