@calliopelabs/ui 0.13.0 → 0.14.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/README.md +50 -3
- package/dist/agent/AgentChat.d.ts +56 -0
- package/dist/agent/AgentChat.js +76 -0
- package/dist/agent/AgentLauncher.d.ts +25 -0
- package/dist/agent/AgentLauncher.js +25 -0
- package/dist/agent/AgentProvider.d.ts +82 -0
- package/dist/agent/AgentProvider.js +176 -0
- package/dist/agent/CalliopeAgent.d.ts +43 -0
- package/dist/agent/CalliopeAgent.js +111 -0
- package/dist/agent/controller.d.ts +148 -0
- package/dist/agent/controller.js +463 -0
- package/dist/agent/copilotkit/CopilotKitAgentProvider.d.ts +41 -0
- package/dist/agent/copilotkit/CopilotKitAgentProvider.js +145 -0
- package/dist/agent/copilotkit/index.d.ts +6 -0
- package/dist/agent/copilotkit/index.js +6 -0
- package/dist/agent/engine.d.ts +41 -0
- package/dist/agent/engine.js +93 -0
- package/dist/agent/index.d.ts +15 -0
- package/dist/agent/index.js +15 -0
- package/dist/agent/mock.d.ts +34 -0
- package/dist/agent/mock.js +112 -0
- package/dist/agent/surface.d.ts +30 -0
- package/dist/agent/surface.js +90 -0
- package/dist/agent/tool.d.ts +98 -0
- package/dist/agent/tool.js +81 -0
- package/dist/appearance/brands.d.ts +14 -12
- package/dist/appearance/brands.js +18 -51
- package/dist/chat/AgentProgress.d.ts +19 -0
- package/dist/chat/AgentProgress.js +34 -0
- package/dist/chat/Composer.d.ts +31 -0
- package/dist/chat/Composer.js +44 -0
- package/dist/chat/MessageBubble.d.ts +23 -0
- package/dist/chat/MessageBubble.js +26 -0
- package/dist/chat/MessageList.d.ts +36 -0
- package/dist/chat/MessageList.js +68 -0
- package/dist/chat/ModelPicker.d.ts +17 -0
- package/dist/chat/ModelPicker.js +13 -0
- package/dist/chat/ToolCallCard.d.ts +27 -0
- package/dist/chat/ToolCallCard.js +52 -0
- package/dist/chat/model.d.ts +123 -0
- package/dist/chat/model.js +337 -0
- package/dist/chat/patch.d.ts +12 -0
- package/dist/chat/patch.js +149 -0
- package/dist/chat/useAgUiRun.d.ts +52 -0
- package/dist/chat/useAgUiRun.js +137 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +10 -0
- package/dist/primitives/Button.d.ts +1 -1
- package/dist/provider/messages.d.ts +66 -0
- package/dist/provider/messages.js +71 -0
- package/dist/theme/mappings/palette.d.ts +52 -0
- package/dist/theme/mappings/palette.js +208 -0
- package/dist/theme/mappings/render.d.ts +9 -0
- package/dist/theme/mappings/render.js +520 -0
- package/dist/theme/mappings/targets.d.ts +43 -0
- package/dist/theme/mappings/targets.js +222 -0
- package/dist/theme/tokens.d.ts +10 -9
- package/dist/theme/tokens.generated.d.ts +353 -0
- package/dist/theme/tokens.generated.js +357 -0
- package/dist/theme/tokens.js +20 -264
- package/dist/tokens/README.jupyterlab.md +125 -0
- package/dist/tokens/README.md +15 -0
- package/dist/tokens/README.native.md +29 -0
- package/dist/tokens/README.plain.md +84 -0
- package/dist/tokens/README.python.md +33 -0
- package/dist/tokens/README.vscode.md +114 -0
- package/dist/tokens/jupyterlab.css +1710 -0
- package/dist/tokens/plain.css +930 -0
- package/dist/tokens/tokens.native.d.ts +85 -0
- package/dist/tokens/tokens.native.js +773 -0
- package/dist/tokens/tokens.py +1043 -0
- package/dist/tokens/vscode.css +1138 -0
- package/package.json +35 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
2
|
+
* The family's brands, kept here so every app and the catalog read the same
|
|
3
|
+
* values. Their colours (sidebar and the product's own accent) live in
|
|
4
|
+
* `tokens/brand/*.json`; this file adds names, grounds, defaults and themes.
|
|
5
|
+
* An app adds its navigation:
|
|
4
6
|
*
|
|
5
7
|
* export const astrolift = defineProduct({ ...astroliftBrand, nav: NAV });
|
|
6
8
|
*/
|
|
@@ -78,12 +80,12 @@ export declare const zentinelleBrand: {
|
|
|
78
80
|
readonly id: "zentinelle";
|
|
79
81
|
readonly name: "Zentinelle AI";
|
|
80
82
|
readonly accent: {
|
|
81
|
-
readonly key: "teal";
|
|
82
|
-
readonly label: "Teal";
|
|
83
83
|
readonly dark: "#37efed";
|
|
84
84
|
readonly onDark: "#0b0b19";
|
|
85
85
|
readonly light: "#0d7470";
|
|
86
86
|
readonly onLight: "#f3fbf7";
|
|
87
|
+
readonly key: "teal";
|
|
88
|
+
readonly label: "Teal";
|
|
87
89
|
};
|
|
88
90
|
readonly sidebar: "#0b0b19";
|
|
89
91
|
readonly grounds: readonly ["black", "charcoal", "graphite", "paper", "mist"];
|
|
@@ -102,12 +104,12 @@ export declare const workbenchBrand: {
|
|
|
102
104
|
readonly id: "workbench";
|
|
103
105
|
readonly name: "Calliope AI Workbench";
|
|
104
106
|
readonly accent: {
|
|
105
|
-
readonly key: "workbench-blue";
|
|
106
|
-
readonly label: "Workbench blue";
|
|
107
107
|
readonly dark: "#8fb8e8";
|
|
108
108
|
readonly onDark: "#0b1120";
|
|
109
109
|
readonly light: "#2f5d94";
|
|
110
110
|
readonly onLight: "#f3fbf7";
|
|
111
|
+
readonly key: "workbench-blue";
|
|
112
|
+
readonly label: "Workbench blue";
|
|
111
113
|
};
|
|
112
114
|
readonly sidebar: "#0e1730";
|
|
113
115
|
readonly grounds: readonly ["navy", "black", "charcoal", "graphite", "paper", "mist"];
|
|
@@ -124,12 +126,12 @@ export declare const chatStudioBrand: {
|
|
|
124
126
|
readonly id: "chat-studio";
|
|
125
127
|
readonly name: "Calliope AI Chat Studio";
|
|
126
128
|
readonly accent: {
|
|
127
|
-
readonly key: "studio-blue";
|
|
128
|
-
readonly label: "Studio blue";
|
|
129
129
|
readonly dark: "#8ab4f8";
|
|
130
130
|
readonly onDark: "#0c0c0c";
|
|
131
131
|
readonly light: "#1d4ed8";
|
|
132
132
|
readonly onLight: "#ffffff";
|
|
133
|
+
readonly key: "studio-blue";
|
|
134
|
+
readonly label: "Studio blue";
|
|
133
135
|
};
|
|
134
136
|
readonly sidebar: "#080808";
|
|
135
137
|
readonly grounds: readonly ["black", "charcoal", "graphite", "paper", "mist"];
|
|
@@ -147,12 +149,12 @@ export declare const loadrBrand: {
|
|
|
147
149
|
readonly id: "loadr";
|
|
148
150
|
readonly name: "Calliope AI Loadr";
|
|
149
151
|
readonly accent: {
|
|
150
|
-
readonly key: "loadr-blue";
|
|
151
|
-
readonly label: "Loadr blue";
|
|
152
152
|
readonly dark: "#89b4fa";
|
|
153
153
|
readonly onDark: "#11111b";
|
|
154
154
|
readonly light: "#1e66f5";
|
|
155
155
|
readonly onLight: "#ffffff";
|
|
156
|
+
readonly key: "loadr-blue";
|
|
157
|
+
readonly label: "Loadr blue";
|
|
156
158
|
};
|
|
157
159
|
readonly sidebar: "#181825";
|
|
158
160
|
readonly grounds: readonly ["black", "charcoal", "graphite", "paper", "mist"];
|
|
@@ -171,12 +173,12 @@ export declare const cykickBrand: {
|
|
|
171
173
|
readonly id: "cykick";
|
|
172
174
|
readonly name: "Cykick";
|
|
173
175
|
readonly accent: {
|
|
174
|
-
readonly key: "cykick-cyan";
|
|
175
|
-
readonly label: "Cykick cyan";
|
|
176
176
|
readonly dark: "#22d3ee";
|
|
177
177
|
readonly onDark: "#07090e";
|
|
178
178
|
readonly light: "#0e7490";
|
|
179
179
|
readonly onLight: "#ffffff";
|
|
180
|
+
readonly key: "cykick-cyan";
|
|
181
|
+
readonly label: "Cykick cyan";
|
|
180
182
|
};
|
|
181
183
|
readonly sidebar: "#07090e";
|
|
182
184
|
readonly grounds: readonly ["black", "charcoal", "graphite", "paper", "mist"];
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { TOKENS } from "../theme/tokens.generated.js";
|
|
1
2
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
3
|
+
* The family's brands, kept here so every app and the catalog read the same
|
|
4
|
+
* values. Their colours (sidebar and the product's own accent) live in
|
|
5
|
+
* `tokens/brand/*.json`; this file adds names, grounds, defaults and themes.
|
|
6
|
+
* An app adds its navigation:
|
|
4
7
|
*
|
|
5
8
|
* export const astrolift = defineProduct({ ...astroliftBrand, nav: NAV });
|
|
6
9
|
*/
|
|
@@ -13,7 +16,7 @@ export const astroliftBrand = {
|
|
|
13
16
|
id: "astrolift",
|
|
14
17
|
name: "Astrolift AI",
|
|
15
18
|
accent: "green",
|
|
16
|
-
sidebar:
|
|
19
|
+
sidebar: TOKENS.brand.astrolift.sidebar,
|
|
17
20
|
grounds: ["black", "charcoal", "graphite", "emerald", "paper", "mist"],
|
|
18
21
|
defaultGround: { dark: "graphite", light: "mist" },
|
|
19
22
|
themes: [
|
|
@@ -47,15 +50,8 @@ export const astroliftBrand = {
|
|
|
47
50
|
export const zentinelleBrand = {
|
|
48
51
|
id: "zentinelle",
|
|
49
52
|
name: "Zentinelle AI",
|
|
50
|
-
accent: {
|
|
51
|
-
|
|
52
|
-
label: "Teal",
|
|
53
|
-
dark: "#37efed",
|
|
54
|
-
onDark: "#0b0b19",
|
|
55
|
-
light: "#0d7470",
|
|
56
|
-
onLight: "#f3fbf7",
|
|
57
|
-
},
|
|
58
|
-
sidebar: "#0b0b19",
|
|
53
|
+
accent: { key: "teal", label: "Teal", ...TOKENS.brand.zentinelle.accent },
|
|
54
|
+
sidebar: TOKENS.brand.zentinelle.sidebar,
|
|
59
55
|
grounds: ["black", "charcoal", "graphite", "paper", "mist"],
|
|
60
56
|
defaultGround: { dark: "black", light: "mist" },
|
|
61
57
|
};
|
|
@@ -63,7 +59,8 @@ export const zentinelleBrand = {
|
|
|
63
59
|
* The brands below are copied from what each product ships today, so nothing
|
|
64
60
|
* changes when an app moves onto the library. Where a product has no value
|
|
65
61
|
* the model needs (usually a light-ground accent), the nearest one that
|
|
66
|
-
* passes the contrast tests is used and
|
|
62
|
+
* passes the contrast tests is used and its token's description says
|
|
63
|
+
* "Derived" for the designer.
|
|
67
64
|
*/
|
|
68
65
|
/**
|
|
69
66
|
* Calliope AI Workbench (JunoHub, the installer's default theme, the desktop
|
|
@@ -74,16 +71,8 @@ export const zentinelleBrand = {
|
|
|
74
71
|
export const workbenchBrand = {
|
|
75
72
|
id: "workbench",
|
|
76
73
|
name: "Calliope AI Workbench",
|
|
77
|
-
accent: {
|
|
78
|
-
|
|
79
|
-
label: "Workbench blue",
|
|
80
|
-
dark: "#8fb8e8",
|
|
81
|
-
onDark: "#0b1120",
|
|
82
|
-
// derived: the hub has no light ground
|
|
83
|
-
light: "#2f5d94",
|
|
84
|
-
onLight: "#f3fbf7",
|
|
85
|
-
},
|
|
86
|
-
sidebar: "#0e1730",
|
|
74
|
+
accent: { key: "workbench-blue", label: "Workbench blue", ...TOKENS.brand.workbench.accent },
|
|
75
|
+
sidebar: TOKENS.brand.workbench.sidebar,
|
|
87
76
|
grounds: ["navy", "black", "charcoal", "graphite", "paper", "mist"],
|
|
88
77
|
defaultGround: { dark: "navy", light: "mist" },
|
|
89
78
|
};
|
|
@@ -94,15 +83,8 @@ export const workbenchBrand = {
|
|
|
94
83
|
export const chatStudioBrand = {
|
|
95
84
|
id: "chat-studio",
|
|
96
85
|
name: "Calliope AI Chat Studio",
|
|
97
|
-
accent: {
|
|
98
|
-
|
|
99
|
-
label: "Studio blue",
|
|
100
|
-
dark: "#8ab4f8",
|
|
101
|
-
onDark: "#0c0c0c",
|
|
102
|
-
light: "#1d4ed8",
|
|
103
|
-
onLight: "#ffffff",
|
|
104
|
-
},
|
|
105
|
-
sidebar: "#080808",
|
|
86
|
+
accent: { key: "studio-blue", label: "Studio blue", ...TOKENS.brand["chat-studio"].accent },
|
|
87
|
+
sidebar: TOKENS.brand["chat-studio"].sidebar,
|
|
106
88
|
grounds: ["black", "charcoal", "graphite", "paper", "mist"],
|
|
107
89
|
defaultGround: { dark: "black", light: "paper" },
|
|
108
90
|
};
|
|
@@ -114,15 +96,8 @@ export const chatStudioBrand = {
|
|
|
114
96
|
export const loadrBrand = {
|
|
115
97
|
id: "loadr",
|
|
116
98
|
name: "Calliope AI Loadr",
|
|
117
|
-
accent: {
|
|
118
|
-
|
|
119
|
-
label: "Loadr blue",
|
|
120
|
-
dark: "#89b4fa",
|
|
121
|
-
onDark: "#11111b",
|
|
122
|
-
light: "#1e66f5",
|
|
123
|
-
onLight: "#ffffff",
|
|
124
|
-
},
|
|
125
|
-
sidebar: "#181825",
|
|
99
|
+
accent: { key: "loadr-blue", label: "Loadr blue", ...TOKENS.brand.loadr.accent },
|
|
100
|
+
sidebar: TOKENS.brand.loadr.sidebar,
|
|
126
101
|
grounds: ["black", "charcoal", "graphite", "paper", "mist"],
|
|
127
102
|
defaultGround: { dark: "charcoal", light: "paper" },
|
|
128
103
|
};
|
|
@@ -135,16 +110,8 @@ export const loadrBrand = {
|
|
|
135
110
|
export const cykickBrand = {
|
|
136
111
|
id: "cykick",
|
|
137
112
|
name: "Cykick",
|
|
138
|
-
accent: {
|
|
139
|
-
|
|
140
|
-
label: "Cykick cyan",
|
|
141
|
-
dark: "#22d3ee",
|
|
142
|
-
onDark: "#07090e",
|
|
143
|
-
// derived: no light accent in native-themes.json
|
|
144
|
-
light: "#0e7490",
|
|
145
|
-
onLight: "#ffffff",
|
|
146
|
-
},
|
|
147
|
-
sidebar: "#07090e",
|
|
113
|
+
accent: { key: "cykick-cyan", label: "Cykick cyan", ...TOKENS.brand.cykick.accent },
|
|
114
|
+
sidebar: TOKENS.brand.cykick.sidebar,
|
|
148
115
|
grounds: ["black", "charcoal", "graphite", "paper", "mist"],
|
|
149
116
|
defaultGround: { dark: "black", light: "mist" },
|
|
150
117
|
defaultCorners: 0,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StatusTone } from "../status/tones.js";
|
|
2
|
+
import type { RunStage } from "../workflow/run.js";
|
|
3
|
+
import type { ChatRunStatus, ChatStep, ChatView } from "./model.js";
|
|
4
|
+
export interface AgentProgressProps {
|
|
5
|
+
/** The run: a `ChatView`, or the part of one this reads. */
|
|
6
|
+
view: Pick<ChatView, "status" | "steps" | "error">;
|
|
7
|
+
/** Names the section. Defaults to "Agent progress". */
|
|
8
|
+
label?: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const CHAT_RUN_TONE: Record<ChatRunStatus, StatusTone>;
|
|
12
|
+
/** A run's steps as the run timeline's stages. */
|
|
13
|
+
export declare function chatStepStages(steps: readonly ChatStep[]): RunStage[];
|
|
14
|
+
/**
|
|
15
|
+
* Where the agent's run stands: its status, and its steps on the library's
|
|
16
|
+
* RunTimeline, so a chat run reads like any other run. The error, when the
|
|
17
|
+
* run failed, sits beside the status.
|
|
18
|
+
*/
|
|
19
|
+
export declare function AgentProgress({ view, label, className }: AgentProgressProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
5
|
+
import { StatusBadge } from "../status/StatusBadge.js";
|
|
6
|
+
import { RunTimeline } from "../workflow/RunTimeline.js";
|
|
7
|
+
export const CHAT_RUN_TONE = {
|
|
8
|
+
idle: "muted",
|
|
9
|
+
running: "info",
|
|
10
|
+
finished: "ok",
|
|
11
|
+
interrupted: "warn",
|
|
12
|
+
cancelled: "muted",
|
|
13
|
+
error: "error",
|
|
14
|
+
};
|
|
15
|
+
const STAGE = {
|
|
16
|
+
running: "running",
|
|
17
|
+
finished: "passed",
|
|
18
|
+
failed: "failed",
|
|
19
|
+
cancelled: "cancelled",
|
|
20
|
+
};
|
|
21
|
+
/** A run's steps as the run timeline's stages. */
|
|
22
|
+
export function chatStepStages(steps) {
|
|
23
|
+
return steps.map((step, i) => ({ id: String(i), label: step.name, status: STAGE[step.status] }));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Where the agent's run stands: its status, and its steps on the library's
|
|
27
|
+
* RunTimeline, so a chat run reads like any other run. The error, when the
|
|
28
|
+
* run failed, sits beside the status.
|
|
29
|
+
*/
|
|
30
|
+
export function AgentProgress({ view, label, className }) {
|
|
31
|
+
const { messages } = useUI();
|
|
32
|
+
const t = messages.chat;
|
|
33
|
+
return (_jsxs("section", { "aria-label": label ?? t.progress, "data-slot": "agent-progress", className: cn("flex min-w-0 flex-col gap-3", className), children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [_jsx(StatusBadge, { tone: CHAT_RUN_TONE[view.status], pulse: view.status === "running", children: t.runStatus(view.status) }), view.status === "error" && view.error && (_jsx("p", { className: "min-w-0 text-sm [overflow-wrap:anywhere] text-danger-fg", children: view.error.message }))] }), view.steps.length > 0 && _jsx(RunTimeline, { stages: chatStepStages(view.steps), label: t.steps })] }));
|
|
34
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export interface ComposerProps {
|
|
3
|
+
/** The draft, when the host holds it. Leave it out and the composer keeps its own. */
|
|
4
|
+
value?: string;
|
|
5
|
+
onValueChange?: (value: string) => void;
|
|
6
|
+
/**
|
|
7
|
+
* Sends the trimmed draft. A composer that keeps its own draft clears it;
|
|
8
|
+
* a host that holds it clears it when it wants to.
|
|
9
|
+
*/
|
|
10
|
+
onSubmit: (text: string) => void;
|
|
11
|
+
/** A run is in flight: send is held, and stop shows when `onStop` is given. */
|
|
12
|
+
running?: boolean;
|
|
13
|
+
onStop?: () => void;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
/** Names the field. Defaults to "Message". */
|
|
17
|
+
label?: string;
|
|
18
|
+
/** Above the field: the files the host has attached, as chips. */
|
|
19
|
+
attachments?: React.ReactNode;
|
|
20
|
+
/** Beside send: an attach button, a ModelPicker. */
|
|
21
|
+
actions?: React.ReactNode;
|
|
22
|
+
autoFocus?: boolean;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Where the person writes. Multi-line, growing with the text; Enter sends,
|
|
27
|
+
* Shift+Enter breaks the line, and nothing sends while an input method is
|
|
28
|
+
* composing. While a run is in flight, send is held and stop is offered.
|
|
29
|
+
* Attachments and actions are slots: the host owns files and models.
|
|
30
|
+
*/
|
|
31
|
+
export declare function Composer({ value, onValueChange, onSubmit, running, onStop, disabled, placeholder, label, attachments, actions, autoFocus, className, }: ComposerProps): React.JSX.Element;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ArrowUpIcon, SquareIcon } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { Button } from "../primitives/Button.js";
|
|
7
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
8
|
+
/**
|
|
9
|
+
* Where the person writes. Multi-line, growing with the text; Enter sends,
|
|
10
|
+
* Shift+Enter breaks the line, and nothing sends while an input method is
|
|
11
|
+
* composing. While a run is in flight, send is held and stop is offered.
|
|
12
|
+
* Attachments and actions are slots: the host owns files and models.
|
|
13
|
+
*/
|
|
14
|
+
export function Composer({ value, onValueChange, onSubmit, running = false, onStop, disabled = false, placeholder, label, attachments, actions, autoFocus, className, }) {
|
|
15
|
+
const { messages } = useUI();
|
|
16
|
+
const t = messages.chat;
|
|
17
|
+
const hintId = React.useId();
|
|
18
|
+
const [own, setOwn] = React.useState("");
|
|
19
|
+
const draft = value ?? own;
|
|
20
|
+
const canSend = !disabled && !running && draft.trim() !== "";
|
|
21
|
+
const change = (next) => {
|
|
22
|
+
if (value === undefined)
|
|
23
|
+
setOwn(next);
|
|
24
|
+
onValueChange?.(next);
|
|
25
|
+
};
|
|
26
|
+
const submit = () => {
|
|
27
|
+
if (!canSend)
|
|
28
|
+
return;
|
|
29
|
+
onSubmit(draft.trim());
|
|
30
|
+
if (value === undefined)
|
|
31
|
+
setOwn("");
|
|
32
|
+
};
|
|
33
|
+
return (_jsxs("form", { "data-slot": "composer", onSubmit: (event) => {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
submit();
|
|
36
|
+
}, className: cn("flex min-w-0 flex-col rounded-lg border border-input bg-background focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 dark:bg-input/30", disabled && "opacity-50", className), children: [attachments && (_jsx("div", { role: "group", "aria-label": t.attachments, className: "flex min-w-0 flex-wrap gap-2 px-3 pt-2", children: attachments })), _jsx("textarea", { "aria-label": label ?? t.composer, "aria-describedby": hintId, value: draft, disabled: disabled, placeholder: placeholder ?? t.placeholder, rows: 1,
|
|
37
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus -- the host asks for it, on a chat screen
|
|
38
|
+
autoFocus: autoFocus, onChange: (event) => change(event.target.value), onKeyDown: (event) => {
|
|
39
|
+
if (event.key !== "Enter" || event.shiftKey || event.nativeEvent.isComposing)
|
|
40
|
+
return;
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
submit();
|
|
43
|
+
}, className: "field-sizing-content max-h-48 min-h-10 w-full min-w-0 resize-none bg-transparent px-3 py-2 text-base outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed md:text-sm" }), _jsx("p", { id: hintId, className: "sr-only", children: t.composerHint }), _jsxs("div", { className: "flex min-w-0 items-center gap-2 px-2 pb-2", children: [_jsx("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-2", children: actions }), running && onStop ? (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: onStop, children: [_jsx(SquareIcon, { "aria-hidden": true }), t.stop] })) : (_jsxs(Button, { type: "submit", size: "sm", disabled: !canSend, children: [_jsx(ArrowUpIcon, { "aria-hidden": true }), t.send] }))] })] }));
|
|
44
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import type { ChatMessage } from "./model.js";
|
|
3
|
+
/**
|
|
4
|
+
* Renders a message's text: the host's markdown renderer, say. It receives
|
|
5
|
+
* the text so far (it grows while the message streams) and must treat it as
|
|
6
|
+
* untrusted. Without one, the text shows as plain text.
|
|
7
|
+
*/
|
|
8
|
+
export type ChatContentRenderer = (content: string, message: ChatMessage) => React.ReactNode;
|
|
9
|
+
export interface MessageBubbleProps {
|
|
10
|
+
message: ChatMessage;
|
|
11
|
+
renderContent?: ChatContentRenderer;
|
|
12
|
+
/** Beside the speaker: a time, a copy button. */
|
|
13
|
+
meta?: React.ReactNode;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* One message, by role: the person's on the right in a muted bubble, the
|
|
18
|
+
* assistant's full width, system and developer notes dashed and quiet, a tool
|
|
19
|
+
* message in mono. While it streams, an empty one says it is thinking and a
|
|
20
|
+
* growing one shows a caret. Text is plain unless the host passes
|
|
21
|
+
* `renderContent`; plain text is never parsed as HTML.
|
|
22
|
+
*/
|
|
23
|
+
export declare function MessageBubble({ message, renderContent, meta, className }: MessageBubbleProps): React.JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
5
|
+
import { StatusDot } from "../status/StatusDot.js";
|
|
6
|
+
const ROLE_CLASS = {
|
|
7
|
+
user: "ml-auto max-w-[85%] rounded-lg bg-muted px-3 py-2",
|
|
8
|
+
assistant: "",
|
|
9
|
+
system: "rounded-lg border border-dashed px-3 py-2 text-xs text-muted-foreground",
|
|
10
|
+
developer: "rounded-lg border border-dashed px-3 py-2 text-xs text-muted-foreground",
|
|
11
|
+
tool: "rounded-lg border bg-card px-3 py-2 font-mono text-xs",
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* One message, by role: the person's on the right in a muted bubble, the
|
|
15
|
+
* assistant's full width, system and developer notes dashed and quiet, a tool
|
|
16
|
+
* message in mono. While it streams, an empty one says it is thinking and a
|
|
17
|
+
* growing one shows a caret. Text is plain unless the host passes
|
|
18
|
+
* `renderContent`; plain text is never parsed as HTML.
|
|
19
|
+
*/
|
|
20
|
+
export function MessageBubble({ message, renderContent, meta, className }) {
|
|
21
|
+
const { messages } = useUI();
|
|
22
|
+
const t = messages.chat;
|
|
23
|
+
const speaker = message.name ?? t.role(message.role);
|
|
24
|
+
const empty = message.content === "";
|
|
25
|
+
return (_jsxs("article", { "aria-label": t.messageFrom(speaker), "aria-busy": message.streaming || undefined, "data-slot": "message-bubble", "data-role": message.role, className: cn("flex min-w-0 flex-col gap-1", ROLE_CLASS[message.role], className), children: [(message.role !== "user" || meta) && (_jsxs("header", { className: "flex min-w-0 items-center gap-2 text-xs text-muted-foreground", children: [message.role !== "user" && _jsx("span", { className: "font-medium", children: speaker }), meta && _jsx("span", { className: "ml-auto flex items-center gap-2", children: meta })] })), message.streaming && empty ? (_jsxs("p", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(StatusDot, { tone: "info", pulse: true }), t.thinking] })) : (_jsxs("div", { className: "min-w-0 text-sm [overflow-wrap:anywhere]", children: [renderContent ? (renderContent(message.content, message)) : (_jsx("p", { className: "whitespace-pre-wrap", children: message.content })), message.streaming && (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": true, className: "ml-0.5 inline-block h-4 w-1.5 translate-y-0.5 bg-foreground/60 motion-safe:animate-pulse" }), _jsx("span", { className: "sr-only", children: t.writing })] }))] }))] }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type ChatContentRenderer } from "./MessageBubble.js";
|
|
3
|
+
import type { ChatToolCall, ChatView } from "./model.js";
|
|
4
|
+
import { type ToolCallCardProps } from "./ToolCallCard.js";
|
|
5
|
+
export interface MessageListProps {
|
|
6
|
+
/** The conversation: a `ChatView`, or the part of one the list reads. */
|
|
7
|
+
view: Pick<ChatView, "messages" | "toolCalls" | "timeline" | "status" | "error">;
|
|
8
|
+
renderContent?: ChatContentRenderer;
|
|
9
|
+
/**
|
|
10
|
+
* Draws a tool call the host has its own component for; return undefined
|
|
11
|
+
* for the default `ToolCallCard`.
|
|
12
|
+
*/
|
|
13
|
+
renderToolCall?: (call: ChatToolCall) => React.ReactNode;
|
|
14
|
+
/** The tool's name for people, per call. */
|
|
15
|
+
toolTitle?: (call: ChatToolCall) => string | undefined;
|
|
16
|
+
onApprove?: ToolCallCardProps["onApprove"];
|
|
17
|
+
onDeny?: ToolCallCardProps["onDeny"];
|
|
18
|
+
/** Offered beside a failed run's error. */
|
|
19
|
+
onRetry?: () => void;
|
|
20
|
+
/** The first page of history is loading. */
|
|
21
|
+
loading?: boolean;
|
|
22
|
+
/** Replaces the default empty state. */
|
|
23
|
+
empty?: React.ReactNode;
|
|
24
|
+
/** Names the list. Defaults to "Conversation". */
|
|
25
|
+
label?: string;
|
|
26
|
+
className?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The conversation in arrival order: message bubbles with tool call cards
|
|
30
|
+
* between them, a failed run's error at the end. It follows the stream while
|
|
31
|
+
* the person is at the bottom and stops following when they scroll up, with
|
|
32
|
+
* a button back to the latest. Each bubble re-renders only when its message
|
|
33
|
+
* changes, so a long conversation streams without virtualization; a host
|
|
34
|
+
* with thousands of turns pages its history.
|
|
35
|
+
*/
|
|
36
|
+
export declare function MessageList({ view, renderContent, renderToolCall, toolTitle, onApprove, onDeny, onRetry, loading, empty, label, className, }: MessageListProps): React.JSX.Element;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { ArrowDownIcon } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { Button } from "../primitives/Button.js";
|
|
7
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
8
|
+
import { EmptyState } from "../states/EmptyState.js";
|
|
9
|
+
import { LoadingState } from "../states/LoadingState.js";
|
|
10
|
+
import { MessageBubble } from "./MessageBubble.js";
|
|
11
|
+
import { ToolCallCard } from "./ToolCallCard.js";
|
|
12
|
+
const MemoBubble = React.memo(MessageBubble);
|
|
13
|
+
const MemoToolCall = React.memo(ToolCallCard);
|
|
14
|
+
/** How close to the end, in pixels, still counts as reading the latest. */
|
|
15
|
+
const PINNED = 48;
|
|
16
|
+
/**
|
|
17
|
+
* The conversation in arrival order: message bubbles with tool call cards
|
|
18
|
+
* between them, a failed run's error at the end. It follows the stream while
|
|
19
|
+
* the person is at the bottom and stops following when they scroll up, with
|
|
20
|
+
* a button back to the latest. Each bubble re-renders only when its message
|
|
21
|
+
* changes, so a long conversation streams without virtualization; a host
|
|
22
|
+
* with thousands of turns pages its history.
|
|
23
|
+
*/
|
|
24
|
+
export function MessageList({ view, renderContent, renderToolCall, toolTitle, onApprove, onDeny, onRetry, loading = false, empty, label, className, }) {
|
|
25
|
+
const { messages } = useUI();
|
|
26
|
+
const t = messages.chat;
|
|
27
|
+
const scroller = React.useRef(null);
|
|
28
|
+
const [pinned, setPinned] = React.useState(true);
|
|
29
|
+
const byId = React.useMemo(() => ({
|
|
30
|
+
messages: new Map(view.messages.map((m) => [m.id, m])),
|
|
31
|
+
calls: new Map(view.toolCalls.map((c) => [c.id, c])),
|
|
32
|
+
}), [view.messages, view.toolCalls]);
|
|
33
|
+
React.useLayoutEffect(() => {
|
|
34
|
+
const el = scroller.current;
|
|
35
|
+
if (pinned && el)
|
|
36
|
+
el.scrollTop = el.scrollHeight;
|
|
37
|
+
}, [pinned, byId, view.error]);
|
|
38
|
+
const jump = () => {
|
|
39
|
+
const el = scroller.current;
|
|
40
|
+
if (el)
|
|
41
|
+
el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
|
|
42
|
+
setPinned(true);
|
|
43
|
+
};
|
|
44
|
+
let body;
|
|
45
|
+
if (loading)
|
|
46
|
+
body = _jsx(LoadingState, { variant: "rows", rows: 3 });
|
|
47
|
+
else if (view.timeline.length === 0 && !view.error)
|
|
48
|
+
body = empty ?? _jsx(EmptyState, { title: t.empty, description: t.emptyDescription });
|
|
49
|
+
else
|
|
50
|
+
body = (_jsxs(_Fragment, { children: [view.timeline.map((item) => {
|
|
51
|
+
if (item.kind === "message") {
|
|
52
|
+
const message = byId.messages.get(item.id);
|
|
53
|
+
return message ? (_jsx(MemoBubble, { message: message, renderContent: renderContent }, `m:${item.id}`)) : null;
|
|
54
|
+
}
|
|
55
|
+
const call = byId.calls.get(item.id);
|
|
56
|
+
if (!call)
|
|
57
|
+
return null;
|
|
58
|
+
const custom = renderToolCall?.(call);
|
|
59
|
+
return custom !== undefined ? (_jsx(React.Fragment, { children: custom }, `t:${item.id}`)) : (_jsx(MemoToolCall, { call: call, title: toolTitle?.(call), onApprove: onApprove, onDeny: onDeny }, `t:${item.id}`));
|
|
60
|
+
}), view.status === "error" && view.error && (_jsxs("div", { role: "alert", className: "flex min-w-0 flex-col gap-2 rounded-lg border border-danger-border bg-danger-bg px-3 py-2 text-sm text-danger-fg sm:flex-row sm:items-center", children: [_jsxs("p", { className: "min-w-0 flex-1 [overflow-wrap:anywhere]", children: [_jsx("span", { className: "font-medium", children: t.runFailed }), ": ", view.error.message] }), onRetry && (_jsx(Button, { variant: "outline", size: "sm", onClick: onRetry, children: t.retry }))] }))] }));
|
|
61
|
+
return (_jsxs("div", { "data-slot": "message-list", className: cn("relative flex min-h-0 flex-col", className), children: [_jsx("div", { ref: scroller, role: "log", "aria-label": label ?? t.conversation, "aria-busy": view.status === "running" || loading || undefined,
|
|
62
|
+
// A scrolling region is reached by keyboard (WCAG 2.1.1).
|
|
63
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
64
|
+
tabIndex: 0, onScroll: (event) => {
|
|
65
|
+
const el = event.currentTarget;
|
|
66
|
+
setPinned(el.scrollHeight - el.scrollTop - el.clientHeight < PINNED);
|
|
67
|
+
}, className: "flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", children: body }), !pinned && (_jsxs(Button, { variant: "outline", size: "sm", onClick: jump, className: "absolute bottom-3 left-1/2 -translate-x-1/2 shadow-md", children: [_jsx(ArrowDownIcon, { "aria-hidden": true }), t.jumpToLatest] }))] }));
|
|
68
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type SelectOption } from "../primitives/Select.js";
|
|
2
|
+
export interface ModelPickerProps {
|
|
3
|
+
/** The models this host offers, in its words. The package names none. */
|
|
4
|
+
options: readonly SelectOption[];
|
|
5
|
+
value: string;
|
|
6
|
+
onValueChange: (value: string) => void;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
/** Names the picker. Defaults to "Model". */
|
|
9
|
+
label?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The model for the next turn, from the options the host passes: which
|
|
14
|
+
* models exist, which the person may use and what each costs are the host's.
|
|
15
|
+
* Small enough for a composer's actions.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ModelPicker({ options, value, onValueChange, disabled, label, className, }: ModelPickerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
4
|
+
import { Select } from "../primitives/Select.js";
|
|
5
|
+
/**
|
|
6
|
+
* The model for the next turn, from the options the host passes: which
|
|
7
|
+
* models exist, which the person may use and what each costs are the host's.
|
|
8
|
+
* Small enough for a composer's actions.
|
|
9
|
+
*/
|
|
10
|
+
export function ModelPicker({ options, value, onValueChange, disabled, label, className, }) {
|
|
11
|
+
const { messages } = useUI();
|
|
12
|
+
return (_jsx(Select, { options: options, value: value, onValueChange: onValueChange, disabled: disabled, "aria-label": label ?? messages.chat.model, size: "sm", className: className }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type ChatToolCall } from "./model.js";
|
|
3
|
+
export interface ToolCallCardProps {
|
|
4
|
+
call: ChatToolCall;
|
|
5
|
+
/** The tool's name for people ("Run SQL"). Defaults to the call's name. */
|
|
6
|
+
title?: string;
|
|
7
|
+
/** Draws the result: a table, a chart. Defaults to the result as text, pretty-printed when it is JSON. */
|
|
8
|
+
renderResult?: (call: ChatToolCall) => React.ReactNode;
|
|
9
|
+
/** Whether the details start open. A call waiting for approval always starts open. */
|
|
10
|
+
defaultOpen?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Approves a call the run paused on, after the confirm step. Awaited; see
|
|
13
|
+
* ConfirmDialog. The host resumes the run (AG-UI `resume`); the card only
|
|
14
|
+
* reports the decision.
|
|
15
|
+
*/
|
|
16
|
+
onApprove?: (call: ChatToolCall) => Promise<unknown> | unknown;
|
|
17
|
+
/** Denies it, after the confirm step, with the reason the person gave. */
|
|
18
|
+
onDeny?: (call: ChatToolCall, reason: string) => Promise<unknown> | unknown;
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One tool call: its name and status, its arguments as they stream and its
|
|
23
|
+
* result or error behind a disclosure, and, when the run paused on it,
|
|
24
|
+
* approve and deny through the library's confirm flow (a denial always with
|
|
25
|
+
* a reason). Host-driven: it never runs or resumes anything.
|
|
26
|
+
*/
|
|
27
|
+
export declare function ToolCallCard({ call, title, renderResult, defaultOpen, onApprove, onDeny, className, }: ToolCallCardProps): React.JSX.Element;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronRightIcon, WrenchIcon } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { ConfirmDialog } from "../overlays/ConfirmDialog.js";
|
|
7
|
+
import { Button } from "../primitives/Button.js";
|
|
8
|
+
import { useUI } from "../provider/UIProvider.js";
|
|
9
|
+
import { StatusBadge } from "../status/StatusBadge.js";
|
|
10
|
+
import { parseToolCallArgs } from "./model.js";
|
|
11
|
+
const TONE = {
|
|
12
|
+
streaming: "info",
|
|
13
|
+
running: "info",
|
|
14
|
+
complete: "ok",
|
|
15
|
+
failed: "error",
|
|
16
|
+
cancelled: "muted",
|
|
17
|
+
approval: "warn",
|
|
18
|
+
};
|
|
19
|
+
/** JSON text shown indented when it parses, as it came otherwise. */
|
|
20
|
+
const pretty = (text) => {
|
|
21
|
+
const value = parseToolCallArgs(text);
|
|
22
|
+
return value === undefined ? text : JSON.stringify(value, null, 2);
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* One tool call: its name and status, its arguments as they stream and its
|
|
26
|
+
* result or error behind a disclosure, and, when the run paused on it,
|
|
27
|
+
* approve and deny through the library's confirm flow (a denial always with
|
|
28
|
+
* a reason). Host-driven: it never runs or resumes anything.
|
|
29
|
+
*/
|
|
30
|
+
export function ToolCallCard({ call, title, renderResult, defaultOpen = false, onApprove, onDeny, className, }) {
|
|
31
|
+
const { messages } = useUI();
|
|
32
|
+
const t = messages.chat;
|
|
33
|
+
const id = React.useId();
|
|
34
|
+
const name = title ?? call.name;
|
|
35
|
+
const waiting = call.approval !== undefined;
|
|
36
|
+
const [open, setOpen] = React.useState(defaultOpen || waiting);
|
|
37
|
+
const [confirming, setConfirming] = React.useState(null);
|
|
38
|
+
// A call that comes to wait for approval opens, so the person sees what they approve.
|
|
39
|
+
const [wasWaiting, setWasWaiting] = React.useState(waiting);
|
|
40
|
+
if (waiting !== wasWaiting) {
|
|
41
|
+
setWasWaiting(waiting);
|
|
42
|
+
if (waiting)
|
|
43
|
+
setOpen(true);
|
|
44
|
+
}
|
|
45
|
+
const status = waiting ? "approval" : call.status;
|
|
46
|
+
const detailsId = `${id}-details`;
|
|
47
|
+
return (_jsxs("section", { "aria-label": t.toolCall(name), "data-slot": "tool-call-card", "data-status": status, className: cn("flex min-w-0 flex-col rounded-lg border bg-card text-sm", className), children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2 px-3 py-2", children: [_jsx(WrenchIcon, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" }), _jsxs("button", { type: "button", "aria-expanded": open, "aria-controls": detailsId, onClick: () => setOpen((o) => !o), className: "inline-flex min-w-0 items-center gap-1 rounded-sm text-left outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring", children: [_jsx(ChevronRightIcon, { "aria-hidden": true, className: cn("size-3.5 shrink-0 transition-transform", open && "rotate-90") }), _jsx("span", { className: "font-mono text-xs font-medium [overflow-wrap:anywhere]", children: name }), " ", _jsx("span", { className: "sr-only", children: t.details })] }), _jsx(StatusBadge, { tone: TONE[status], pulse: status === "streaming" || status === "running", className: "ml-auto", children: t.toolStatus(status) })] }), open && (_jsxs("div", { id: detailsId, className: "flex min-w-0 flex-col gap-2 border-t px-3 py-2", children: [_jsx(Block, { label: t.arguments, children: call.args ? pretty(call.args) : t.noArguments }), call.error !== undefined && (_jsx(Block, { label: t.toolError, tone: "error", children: call.error })), call.result !== undefined &&
|
|
48
|
+
(renderResult ? (_jsx("div", { className: "min-w-0", children: renderResult(call) })) : (_jsx(Block, { label: t.result, children: pretty(call.result) })))] })), waiting && (onApprove || onDeny) && (_jsxs("div", { className: "flex min-w-0 flex-col gap-2 border-t px-3 py-2 sm:flex-row sm:items-center", children: [_jsx("p", { className: "min-w-0 flex-1 [overflow-wrap:anywhere]", children: call.approval?.message ?? t.approvalNeeded }), _jsxs("div", { className: "flex gap-2", children: [onDeny && (_jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirming("deny"), children: t.deny })), onApprove && (_jsx(Button, { size: "sm", onClick: () => setConfirming("approve"), children: t.approve }))] })] })), _jsx(ConfirmDialog, { open: confirming === "approve", onOpenChange: (next) => !next && setConfirming(null), title: t.approveTitle(name), description: call.approval?.message ?? t.approveDescription, confirmLabel: t.approve, onConfirm: () => onApprove?.(call) }), _jsx(ConfirmDialog, { open: confirming === "deny", onOpenChange: (next) => !next && setConfirming(null), title: t.denyTitle(name), description: t.denyDescription, confirmLabel: t.deny, destructive: true, reason: { label: t.denyReason }, onConfirm: (reason) => onDeny?.(call, reason) })] }));
|
|
49
|
+
}
|
|
50
|
+
function Block({ label, tone, children, }) {
|
|
51
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("p", { className: "font-mono text-2xs font-medium tracking-wider text-muted-foreground uppercase", children: label }), _jsx("pre", { role: "region", "aria-label": label, tabIndex: 0, className: cn("max-h-64 min-w-0 overflow-auto rounded-md bg-muted p-2 font-mono text-xs [overflow-wrap:anywhere] whitespace-pre-wrap outline-none focus-visible:ring-2 focus-visible:ring-ring", tone === "error" && "bg-danger-bg text-danger-fg"), children: children })] }));
|
|
52
|
+
}
|