@marimo-team/islands 0.25.3-dev266 → 0.25.3-dev268
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-eLHPh6uu.js} +9 -9
- package/dist/{ErrorBoundary-CvxlC3F1.js → ErrorBoundary-CQYAoD7X.js} +64 -69
- package/dist/{abnfDiagram-VCTEODGH-DzrZZXRD.js → abnfDiagram-VCTEODGH-hqd0IBtX.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-BzLAFcc0.js} +1 -1
- package/dist/{strings-BCKpLFNU.js → assertNever-DIU3JJbt.js} +14 -14
- package/dist/{chat-ui-DFxNgzXF.js → chat-ui-BAnLgpTe.js} +1964 -1740
- package/dist/{chunk-IMKFNOWR-B7xytD4B.js → chunk-IMKFNOWR-DOeVvuyE.js} +1 -1
- package/dist/{chunk-SHT3W25Y-I8ZFP5L1.js → chunk-SHT3W25Y-DZoFI8wR.js} +1 -1
- package/dist/{chunk-SVP7TREG-Di-uAPdT.js → chunk-SVP7TREG-CWu86PSW.js} +1 -1
- package/dist/{chunk-TICWLB2K-BFpUy0ud.js → chunk-TICWLB2K-BrM0GoP3.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-Cqm4JGpx.js → classDiagram-ZZMXUADV-DYi4V2bB.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-DFHRJpN3.js → classDiagram-v2-VYDZK3BY-tN7FIk-m.js} +2 -2
- package/dist/{code-visibility-SRM1JiD_.js → code-visibility-DcZ_RwaH.js} +44 -47
- package/dist/{common-yiRhZ6ZZ.js → common-BGeQilvh.js} +5846 -5519
- package/dist/{config-CmeG_Pb6.js → config-BsPruWMR.js} +2 -2
- package/dist/{createReducer-C_syF8Td.js → createReducer-DdtT_7ji.js} +1 -1
- package/dist/{cynefinDiagram-5FMLGOSQ-cuGOeTBC.js → cynefinDiagram-5FMLGOSQ-D5nCnDc-.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-DJ76aC4k.js → dagre-GXQ25YYZ-BHkd7f_i.js} +1 -1
- package/dist/{data-grid-overlay-editor-DLTzcQ4O.js → data-grid-overlay-editor-Dc4lW4A3.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-DLLc0sw7.js → diagram-S7CK7UJ4-D1ALppT2.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-Tz_d6qmk.js → diagram-UQ7AKVKN-DLv0b83L.js} +1 -1
- package/dist/{diagram-VX7I27RA-D7SzPrDN.js → diagram-VX7I27RA-7XJ7J6qg.js} +1 -1
- package/dist/{diagram-Z3DM3KII-qr37XV58.js → diagram-Z3DM3KII-7M7zbHp_.js} +1 -1
- package/dist/{dialog-BtprQCUw.js → dialog-DzCj1qQv.js} +1 -1
- package/dist/{dropdown-menu-CkJ7ErY6.js → dropdown-menu-CdXE15an.js} +1214 -1208
- package/dist/{ebnfDiagram-PWID7BFC-BKOQb4J0.js → ebnfDiagram-PWID7BFC-DHH1qlmV.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-CM15Vq68.js → erDiagram-RLTQ6QDP-fI_0dGef.js} +1 -1
- package/dist/{error-banner-DbRFOd2O.js → error-banner-DPc9eIw5.js} +3 -3
- package/dist/{error-entries-CMoaUX9V.js → error-entries-CxN-Nq3V.js} +524 -456
- package/dist/{feedback-button-DBUrE4OL.js → feedback-button-BqaeQcvX.js} +77 -77
- package/dist/{flowDiagram-HODETNUW-Dt3N5yFT.js → flowDiagram-HODETNUW-Brg9MA6i.js} +2 -2
- package/dist/{formats-gGospKWJ.js → formats-DbSvAPY-.js} +1 -1
- package/dist/{glide-data-editor-Cfv4HeOh.js → glide-data-editor-CiijPXA7.js} +2042 -2041
- package/dist/{glide-utils-Ba9WD2IN.js → glide-utils-BSw3SSHF.js} +29 -35
- package/dist/{globals-dIbBIR5-.js → globals-CpmSWKI_.js} +2 -2
- package/dist/{html-to-image-D_5CMreP.js → html-to-image-8sywF4my.js} +5452 -5751
- package/dist/{infoDiagram-27XIBGKW-C7axJMCl.js → infoDiagram-27XIBGKW-E9e403YT.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-CdC9Eur7.js → ishikawaDiagram-5VMMS53U-C-RlYDRo.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-BHnMIgD7.js → kanban-definition-UXKFOSKX-CLFl_uqK.js} +1 -1
- package/dist/main.js +6294 -6324
- package/dist/{mermaid-DFYN_PXd.js → mermaid-rd09e1JL.js} +35 -35
- package/dist/{mindmap-definition-YA3MSWOX-BagHoCGU.js → mindmap-definition-YA3MSWOX-BQ8TWtyB.js} +1 -1
- package/dist/{number-overlay-editor-CTiJp5Mj.js → number-overlay-editor-Ft4NiAEN.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-8IIZczip.js → pegDiagram-XKGWAZYB-CN8UHFPi.js} +2 -2
- package/dist/pencil-DcT8AE0h.js +10 -0
- package/dist/{pieDiagram-E7YTZNPT-CE6dKBIr.js → pieDiagram-E7YTZNPT-DGuJ0EBd.js} +1 -1
- package/dist/{process-output-CGFNVC60.js → process-output-Dku5POvm.js} +3 -3
- package/dist/{railroadDiagram-O6MQD6OU-CkexPq3b.js → railroadDiagram-O6MQD6OU-BPDA8wy8.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-B0-OkuAe.js → requirementDiagram-BXWQKSXE-ChzLGbGW.js} +1 -1
- package/dist/{reveal-component-DH09ygHo.js → reveal-component-D0cfwbbl.js} +1094 -1094
- package/dist/{select-C33FVPPN.js → select-OxgozOC8.js} +1 -1
- package/dist/{sequenceDiagram-WJ2MYXX4-Dxysg8DX.js → sequenceDiagram-WJ2MYXX4-DFX-bE6O.js} +1 -1
- package/dist/{spec-DH1lmF7Q.js → spec-oJn0RhpQ.js} +2 -2
- package/dist/{stateDiagram-D77RDMKH-DTEN3-OJ.js → stateDiagram-D77RDMKH-BR7cI3wK.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-vktk3Xc5.js → stateDiagram-v2-MP3YSRHH-oGqB5UDI.js} +2 -2
- package/dist/{static-state-BldGd99T.js → static-state-CuV6Pc3a.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-Cjveh9OM.js → swimlanes-42K2YHIH-DwSZl1yC.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-Bdr7Ev_2.js → swimlanesDiagram-VR7AAH4N-BHlcd-Ri.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-KIuDcefn.js → timeline-definition-24CTP7MA-DMmP-G2O.js} +1 -1
- package/dist/{toast-CY1_Qv41.js → toast-DbBHmxmB.js} +2 -2
- package/dist/{useAsyncData-DsZMvNXn.js → useAsyncData-BYIAoMqn.js} +1 -1
- package/dist/{useDateFormatter-Dk_xFci4.js → useDateFormatter-ButWkdcK.js} +1 -1
- package/dist/{useDeepCompareMemoize-KMBBux8C.js → useDeepCompareMemoize-Bz0c5GN6.js} +1 -1
- package/dist/{useTheme-BZ2GGLgm.js → useTheme-DPLobSFs.js} +7 -7
- package/dist/{utils-DXkj82sK.js → utils-EBklsC5R.js} +31 -31
- package/dist/{vega-component-C-lqjV0q.js → vega-component-rxIxAAh9.js} +7 -7
- package/dist/{vennDiagram-4TSXK5OY-DrIOUHfb.js → vennDiagram-4TSXK5OY-Db8GtH1G.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-CJNjf4OH.js → wardleyDiagram-VM6X3IG4-DmIiC3FU.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-DEshFho-.js → xychartDiagram-S5SC5T6Z-DS9OPrNq.js} +1 -1
- package/package.json +1 -1
- package/src/__mocks__/executions.ts +195 -0
- package/src/__mocks__/notebook.ts +0 -1
- package/src/__tests__/CellStatus.test.tsx +19 -20
- 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/dependency-graph/minimap-content.tsx +3 -2
- package/src/components/editor/__tests__/data-attributes.test.tsx +0 -7
- package/src/components/editor/__tests__/output-persistence.test.tsx +2 -2
- package/src/components/editor/actions/__tests__/useCellActionButton.test.tsx +19 -1
- package/src/components/editor/actions/useCellActionButton.tsx +10 -3
- package/src/components/editor/ai/completion-handlers.tsx +0 -37
- package/src/components/editor/alerts/stdin-blocking-alert.tsx +3 -1
- package/src/components/editor/cell/CellStatus.tsx +23 -19
- package/src/components/editor/cell/RunButton.tsx +16 -6
- package/src/components/editor/cell/StagedAICell.tsx +51 -144
- package/src/components/editor/cell/__tests__/useRunCells.test.ts +45 -0
- package/src/components/editor/cell/cell-context-menu.tsx +4 -1
- package/src/components/editor/cell/code/cell-editor.tsx +7 -5
- package/src/components/editor/cell/collapse.tsx +4 -0
- package/src/components/editor/cell/useRunCells.ts +18 -3
- 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 +8 -4
- package/src/components/editor/header/status.tsx +4 -2
- package/src/components/editor/links/cell-link.tsx +4 -4
- package/src/components/editor/navigation/navigation.ts +3 -2
- package/src/components/editor/notebook-cell.tsx +85 -94
- package/src/components/editor/renderers/grid-layout/__tests__/grid-layout.test.tsx +75 -0
- package/src/components/editor/renderers/grid-layout/grid-layout.tsx +22 -16
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -1
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +12 -33
- package/src/components/editor/stdin-blocking-alert.tsx +3 -1
- package/src/components/scratchpad/scratchpad.tsx +4 -1
- package/src/components/slides/minimap.tsx +4 -2
- package/src/components/slides/reveal-component.tsx +14 -10
- package/src/components/slides/slide-cell-view.tsx +28 -24
- package/src/components/slides/slide.tsx +3 -4
- 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/__tests__/__snapshots__/cells.test.ts.snap +0 -389
- package/src/core/cells/__tests__/cell.test.ts +49 -154
- package/src/core/cells/__tests__/cells.test.ts +18 -127
- package/src/core/cells/__tests__/pending-delete-service.test.tsx +11 -20
- package/src/core/cells/__tests__/utils.test.ts +75 -247
- package/src/core/cells/actions.ts +5 -2
- package/src/core/cells/cell.ts +31 -133
- package/src/core/cells/cells.ts +14 -29
- package/src/core/cells/document.ts +3 -3
- package/src/core/cells/focus.ts +27 -6
- package/src/core/cells/layout.ts +1 -1
- package/src/core/cells/pending-delete-service.ts +5 -7
- package/src/core/cells/scrollCellIntoView.ts +6 -0
- package/src/core/cells/types.ts +13 -36
- package/src/core/cells/utils.ts +37 -68
- package/src/core/codemirror/placeholder/__tests__/extensions.test.ts +59 -0
- package/src/core/codemirror/placeholder/extensions.ts +23 -0
- package/src/core/edit-app.tsx +2 -1
- package/src/core/executions/__tests__/atoms.test.ts +99 -0
- package/src/core/executions/__tests__/select.test.ts +268 -0
- package/src/core/executions/atoms.ts +206 -0
- package/src/core/executions/hooks.ts +31 -0
- package/src/core/executions/index.ts +31 -0
- package/src/core/executions/select.ts +175 -0
- package/src/core/export/__tests__/hooks.test.ts +0 -5
- package/src/core/islands/bootstrap.ts +4 -1
- package/src/core/islands/components/output-wrapper.tsx +5 -5
- package/src/core/kernel/handlers.ts +2 -11
- package/src/core/kernel/state.ts +0 -15
- package/src/core/lifecycle/render-policy.ts +12 -26
- package/src/core/network/__tests__/requests-lazy.test.ts +5 -5
- package/src/core/network/requests-lazy.ts +3 -3
- package/src/core/notebook/operations.ts +1 -1
- package/src/core/run-app.tsx +2 -1
- package/src/core/runtime/__tests__/adapter.test.ts +3 -3
- package/src/core/runtime/adapter.ts +4 -4
- package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +34 -0
- package/src/core/session/useNotebookSession.tsx +10 -5
- package/src/core/wasm/bridge.ts +1 -1
- package/src/css/app/Cell.css +7 -36
- package/src/plugins/core/registerReactComponent.tsx +4 -13
- package/src/plugins/impl/chat/chat-ui.tsx +2 -3
- package/src/plugins/impl/common/error-banner.tsx +5 -3
- package/src/stories/cell-status.stories.tsx +17 -10
- package/src/stories/cell.stories.tsx +0 -31
- package/src/stories/layout/vertical/one-column.stories.tsx +0 -9
- package/src/utils/time.ts +10 -0
- 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
|
@@ -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
|
}
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
21
21
|
import useEvent from "react-use-event-hook";
|
|
22
22
|
import { Button } from "@/components/ui/button";
|
|
23
|
-
import { LoadingEllipsis } from "@/components/icons/loading-ellipsis";
|
|
24
23
|
import { replaceMessagesInChat } from "@/core/ai/chat-utils";
|
|
25
24
|
import { useAIConfigActions } from "@/core/ai/config";
|
|
26
25
|
import { AI_SDK_UI_THROTTLE_MS } from "@/core/ai/constants";
|
|
@@ -43,7 +42,6 @@ import { useRequestClient } from "@/core/network/requests";
|
|
|
43
42
|
import { useRuntimeManager } from "@/core/runtime/config";
|
|
44
43
|
import { isWasm } from "@/core/wasm/utils";
|
|
45
44
|
import { ErrorBanner } from "@/plugins/impl/common/error-banner";
|
|
46
|
-
import { cn } from "@/utils/cn";
|
|
47
45
|
import { Logger } from "@/utils/Logger";
|
|
48
46
|
import { AIModelDropdown } from "../ai/ai-model-dropdown";
|
|
49
47
|
import { useOpenSettingsToTab } from "../app-config/state";
|
|
@@ -52,6 +50,7 @@ import {
|
|
|
52
50
|
usePairWithAgentModal,
|
|
53
51
|
} from "../editor/actions/pair-with-agent";
|
|
54
52
|
import { PromptInput } from "../editor/ai/prompt-input";
|
|
53
|
+
import { PendingAICells } from "../editor/chrome/wrapper/pending-ai-cells";
|
|
55
54
|
import {
|
|
56
55
|
addContextCompletion,
|
|
57
56
|
isContextAttachment,
|
|
@@ -76,7 +75,8 @@ import {
|
|
|
76
75
|
getAbortReasonFromChunk,
|
|
77
76
|
resolveChatAbortReason,
|
|
78
77
|
} from "./chat-abort";
|
|
79
|
-
import {
|
|
78
|
+
import { AgentActivity } from "./agent-activity";
|
|
79
|
+
import { getAnswerText, renderUIMessage } from "./chat-display";
|
|
80
80
|
import { ChatHistoryPopover } from "./chat-history-popover";
|
|
81
81
|
import { CapabilitiesPopover } from "./capabilities-popover";
|
|
82
82
|
import { NotebookAccessSelector } from "./notebook-access-selector";
|
|
@@ -87,7 +87,6 @@ import {
|
|
|
87
87
|
handleToolCall,
|
|
88
88
|
hasPendingToolCalls,
|
|
89
89
|
hasUnresolvedToolCalls,
|
|
90
|
-
isLastMessageReasoning,
|
|
91
90
|
PROVIDERS_THAT_SUPPORT_ATTACHMENTS,
|
|
92
91
|
type QueuedUserMessage,
|
|
93
92
|
shouldFlushQueue,
|
|
@@ -98,7 +97,9 @@ import { getCodes } from "@/core/codemirror/copilot/getCodes";
|
|
|
98
97
|
import { focusInputAndMoveToEnd } from "@/core/codemirror/utils";
|
|
99
98
|
import ScrollToBottomButton from "./acp/scroll-to-bottom-button";
|
|
100
99
|
import { useChatControllerId } from "./use-chat-controller-id";
|
|
101
|
-
import { useChatGeneration } from "./use-chat-generation";
|
|
100
|
+
import { isChatBusy, useChatGeneration } from "./use-chat-generation";
|
|
101
|
+
import { useAssistantTurn } from "./use-assistant-turn";
|
|
102
|
+
import { UserMessage } from "./user-message";
|
|
102
103
|
|
|
103
104
|
// Default mode for the AI
|
|
104
105
|
const DEFAULT_MODE = "ask";
|
|
@@ -190,9 +191,9 @@ interface ChatMessageProps {
|
|
|
190
191
|
message: UIMessage;
|
|
191
192
|
index: number;
|
|
192
193
|
onEdit: (index: number, newValue: string) => void;
|
|
193
|
-
isStreamingReasoning: boolean;
|
|
194
194
|
isLast: boolean;
|
|
195
195
|
isActive: boolean;
|
|
196
|
+
turnStartedAt: number | null;
|
|
196
197
|
addToolApprovalResponse?: ChatAddToolApproveResponseFunction;
|
|
197
198
|
}
|
|
198
199
|
|
|
@@ -201,78 +202,40 @@ const ChatMessageDisplay: React.FC<ChatMessageProps> = memo(
|
|
|
201
202
|
message,
|
|
202
203
|
index,
|
|
203
204
|
onEdit,
|
|
204
|
-
isStreamingReasoning,
|
|
205
205
|
isLast,
|
|
206
206
|
isActive,
|
|
207
|
+
turnStartedAt,
|
|
207
208
|
addToolApprovalResponse,
|
|
208
209
|
}) => {
|
|
209
|
-
|
|
210
|
-
const textParts = message.parts?.filter(
|
|
211
|
-
(p): p is TextUIPart => p.type === "text",
|
|
212
|
-
);
|
|
213
|
-
const content = textParts?.map((p) => p.text).join("\n");
|
|
214
|
-
const fileParts = message.parts?.filter(
|
|
215
|
-
(p): p is FileUIPart => p.type === "file",
|
|
216
|
-
);
|
|
217
|
-
|
|
210
|
+
if (message.role === "user") {
|
|
218
211
|
return (
|
|
219
|
-
<div className="
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
key={message.id}
|
|
225
|
-
value={content}
|
|
226
|
-
placeholder="Type your message. Use @ to reference variables and cells."
|
|
227
|
-
onChange={() => {
|
|
228
|
-
// noop
|
|
229
|
-
}}
|
|
230
|
-
onSubmit={(_e, newValue) => {
|
|
231
|
-
if (!newValue.trim()) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
onEdit(index, newValue);
|
|
235
|
-
}}
|
|
236
|
-
onClose={() => {
|
|
237
|
-
// noop
|
|
238
|
-
}}
|
|
212
|
+
<div className="group flex justify-end">
|
|
213
|
+
<UserMessage
|
|
214
|
+
message={message}
|
|
215
|
+
canEdit={!isActive}
|
|
216
|
+
onEdit={(newValue) => onEdit(index, newValue)}
|
|
239
217
|
/>
|
|
240
218
|
</div>
|
|
241
219
|
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const renderOtherMessage = (message: UIMessage) => {
|
|
245
|
-
const textParts = message.parts.filter(
|
|
246
|
-
(p): p is TextUIPart => p.type === "text",
|
|
247
|
-
);
|
|
248
|
-
const content = textParts.map((p) => p.text).join("\n");
|
|
220
|
+
}
|
|
249
221
|
|
|
250
|
-
|
|
222
|
+
return (
|
|
223
|
+
<div className="group relative flex justify-start">
|
|
251
224
|
<div className="w-[95%] wrap-break-word">
|
|
252
225
|
<div className="absolute right-1 top-1 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity">
|
|
253
|
-
<CopyClipboardIcon
|
|
226
|
+
<CopyClipboardIcon
|
|
227
|
+
className="h-3 w-3"
|
|
228
|
+
value={() => getAnswerText(message)}
|
|
229
|
+
/>
|
|
254
230
|
</div>
|
|
255
231
|
{renderUIMessage({
|
|
256
232
|
message,
|
|
257
|
-
isStreamingReasoning,
|
|
258
233
|
isLast,
|
|
259
234
|
isActive,
|
|
235
|
+
turnStartedAt,
|
|
260
236
|
addToolApprovalResponse,
|
|
261
237
|
})}
|
|
262
238
|
</div>
|
|
263
|
-
);
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
return (
|
|
267
|
-
<div
|
|
268
|
-
className={cn(
|
|
269
|
-
"flex group relative",
|
|
270
|
-
message.role === "user" ? "justify-end" : "justify-start",
|
|
271
|
-
)}
|
|
272
|
-
>
|
|
273
|
-
{message.role === "user"
|
|
274
|
-
? renderUserMessage(message)
|
|
275
|
-
: renderOtherMessage(message)}
|
|
276
239
|
</div>
|
|
277
240
|
);
|
|
278
241
|
},
|
|
@@ -483,6 +446,8 @@ const ChatPanelBody = () => {
|
|
|
483
446
|
lastAbortReasonRef.current = null;
|
|
484
447
|
setAbortNotice(null);
|
|
485
448
|
});
|
|
449
|
+
const turn = useAssistantTurn();
|
|
450
|
+
const resetTurn = turn.reset;
|
|
486
451
|
const runtimeManager = useRuntimeManager();
|
|
487
452
|
const { invokeAiTool } = useRequestClient();
|
|
488
453
|
|
|
@@ -505,6 +470,7 @@ const ChatPanelBody = () => {
|
|
|
505
470
|
error,
|
|
506
471
|
status,
|
|
507
472
|
regenerate,
|
|
473
|
+
setMessages,
|
|
508
474
|
stop,
|
|
509
475
|
addToolOutput,
|
|
510
476
|
addToolApprovalResponse,
|
|
@@ -512,7 +478,8 @@ const ChatPanelBody = () => {
|
|
|
512
478
|
} = useChat({
|
|
513
479
|
id: chatControllerId,
|
|
514
480
|
throttle: AI_SDK_UI_THROTTLE_MS,
|
|
515
|
-
sendAutomaticallyWhen: ({ messages }) =>
|
|
481
|
+
sendAutomaticallyWhen: ({ messages }) =>
|
|
482
|
+
!turn.hasEnded && hasPendingToolCalls(messages),
|
|
516
483
|
messages: activeChat?.messages || [], // initial messages
|
|
517
484
|
transport: new StreamingChunkTransport(
|
|
518
485
|
{
|
|
@@ -559,7 +526,26 @@ const ChatPanelBody = () => {
|
|
|
559
526
|
}
|
|
560
527
|
},
|
|
561
528
|
),
|
|
562
|
-
onFinish: ({
|
|
529
|
+
onFinish: ({
|
|
530
|
+
messages: finishedMessages,
|
|
531
|
+
isError,
|
|
532
|
+
isAbort,
|
|
533
|
+
finishReason,
|
|
534
|
+
}) => {
|
|
535
|
+
// useChat routes every chat's callbacks to the latest one. Its
|
|
536
|
+
// setMessages isn't a React setter: the updater runs synchronously.
|
|
537
|
+
const finished: { messages?: typeof finishedMessages } = {};
|
|
538
|
+
setMessages((current) => {
|
|
539
|
+
if (current.at(-1)?.id !== finishedMessages.at(-1)?.id) {
|
|
540
|
+
return current;
|
|
541
|
+
}
|
|
542
|
+
finished.messages = turn.finish(finishedMessages, { isAbort, isError });
|
|
543
|
+
return finished.messages;
|
|
544
|
+
});
|
|
545
|
+
const { messages } = finished;
|
|
546
|
+
if (!messages) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
563
549
|
setChatState((prev) => {
|
|
564
550
|
return replaceMessagesInChat({
|
|
565
551
|
chatState: prev,
|
|
@@ -604,6 +590,7 @@ const ChatPanelBody = () => {
|
|
|
604
590
|
|
|
605
591
|
const sendUserMessage = useEvent((parts: ChatMessagePart[]) => {
|
|
606
592
|
clearAbortState();
|
|
593
|
+
turn.start();
|
|
607
594
|
sendMessage({ role: "user", parts });
|
|
608
595
|
});
|
|
609
596
|
|
|
@@ -628,11 +615,43 @@ const ChatPanelBody = () => {
|
|
|
628
615
|
},
|
|
629
616
|
);
|
|
630
617
|
|
|
631
|
-
const isLoading =
|
|
618
|
+
const isLoading = isChatBusy(status);
|
|
619
|
+
const isTurnActive = turn.isActive({
|
|
620
|
+
isLoading,
|
|
621
|
+
hasError: error != null,
|
|
622
|
+
messages,
|
|
623
|
+
});
|
|
624
|
+
useChatGeneration(isTurnActive);
|
|
625
|
+
|
|
626
|
+
const stopTurn = useEvent(() => {
|
|
627
|
+
if (isLoading) {
|
|
628
|
+
turn.stop();
|
|
629
|
+
void stop();
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
// Between requests there is no stream to abort, so `onFinish` won't run.
|
|
633
|
+
const stopped = turn.finish(messages, { isAbort: true, isError: false });
|
|
634
|
+
setMessages(stopped);
|
|
635
|
+
setChatState((prev) =>
|
|
636
|
+
replaceMessagesInChat({
|
|
637
|
+
chatState: prev,
|
|
638
|
+
chatId: prev.activeChatId,
|
|
639
|
+
messages: stopped,
|
|
640
|
+
}),
|
|
641
|
+
);
|
|
642
|
+
tryFlushQueuedMessages(stopped, { isError: false, isAbort: true });
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
const respondToApproval = useEvent(
|
|
646
|
+
(response: Parameters<ChatAddToolApproveResponseFunction>[0]) => {
|
|
647
|
+
turn.resume(messages.at(-1));
|
|
648
|
+
return addToolApprovalResponse(response);
|
|
649
|
+
},
|
|
650
|
+
);
|
|
632
651
|
// useEvent reads the latest loading state, even when called after an await
|
|
633
652
|
// (e.g. resolving @-context) that outlives the original render's closure.
|
|
634
653
|
const submitOrQueue = useEvent((parts: ChatMessagePart[]) => {
|
|
635
|
-
if (
|
|
654
|
+
if (isTurnActive || hasQueuedRef.current) {
|
|
636
655
|
enqueueUserMessage(parts);
|
|
637
656
|
} else {
|
|
638
657
|
sendUserMessage(parts);
|
|
@@ -665,10 +684,6 @@ const ChatPanelBody = () => {
|
|
|
665
684
|
});
|
|
666
685
|
});
|
|
667
686
|
|
|
668
|
-
// Check if we're currently streaming reasoning in the latest message
|
|
669
|
-
const isStreamingReasoning =
|
|
670
|
-
isLoading && messages.length > 0 && isLastMessageReasoning(messages);
|
|
671
|
-
|
|
672
687
|
// Pin to the bottom while the user is already there.
|
|
673
688
|
useEffect(() => {
|
|
674
689
|
setIsScrolledToBottom(true);
|
|
@@ -676,6 +691,11 @@ const ChatPanelBody = () => {
|
|
|
676
691
|
clearAbortState();
|
|
677
692
|
}, [activeChatId, clearQueuedMessages, clearAbortState]);
|
|
678
693
|
|
|
694
|
+
// Creating a chat keeps the draft's controller, so its turn survives.
|
|
695
|
+
useEffect(() => {
|
|
696
|
+
resetTurn();
|
|
697
|
+
}, [chatControllerId, resetTurn]);
|
|
698
|
+
|
|
679
699
|
useEffect(() => {
|
|
680
700
|
if (!isScrolledToBottom) {
|
|
681
701
|
return;
|
|
@@ -730,6 +750,7 @@ const ChatPanelBody = () => {
|
|
|
730
750
|
const { contextPart, attachments } =
|
|
731
751
|
await resolveChatContext(initialMessage);
|
|
732
752
|
|
|
753
|
+
turn.start();
|
|
733
754
|
sendMessage({
|
|
734
755
|
role: "user",
|
|
735
756
|
parts: [
|
|
@@ -787,6 +808,7 @@ const ChatPanelBody = () => {
|
|
|
787
808
|
const { contextPart, attachments } = await resolveChatContext(newValue);
|
|
788
809
|
|
|
789
810
|
const messageId = editedMessage.id;
|
|
811
|
+
turn.start();
|
|
790
812
|
sendMessage({
|
|
791
813
|
messageId: messageId, // replace the message
|
|
792
814
|
role: "user",
|
|
@@ -825,6 +847,7 @@ const ChatPanelBody = () => {
|
|
|
825
847
|
|
|
826
848
|
const handleReload = () => {
|
|
827
849
|
clearAbortState();
|
|
850
|
+
turn.start();
|
|
828
851
|
regenerate();
|
|
829
852
|
};
|
|
830
853
|
|
|
@@ -889,8 +912,8 @@ const ChatPanelBody = () => {
|
|
|
889
912
|
inputRef={newThreadInputRef}
|
|
890
913
|
setInput={setNewThreadInput}
|
|
891
914
|
onSubmit={handleNewThreadSubmit}
|
|
892
|
-
isLoading={
|
|
893
|
-
onStop={
|
|
915
|
+
isLoading={isTurnActive}
|
|
916
|
+
onStop={stopTurn}
|
|
894
917
|
fileInputRef={fileInputRef}
|
|
895
918
|
onAddFiles={addFiles}
|
|
896
919
|
onClose={handleOnCloseThread}
|
|
@@ -901,8 +924,8 @@ const ChatPanelBody = () => {
|
|
|
901
924
|
setInput={setInput}
|
|
902
925
|
onSubmit={handleChatInputSubmit}
|
|
903
926
|
inputRef={newMessageInputRef}
|
|
904
|
-
isLoading={
|
|
905
|
-
onStop={
|
|
927
|
+
isLoading={isTurnActive}
|
|
928
|
+
onStop={stopTurn}
|
|
906
929
|
onClose={() => newMessageInputRef.current?.editor?.blur()}
|
|
907
930
|
fileInputRef={fileInputRef}
|
|
908
931
|
onAddFiles={addFiles}
|
|
@@ -946,18 +969,35 @@ const ChatPanelBody = () => {
|
|
|
946
969
|
ref={scrollContainerRef}
|
|
947
970
|
onScroll={handleScroll}
|
|
948
971
|
>
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
972
|
+
<TooltipProvider>
|
|
973
|
+
{messages.map((message, idx) => {
|
|
974
|
+
const isLast = idx === messages.length - 1;
|
|
975
|
+
return (
|
|
976
|
+
<ChatMessageDisplay
|
|
977
|
+
key={message.id}
|
|
978
|
+
message={message}
|
|
979
|
+
index={idx}
|
|
980
|
+
onEdit={handleMessageEdit}
|
|
981
|
+
isLast={isLast}
|
|
982
|
+
isActive={isTurnActive}
|
|
983
|
+
turnStartedAt={isLast ? turn.startedAt : null}
|
|
984
|
+
addToolApprovalResponse={respondToApproval}
|
|
985
|
+
/>
|
|
986
|
+
);
|
|
987
|
+
})}
|
|
988
|
+
</TooltipProvider>
|
|
989
|
+
|
|
990
|
+
{isTurnActive && messages.at(-1)?.role === "user" && (
|
|
991
|
+
<AgentActivity
|
|
992
|
+
running={true}
|
|
993
|
+
thinking={true}
|
|
994
|
+
timing={
|
|
995
|
+
turn.startedAt == null
|
|
996
|
+
? undefined
|
|
997
|
+
: { startedAt: turn.startedAt }
|
|
998
|
+
}
|
|
959
999
|
/>
|
|
960
|
-
)
|
|
1000
|
+
)}
|
|
961
1001
|
|
|
962
1002
|
{queuedMessages.map((message) => (
|
|
963
1003
|
<QueuedMessageDisplay key={message.id} message={message} />
|
|
@@ -990,30 +1030,7 @@ const ChatPanelBody = () => {
|
|
|
990
1030
|
/>
|
|
991
1031
|
</div>
|
|
992
1032
|
|
|
993
|
-
{
|
|
994
|
-
<div className="flex shrink-0 items-center gap-2 px-3 pt-2 text-xs text-muted-foreground">
|
|
995
|
-
<div className="flex items-center gap-1.5">
|
|
996
|
-
<div aria-hidden={true}>
|
|
997
|
-
<LoadingEllipsis
|
|
998
|
-
size={3}
|
|
999
|
-
gap="gap-0.5"
|
|
1000
|
-
className="motion-reduce:[&_svg]:animate-none"
|
|
1001
|
-
/>
|
|
1002
|
-
</div>
|
|
1003
|
-
<output>Working</output>
|
|
1004
|
-
</div>
|
|
1005
|
-
<span aria-hidden={true} className="h-3 w-px bg-border" />
|
|
1006
|
-
<Button
|
|
1007
|
-
variant="text"
|
|
1008
|
-
size="xs"
|
|
1009
|
-
className="chat-control"
|
|
1010
|
-
onClick={stop}
|
|
1011
|
-
>
|
|
1012
|
-
Stop generation
|
|
1013
|
-
</Button>
|
|
1014
|
-
</div>
|
|
1015
|
-
)}
|
|
1016
|
-
|
|
1033
|
+
{!isTurnActive && <PendingAICells />}
|
|
1017
1034
|
<div className="shrink-0 p-3">{composer}</div>
|
|
1018
1035
|
</div>
|
|
1019
1036
|
);
|