quassel 0.1.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/ChatInputToolbar.d.ts +32 -0
- package/ChatMessages.d.ts +40 -0
- package/ChatPanel.d.ts +15 -0
- package/ChatSendContext.d.ts +12 -0
- package/DetailModeSwitch.d.ts +18 -0
- package/LICENSE +21 -0
- package/Markdown.d.ts +26 -0
- package/MessageActions.d.ts +17 -0
- package/QuestionCard.d.ts +12 -0
- package/README.md +99 -0
- package/StepPopover.d.ts +12 -0
- package/TimestampSwitch.d.ts +8 -0
- package/WorkingScenes.d.ts +10 -0
- package/announce.d.ts +2 -0
- package/chunk-DOLTANTM.js +84 -0
- package/chunk-DOLTANTM.js.map +7 -0
- package/css/chat.css +927 -0
- package/css/foundation/base.css +24 -0
- package/css/foundation/index.css +3 -0
- package/css/foundation/panel.css +111 -0
- package/css/foundation/tokens.css +68 -0
- package/css/foundation/wash.css +26 -0
- package/css/question.css +149 -0
- package/events.d.ts +88 -0
- package/events.js +11 -0
- package/events.js.map +7 -0
- package/icons.d.ts +15 -0
- package/index.d.ts +16 -0
- package/index.js +1661 -0
- package/index.js.map +7 -0
- package/options.d.ts +42 -0
- package/package.json +53 -0
- package/server/http.d.ts +17 -0
- package/server/index.d.ts +6 -0
- package/server/index.js +478 -0
- package/server/index.js.map +7 -0
- package/server/manager.d.ts +12 -0
- package/server/openai.d.ts +16 -0
- package/server/session.d.ts +27 -0
- package/server/store.d.ts +18 -0
- package/server/types.d.ts +62 -0
- package/texts.d.ts +39 -0
- package/timestamps.d.ts +5 -0
- package/types.d.ts +22 -0
- package/useChat.d.ts +14 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { ChatTexts } from "./texts";
|
|
3
|
+
import type { SendShortcut } from "./options";
|
|
4
|
+
/** Ein Toolbar-Knopf, deklarativ: Symbol, Text oder beides - plus Klick-Handler. */
|
|
5
|
+
export interface ToolbarAction {
|
|
6
|
+
icon?: ReactNode;
|
|
7
|
+
label?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
active?: boolean;
|
|
11
|
+
onClick: () => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Die Eingabe-Karte: rahmenlose Textarea oben, Toolbar unter einer Haarlinie. Eigene
|
|
15
|
+
* Knöpfe kommen deklarativ über `actions` oder frei über die Slots links/rechts; im Lauf
|
|
16
|
+
* bleibt der Stop-Knopf sichtbar, und Tippen zeigt daneben den Senden-Knopf, der zum
|
|
17
|
+
* Dazwischenfunken wird. `rows` bestimmt die Starthöhe; mit `maxRows` wächst die Textarea
|
|
18
|
+
* mit dem Inhalt bis zu dieser Zeilenzahl und scrollt danach.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ChatInputToolbar({ onSend, onStop, running, disabled, rows, maxRows, actions, toolbarLeft, toolbarRight, texts, sendShortcut, }: {
|
|
21
|
+
onSend: (text: string) => void | Promise<void>;
|
|
22
|
+
onStop?: () => void;
|
|
23
|
+
running?: boolean;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
rows?: number;
|
|
26
|
+
maxRows?: number;
|
|
27
|
+
actions?: ToolbarAction[];
|
|
28
|
+
toolbarLeft?: ReactNode;
|
|
29
|
+
toolbarRight?: ReactNode;
|
|
30
|
+
texts?: Partial<ChatTexts>;
|
|
31
|
+
sendShortcut?: SendShortcut;
|
|
32
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { type ChatAppearance, type TimestampOptions, type CodeBlockOptions, type BubbleOptions, type MessageActionsOptions } from "./options";
|
|
3
|
+
import { ChatAnnouncement, DetailMode, Message, ToolInfo } from "./types";
|
|
4
|
+
import { ChatTexts } from "./texts";
|
|
5
|
+
import { type LinkClickHandler } from "./Markdown";
|
|
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.
|
|
9
|
+
*/
|
|
10
|
+
export declare function ChatMessages({ messages, detailMode, running, working, workingTimeoutMs, stepsExpandable, texts, toolArgumentsText, renderTool, onAnswerQuestion, onLinkClick, announce, emptyState, className, showTimestamps, bottomThreshold, scrollerRef, appearance, timestampOptions, codeBlockOptions, bubbleOptions, messageActions, }: {
|
|
11
|
+
messages: Message[];
|
|
12
|
+
appearance?: ChatAppearance;
|
|
13
|
+
timestampOptions?: TimestampOptions;
|
|
14
|
+
codeBlockOptions?: CodeBlockOptions;
|
|
15
|
+
bubbleOptions?: BubbleOptions;
|
|
16
|
+
messageActions?: MessageActionsOptions;
|
|
17
|
+
detailMode?: DetailMode;
|
|
18
|
+
running?: boolean;
|
|
19
|
+
working?: ReactNode;
|
|
20
|
+
/** Ohne neue Events verschwindet der Working-Indikator nach dieser Zeit, Default 20s. */
|
|
21
|
+
workingTimeoutMs?: number;
|
|
22
|
+
/** false = Denk- und Werkzeug-Schritte lassen sich nicht aufklappen (kein Popover). */
|
|
23
|
+
stepsExpandable?: boolean;
|
|
24
|
+
texts?: Partial<ChatTexts>;
|
|
25
|
+
toolArgumentsText?: (tool: ToolInfo) => string;
|
|
26
|
+
/** Eigene Darstellung fuer einzelne Werkzeuge; undefined = Standarddarstellung. */
|
|
27
|
+
renderTool?: (tool: ToolInfo) => ReactNode | undefined;
|
|
28
|
+
onAnswerQuestion?: (callId: string, text: string) => void;
|
|
29
|
+
/** Faengt Klicks auf Markdown-Links ab; true = behandelt, der Browser folgt nicht. */
|
|
30
|
+
onLinkClick?: LinkClickHandler;
|
|
31
|
+
/** Screenreader-Ansage je fertiger Antwort und neuer Rückfrage: Default ist ihr Text, eine Funktion liefert eigenen Text (undefined = still), false schaltet ab. */
|
|
32
|
+
announce?: false | ((announcement: ChatAnnouncement) => string | undefined);
|
|
33
|
+
emptyState?: ReactNode;
|
|
34
|
+
className?: string;
|
|
35
|
+
/** true = dezente HH:MM-Spalte links an jedem Block (Message.at). */
|
|
36
|
+
showTimestamps?: boolean;
|
|
37
|
+
bottomThreshold?: number;
|
|
38
|
+
/** Exposes the scroll container so hosts can scroll without querying the DOM. */
|
|
39
|
+
scrollerRef?: (element: HTMLDivElement | null) => void;
|
|
40
|
+
}): import("react").JSX.Element;
|
package/ChatPanel.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ReactNode, type Ref } from "react";
|
|
2
|
+
import { type ChatAppearance } from "./options";
|
|
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.
|
|
6
|
+
*/
|
|
7
|
+
export declare function ChatPanel({ children, composer, className, nodeRef, appearance, scrollOnSend, }: {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
/** Die Eingabe; sie liegt ueber dem Verlauf, nicht darunter. */
|
|
10
|
+
composer?: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
nodeRef?: Ref<HTMLDivElement>;
|
|
13
|
+
appearance?: ChatAppearance;
|
|
14
|
+
scrollOnSend?: boolean;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { KeyboardEvent } from "react";
|
|
2
|
+
import type { SendShortcut } from "./options";
|
|
3
|
+
export declare const ChatSendContext: import("react").Context<{
|
|
4
|
+
onSent: () => void;
|
|
5
|
+
registerJump: (jump: () => void) => () => void;
|
|
6
|
+
} | undefined>;
|
|
7
|
+
export declare function isSendKey(event: KeyboardEvent<HTMLTextAreaElement>, shortcut: SendShortcut): boolean;
|
|
8
|
+
export declare function useChatSubmit(onSend: (text: string) => void | Promise<void>): {
|
|
9
|
+
submit: (text: string) => Promise<boolean>;
|
|
10
|
+
sending: boolean;
|
|
11
|
+
error: string | undefined;
|
|
12
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DetailMode } from "./types";
|
|
2
|
+
import { ChatTexts } from "./texts";
|
|
3
|
+
export declare const DETAIL_MODES: readonly DetailMode[];
|
|
4
|
+
export declare const detailModeLabel: (mode: DetailMode, texts?: Partial<ChatTexts>) => string;
|
|
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.
|
|
8
|
+
*/
|
|
9
|
+
export declare function DetailModeSwitch({ mode, onChange, modes, collapsible, texts, className, }: {
|
|
10
|
+
mode: DetailMode;
|
|
11
|
+
onChange: (mode: DetailMode) => void;
|
|
12
|
+
/** Auswahl und Reihenfolge des Weiterschaltens; Default sind alle sechs Modi. */
|
|
13
|
+
modes?: readonly DetailMode[];
|
|
14
|
+
/** false = die Beschriftung bleibt auch in einer schmalen Eingabe-Karte stehen. */
|
|
15
|
+
collapsible?: boolean;
|
|
16
|
+
texts?: Partial<ChatTexts>;
|
|
17
|
+
className?: string;
|
|
18
|
+
}): import("react").JSX.Element;
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ronald Schlenker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Markdown.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import type { CodeBlockOptions } from "./options";
|
|
3
|
+
import { type ChatTexts } from "./texts";
|
|
4
|
+
export declare function MarkdownCodeBlocks({ children, options, texts }: {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
options?: CodeBlockOptions;
|
|
7
|
+
texts?: Partial<ChatTexts>;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
/**
|
|
10
|
+
* Schlanker Markdown-Renderer ohne Abhängigkeit: Überschriften, **fett**, *kursiv*,
|
|
11
|
+
* `code`, Links, Listen, Blockquotes, Code-Blöcke und Tabellen. Bewusst sicher (reines
|
|
12
|
+
* React, kein dangerouslySetInnerHTML) und streaming-robust: zeilenbasiert, unvollständige
|
|
13
|
+
* Blöcke werden so weit gerendert wie vorhanden.
|
|
14
|
+
*/
|
|
15
|
+
/** true = der Host hat den Link behandelt, der Browser folgt ihm nicht. */
|
|
16
|
+
export type LinkClickHandler = (href: string, label: string) => boolean;
|
|
17
|
+
/** Faengt Klicks auf Markdown-Links im Teilbaum ab, z.B. fuer eigene Ziele der Gastseite. */
|
|
18
|
+
export declare function MarkdownLinks({ children, onLinkClick }: {
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
onLinkClick?: LinkClickHandler;
|
|
21
|
+
}): import("react").JSX.Element;
|
|
22
|
+
export declare function Markdown({ text }: {
|
|
23
|
+
text: string;
|
|
24
|
+
}): import("react").JSX.Element;
|
|
25
|
+
/** Dieselbe Syntax wie der Renderer, als Klartext ohne Markup, etwa für Screenreader-Ansagen. */
|
|
26
|
+
export declare function markdownPlainText(text: string): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { MessageActionsOptions } from "./options";
|
|
3
|
+
import type { ChatTexts } from "./texts";
|
|
4
|
+
import type { Message } from "./types";
|
|
5
|
+
export declare function ActionButton({ label, icon, disabled, onClick, successLabel }: {
|
|
6
|
+
label: string;
|
|
7
|
+
icon?: ReactNode;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
onClick: () => void | Promise<void>;
|
|
10
|
+
successLabel?: string;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
12
|
+
export declare function copyText(text: string, error: string): Promise<void>;
|
|
13
|
+
export declare function MessageActions({ message, options, texts }: {
|
|
14
|
+
message: Message;
|
|
15
|
+
options?: MessageActionsOptions;
|
|
16
|
+
texts: ChatTexts;
|
|
17
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Question } from "./types";
|
|
2
|
+
import type { ChatTexts } from "./texts";
|
|
3
|
+
/**
|
|
4
|
+
* Rückfrage-Karte: Optionen untereinander, wahlweise Einzel- oder Mehrfachauswahl
|
|
5
|
+
* (question.multi), dazu immer eine freie Antwortzeile. Nach der Antwort nur noch Beleg.
|
|
6
|
+
*/
|
|
7
|
+
export declare function QuestionCard({ text, question, texts, onAnswer, }: {
|
|
8
|
+
text: string;
|
|
9
|
+
question: Question;
|
|
10
|
+
texts: ChatTexts;
|
|
11
|
+
onAnswer: (text: string) => void;
|
|
12
|
+
}): import("react").JSX.Element;
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# quassel
|
|
2
|
+
|
|
3
|
+
Composable chat building blocks for React: a streaming transcript with thinking and tool steps,
|
|
4
|
+
clarifying questions, an input card, a small CSS foundation, and an event contract with a
|
|
5
|
+
ready-made backend for OpenAI-compatible endpoints. One package, no runtime dependencies.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## What you can build
|
|
10
|
+
|
|
11
|
+
**Agent chats that show their work.** Thinking and tool calls appear as steps. Pick how much
|
|
12
|
+
of them the user sees - from answers only to fully expanded - and let them switch at runtime.
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
**Clarifying questions.** The agent asks, the user picks one or several options or answers
|
|
17
|
+
freely. Answered questions stay in the transcript as a record.
|
|
18
|
+
|
|
19
|
+
**Multi-party conversations.** Colored bubbles with labels for agents that talk to each other.
|
|
20
|
+
|
|
21
|
+
<p>
|
|
22
|
+
<img src="docs/images/questions.png" alt="Clarifying questions with single and multiple choice" width="49%">
|
|
23
|
+
<img src="docs/images/party.png" alt="Planner and reviewer as labeled bubbles" width="49%">
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
**Your look, not a fork.** Every color, radius and font is a `--qsl-*` token. A theme is a class
|
|
27
|
+
that overrides tokens; dark mode follows the system or `data-theme`.
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
## Highlights
|
|
34
|
+
|
|
35
|
+
- Streaming-safe Markdown with tables and code, no dependencies, no `dangerouslySetInnerHTML`.
|
|
36
|
+
- Steering: typing while a run is active sends into the running turn.
|
|
37
|
+
- Timestamps, day separators, message actions and copy buttons for code blocks.
|
|
38
|
+
- Screen reader friendly: the transcript is not a live region; finished replies and new
|
|
39
|
+
questions are announced once, with the host in control of the wording.
|
|
40
|
+
- All labels are replaceable through `texts` (defaults are German).
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
npm install quassel
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| Import | Contents |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `quassel` | React components, `useChat`, types |
|
|
51
|
+
| `quassel/events` | The event contract and the pure reducer `applyEvent`, without React |
|
|
52
|
+
| `quassel/server` | Node backend for OpenAI-compatible endpoints with sessions and SSE |
|
|
53
|
+
| `quassel/chat.css` | Component styles including the tokens |
|
|
54
|
+
| `quassel/foundation.css` | Tokens, color wash and glass panel for the whole page |
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { ChatInputToolbar, ChatMessages, ChatPanel, useChat } from "quassel";
|
|
58
|
+
import "quassel/chat.css";
|
|
59
|
+
|
|
60
|
+
export function Chat() {
|
|
61
|
+
const { messages, running, send, stop } = useChat("http://localhost:3300/chat/demo");
|
|
62
|
+
return (
|
|
63
|
+
<ChatPanel composer={<ChatInputToolbar onSend={send} onStop={stop} running={running} />}>
|
|
64
|
+
<ChatMessages messages={messages} running={running} detailMode="grouped" />
|
|
65
|
+
</ChatPanel>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { createServer } from "node:http";
|
|
72
|
+
import { createChatHandler, SessionManager } from "quassel/server";
|
|
73
|
+
|
|
74
|
+
const manager = new SessionManager({ baseUrl: "http://localhost:11434/v1", model: "qwen3:4b" });
|
|
75
|
+
const handler = createChatHandler({ manager });
|
|
76
|
+
|
|
77
|
+
createServer(async (req, res) => {
|
|
78
|
+
if (!(await handler(req, res))) res.writeHead(404).end();
|
|
79
|
+
}).listen(3300);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Any backend that emits the `quassel/events` stream works with the same components.
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
pnpm install
|
|
88
|
+
pnpm dev # gallery on http://localhost:3210
|
|
89
|
+
pnpm check # type check
|
|
90
|
+
pnpm release # build and publish to npm (see docs/guide.md)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The full build guide - every prop, the event contract, the backend - is in
|
|
94
|
+
[docs/guide.md](https://github.com/SchlenkR/quassel/blob/main/docs/guide.md) (German). It is written so that an AI assistant can build an
|
|
95
|
+
application with quassel from it.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
[MIT](https://github.com/SchlenkR/quassel/blob/main/LICENSE)
|
package/StepPopover.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Message } from "./types";
|
|
2
|
+
import { ChatTexts } from "./texts";
|
|
3
|
+
export declare function StepPopover({ message, position, texts, toolArgumentsText, onClose, }: {
|
|
4
|
+
message: Message;
|
|
5
|
+
position: {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
};
|
|
9
|
+
texts: ChatTexts;
|
|
10
|
+
toolArgumentsText: (tool: NonNullable<Message["tool"]>) => string;
|
|
11
|
+
onClose: () => void;
|
|
12
|
+
}): import("react").ReactPortal;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ChatTexts } from "./texts";
|
|
2
|
+
export declare function TimestampSwitch({ showTimestamps, onChange, collapsible, texts, className, }: {
|
|
3
|
+
showTimestamps: boolean;
|
|
4
|
+
onChange: (showTimestamps: boolean) => void;
|
|
5
|
+
collapsible?: boolean;
|
|
6
|
+
texts?: Partial<ChatTexts>;
|
|
7
|
+
className?: string;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface WorkingScenesProps {
|
|
2
|
+
/** Text fuer Screenreader, Default "Arbeitet ...". */
|
|
3
|
+
label?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Der Default-Working-Indikator: zufaellig rotierende Mini-Szenen. Zufaellige Reihenfolge
|
|
7
|
+
* und Startphase, damit dieselben Szenen nie wie eine Schleife wirken. Ueber die
|
|
8
|
+
* working-Prop von ChatMessages laesst er sich komplett ersetzen.
|
|
9
|
+
*/
|
|
10
|
+
export declare function WorkingScenes({ label }: WorkingScenesProps): import("react").JSX.Element;
|
package/announce.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// packages/quassel/src/events.ts
|
|
2
|
+
function closeLast(messages) {
|
|
3
|
+
const last = messages[messages.length - 1];
|
|
4
|
+
if (!last || last.closed) {
|
|
5
|
+
return messages;
|
|
6
|
+
}
|
|
7
|
+
return [...messages.slice(0, -1), { ...last, closed: true }];
|
|
8
|
+
}
|
|
9
|
+
function appendDelta(messages, role, delta, at) {
|
|
10
|
+
const last = messages[messages.length - 1];
|
|
11
|
+
if (last && last.role === role && !last.closed) {
|
|
12
|
+
return [...messages.slice(0, -1), { ...last, text: last.text + delta }];
|
|
13
|
+
}
|
|
14
|
+
return [...closeLast(messages), { key: crypto.randomUUID(), role, text: delta, at }];
|
|
15
|
+
}
|
|
16
|
+
function applyEvent(messages, event) {
|
|
17
|
+
switch (event.kind) {
|
|
18
|
+
case "reset":
|
|
19
|
+
return [];
|
|
20
|
+
case "user":
|
|
21
|
+
return [...closeLast(messages), { key: crypto.randomUUID(), role: "user", text: event.text, at: event.at }];
|
|
22
|
+
case "text":
|
|
23
|
+
return appendDelta(messages, "assistant", event.delta, event.at);
|
|
24
|
+
case "thinking":
|
|
25
|
+
return appendDelta(messages, "thinking", event.delta, event.at);
|
|
26
|
+
case "tool":
|
|
27
|
+
return [
|
|
28
|
+
...closeLast(messages),
|
|
29
|
+
{
|
|
30
|
+
key: crypto.randomUUID(),
|
|
31
|
+
role: "tool",
|
|
32
|
+
text: event.label ?? event.name,
|
|
33
|
+
closed: true,
|
|
34
|
+
tool: { id: event.id, name: event.name, arguments: event.arguments },
|
|
35
|
+
at: event.at
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
case "tool-result":
|
|
39
|
+
return messages.map(
|
|
40
|
+
(message) => message.tool?.id === event.id ? { ...message, tool: { ...message.tool, result: event.result, isError: event.isError } } : message
|
|
41
|
+
);
|
|
42
|
+
case "question":
|
|
43
|
+
return [
|
|
44
|
+
...closeLast(messages),
|
|
45
|
+
{
|
|
46
|
+
key: crypto.randomUUID(),
|
|
47
|
+
role: "question",
|
|
48
|
+
text: event.text,
|
|
49
|
+
closed: true,
|
|
50
|
+
question: { callId: event.callId, options: event.options, multi: event.multi },
|
|
51
|
+
at: event.at
|
|
52
|
+
}
|
|
53
|
+
];
|
|
54
|
+
case "question-answered":
|
|
55
|
+
return messages.map(
|
|
56
|
+
(message) => message.question?.callId === event.callId ? { ...message, question: { ...message.question, answer: event.answer } } : message
|
|
57
|
+
);
|
|
58
|
+
case "system":
|
|
59
|
+
return [...closeLast(messages), { key: crypto.randomUUID(), role: "system", text: event.text, closed: true, at: event.at }];
|
|
60
|
+
case "turn-done":
|
|
61
|
+
return closeLast(messages);
|
|
62
|
+
default:
|
|
63
|
+
return messages;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function prettyJson(value) {
|
|
67
|
+
try {
|
|
68
|
+
return JSON.stringify(JSON.parse(value), null, 2);
|
|
69
|
+
} catch {
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function compactToolLine(tool) {
|
|
74
|
+
const args = tool.arguments.replace(/\s+/g, " ").trim();
|
|
75
|
+
const line = args && args !== "{}" ? `${tool.name} ${args}` : tool.name;
|
|
76
|
+
return line.length > 160 ? `${line.slice(0, 160)} ...` : line;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export {
|
|
80
|
+
applyEvent,
|
|
81
|
+
prettyJson,
|
|
82
|
+
compactToolLine
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=chunk-DOLTANTM.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/events.ts"],
|
|
4
|
+
"sourcesContent": ["export type Role = \"user\" | \"assistant\" | \"thinking\" | \"tool\" | \"system\" | \"question\";\n\nexport interface ToolInfo {\n id: string;\n name: string;\n arguments: string;\n result?: string;\n isError?: boolean;\n}\n\n/** Eine R\u00FCckfrage des Assistenten. `answer` gesetzt hei\u00DFt: beantwortet, die Karte ist nur noch Beleg. */\nexport interface Question {\n callId: string;\n options: string[];\n multi?: boolean;\n answer?: string;\n}\n\nexport interface Message {\n key: string;\n role: Role;\n text: string;\n closed?: boolean;\n tool?: ToolInfo;\n question?: Question;\n /** ISO-Zeitpunkt der Nachricht; Anzeige optional (ChatMessages showTimestamps). */\n at?: string;\n /** Farbige Sprechblase statt Fliesstext, z.B. fuer Mehrparteien-Gespraeche. */\n bubble?: { color: string; side: \"start\" | \"end\"; label?: string };\n}\n\nexport type ChatEvent =\n | { kind: \"reset\" }\n | { kind: \"user\"; text: string; at?: string }\n | { kind: \"text\"; delta: string; at?: string }\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: \"question\"; callId: string; text: string; options: string[]; multi?: boolean; at?: string }\n | { kind: \"question-answered\"; callId: string; answer: string }\n | { kind: \"system\"; text: string; at?: string }\n | { kind: \"status\"; running: boolean }\n | { kind: \"turn-done\" }\n | { kind: \"extension\"; pluginId: string; type: string; payload?: unknown; at?: string };\n\nfunction closeLast(messages: Message[]): Message[] {\n const last = messages[messages.length - 1];\n if (!last || last.closed) {\n return messages;\n }\n return [...messages.slice(0, -1), { ...last, closed: true }];\n}\n\nfunction appendDelta(messages: Message[], role: Role, delta: string, at?: string): Message[] {\n const last = messages[messages.length - 1];\n if (last && last.role === role && !last.closed) {\n return [...messages.slice(0, -1), { ...last, text: last.text + delta }];\n }\n return [...closeLast(messages), { key: crypto.randomUUID(), role, text: delta, at }];\n}\n\n/** Der Streaming-Kern: Deltas verschmelzen mit dem letzten offenen Block, alles andere schlie\u00DFt ihn. */\nexport function applyEvent(messages: Message[], event: ChatEvent): Message[] {\n switch (event.kind) {\n case \"reset\":\n return [];\n case \"user\":\n return [...closeLast(messages), { key: crypto.randomUUID(), role: \"user\", text: event.text, at: event.at }];\n case \"text\":\n return appendDelta(messages, \"assistant\", event.delta, event.at);\n case \"thinking\":\n return appendDelta(messages, \"thinking\", event.delta, event.at);\n case \"tool\":\n return [\n ...closeLast(messages),\n {\n key: crypto.randomUUID(),\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 return messages.map((message) =>\n message.tool?.id === event.id\n ? { ...message, tool: { ...message.tool, result: event.result, isError: event.isError } }\n : message,\n );\n case \"question\":\n return [\n ...closeLast(messages),\n {\n key: crypto.randomUUID(),\n role: \"question\",\n text: event.text,\n closed: true,\n question: { callId: event.callId, options: event.options, multi: event.multi },\n at: event.at,\n },\n ];\n case \"question-answered\":\n return messages.map((message) =>\n message.question?.callId === event.callId\n ? { ...message, question: { ...message.question, answer: event.answer } }\n : message,\n );\n case \"system\":\n return [...closeLast(messages), { key: crypto.randomUUID(), role: \"system\", text: event.text, closed: true, at: event.at }];\n case \"turn-done\":\n return closeLast(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"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|