@marimo-team/islands 0.25.3-dev265 → 0.25.3-dev267
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-C_hj7Poi.js → ConnectedDataExplorerComponent-C4yCN38h.js} +1 -1
- package/dist/{ErrorBoundary-DzYPyiOs.js → ErrorBoundary-CewXPyL5.js} +64 -69
- package/dist/{abnfDiagram-VCTEODGH-DzrZZXRD.js → abnfDiagram-VCTEODGH-o6Ay6Q63.js} +2 -2
- package/dist/{any-language-editor-CFeWfC3o.js → any-language-editor-DnG26QXh.js} +1 -1
- package/dist/{architectureDiagram-5GKGNRK7-iF_snyFL.js → architectureDiagram-5GKGNRK7-BhiwwlVg.js} +1 -1
- package/dist/{chat-ui-DFxNgzXF.js → chat-ui-CbgR5ivy.js} +1964 -1740
- package/dist/{chunk-IMKFNOWR-B7xytD4B.js → chunk-IMKFNOWR-WMSMf4Nj.js} +1 -1
- package/dist/{chunk-SHT3W25Y-I8ZFP5L1.js → chunk-SHT3W25Y-BRfADrzD.js} +1 -1
- package/dist/{chunk-SVP7TREG-Di-uAPdT.js → chunk-SVP7TREG-CuK5QFre.js} +1 -1
- package/dist/{chunk-TICWLB2K-BFpUy0ud.js → chunk-TICWLB2K-Bcun_jhJ.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-Cqm4JGpx.js → classDiagram-ZZMXUADV-CgWDbUDT.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-DFHRJpN3.js → classDiagram-v2-VYDZK3BY-DYmc9SFU.js} +2 -2
- package/dist/{code-visibility-CJExrSFe.js → code-visibility-BKBl9Rh6.js} +9 -9
- package/dist/{common-5mM7a8cm.js → common-N3Jd_6m9.js} +5944 -5626
- package/dist/{cynefinDiagram-5FMLGOSQ-cuGOeTBC.js → cynefinDiagram-5FMLGOSQ-PPKWipjT.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-DJ76aC4k.js → dagre-GXQ25YYZ-niRBZh--.js} +1 -1
- package/dist/{data-grid-overlay-editor--Un2Lk6m.js → data-grid-overlay-editor-Cp59CrqZ.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-DLLc0sw7.js → diagram-S7CK7UJ4-DPiP9rot.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-Tz_d6qmk.js → diagram-UQ7AKVKN-B0WZuaxV.js} +1 -1
- package/dist/{diagram-VX7I27RA-D7SzPrDN.js → diagram-VX7I27RA-Rn42WBuN.js} +1 -1
- package/dist/{diagram-Z3DM3KII-qr37XV58.js → diagram-Z3DM3KII-BB93--ry.js} +1 -1
- package/dist/{dropdown-menu-CkJ7ErY6.js → dropdown-menu-BpImEfn3.js} +1211 -1205
- package/dist/{ebnfDiagram-PWID7BFC-BKOQb4J0.js → ebnfDiagram-PWID7BFC-V4t5h6mZ.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-CM15Vq68.js → erDiagram-RLTQ6QDP-C1MsAKXd.js} +1 -1
- package/dist/{error-banner-DbRFOd2O.js → error-banner-DPc9eIw5.js} +3 -3
- package/dist/{error-entries-CMoaUX9V.js → error-entries-CJGdD2ky.js} +49 -42
- package/dist/{feedback-button-C2NUXbzh.js → feedback-button-Qc1f9iPq.js} +70 -70
- package/dist/{flowDiagram-HODETNUW-Dt3N5yFT.js → flowDiagram-HODETNUW-DHjeJP96.js} +2 -2
- package/dist/{glide-data-editor-Dj9U-NK9.js → glide-data-editor-hCq3K0sd.js} +2042 -2041
- package/dist/{glide-utils-Ba9WD2IN.js → glide-utils-DC3sk6EX.js} +28 -34
- package/dist/{globals-CkbsxIrq.js → globals-B2_uMYDY.js} +1 -1
- package/dist/{html-to-image-D_5CMreP.js → html-to-image-S7spRQCi.js} +5526 -5828
- package/dist/{infoDiagram-27XIBGKW-C7axJMCl.js → infoDiagram-27XIBGKW-Dovv6iKx.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-CdC9Eur7.js → ishikawaDiagram-5VMMS53U-D2nyygsc.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-BHnMIgD7.js → kanban-definition-UXKFOSKX-KyVx0uTY.js} +1 -1
- package/dist/main.js +1288 -1287
- package/dist/{mermaid-DFYN_PXd.js → mermaid-B-jKSwGt.js} +33 -33
- package/dist/{mindmap-definition-YA3MSWOX-BagHoCGU.js → mindmap-definition-YA3MSWOX-Bz9tKQFl.js} +1 -1
- package/dist/{number-overlay-editor-H8QAAmdr.js → number-overlay-editor-CfNYvbdz.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-8IIZczip.js → pegDiagram-XKGWAZYB-C_qpq6Tv.js} +2 -2
- package/dist/pencil-DcT8AE0h.js +10 -0
- package/dist/{pieDiagram-E7YTZNPT-CE6dKBIr.js → pieDiagram-E7YTZNPT-Bp9ucgI4.js} +1 -1
- package/dist/{process-output-CGFNVC60.js → process-output-f34bkXTm.js} +2 -2
- package/dist/{railroadDiagram-O6MQD6OU-CkexPq3b.js → railroadDiagram-O6MQD6OU-tq-VEH6j.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-B0-OkuAe.js → requirementDiagram-BXWQKSXE-DW-vHadb.js} +1 -1
- package/dist/{reveal-component-BperrCFw.js → reveal-component-CUxv-kkP.js} +9 -9
- package/dist/{sequenceDiagram-WJ2MYXX4-Dxysg8DX.js → sequenceDiagram-WJ2MYXX4-Dv9U9qRP.js} +1 -1
- package/dist/{stateDiagram-D77RDMKH-DTEN3-OJ.js → stateDiagram-D77RDMKH-DTmPQ0EB.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-vktk3Xc5.js → stateDiagram-v2-MP3YSRHH-CkCif0Iw.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-Cjveh9OM.js → swimlanes-42K2YHIH-21kCq6l8.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-Bdr7Ev_2.js → swimlanesDiagram-VR7AAH4N-BpRJP6KU.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-KIuDcefn.js → timeline-definition-24CTP7MA-Ckgh4lS4.js} +1 -1
- package/dist/{toast-CY1_Qv41.js → toast-DpczUryP.js} +1 -1
- package/dist/{vega-component-C-lqjV0q.js → vega-component-CF9kI6C4.js} +1 -1
- package/dist/{vennDiagram-4TSXK5OY-DrIOUHfb.js → vennDiagram-4TSXK5OY-CDei2Bt3.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-CJNjf4OH.js → wardleyDiagram-VM6X3IG4-CXavT601.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-DEshFho-.js → xychartDiagram-S5SC5T6Z-CvJhcZri.js} +1 -1
- package/package.json +1 -1
- package/src/components/chat/__tests__/chat-display.test.tsx +157 -86
- package/src/components/chat/__tests__/chat-utils.test.ts +42 -0
- package/src/components/chat/__tests__/turn-timing.test.ts +84 -0
- package/src/components/chat/__tests__/use-assistant-turn.test.ts +95 -0
- package/src/components/chat/__tests__/user-message.test.tsx +27 -0
- package/src/components/chat/acp/agent-panel.tsx +2 -0
- package/src/components/chat/agent-activity.tsx +132 -0
- package/src/components/chat/chat-display.tsx +125 -96
- package/src/components/chat/chat-panel.tsx +123 -106
- package/src/components/chat/chat-utils.ts +12 -24
- package/src/components/chat/tool-call/__tests__/tool-labels.test.ts +76 -0
- package/src/components/chat/tool-call/__tests__/tool-step.test.tsx +57 -0
- package/src/components/chat/tool-call/shared.ts +6 -0
- package/src/components/chat/tool-call/tool-call-view.tsx +22 -76
- package/src/components/chat/tool-call/tool-labels.ts +172 -0
- package/src/components/chat/tool-call/tool-step.tsx +145 -0
- package/src/components/chat/turn-timing.ts +73 -0
- package/src/components/chat/use-assistant-turn.ts +65 -0
- package/src/components/chat/use-chat-generation.ts +5 -4
- package/src/components/chat/user-message.tsx +145 -0
- package/src/components/editor/actions/__tests__/useCellActionButton.test.tsx +18 -1
- package/src/components/editor/actions/useCellActionButton.tsx +3 -0
- package/src/components/editor/ai/completion-handlers.tsx +0 -37
- package/src/components/editor/cell/StagedAICell.tsx +51 -144
- package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +193 -32
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +2 -2
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +145 -81
- package/src/components/editor/chrome/wrapper/pending-ai-changes-badge.tsx +25 -0
- package/src/components/editor/chrome/wrapper/sidebar.tsx +6 -0
- package/src/components/editor/links/cell-link.tsx +4 -4
- package/src/components/editor/notebook-cell.tsx +3 -11
- package/src/components/ui/toast.tsx +1 -1
- package/src/core/ai/__tests__/staged-cells.test.ts +74 -126
- package/src/core/ai/staged-cells.ts +48 -38
- package/src/core/cells/scrollCellIntoView.ts +6 -0
- package/src/core/codemirror/placeholder/__tests__/extensions.test.ts +59 -0
- package/src/core/codemirror/placeholder/extensions.ts +23 -0
- package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +34 -0
- package/src/core/session/useNotebookSession.tsx +7 -2
- package/src/css/app/Cell.css +7 -36
- package/src/plugins/impl/chat/chat-ui.tsx +2 -3
- package/src/plugins/impl/common/error-banner.tsx +5 -3
- package/src/components/chat/reasoning-accordion.tsx +0 -61
- package/src/components/chat/tool-call/tool-activity-group.tsx +0 -49
- package/src/components/chat/tool-call/tool-error-card.tsx +0 -81
- package/src/components/chat/tool-call/tool-history-row.tsx +0 -147
- package/src/components/icons/loading-ellipsis.tsx +0 -46
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { ChevronRightIcon } from "lucide-react";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { cn } from "@/utils/cn";
|
|
6
|
+
import { formatDuration, type TurnTiming } from "./turn-timing";
|
|
7
|
+
|
|
8
|
+
interface AgentActivityProps {
|
|
9
|
+
running: boolean;
|
|
10
|
+
thinking?: boolean;
|
|
11
|
+
interrupted?: boolean;
|
|
12
|
+
defaultOpen?: boolean;
|
|
13
|
+
timing?: TurnTiming;
|
|
14
|
+
toolCount?: number;
|
|
15
|
+
failedCount?: number;
|
|
16
|
+
steps?: React.ReactNode[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const AgentActivity: React.FC<AgentActivityProps> = ({
|
|
20
|
+
running,
|
|
21
|
+
thinking = false,
|
|
22
|
+
interrupted = false,
|
|
23
|
+
defaultOpen = false,
|
|
24
|
+
timing,
|
|
25
|
+
toolCount = 0,
|
|
26
|
+
failedCount = 0,
|
|
27
|
+
steps = NO_STEPS,
|
|
28
|
+
}) => {
|
|
29
|
+
const [userOpen, setUserOpen] = React.useState<boolean>();
|
|
30
|
+
const open = userOpen ?? (running || defaultOpen);
|
|
31
|
+
const now = useNow(running);
|
|
32
|
+
const hasSteps = steps.length > 0;
|
|
33
|
+
|
|
34
|
+
const header = running ? (
|
|
35
|
+
<>
|
|
36
|
+
<output className={SHIMMER_CLASS}>
|
|
37
|
+
{thinking ? "Thinking…" : "Working…"}
|
|
38
|
+
</output>
|
|
39
|
+
{timing && (
|
|
40
|
+
<span className="tabular-nums">
|
|
41
|
+
{formatDuration(now - timing.startedAt)}
|
|
42
|
+
</span>
|
|
43
|
+
)}
|
|
44
|
+
</>
|
|
45
|
+
) : (
|
|
46
|
+
<>
|
|
47
|
+
<span>{doneLabel({ interrupted, timing, toolCount })}</span>
|
|
48
|
+
{failedCount > 0 && (
|
|
49
|
+
<span className="text-(--red-11)">· {failedCount} failed</span>
|
|
50
|
+
)}
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className="my-1 w-full">
|
|
56
|
+
{hasSteps ? (
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
onClick={() => setUserOpen(!open)}
|
|
60
|
+
aria-expanded={open}
|
|
61
|
+
className="group/activity -mx-1.5 flex items-center gap-1.5 rounded-md px-1.5 py-0.5 text-xs text-muted-foreground transition-colors hover:bg-foreground/6 hover:text-foreground focus-visible:outline-1 focus-visible:outline-(--slate-8)"
|
|
62
|
+
>
|
|
63
|
+
{header}
|
|
64
|
+
<ChevronRightIcon
|
|
65
|
+
aria-hidden={true}
|
|
66
|
+
className={cn(
|
|
67
|
+
"size-3 shrink-0 opacity-60 transition-transform group-hover/activity:opacity-100",
|
|
68
|
+
open && "rotate-90",
|
|
69
|
+
)}
|
|
70
|
+
/>
|
|
71
|
+
</button>
|
|
72
|
+
) : (
|
|
73
|
+
<div className="flex items-center gap-1.5 py-0.5 text-xs text-muted-foreground">
|
|
74
|
+
{header}
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
{open && hasSteps && (
|
|
78
|
+
<div className="mt-1 mb-2 ml-1.5 flex flex-col gap-1 border-l border-foreground/15 pl-3">
|
|
79
|
+
{steps}
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const NO_STEPS: React.ReactNode[] = [];
|
|
87
|
+
|
|
88
|
+
const SHIMMER_CLASS =
|
|
89
|
+
"animate-shimmer bg-[linear-gradient(90deg,var(--muted-foreground)_35%,var(--foreground)_50%,var(--muted-foreground)_65%)] bg-size-[200%_100%] bg-clip-text text-transparent motion-reduce:animate-none";
|
|
90
|
+
|
|
91
|
+
function doneLabel({
|
|
92
|
+
interrupted,
|
|
93
|
+
timing,
|
|
94
|
+
toolCount,
|
|
95
|
+
}: {
|
|
96
|
+
interrupted: boolean;
|
|
97
|
+
timing?: TurnTiming;
|
|
98
|
+
toolCount: number;
|
|
99
|
+
}): string {
|
|
100
|
+
const duration =
|
|
101
|
+
timing?.finishedAt == null
|
|
102
|
+
? undefined
|
|
103
|
+
: formatDuration(timing.finishedAt - timing.startedAt);
|
|
104
|
+
if (interrupted) {
|
|
105
|
+
return duration ? `Stopped after ${duration}` : "Stopped";
|
|
106
|
+
}
|
|
107
|
+
if (toolCount === 0) {
|
|
108
|
+
return duration ? `Thought for ${duration}` : "Thought process";
|
|
109
|
+
}
|
|
110
|
+
if (duration) {
|
|
111
|
+
return `Worked for ${duration}`;
|
|
112
|
+
}
|
|
113
|
+
return `Worked through ${toolCount} ${toolCount === 1 ? "step" : "steps"}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function useNow(ticking: boolean): number {
|
|
117
|
+
const [now, setNow] = React.useState(() => Date.now());
|
|
118
|
+
React.useEffect(() => {
|
|
119
|
+
if (!ticking) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const tick = () => setNow(Date.now());
|
|
123
|
+
// Catch up at once when resuming, e.g. after a tool approval.
|
|
124
|
+
const frame = requestAnimationFrame(tick);
|
|
125
|
+
const id = setInterval(tick, 1000);
|
|
126
|
+
return () => {
|
|
127
|
+
cancelAnimationFrame(frame);
|
|
128
|
+
clearInterval(id);
|
|
129
|
+
};
|
|
130
|
+
}, [ticking]);
|
|
131
|
+
return now;
|
|
132
|
+
}
|
|
@@ -7,103 +7,95 @@ import type {
|
|
|
7
7
|
ToolUIPart,
|
|
8
8
|
UIMessage,
|
|
9
9
|
} from "ai";
|
|
10
|
-
import { ExternalLinkIcon, FileTextIcon } from "lucide-react";
|
|
10
|
+
import { BrainIcon, ExternalLinkIcon, FileTextIcon } from "lucide-react";
|
|
11
11
|
import React from "react";
|
|
12
12
|
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
13
13
|
import { logNever } from "@/utils/assertNever";
|
|
14
|
-
import {
|
|
15
|
-
import { ChatMarkdown } from "./chat-markdown";
|
|
14
|
+
import { AgentActivity } from "./agent-activity";
|
|
16
15
|
import { AttachmentRenderer, SourceChip } from "./chat-components";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
16
|
+
import { ChatMarkdown } from "./chat-markdown";
|
|
17
|
+
import { PENDING_TOOL_STATES } from "./tool-call/shared";
|
|
19
18
|
import { ToolCallView } from "./tool-call/tool-call-view";
|
|
19
|
+
import { getTurnTiming } from "./turn-timing";
|
|
20
|
+
|
|
21
|
+
type Part = UIMessage["parts"][number];
|
|
22
|
+
interface IndexedPart {
|
|
23
|
+
part: Part;
|
|
24
|
+
index: number;
|
|
25
|
+
}
|
|
20
26
|
|
|
21
27
|
export const renderUIMessage = ({
|
|
22
28
|
message,
|
|
23
|
-
isStreamingReasoning,
|
|
24
29
|
isLast,
|
|
25
30
|
isActive,
|
|
31
|
+
turnStartedAt,
|
|
26
32
|
addToolApprovalResponse,
|
|
27
33
|
}: {
|
|
28
34
|
message: UIMessage;
|
|
29
|
-
isStreamingReasoning: boolean;
|
|
30
35
|
isLast: boolean;
|
|
31
36
|
/** Whether the chat is currently streaming/submitting a response. */
|
|
32
37
|
isActive: boolean;
|
|
38
|
+
turnStartedAt?: number | null;
|
|
33
39
|
addToolApprovalResponse?: ChatAddToolApproveResponseFunction;
|
|
34
|
-
}) => {
|
|
40
|
+
}): React.ReactNode[] => {
|
|
41
|
+
const running = isActive && isLast;
|
|
42
|
+
const { work, rest } = splitTurn(message.parts);
|
|
35
43
|
const content: React.ReactNode[] = [];
|
|
36
|
-
let activity: { part: ToolUIPart | DynamicToolUIPart; index: number }[] = [];
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
if (work.length > 0 || (running && rest.length === 0)) {
|
|
46
|
+
const tools = work.flatMap(({ part }) =>
|
|
47
|
+
isAnyToolPart(part) ? [part] : [],
|
|
48
|
+
);
|
|
49
|
+
const timing =
|
|
50
|
+
running && turnStartedAt != null
|
|
51
|
+
? { startedAt: turnStartedAt }
|
|
52
|
+
: getTurnTiming(message);
|
|
42
53
|
content.push(
|
|
43
|
-
<
|
|
44
|
-
key=
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
<AgentActivity
|
|
55
|
+
key="activity"
|
|
56
|
+
running={running}
|
|
57
|
+
thinking={
|
|
58
|
+
rest.length === 0 &&
|
|
59
|
+
(work.length === 0 || work.at(-1)?.part.type === "reasoning")
|
|
60
|
+
}
|
|
61
|
+
interrupted={
|
|
62
|
+
!running &&
|
|
63
|
+
(timing?.stopped === true ||
|
|
64
|
+
tools.some((part) => PENDING_TOOL_STATES.has(part.state)))
|
|
65
|
+
}
|
|
66
|
+
defaultOpen={!rest.some(({ part }) => !isPendingApproval(part))}
|
|
67
|
+
timing={timing}
|
|
68
|
+
toolCount={tools.length}
|
|
69
|
+
failedCount={
|
|
70
|
+
tools.filter((part) => part.state === "output-error").length
|
|
71
|
+
}
|
|
72
|
+
steps={work.map(({ part, index }) => renderPart(part, index, true))}
|
|
73
|
+
/>,
|
|
53
74
|
);
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// SDK step boundaries and invisible parts should not split visible activity.
|
|
59
|
-
if (
|
|
60
|
-
part.type === "step-start" ||
|
|
61
|
-
isDataPart(part) ||
|
|
62
|
-
(part.type === "text" && !part.text.trim())
|
|
63
|
-
) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (
|
|
67
|
-
(isToolPart(part) || part.type === "dynamic-tool") &&
|
|
68
|
-
part.state !== "output-error" &&
|
|
69
|
-
part.state !== "output-denied" &&
|
|
70
|
-
part.state !== "approval-requested"
|
|
71
|
-
) {
|
|
72
|
-
activity.push({ part, index });
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
flushActivity();
|
|
76
|
-
content.push(renderUIMessagePart(part, index));
|
|
77
|
-
});
|
|
78
|
-
flushActivity();
|
|
79
|
-
|
|
75
|
+
}
|
|
76
|
+
for (const { part, index } of rest) {
|
|
77
|
+
content.push(renderPart(part, index, false));
|
|
78
|
+
}
|
|
80
79
|
return content;
|
|
81
80
|
|
|
82
|
-
function
|
|
83
|
-
part
|
|
84
|
-
index: number,
|
|
85
|
-
) {
|
|
86
|
-
if (isToolPart(part)) {
|
|
81
|
+
function renderPart(part: Part, index: number, inActivity: boolean) {
|
|
82
|
+
if (isToolPart(part) || part.type === "dynamic-tool") {
|
|
87
83
|
return (
|
|
88
84
|
<ToolCallView
|
|
89
85
|
key={index}
|
|
90
|
-
|
|
91
|
-
toolName={part.type}
|
|
86
|
+
toolName={part.type === "dynamic-tool" ? part.toolName : part.type}
|
|
92
87
|
result={part.output}
|
|
93
88
|
errorText={part.state === "output-error" ? part.errorText : undefined}
|
|
94
|
-
className="my-1"
|
|
95
89
|
state={part.state}
|
|
96
90
|
input={part.input}
|
|
97
91
|
approval={part.approval}
|
|
98
92
|
onApprove={addToolApprovalResponse}
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
isActive={running}
|
|
94
|
+
className={inActivity ? undefined : "my-1"}
|
|
101
95
|
/>
|
|
102
96
|
);
|
|
103
97
|
}
|
|
104
|
-
|
|
105
98
|
if (isDataPart(part)) {
|
|
106
|
-
Logger.debug("Found data part", part);
|
|
107
99
|
return null;
|
|
108
100
|
}
|
|
109
101
|
|
|
@@ -114,26 +106,30 @@ export const renderUIMessage = ({
|
|
|
114
106
|
if (part.text.includes("<marimo-")) {
|
|
115
107
|
return (
|
|
116
108
|
<React.Fragment key={index}>
|
|
117
|
-
{renderHTML({
|
|
118
|
-
html: part.text,
|
|
119
|
-
})}
|
|
109
|
+
{renderHTML({ html: part.text })}
|
|
120
110
|
</React.Fragment>
|
|
121
111
|
);
|
|
122
112
|
}
|
|
123
|
-
return
|
|
113
|
+
return (
|
|
114
|
+
<div key={index} className={inActivity ? "py-1.5" : undefined}>
|
|
115
|
+
<ChatMarkdown content={part.text} />
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
124
118
|
case "reasoning":
|
|
125
119
|
return (
|
|
126
|
-
<
|
|
120
|
+
<div
|
|
127
121
|
key={index}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
122
|
+
className="flex gap-1.5 py-1.5 text-muted-foreground"
|
|
123
|
+
>
|
|
124
|
+
<BrainIcon
|
|
125
|
+
aria-hidden={true}
|
|
126
|
+
className="-ml-px mt-1 size-3 shrink-0"
|
|
127
|
+
/>
|
|
128
|
+
<div className="min-w-0 italic [&_p]:my-1 [&_p:first-child]:mt-0">
|
|
129
|
+
<span className="sr-only">Reasoning: </span>
|
|
130
|
+
<ChatMarkdown content={part.text} />
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
137
133
|
);
|
|
138
134
|
case "file":
|
|
139
135
|
return <AttachmentRenderer attachment={part} key={index} />;
|
|
@@ -148,23 +144,6 @@ export const renderUIMessage = ({
|
|
|
148
144
|
}}
|
|
149
145
|
/>
|
|
150
146
|
);
|
|
151
|
-
case "dynamic-tool":
|
|
152
|
-
return (
|
|
153
|
-
<ToolCallView
|
|
154
|
-
key={index}
|
|
155
|
-
toolName={part.toolName}
|
|
156
|
-
result={part.output}
|
|
157
|
-
errorText={
|
|
158
|
-
part.state === "output-error" ? part.errorText : undefined
|
|
159
|
-
}
|
|
160
|
-
state={part.state}
|
|
161
|
-
input={part.input}
|
|
162
|
-
approval={part.approval}
|
|
163
|
-
onApprove={addToolApprovalResponse}
|
|
164
|
-
isLive={isLast}
|
|
165
|
-
isActive={isActive}
|
|
166
|
-
/>
|
|
167
|
-
);
|
|
168
147
|
case "source-document":
|
|
169
148
|
return (
|
|
170
149
|
<SourceChip
|
|
@@ -199,12 +178,62 @@ export const renderUIMessage = ({
|
|
|
199
178
|
}
|
|
200
179
|
};
|
|
201
180
|
|
|
202
|
-
function
|
|
181
|
+
export function getAnswerText(message: UIMessage): string {
|
|
182
|
+
const textOf = (parts: IndexedPart[]) =>
|
|
183
|
+
parts.flatMap(({ part }) => (part.type === "text" ? [part.text] : []));
|
|
184
|
+
const { work, rest } = splitTurn(message.parts);
|
|
185
|
+
const answer = textOf(rest);
|
|
186
|
+
return (answer.length > 0 ? answer : textOf(work)).join("\n");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function splitTurn(parts: UIMessage["parts"]): {
|
|
190
|
+
work: IndexedPart[];
|
|
191
|
+
rest: IndexedPart[];
|
|
192
|
+
} {
|
|
193
|
+
const visible = parts
|
|
194
|
+
.map((part, index) => ({ part, index }))
|
|
195
|
+
.filter(({ part }) => isVisible(part));
|
|
196
|
+
const lastWork = visible.findLastIndex(
|
|
197
|
+
({ part }) =>
|
|
198
|
+
part.type === "reasoning" ||
|
|
199
|
+
(isAnyToolPart(part) && !isPendingApproval(part)),
|
|
200
|
+
);
|
|
201
|
+
return {
|
|
202
|
+
work: visible.filter(
|
|
203
|
+
({ part }, i) => i <= lastWork && !isPendingApproval(part),
|
|
204
|
+
),
|
|
205
|
+
rest: visible.filter(
|
|
206
|
+
({ part }, i) => i > lastWork || isPendingApproval(part),
|
|
207
|
+
),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function isPendingApproval(part: Part): boolean {
|
|
212
|
+
return isAnyToolPart(part) && part.state === "approval-requested";
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function isVisible(part: Part): boolean {
|
|
216
|
+
switch (part.type) {
|
|
217
|
+
case "step-start":
|
|
218
|
+
return false;
|
|
219
|
+
case "text":
|
|
220
|
+
return part.text.trim().length > 0;
|
|
221
|
+
case "reasoning":
|
|
222
|
+
// Some models (e.g. OpenAI's o-series) emit empty reasoning parts.
|
|
223
|
+
return part.text.trim().length > 0;
|
|
224
|
+
default:
|
|
225
|
+
return !isDataPart(part);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isAnyToolPart(part: Part): part is ToolUIPart | DynamicToolUIPart {
|
|
230
|
+
return isToolPart(part) || part.type === "dynamic-tool";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function isToolPart(part: Part): part is ToolUIPart {
|
|
203
234
|
return part.type.startsWith("tool-");
|
|
204
235
|
}
|
|
205
236
|
|
|
206
|
-
function isDataPart(
|
|
207
|
-
part: UIMessage["parts"][number],
|
|
208
|
-
): part is DataUIPart<Record<string, unknown>> {
|
|
237
|
+
function isDataPart(part: Part): part is DataUIPart<Record<string, unknown>> {
|
|
209
238
|
return part.type.startsWith("data-");
|
|
210
239
|
}
|