@marimo-team/islands 0.15.3 → 0.15.5
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/dist/{ConnectedDataExplorerComponent-DfvW3rBn.js → ConnectedDataExplorerComponent-CBeIYi8p.js} +2 -2
- package/dist/{ImageComparisonComponent-XaJshw7d.js → ImageComparisonComponent-Bk0a0xBq.js} +1 -1
- package/dist/{_baseUniq-dN9WKF9m.js → _baseUniq-utU5_Vu-.js} +1 -1
- package/dist/{any-language-editor-CpFniVi-.js → any-language-editor-PrUUh2lr.js} +1 -1
- package/dist/{architectureDiagram-W76B3OCA-Bpg85ZKv.js → architectureDiagram-W76B3OCA-D-vOp0UU.js} +4 -4
- package/dist/assets/{worker-Y-Q4G-N2.js → worker-BcG8m3h5.js} +3 -3
- package/dist/{blockDiagram-QIGZ2CNN-DS1kOHlW.js → blockDiagram-QIGZ2CNN-IG-z8q8A.js} +5 -5
- package/dist/{c4Diagram-FPNF74CW-CyRVKssw.js → c4Diagram-FPNF74CW-5AEXIX3t.js} +2 -2
- package/dist/{channel-BilGXox7.js → channel-ECVsTGGL.js} +1 -1
- package/dist/{chunk-4BX2VUAB-CZR39zCO.js → chunk-4BX2VUAB-DfJcd9e-.js} +1 -1
- package/dist/{chunk-55IACEB6-BIH-MYov.js → chunk-55IACEB6-BwT8MejR.js} +1 -1
- package/dist/{chunk-FMBD7UC4-4PZXFZE8.js → chunk-FMBD7UC4-DW7uxNR6.js} +1 -1
- package/dist/{chunk-K7UQS3LO-CEvWKznk.js → chunk-K7UQS3LO-BGn2ZPDQ.js} +4 -4
- package/dist/{chunk-QN33PNHL-D5LO5Jq_.js → chunk-QN33PNHL-BcIbOumv.js} +1 -1
- package/dist/{chunk-QZHKN3VN-6gwUonWI.js → chunk-QZHKN3VN-CMSnhk6x.js} +1 -1
- package/dist/{chunk-TVAH2DTR-3gm06QdU.js → chunk-TVAH2DTR-CZF2JRya.js} +3 -3
- package/dist/{chunk-TZMSLE5B-Cm8Iy9bO.js → chunk-TZMSLE5B-BHzN_BY6.js} +1 -1
- package/dist/{classDiagram-v2-RKCZMP56-DC529O_z.js → classDiagram-KNZD7YFC-2H7MseyB.js} +2 -2
- package/dist/{classDiagram-KNZD7YFC-DC529O_z.js → classDiagram-v2-RKCZMP56-2H7MseyB.js} +2 -2
- package/dist/{clone-CLoRX3j6.js → clone-DKQcSK7N.js} +1 -1
- package/dist/{cose-bilkent-S5V4N54A-qf5DlS6Y.js → cose-bilkent-S5V4N54A-CgvKFxTr.js} +2 -2
- package/dist/{dagre-5GWH7T2D-Ceocls0m.js → dagre-5GWH7T2D-VNFIipzt.js} +6 -6
- package/dist/{data-grid-overlay-editor-AqDS_UKe.js → data-grid-overlay-editor-XdqkKCVx.js} +2 -2
- package/dist/{diagram-N5W7TBWH-CP66oSiv.js → diagram-N5W7TBWH-D1s8h-eH.js} +5 -5
- package/dist/{diagram-QEK2KX5R-_YD4kxxi.js → diagram-QEK2KX5R-DOa-AstT.js} +3 -3
- package/dist/{diagram-S2PKOQOG-Cnj8T-OP.js → diagram-S2PKOQOG-CFZ-Y2zi.js} +3 -3
- package/dist/{dockerfile-Cm8cRYCN.js → dockerfile-zE-2DWBS.js} +1 -1
- package/dist/{erDiagram-AWTI2OKA-CGnvoHx6.js → erDiagram-AWTI2OKA-WxUYJfbS.js} +4 -4
- package/dist/{flowDiagram-PVAE7QVJ-DG-pr9R9.js → flowDiagram-PVAE7QVJ-dDZH2O1W.js} +5 -5
- package/dist/{ganttDiagram-OWAHRB6G-JmChtxvn.js → ganttDiagram-OWAHRB6G-D3CCqPQq.js} +4 -4
- package/dist/{gitGraphDiagram-NY62KEGX-D8wLfOPd.js → gitGraphDiagram-NY62KEGX-BHFylEwc.js} +4 -4
- package/dist/{glide-data-editor-9nC3iCIZ.js → glide-data-editor-D0aJSGV_.js} +3 -3
- package/dist/{graph-CoRe7vAN.js → graph-BPGEu6c8.js} +3 -3
- package/dist/{index-6qYeHHjQ.js → index-Bx2b23rX.js} +3 -3
- package/dist/{index-BthgsgYX.js → index-DotQhzoN.js} +1 -1
- package/dist/{index-jkm77Jrz.js → index-HtOEKQ3O.js} +1 -1
- package/dist/{index-BpzLh4Qe.js → index-eDB61tLS.js} +1 -1
- package/dist/{infoDiagram-STP46IZ2-BlXxvOrR.js → infoDiagram-STP46IZ2-DWhhqGPi.js} +2 -2
- package/dist/{journeyDiagram-BIP6EPQ6-CNRYs_Fc.js → journeyDiagram-BIP6EPQ6-CU8FpryL.js} +3 -3
- package/dist/{kanban-definition-6OIFK2YF-B9HeMAuP.js → kanban-definition-6OIFK2YF-CWhF_a4g.js} +2 -2
- package/dist/{layout-m2vOUiW1.js → layout-DGonEvAZ.js} +4 -4
- package/dist/{linear-DU6Q5CX3.js → linear-Cww2a6nQ.js} +1 -1
- package/dist/{main-BD2KGFpU.js → main-Bc0LY9fB.js} +20636 -20608
- package/dist/main.js +1 -1
- package/dist/{mermaid-HVCtvbyx.js → mermaid-DpJuOhRr.js} +30 -30
- package/dist/{min-DcGMA4e_.js → min-CFQjsG4L.js} +2 -2
- package/dist/{mindmap-definition-Q6HEUPPD-BW8UmIDQ.js → mindmap-definition-Q6HEUPPD-K513Ef1t.js} +3 -3
- package/dist/{number-overlay-editor-D8Hl0Syo.js → number-overlay-editor-DuSchUfE.js} +2 -2
- package/dist/{pieDiagram-ADFJNKIX-Bg-3zg5U.js → pieDiagram-ADFJNKIX-DAIIUJJO.js} +3 -3
- package/dist/{quadrantDiagram-LMRXKWRM-BO4IG6Yz.js → quadrantDiagram-LMRXKWRM-yuf-j7Os.js} +2 -2
- package/dist/{react-plotly-dkvHVuRb.js → react-plotly-B378DZ9U.js} +1 -1
- package/dist/{requirementDiagram-4UW4RH46-5sdTguSM.js → requirementDiagram-4UW4RH46-BBWvEl6q.js} +3 -3
- package/dist/{sankeyDiagram-GR3RE2ED-Buhlv9OI.js → sankeyDiagram-GR3RE2ED-B_TwV-dS.js} +1 -1
- package/dist/{sequenceDiagram-C3RYC4MD-C3qsM2UP.js → sequenceDiagram-C3RYC4MD-BVC6lltp.js} +3 -3
- package/dist/{slides-component-D209A0-s.js → slides-component-CPX3S0Y9.js} +1 -1
- package/dist/{stateDiagram-KXAO66HF-CopJ7G6P.js → stateDiagram-KXAO66HF-BCU1tYTD.js} +4 -4
- package/dist/{stateDiagram-v2-UMBNRL4Z-CejL8AKf.js → stateDiagram-v2-UMBNRL4Z-BdvN6wTu.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{time-BwSBitlN.js → time-CSIip6fV.js} +2 -2
- package/dist/{timeline-definition-XQNQX7LJ-DzMNTX-C.js → timeline-definition-XQNQX7LJ-CCxCPNQI.js} +1 -1
- package/dist/{treemap-75Q7IDZK-zeJG07dk.js → treemap-75Q7IDZK-Du6v0BzD.js} +5 -5
- package/dist/{vega-component-CUkiTayd.js → vega-component-Da93sTnp.js} +2 -2
- package/dist/{xychartDiagram-6GGTOJPD-DiENNXMS.js → xychartDiagram-6GGTOJPD-Oq6xaZKR.js} +2 -2
- package/package.json +6 -3
- package/src/components/ai/ai-provider-icon.tsx +5 -1
- package/src/components/chat/acp/__tests__/__snapshots__/prompt.test.ts.snap +304 -0
- package/src/components/chat/acp/__tests__/atoms.test.ts +56 -0
- package/src/components/chat/acp/__tests__/prompt.test.ts +12 -0
- package/src/components/chat/acp/__tests__/state.test.ts +621 -0
- package/src/components/chat/acp/agent-docs.tsx +78 -0
- package/src/components/chat/acp/agent-panel.css +23 -0
- package/src/components/chat/acp/agent-panel.tsx +715 -0
- package/src/components/chat/acp/agent-selector.tsx +138 -0
- package/src/components/chat/acp/blocks.tsx +664 -0
- package/src/components/chat/acp/common.tsx +198 -0
- package/src/components/chat/acp/prompt.ts +284 -0
- package/src/components/chat/acp/scroll-to-bottom-button.tsx +50 -0
- package/src/components/chat/acp/session-tabs.tsx +138 -0
- package/src/components/chat/acp/state.ts +263 -0
- package/src/components/chat/acp/thread.tsx +121 -0
- package/src/components/chat/acp/types.ts +63 -0
- package/src/components/chat/acp/utils.ts +45 -0
- package/src/components/chat/tool-call-accordion.tsx +1 -1
- package/src/components/editor/chrome/types.ts +10 -0
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +17 -3
- package/src/core/config/feature-flag.tsx +2 -0
- package/src/plugins/impl/vega/vega.css +121 -0
- package/src/utils/Logger.ts +5 -6
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
import type {
|
|
3
|
+
ContentBlock,
|
|
4
|
+
ToolCallContent,
|
|
5
|
+
ToolCallLocation,
|
|
6
|
+
} from "@zed-industries/agent-client-protocol";
|
|
7
|
+
import { capitalize } from "lodash-es";
|
|
8
|
+
import {
|
|
9
|
+
FileAudio2Icon,
|
|
10
|
+
FileIcon,
|
|
11
|
+
FileImageIcon,
|
|
12
|
+
FileJsonIcon,
|
|
13
|
+
FileTextIcon,
|
|
14
|
+
FileVideoCameraIcon,
|
|
15
|
+
RotateCcwIcon,
|
|
16
|
+
WifiIcon,
|
|
17
|
+
WifiOffIcon,
|
|
18
|
+
WrenchIcon,
|
|
19
|
+
XCircleIcon,
|
|
20
|
+
XIcon,
|
|
21
|
+
} from "lucide-react";
|
|
22
|
+
import React from "react";
|
|
23
|
+
import { Streamdown } from "streamdown";
|
|
24
|
+
import { mergeToolCalls } from "use-acp";
|
|
25
|
+
import { JsonOutput } from "@/components/editor/output/JsonOutput";
|
|
26
|
+
import { Button } from "@/components/ui/button";
|
|
27
|
+
import {
|
|
28
|
+
Popover,
|
|
29
|
+
PopoverContent,
|
|
30
|
+
PopoverTrigger,
|
|
31
|
+
} from "@/components/ui/popover";
|
|
32
|
+
import { logNever } from "@/utils/assertNever";
|
|
33
|
+
import { cn } from "@/utils/cn";
|
|
34
|
+
import { type Base64String, base64ToDataURL } from "@/utils/json/base64";
|
|
35
|
+
import { Strings } from "@/utils/strings";
|
|
36
|
+
import { SimpleAccordion } from "./common";
|
|
37
|
+
import type {
|
|
38
|
+
AgentNotificationEvent,
|
|
39
|
+
AgentThoughtNotificationEvent,
|
|
40
|
+
ConnectionChangeNotificationEvent,
|
|
41
|
+
ContentBlockOf,
|
|
42
|
+
ErrorNotificationEvent,
|
|
43
|
+
PlanNotificationEvent,
|
|
44
|
+
SessionNotificationEventData,
|
|
45
|
+
ToolCallNotificationEvent,
|
|
46
|
+
ToolCallUpdateNotificationEvent,
|
|
47
|
+
UserNotificationEvent,
|
|
48
|
+
} from "./types";
|
|
49
|
+
import {
|
|
50
|
+
isAgentMessages,
|
|
51
|
+
isAgentThoughts,
|
|
52
|
+
isPlans,
|
|
53
|
+
isToolCalls,
|
|
54
|
+
isUserMessages,
|
|
55
|
+
} from "./utils";
|
|
56
|
+
|
|
57
|
+
export const ErrorBlock = (props: {
|
|
58
|
+
data: ErrorNotificationEvent["data"];
|
|
59
|
+
onRetry?: () => void;
|
|
60
|
+
onDismiss?: () => void;
|
|
61
|
+
}) => {
|
|
62
|
+
const { message } = props.data;
|
|
63
|
+
|
|
64
|
+
// Don't show WebSocket connection errors
|
|
65
|
+
if (message.includes("WebSocket")) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
className="border border-[var(--red-6)] bg-[var(--red-2)] rounded-lg p-4 my-2"
|
|
72
|
+
data-block-type="error"
|
|
73
|
+
>
|
|
74
|
+
<div className="flex items-start gap-3">
|
|
75
|
+
<div className="flex-shrink-0">
|
|
76
|
+
<XCircleIcon className="h-5 w-5 text-[var(--red-11)]" />
|
|
77
|
+
</div>
|
|
78
|
+
<div className="flex-1 min-w-0">
|
|
79
|
+
<div className="flex items-center gap-2 mb-2">
|
|
80
|
+
<h4 className="text-sm font-medium text-[var(--red-11)]">
|
|
81
|
+
Agent Error
|
|
82
|
+
</h4>
|
|
83
|
+
</div>
|
|
84
|
+
<div className="text-sm text-[var(--red-11)] leading-relaxed mb-3">
|
|
85
|
+
{message}
|
|
86
|
+
</div>
|
|
87
|
+
<div className="flex items-center gap-2">
|
|
88
|
+
{props.onRetry && (
|
|
89
|
+
<Button
|
|
90
|
+
size="xs"
|
|
91
|
+
variant="outline"
|
|
92
|
+
onClick={props.onRetry}
|
|
93
|
+
className="text-[var(--red-11)] border-[var(--red-6)] hover:bg-[var(--red-3)]"
|
|
94
|
+
>
|
|
95
|
+
<RotateCcwIcon className="h-3 w-3 mr-1" />
|
|
96
|
+
Retry
|
|
97
|
+
</Button>
|
|
98
|
+
)}
|
|
99
|
+
{props.onDismiss && (
|
|
100
|
+
<Button
|
|
101
|
+
size="xs"
|
|
102
|
+
variant="ghost"
|
|
103
|
+
onClick={props.onDismiss}
|
|
104
|
+
className="text-[var(--red-10)] hover:bg-[var(--red-3)]"
|
|
105
|
+
>
|
|
106
|
+
<XIcon className="h-3 w-3 mr-1" />
|
|
107
|
+
Dismiss
|
|
108
|
+
</Button>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const ConnectionChangeBlock = (props: {
|
|
118
|
+
data: ConnectionChangeNotificationEvent["data"];
|
|
119
|
+
isConnected: boolean;
|
|
120
|
+
onRetry?: () => void;
|
|
121
|
+
timestamp?: number;
|
|
122
|
+
}) => {
|
|
123
|
+
const { status } = props.data;
|
|
124
|
+
|
|
125
|
+
if (props.isConnected) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const getStatusConfig = () => {
|
|
130
|
+
switch (status) {
|
|
131
|
+
case "connected":
|
|
132
|
+
return {
|
|
133
|
+
icon: <WifiIcon className="h-4 w-4" />,
|
|
134
|
+
title: "Connected to Agent",
|
|
135
|
+
message: "Successfully established connection with the AI agent",
|
|
136
|
+
bgColor: "bg-[var(--blue-2)]",
|
|
137
|
+
borderColor: "border-[var(--blue-6)]",
|
|
138
|
+
textColor: "text-[var(--blue-11)]",
|
|
139
|
+
iconColor: "text-[var(--blue-10)]",
|
|
140
|
+
};
|
|
141
|
+
case "disconnected":
|
|
142
|
+
return {
|
|
143
|
+
icon: <WifiOffIcon className="h-4 w-4" />,
|
|
144
|
+
title: "Disconnected from Agent",
|
|
145
|
+
message: "Connection to the AI agent has been lost",
|
|
146
|
+
bgColor: "bg-[var(--amber-2)]",
|
|
147
|
+
borderColor: "border-[var(--amber-6)]",
|
|
148
|
+
textColor: "text-[var(--amber-11)]",
|
|
149
|
+
iconColor: "text-[var(--amber-10)]",
|
|
150
|
+
};
|
|
151
|
+
case "connecting":
|
|
152
|
+
return {
|
|
153
|
+
icon: <WifiIcon className="h-4 w-4 animate-pulse" />,
|
|
154
|
+
title: "Connecting to Agent",
|
|
155
|
+
message: "Establishing connection with the AI agent...",
|
|
156
|
+
bgColor: "bg-[var(--gray-2)]",
|
|
157
|
+
borderColor: "border-[var(--gray-6)]",
|
|
158
|
+
textColor: "text-[var(--gray-11)]",
|
|
159
|
+
iconColor: "text-[var(--gray-10)]",
|
|
160
|
+
};
|
|
161
|
+
case "error":
|
|
162
|
+
return {
|
|
163
|
+
icon: <WifiOffIcon className="h-4 w-4" />,
|
|
164
|
+
title: "Connection Error",
|
|
165
|
+
message: "Failed to connect to the AI agent",
|
|
166
|
+
bgColor: "bg-[var(--red-2)]",
|
|
167
|
+
borderColor: "border-[var(--red-6)]",
|
|
168
|
+
textColor: "text-[var(--red-11)]",
|
|
169
|
+
iconColor: "text-[var(--red-10)]",
|
|
170
|
+
};
|
|
171
|
+
default:
|
|
172
|
+
return {
|
|
173
|
+
icon: <WifiOffIcon className="h-4 w-4" />,
|
|
174
|
+
title: "Connection Status Changed",
|
|
175
|
+
message: `Agent connection status: ${status}`,
|
|
176
|
+
bgColor: "bg-[var(--gray-2)]",
|
|
177
|
+
borderColor: "border-[var(--gray-6)]",
|
|
178
|
+
textColor: "text-[var(--gray-11)]",
|
|
179
|
+
iconColor: "text-[var(--gray-10)]",
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const config = getStatusConfig();
|
|
185
|
+
const showRetry = status === "disconnected" || status === "error";
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<div
|
|
189
|
+
className={`border ${config.borderColor} ${config.bgColor} rounded-lg p-3 my-2`}
|
|
190
|
+
data-block-type="connection-change"
|
|
191
|
+
data-status={status}
|
|
192
|
+
>
|
|
193
|
+
<div className="flex items-start gap-3">
|
|
194
|
+
<div className={`flex-shrink-0 ${config.iconColor}`}>{config.icon}</div>
|
|
195
|
+
<div className="flex-1 min-w-0">
|
|
196
|
+
<div className="flex items-center gap-2 mb-1">
|
|
197
|
+
<h4 className={`text-sm font-medium ${config.textColor}`}>
|
|
198
|
+
{config.title}
|
|
199
|
+
</h4>
|
|
200
|
+
{props.timestamp && (
|
|
201
|
+
<span className="text-xs text-muted-foreground">
|
|
202
|
+
{new Date(props.timestamp).toLocaleTimeString()}
|
|
203
|
+
</span>
|
|
204
|
+
)}
|
|
205
|
+
</div>
|
|
206
|
+
<div className={`text-sm ${config.textColor} opacity-90 mb-2`}>
|
|
207
|
+
{config.message}
|
|
208
|
+
</div>
|
|
209
|
+
{showRetry && props.onRetry && (
|
|
210
|
+
<Button
|
|
211
|
+
size="xs"
|
|
212
|
+
variant="outline"
|
|
213
|
+
onClick={props.onRetry}
|
|
214
|
+
className={`${config.textColor} ${config.borderColor} hover:${config.bgColor}`}
|
|
215
|
+
>
|
|
216
|
+
<RotateCcwIcon className="h-3 w-3 mr-1" />
|
|
217
|
+
Retry Connection
|
|
218
|
+
</Button>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export const AgentThoughtsBlock = (props: {
|
|
227
|
+
startTimestamp: number;
|
|
228
|
+
endTimestamp: number;
|
|
229
|
+
data: AgentThoughtNotificationEvent[];
|
|
230
|
+
}) => {
|
|
231
|
+
const startAsSeconds = props.startTimestamp / 1000;
|
|
232
|
+
const endAsSeconds = props.endTimestamp / 1000;
|
|
233
|
+
const totalSeconds = Math.round(endAsSeconds - startAsSeconds) || "1";
|
|
234
|
+
return (
|
|
235
|
+
<div className="text-xs text-muted-foreground">
|
|
236
|
+
<SimpleAccordion title={`Thought for ${totalSeconds}s`}>
|
|
237
|
+
<div className="flex flex-col gap-2 text-muted-foreground">
|
|
238
|
+
<ContentBlocks data={props.data.map((item) => item.content)} />
|
|
239
|
+
</div>
|
|
240
|
+
</SimpleAccordion>
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export const PlansBlock = (props: { data: PlanNotificationEvent[] }) => {
|
|
246
|
+
const plans = props.data.flatMap((item) => item.entries);
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<div className="rounded-lg border bg-background p-2 text-xs">
|
|
250
|
+
<div className="flex items-center justify-between mb-2">
|
|
251
|
+
<span className="font-medium text-muted-foreground">
|
|
252
|
+
To-dos{" "}
|
|
253
|
+
<span className="font-normal text-muted-foreground">
|
|
254
|
+
{plans.length}
|
|
255
|
+
</span>
|
|
256
|
+
</span>
|
|
257
|
+
</div>
|
|
258
|
+
<ul className="flex flex-col gap-1">
|
|
259
|
+
{plans.map((item, index) => (
|
|
260
|
+
<li
|
|
261
|
+
key={`${item.status}-${index}`}
|
|
262
|
+
className={`
|
|
263
|
+
flex items-center gap-2 px-2 py-1 rounded
|
|
264
|
+
`}
|
|
265
|
+
>
|
|
266
|
+
<input
|
|
267
|
+
type="checkbox"
|
|
268
|
+
checked={item.status === "completed"}
|
|
269
|
+
readOnly={true}
|
|
270
|
+
className="accent-primary h-4 w-4 rounded border border-muted-foreground/30"
|
|
271
|
+
tabIndex={-1}
|
|
272
|
+
/>
|
|
273
|
+
<span
|
|
274
|
+
className={cn(
|
|
275
|
+
"text-xs",
|
|
276
|
+
item.status === "completed" &&
|
|
277
|
+
"line-through text-muted-foreground",
|
|
278
|
+
)}
|
|
279
|
+
>
|
|
280
|
+
{item.content}
|
|
281
|
+
</span>
|
|
282
|
+
</li>
|
|
283
|
+
))}
|
|
284
|
+
</ul>
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export const UserMessagesBlock = (props: { data: UserNotificationEvent[] }) => {
|
|
290
|
+
return (
|
|
291
|
+
<div className="flex flex-col gap-2 text-muted-foreground border p-2 bg-background rounded break-words">
|
|
292
|
+
<ContentBlocks data={props.data.map((item) => item.content)} />
|
|
293
|
+
</div>
|
|
294
|
+
);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
export const AgentMessagesBlock = (props: {
|
|
298
|
+
data: AgentNotificationEvent[];
|
|
299
|
+
}) => {
|
|
300
|
+
return (
|
|
301
|
+
<div className="flex flex-col gap-2">
|
|
302
|
+
<ContentBlocks data={props.data.map((item) => item.content)} />
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
export const ContentBlocks = (props: { data: ContentBlock[] }) => {
|
|
308
|
+
const renderBlock = (block: ContentBlock) => {
|
|
309
|
+
if (block.type === "text") {
|
|
310
|
+
return <Streamdown>{block.text}</Streamdown>;
|
|
311
|
+
}
|
|
312
|
+
if (block.type === "image") {
|
|
313
|
+
return <ImageBlock data={block} />;
|
|
314
|
+
}
|
|
315
|
+
if (block.type === "audio") {
|
|
316
|
+
return <AudioBlock data={block} />;
|
|
317
|
+
}
|
|
318
|
+
if (block.type === "resource") {
|
|
319
|
+
return <ResourceBlock data={block} />;
|
|
320
|
+
}
|
|
321
|
+
if (block.type === "resource_link") {
|
|
322
|
+
return <ResourceLinkBlock data={block} />;
|
|
323
|
+
}
|
|
324
|
+
logNever(block);
|
|
325
|
+
return null;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
return (
|
|
329
|
+
<div>
|
|
330
|
+
{props.data.map((item, index) => {
|
|
331
|
+
return (
|
|
332
|
+
<React.Fragment key={`${item.type}-${index}`}>
|
|
333
|
+
{renderBlock(item)}
|
|
334
|
+
</React.Fragment>
|
|
335
|
+
);
|
|
336
|
+
})}
|
|
337
|
+
</div>
|
|
338
|
+
);
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
export const ImageBlock = (props: { data: ContentBlockOf<"image"> }) => {
|
|
342
|
+
return (
|
|
343
|
+
<img
|
|
344
|
+
src={`data:${props.data.mimeType};base64,${props.data.data}`}
|
|
345
|
+
alt={props.data.uri ?? ""}
|
|
346
|
+
/>
|
|
347
|
+
);
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
export const AudioBlock = (props: { data: ContentBlockOf<"audio"> }) => {
|
|
351
|
+
return (
|
|
352
|
+
<audio
|
|
353
|
+
src={`data:${props.data.mimeType};base64,${props.data.data}`}
|
|
354
|
+
controls={true}
|
|
355
|
+
>
|
|
356
|
+
<track kind="captions" />
|
|
357
|
+
</audio>
|
|
358
|
+
);
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
export const ResourceBlock = (props: { data: ContentBlockOf<"resource"> }) => {
|
|
362
|
+
if ("text" in props.data.resource) {
|
|
363
|
+
return (
|
|
364
|
+
<Popover>
|
|
365
|
+
<PopoverTrigger>
|
|
366
|
+
<span className="flex items-center gap-1">
|
|
367
|
+
{props.data.resource.mimeType && (
|
|
368
|
+
<MimeIcon mimeType={props.data.resource.mimeType} />
|
|
369
|
+
)}
|
|
370
|
+
{props.data.resource.uri}
|
|
371
|
+
</span>
|
|
372
|
+
</PopoverTrigger>
|
|
373
|
+
<PopoverContent className="max-h-96 overflow-y-auto scrollbar-thin">
|
|
374
|
+
<Streamdown>{props.data.resource.text}</Streamdown>
|
|
375
|
+
</PopoverContent>
|
|
376
|
+
</Popover>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if ("blob" in props.data.resource) {
|
|
381
|
+
if (props.data.resource.mimeType?.startsWith("image/")) {
|
|
382
|
+
return (
|
|
383
|
+
<ImageBlock
|
|
384
|
+
data={{
|
|
385
|
+
type: "image",
|
|
386
|
+
mimeType: props.data.resource.mimeType,
|
|
387
|
+
data: props.data.resource.blob,
|
|
388
|
+
}}
|
|
389
|
+
/>
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
if (props.data.resource.mimeType?.startsWith("audio/")) {
|
|
393
|
+
return (
|
|
394
|
+
<AudioBlock
|
|
395
|
+
data={{
|
|
396
|
+
type: "audio",
|
|
397
|
+
mimeType: props.data.resource.mimeType,
|
|
398
|
+
data: props.data.resource.blob,
|
|
399
|
+
}}
|
|
400
|
+
/>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
const dataURL = base64ToDataURL(
|
|
404
|
+
props.data.resource.blob as Base64String,
|
|
405
|
+
props.data.resource.mimeType ?? "",
|
|
406
|
+
);
|
|
407
|
+
return (
|
|
408
|
+
<a href={dataURL} className="flex items-center gap-1" download={true}>
|
|
409
|
+
{props.data.resource.mimeType && (
|
|
410
|
+
<MimeIcon mimeType={props.data.resource.mimeType} />
|
|
411
|
+
)}
|
|
412
|
+
{props.data.resource.uri}
|
|
413
|
+
</a>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
logNever(props.data.resource);
|
|
417
|
+
return null;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
export const ResourceLinkBlock = (props: {
|
|
421
|
+
data: ContentBlockOf<"resource_link">;
|
|
422
|
+
}) => {
|
|
423
|
+
if (props.data.uri.startsWith("http")) {
|
|
424
|
+
return (
|
|
425
|
+
<a
|
|
426
|
+
href={props.data.uri}
|
|
427
|
+
target="_blank"
|
|
428
|
+
rel="noopener noreferrer"
|
|
429
|
+
className="text-link hover:underline"
|
|
430
|
+
>
|
|
431
|
+
{props.data.name}
|
|
432
|
+
</a>
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return (
|
|
437
|
+
<span className="flex items-center gap-1">
|
|
438
|
+
{props.data.mimeType && <MimeIcon mimeType={props.data.mimeType} />}
|
|
439
|
+
{props.data.name || props.data.title || props.data.uri}
|
|
440
|
+
</span>
|
|
441
|
+
);
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
export const MimeIcon = (props: { mimeType: string }) => {
|
|
445
|
+
if (props.mimeType.startsWith("image/")) {
|
|
446
|
+
return <FileImageIcon className="h-2 w-2" />;
|
|
447
|
+
}
|
|
448
|
+
if (props.mimeType.startsWith("audio/")) {
|
|
449
|
+
return <FileAudio2Icon className="h-2 w-2" />;
|
|
450
|
+
}
|
|
451
|
+
if (props.mimeType.startsWith("video/")) {
|
|
452
|
+
return <FileVideoCameraIcon className="h-2 w-2" />;
|
|
453
|
+
}
|
|
454
|
+
if (props.mimeType.startsWith("text/")) {
|
|
455
|
+
return <FileTextIcon className="h-2 w-2" />;
|
|
456
|
+
}
|
|
457
|
+
if (props.mimeType.startsWith("application/")) {
|
|
458
|
+
return <FileJsonIcon className="h-2 w-2" />;
|
|
459
|
+
}
|
|
460
|
+
return <FileIcon className="h-2 w-2" />;
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
export const SessionNotificationsBlock = <
|
|
464
|
+
T extends SessionNotificationEventData,
|
|
465
|
+
>(props: {
|
|
466
|
+
data: T[];
|
|
467
|
+
startTimestamp: number;
|
|
468
|
+
endTimestamp: number;
|
|
469
|
+
}) => {
|
|
470
|
+
if (props.data.length === 0) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
const kind = props.data[0].sessionUpdate;
|
|
474
|
+
|
|
475
|
+
const renderItems = (items: T[]) => {
|
|
476
|
+
if (isToolCalls(items)) {
|
|
477
|
+
return <ToolNotificationsBlock data={items} />;
|
|
478
|
+
}
|
|
479
|
+
if (isAgentThoughts(items)) {
|
|
480
|
+
return (
|
|
481
|
+
<AgentThoughtsBlock
|
|
482
|
+
startTimestamp={props.startTimestamp}
|
|
483
|
+
endTimestamp={props.endTimestamp}
|
|
484
|
+
data={items}
|
|
485
|
+
/>
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
if (isUserMessages(items)) {
|
|
489
|
+
return <UserMessagesBlock data={items} />;
|
|
490
|
+
}
|
|
491
|
+
if (isAgentMessages(items)) {
|
|
492
|
+
return <AgentMessagesBlock data={items} />;
|
|
493
|
+
}
|
|
494
|
+
if (isPlans(items)) {
|
|
495
|
+
return <PlansBlock data={items} />;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return (
|
|
499
|
+
<SimpleAccordion title={items[0].sessionUpdate}>
|
|
500
|
+
<JsonOutput data={items} format="tree" className="max-h-64" />
|
|
501
|
+
</SimpleAccordion>
|
|
502
|
+
);
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
return (
|
|
506
|
+
<div className="flex flex-col text-sm gap-2" data-block-type={kind}>
|
|
507
|
+
{renderItems(props.data)}
|
|
508
|
+
</div>
|
|
509
|
+
);
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
export const ToolNotificationsBlock = (props: {
|
|
513
|
+
data: Array<ToolCallNotificationEvent | ToolCallUpdateNotificationEvent>;
|
|
514
|
+
}) => {
|
|
515
|
+
const toolCalls = mergeToolCalls(props.data);
|
|
516
|
+
|
|
517
|
+
return (
|
|
518
|
+
<div className="flex flex-col text-muted-foreground">
|
|
519
|
+
{toolCalls.map((item) => (
|
|
520
|
+
<SimpleAccordion
|
|
521
|
+
key={item.toolCallId}
|
|
522
|
+
status={
|
|
523
|
+
item.status === "completed"
|
|
524
|
+
? "success"
|
|
525
|
+
: item.status === "failed"
|
|
526
|
+
? "error"
|
|
527
|
+
: item.status === "in_progress" || item.status === "pending"
|
|
528
|
+
? "loading"
|
|
529
|
+
: undefined
|
|
530
|
+
}
|
|
531
|
+
title={
|
|
532
|
+
<span data-tool-data={JSON.stringify(item)}>
|
|
533
|
+
{handleBackticks(toolTitle(item))}
|
|
534
|
+
</span>
|
|
535
|
+
}
|
|
536
|
+
defaultIcon={<WrenchIcon className="h-3 w-3" />}
|
|
537
|
+
>
|
|
538
|
+
<ToolBodyBlock data={item} />
|
|
539
|
+
</SimpleAccordion>
|
|
540
|
+
))}
|
|
541
|
+
</div>
|
|
542
|
+
);
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
export const DiffBlocks = (props: {
|
|
546
|
+
data: Array<Extract<ToolCallContent, { type: "diff" }>>;
|
|
547
|
+
}) => {
|
|
548
|
+
return (
|
|
549
|
+
<div className="flex flex-col gap-2 text-muted-foreground">
|
|
550
|
+
{props.data.map((item) => {
|
|
551
|
+
return (
|
|
552
|
+
<div
|
|
553
|
+
key={item.path}
|
|
554
|
+
className="border rounded-md overflow-hidden bg-[var(--gray-2)] max-h-64 overflow-y-auto scrollbar-thin"
|
|
555
|
+
>
|
|
556
|
+
{/* File path header */}
|
|
557
|
+
<div className="px-2 py-1 bg-[var(--gray-2)] border-b text-xs font-medium text-[var(--gray-11)]">
|
|
558
|
+
{item.path}
|
|
559
|
+
</div>
|
|
560
|
+
|
|
561
|
+
<div className="font-mono text-xs">
|
|
562
|
+
{/* Removed lines */}
|
|
563
|
+
{item.oldText && (
|
|
564
|
+
<div className="px-3 py-1 border-b bg-[var(--red-2)] border-b-[var(--red-6)]">
|
|
565
|
+
<div className="flex">
|
|
566
|
+
<span className="text-[var(--red-11)] select-none mr-2">
|
|
567
|
+
-
|
|
568
|
+
</span>
|
|
569
|
+
<pre className="text-[var(--red-11)] px-1 rounded whitespace-pre-wrap break-words flex-1 line-through opacity-80">
|
|
570
|
+
{item.oldText}
|
|
571
|
+
</pre>
|
|
572
|
+
</div>
|
|
573
|
+
</div>
|
|
574
|
+
)}
|
|
575
|
+
|
|
576
|
+
{/* Added lines */}
|
|
577
|
+
{item.newText && (
|
|
578
|
+
<div className="px-3 py-1 bg-[var(--grass-2)] border-b-[var(--grass-6)]">
|
|
579
|
+
<div className="flex">
|
|
580
|
+
<span className="text-[var(--grass-11)] select-none mr-2">
|
|
581
|
+
+
|
|
582
|
+
</span>
|
|
583
|
+
<pre className="text-[var(--grass-11)] px-1 rounded whitespace-pre-wrap break-words flex-1 italic opacity-90">
|
|
584
|
+
{item.newText}
|
|
585
|
+
</pre>
|
|
586
|
+
</div>
|
|
587
|
+
</div>
|
|
588
|
+
)}
|
|
589
|
+
</div>
|
|
590
|
+
</div>
|
|
591
|
+
);
|
|
592
|
+
})}
|
|
593
|
+
</div>
|
|
594
|
+
);
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
function toolTitle(
|
|
598
|
+
item: Pick<ToolCallUpdateNotificationEvent, "title" | "kind" | "locations">,
|
|
599
|
+
) {
|
|
600
|
+
const prefix =
|
|
601
|
+
item.title || Strings.startCase(item.kind || "") || "Tool call";
|
|
602
|
+
const firstLocation = item.locations?.[0];
|
|
603
|
+
// Add the first location if it is not in the title already
|
|
604
|
+
if (firstLocation && !prefix.includes(firstLocation.path)) {
|
|
605
|
+
return `${prefix}: ${firstLocation.path}`;
|
|
606
|
+
}
|
|
607
|
+
return prefix;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function handleBackticks(text: string) {
|
|
611
|
+
if (text.startsWith("`") && text.endsWith("`")) {
|
|
612
|
+
return <i>{text.slice(1, -1)}</i>;
|
|
613
|
+
}
|
|
614
|
+
return text;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export const LocationsBlock = (props: { data: ToolCallLocation[] }) => {
|
|
618
|
+
// Only show locations if there are multiple locations, otherwise it is
|
|
619
|
+
// in the title
|
|
620
|
+
if (props.data.length <= 1) {
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const locations = props.data.map((item) => {
|
|
625
|
+
if (item.line) {
|
|
626
|
+
return `${item.path}:${item.line}`;
|
|
627
|
+
}
|
|
628
|
+
return item.path;
|
|
629
|
+
});
|
|
630
|
+
return <div className="flex flex-col gap-2">{locations.join("\n")}</div>;
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
export const ToolBodyBlock = (props: {
|
|
634
|
+
data:
|
|
635
|
+
| Omit<ToolCallNotificationEvent, "sessionUpdate">
|
|
636
|
+
| Omit<ToolCallUpdateNotificationEvent, "sessionUpdate">;
|
|
637
|
+
}) => {
|
|
638
|
+
const content = props.data.content
|
|
639
|
+
?.filter((item) => item.type === "content")
|
|
640
|
+
.map((item) => item.content);
|
|
641
|
+
const diffs = props.data.content?.filter((item) => item.type === "diff");
|
|
642
|
+
const locations = props.data.locations;
|
|
643
|
+
const isFailed = props.data.status === "failed";
|
|
644
|
+
const hasLocations = locations && locations.length > 0;
|
|
645
|
+
|
|
646
|
+
if (content?.length === 0 && diffs?.length === 0 && hasLocations) {
|
|
647
|
+
return (
|
|
648
|
+
<div className="flex flex-col gap-2 pr-2">
|
|
649
|
+
<span className="text-xs text-muted-foreground">
|
|
650
|
+
{capitalize(props.data.kind || "")}{" "}
|
|
651
|
+
{locations?.map((item) => item.path).join(", ")}
|
|
652
|
+
</span>
|
|
653
|
+
</div>
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return (
|
|
658
|
+
<div className="flex flex-col gap-2 pr-2">
|
|
659
|
+
{locations && <LocationsBlock data={locations} />}
|
|
660
|
+
{content && <ContentBlocks data={content} />}
|
|
661
|
+
{diffs && !isFailed && <DiffBlocks data={diffs} />}
|
|
662
|
+
</div>
|
|
663
|
+
);
|
|
664
|
+
};
|