@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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { UIMessage } from "ai";
|
|
4
|
+
|
|
5
|
+
export interface TurnTiming {
|
|
6
|
+
startedAt: number;
|
|
7
|
+
finishedAt?: number;
|
|
8
|
+
stopped?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getTurnTiming(message: UIMessage): TurnTiming | undefined {
|
|
12
|
+
const metadata = message.metadata;
|
|
13
|
+
if (typeof metadata !== "object" || metadata === null) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const { startedAt, finishedAt, stopped } = metadata as Record<
|
|
17
|
+
string,
|
|
18
|
+
unknown
|
|
19
|
+
>;
|
|
20
|
+
if (typeof startedAt !== "number") {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
startedAt,
|
|
25
|
+
finishedAt: typeof finishedAt === "number" ? finishedAt : undefined,
|
|
26
|
+
stopped: stopped === true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function withTurnTiming(
|
|
31
|
+
messages: UIMessage[],
|
|
32
|
+
turn: { startedAt: number | null; stopped: boolean },
|
|
33
|
+
now = Date.now(),
|
|
34
|
+
): UIMessage[] {
|
|
35
|
+
const last = messages.at(-1);
|
|
36
|
+
if (last?.role !== "assistant") {
|
|
37
|
+
return messages;
|
|
38
|
+
}
|
|
39
|
+
const startedAt = turn.startedAt ?? getTurnTiming(last)?.startedAt;
|
|
40
|
+
if (startedAt == null) {
|
|
41
|
+
return messages;
|
|
42
|
+
}
|
|
43
|
+
const metadata =
|
|
44
|
+
typeof last.metadata === "object" && last.metadata !== null
|
|
45
|
+
? last.metadata
|
|
46
|
+
: {};
|
|
47
|
+
return [
|
|
48
|
+
...messages.slice(0, -1),
|
|
49
|
+
{
|
|
50
|
+
...last,
|
|
51
|
+
metadata: {
|
|
52
|
+
...metadata,
|
|
53
|
+
startedAt,
|
|
54
|
+
finishedAt: now,
|
|
55
|
+
stopped: turn.stopped,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function formatDuration(ms: number): string {
|
|
62
|
+
const totalSeconds = Math.max(1, Math.round(ms / 1000));
|
|
63
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
64
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
65
|
+
const seconds = totalSeconds % 60;
|
|
66
|
+
if (hours > 0) {
|
|
67
|
+
return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
68
|
+
}
|
|
69
|
+
if (minutes > 0) {
|
|
70
|
+
return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
71
|
+
}
|
|
72
|
+
return `${seconds}s`;
|
|
73
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { UIMessage } from "ai";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import useEvent from "react-use-event-hook";
|
|
6
|
+
import { willAutoContinue } from "./chat-utils";
|
|
7
|
+
import { getTurnTiming, withTurnTiming } from "./turn-timing";
|
|
8
|
+
|
|
9
|
+
export function useAssistantTurn() {
|
|
10
|
+
const [turn, setTurn] = useState<{
|
|
11
|
+
startedAt: number;
|
|
12
|
+
ended: boolean;
|
|
13
|
+
} | null>(null);
|
|
14
|
+
|
|
15
|
+
const start = useEvent(() =>
|
|
16
|
+
setTurn({ startedAt: Date.now(), ended: false }),
|
|
17
|
+
);
|
|
18
|
+
const reset = useEvent(() => setTurn(null));
|
|
19
|
+
const stop = useEvent(() =>
|
|
20
|
+
setTurn((prev) => prev && { ...prev, ended: true }),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// Shift the start forward so time spent waiting on the user isn't counted.
|
|
24
|
+
const resume = useEvent((reply: UIMessage | undefined) => {
|
|
25
|
+
const timing = reply && getTurnTiming(reply);
|
|
26
|
+
const workedMs =
|
|
27
|
+
timing?.finishedAt == null ? 0 : timing.finishedAt - timing.startedAt;
|
|
28
|
+
setTurn({ startedAt: Date.now() - workedMs, ended: false });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const finish = useEvent(
|
|
32
|
+
(finished: UIMessage[], opts: { isAbort: boolean; isError: boolean }) => {
|
|
33
|
+
if (opts.isAbort || opts.isError) {
|
|
34
|
+
stop();
|
|
35
|
+
}
|
|
36
|
+
return withTurnTiming(finished, {
|
|
37
|
+
startedAt: turn?.startedAt ?? null,
|
|
38
|
+
stopped: opts.isAbort,
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Between requests of one turn the SDK briefly reports "ready".
|
|
44
|
+
const isActive = (state: {
|
|
45
|
+
isLoading: boolean;
|
|
46
|
+
hasError: boolean;
|
|
47
|
+
messages: UIMessage[];
|
|
48
|
+
}) =>
|
|
49
|
+
state.isLoading ||
|
|
50
|
+
(turn != null &&
|
|
51
|
+
!turn.ended &&
|
|
52
|
+
!state.hasError &&
|
|
53
|
+
willAutoContinue(state.messages));
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
startedAt: turn?.startedAt ?? null,
|
|
57
|
+
hasEnded: turn?.ended === true,
|
|
58
|
+
start,
|
|
59
|
+
resume,
|
|
60
|
+
stop,
|
|
61
|
+
reset,
|
|
62
|
+
finish,
|
|
63
|
+
isActive,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -5,15 +5,16 @@ import { useSetAtom } from "jotai";
|
|
|
5
5
|
import { useEffect } from "react";
|
|
6
6
|
import { chatGenerationInProgressAtom } from "@/core/ai/state";
|
|
7
7
|
|
|
8
|
-
export function
|
|
8
|
+
export function isChatBusy(status: ChatStatus): boolean {
|
|
9
|
+
return status === "submitted" || status === "streaming";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useChatGeneration(isGenerating: boolean) {
|
|
9
13
|
const setGenerating = useSetAtom(chatGenerationInProgressAtom);
|
|
10
|
-
const isGenerating = status === "submitted" || status === "streaming";
|
|
11
14
|
|
|
12
15
|
useEffect(() => {
|
|
13
16
|
setGenerating(isGenerating);
|
|
14
17
|
// useChat stops its request when the panel unmounts or becomes hidden.
|
|
15
18
|
return () => setGenerating(false);
|
|
16
19
|
}, [isGenerating, setGenerating]);
|
|
17
|
-
|
|
18
|
-
return isGenerating;
|
|
19
20
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
|
4
|
+
import type { FileUIPart, TextUIPart, UIMessage } from "ai";
|
|
5
|
+
import { PencilIcon } from "lucide-react";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { Tooltip } from "@/components/ui/tooltip";
|
|
9
|
+
import { focusInputAndMoveToEnd } from "@/core/codemirror/utils";
|
|
10
|
+
import { PromptInput } from "../editor/ai/prompt-input";
|
|
11
|
+
import { CopyClipboardIcon } from "../icons/copy-icon";
|
|
12
|
+
import { AttachmentRenderer } from "./chat-components";
|
|
13
|
+
|
|
14
|
+
interface UserMessageProps {
|
|
15
|
+
message: UIMessage;
|
|
16
|
+
canEdit: boolean;
|
|
17
|
+
onEdit: (newValue: string) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const UserMessage: React.FC<UserMessageProps> = ({
|
|
21
|
+
message,
|
|
22
|
+
canEdit,
|
|
23
|
+
onEdit,
|
|
24
|
+
}) => {
|
|
25
|
+
const content = message.parts
|
|
26
|
+
.filter((p): p is TextUIPart => p.type === "text")
|
|
27
|
+
.map((p) => p.text)
|
|
28
|
+
.join("\n");
|
|
29
|
+
const attachments = message.parts
|
|
30
|
+
.filter((p): p is FileUIPart => p.type === "file")
|
|
31
|
+
.map((part, idx) => (
|
|
32
|
+
// Sent parts never reorder, and a file attached twice repeats its URL.
|
|
33
|
+
// react-doctor-disable-next-line react-doctor/no-array-index-as-key
|
|
34
|
+
<AttachmentRenderer attachment={part} key={idx} />
|
|
35
|
+
));
|
|
36
|
+
|
|
37
|
+
const [draft, setDraft] = React.useState<string | null>(null);
|
|
38
|
+
const inputRef = React.useRef<ReactCodeMirrorRef>(null);
|
|
39
|
+
const editButtonRef = React.useRef<HTMLButtonElement>(null);
|
|
40
|
+
const editing = draft != null;
|
|
41
|
+
|
|
42
|
+
const startEditing = () => {
|
|
43
|
+
setDraft(content);
|
|
44
|
+
focusInputAndMoveToEnd(inputRef);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const cancel = () => {
|
|
48
|
+
setDraft(null);
|
|
49
|
+
requestAnimationFrame(() => editButtonRef.current?.focus());
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const submit = (value: string) => {
|
|
53
|
+
if (!canEdit || !value.trim()) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
onEdit(value);
|
|
57
|
+
setDraft(null);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (editing) {
|
|
61
|
+
return (
|
|
62
|
+
<div className="w-full rounded-[10px] border border-border bg-background">
|
|
63
|
+
{attachments}
|
|
64
|
+
<div className="px-2 py-1.5">
|
|
65
|
+
<PromptInput
|
|
66
|
+
inputRef={inputRef}
|
|
67
|
+
className="max-h-[400px]"
|
|
68
|
+
value={draft}
|
|
69
|
+
onChange={setDraft}
|
|
70
|
+
onSubmit={(_e, value) => submit(value)}
|
|
71
|
+
onClose={cancel}
|
|
72
|
+
placeholder="Type your message. Use @ to reference variables and cells."
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
<div className="flex items-center gap-2 border-t border-border/20 px-3 py-1.5">
|
|
76
|
+
<span className="mr-auto text-xs text-muted-foreground">
|
|
77
|
+
Responses after this message will be replaced.
|
|
78
|
+
</span>
|
|
79
|
+
<Button variant="text" size="xs" onClick={cancel}>
|
|
80
|
+
Cancel
|
|
81
|
+
</Button>
|
|
82
|
+
<Button
|
|
83
|
+
size="xs"
|
|
84
|
+
disabled={!canEdit || !draft.trim()}
|
|
85
|
+
onClick={() => submit(draft)}
|
|
86
|
+
>
|
|
87
|
+
Send
|
|
88
|
+
</Button>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="flex max-w-[90%] flex-col items-end gap-1">
|
|
96
|
+
{attachments}
|
|
97
|
+
<div className="rounded-[10px] bg-(--slate-3) px-3 py-1.5 whitespace-pre-wrap wrap-break-word">
|
|
98
|
+
<MentionText text={content} />
|
|
99
|
+
</div>
|
|
100
|
+
<div className="flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">
|
|
101
|
+
<Tooltip content="Edit">
|
|
102
|
+
<Button
|
|
103
|
+
ref={editButtonRef}
|
|
104
|
+
variant="text"
|
|
105
|
+
size="icon"
|
|
106
|
+
aria-label="Edit message"
|
|
107
|
+
className="chat-control"
|
|
108
|
+
disabled={!canEdit}
|
|
109
|
+
onClick={startEditing}
|
|
110
|
+
>
|
|
111
|
+
<PencilIcon className="size-3" />
|
|
112
|
+
</Button>
|
|
113
|
+
</Tooltip>
|
|
114
|
+
<CopyClipboardIcon
|
|
115
|
+
value={content}
|
|
116
|
+
className="size-3"
|
|
117
|
+
buttonClassName="chat-control flex size-6 items-center justify-center rounded-md"
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const MENTION = /([@#])[\w-]+:\/\/([\w./-]+)/g;
|
|
125
|
+
|
|
126
|
+
const MentionText: React.FC<{ text: string }> = ({ text }) => {
|
|
127
|
+
const nodes: React.ReactNode[] = [];
|
|
128
|
+
let last = 0;
|
|
129
|
+
for (const match of text.matchAll(MENTION)) {
|
|
130
|
+
nodes.push(text.slice(last, match.index));
|
|
131
|
+
nodes.push(
|
|
132
|
+
<span
|
|
133
|
+
key={match.index}
|
|
134
|
+
title={match[0].slice(1)}
|
|
135
|
+
className="rounded-sm bg-(--slate-5) px-1 font-mono text-[0.85em]"
|
|
136
|
+
>
|
|
137
|
+
{match[1]}
|
|
138
|
+
{match[2]}
|
|
139
|
+
</span>,
|
|
140
|
+
);
|
|
141
|
+
last = match.index + match[0].length;
|
|
142
|
+
}
|
|
143
|
+
nodes.push(text.slice(last));
|
|
144
|
+
return nodes;
|
|
145
|
+
};
|
|
@@ -7,11 +7,12 @@ import {
|
|
|
7
7
|
cellGraphsAtom,
|
|
8
8
|
isVariableAffectedBySelectedCell,
|
|
9
9
|
} from "@/components/editor/chrome/wrapper/minimap-state";
|
|
10
|
-
import { useCellData
|
|
10
|
+
import { useCellData } from "@/core/cells/cells";
|
|
11
11
|
import { useCellActions } from "@/core/cells/actions";
|
|
12
12
|
import { cellFocusAtom, useCellFocusActions } from "@/core/cells/focus";
|
|
13
13
|
import type { CellId } from "@/core/cells/ids";
|
|
14
14
|
import { useLayout } from "@/core/cells/layout";
|
|
15
|
+
import { useCellExecution } from "@/core/executions";
|
|
15
16
|
import { useVariables } from "@/core/variables/state";
|
|
16
17
|
import { cn } from "@/utils/cn";
|
|
17
18
|
import { extractCellPreview } from "./utils/cell-preview";
|
|
@@ -32,7 +33,7 @@ const MinimapCell: React.FC<MinimapCellProps> = (props) => {
|
|
|
32
33
|
id: props.cellId,
|
|
33
34
|
graph: graphs[props.cellId],
|
|
34
35
|
code: useCellData(props.cellId).code,
|
|
35
|
-
hasError:
|
|
36
|
+
hasError: useCellExecution(props.cellId).outcome === "error",
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
let selectedCell: undefined | { id: CellId; graph: CellGraph };
|
|
@@ -104,16 +104,9 @@ describe("Cell data attributes", () => {
|
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
notebook.cellRuntime[cid] = createCellRuntimeState({
|
|
107
|
-
status: "idle",
|
|
108
107
|
output: null,
|
|
109
108
|
consoleOutputs: [],
|
|
110
|
-
interrupted: false,
|
|
111
|
-
errored: false,
|
|
112
|
-
stopped: false,
|
|
113
109
|
staleInputs: false,
|
|
114
|
-
runStartTimestamp: null,
|
|
115
|
-
lastRunStartTimestamp: null,
|
|
116
|
-
runElapsedTimeMs: null,
|
|
117
110
|
debuggerActive: false,
|
|
118
111
|
outline: null,
|
|
119
112
|
});
|
|
@@ -4,6 +4,7 @@ import { createStore, Provider } from "jotai";
|
|
|
4
4
|
import { beforeAll, describe, expect, it } from "vitest";
|
|
5
5
|
import { SetupMocks } from "@/__mocks__/common";
|
|
6
6
|
import { mockDocumentModel } from "@/__mocks__/document";
|
|
7
|
+
import { MockExecutions } from "@/__mocks__/executions";
|
|
7
8
|
import { MockNotebook } from "@/__mocks__/notebook";
|
|
8
9
|
import { MockRequestClient } from "@/__mocks__/requests";
|
|
9
10
|
import { cellId } from "@/__tests__/branded";
|
|
@@ -50,7 +51,6 @@ function seedNotebook(
|
|
|
50
51
|
},
|
|
51
52
|
});
|
|
52
53
|
notebook.cellRuntime[cid] = createCellRuntimeState({
|
|
53
|
-
status: "idle",
|
|
54
54
|
output: {
|
|
55
55
|
channel: "output",
|
|
56
56
|
mimetype: "text/html",
|
|
@@ -207,8 +207,8 @@ describe("output persistence across mode changes", () => {
|
|
|
207
207
|
|
|
208
208
|
it("hides errored cells while presenting without unmounting them", () => {
|
|
209
209
|
const { store, wrapper } = createTestWrapper();
|
|
210
|
+
MockExecutions.seed(store, [MockExecutions.errored(cid)]);
|
|
210
211
|
seedNotebook(store, {
|
|
211
|
-
errored: true,
|
|
212
212
|
output: {
|
|
213
213
|
channel: "marimo-error",
|
|
214
214
|
mimetype: "application/vnd.marimo+error",
|
|
@@ -10,6 +10,7 @@ import { partial } from "@/__mocks__/partial";
|
|
|
10
10
|
import { MockRequestClient } from "@/__mocks__/requests";
|
|
11
11
|
import { ModalProvider } from "@/components/modal/ImperativeModal";
|
|
12
12
|
import { toast } from "@/components/ui/use-toast";
|
|
13
|
+
import { type Edit, stagedAICellsAtom } from "@/core/ai/staged-cells";
|
|
13
14
|
import { CellId } from "@/core/cells/ids";
|
|
14
15
|
import { requestClientAtom } from "@/core/network/requests";
|
|
15
16
|
import type { NotebookReplica } from "@/core/notebook/replica";
|
|
@@ -21,8 +22,11 @@ import { useCellActionButtons } from "../useCellActionButton";
|
|
|
21
22
|
vi.mock("@/utils/copy", () => ({ copyToClipboard: vi.fn() }));
|
|
22
23
|
vi.mock("@/components/ui/use-toast", () => ({ toast: vi.fn() }));
|
|
23
24
|
|
|
24
|
-
function renderActions(cellId: CellId, name: string) {
|
|
25
|
+
function renderActions(cellId: CellId, name: string, edit?: Edit) {
|
|
25
26
|
const store = createStore();
|
|
27
|
+
if (edit) {
|
|
28
|
+
store.set(stagedAICellsAtom, new Map([[cellId, edit]]));
|
|
29
|
+
}
|
|
26
30
|
const { model, documentAtom, stop } = mockDocumentModel(store);
|
|
27
31
|
onTestFinished(stop);
|
|
28
32
|
store.set(requestClientAtom, MockRequestClient.create());
|
|
@@ -48,6 +52,7 @@ function renderActions(cellId: CellId, name: string) {
|
|
|
48
52
|
name,
|
|
49
53
|
config: { disabled: false, hide_code: false, column: null },
|
|
50
54
|
status: "idle",
|
|
55
|
+
blocked: false,
|
|
51
56
|
hasOutput: false,
|
|
52
57
|
hasConsoleOutput: false,
|
|
53
58
|
getEditorView: () => null,
|
|
@@ -111,3 +116,16 @@ describe("useCellActionButtons", () => {
|
|
|
111
116
|
expect(getAction(groups, "Copy cell ID").disabled).toBeUndefined();
|
|
112
117
|
});
|
|
113
118
|
});
|
|
119
|
+
|
|
120
|
+
it("offers individual AI review actions in the cell menu only for pending changes", () => {
|
|
121
|
+
const id = CellId.create();
|
|
122
|
+
const pending = renderActions(id, "_", { type: "add_cell" });
|
|
123
|
+
expect(pending[0].map((action) => action.label)).toEqual([
|
|
124
|
+
"Keep cell",
|
|
125
|
+
"Discard cell",
|
|
126
|
+
]);
|
|
127
|
+
const ordinary = renderActions(id, "_");
|
|
128
|
+
expect(ordinary.flat().some((action) => action.label === "Keep cell")).toBe(
|
|
129
|
+
false,
|
|
130
|
+
);
|
|
131
|
+
});
|
|
@@ -56,13 +56,15 @@ import {
|
|
|
56
56
|
appWidthAtom,
|
|
57
57
|
autoInstantiateAtom,
|
|
58
58
|
} from "@/core/config/config";
|
|
59
|
-
import type {
|
|
59
|
+
import type { CellRunStatus } from "@/core/executions";
|
|
60
|
+
import type { CellConfig } from "@/core/network/types";
|
|
60
61
|
import { canEditAtom } from "@/core/notebook";
|
|
61
62
|
import { canLinkToCell, createCellLink } from "@/utils/cell-urls";
|
|
62
63
|
import { copyToClipboard } from "@/utils/copy";
|
|
63
64
|
import { downloadCellOutputAsImage } from "@/utils/download";
|
|
64
65
|
import { MarkdownIcon, PythonIcon } from "../cell/code/icons";
|
|
65
66
|
import { useDeleteCellCallback } from "../cell/useDeleteCell";
|
|
67
|
+
import { useStagedAICellActions } from "../cell/StagedAICell";
|
|
66
68
|
import { useRunCell } from "../cell/useRunCells";
|
|
67
69
|
import { useSplitCellCallback } from "../cell/useSplitCell";
|
|
68
70
|
import { NameCellInput } from "./name-cell-input";
|
|
@@ -73,7 +75,9 @@ export interface CellActionButtonProps extends Pick<
|
|
|
73
75
|
"name" | "config"
|
|
74
76
|
> {
|
|
75
77
|
cellId: CellId;
|
|
76
|
-
status:
|
|
78
|
+
status: CellRunStatus;
|
|
79
|
+
/** Whether a cell it depends on is disabled. */
|
|
80
|
+
blocked: boolean;
|
|
77
81
|
hasOutput: boolean;
|
|
78
82
|
hasConsoleOutput: boolean;
|
|
79
83
|
getEditorView: () => EditorView | null;
|
|
@@ -93,6 +97,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
|
|
|
93
97
|
clearCellOutput,
|
|
94
98
|
createCell,
|
|
95
99
|
} = useCellActions();
|
|
100
|
+
const stagedActions = useStagedAICellActions(cell?.cellId);
|
|
96
101
|
const showHiddenCode = useSetAtom(showHiddenCodeAtom);
|
|
97
102
|
const edit = useNotebookDocumentEdit();
|
|
98
103
|
const splitCell = useSplitCellCallback();
|
|
@@ -119,6 +124,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
|
|
|
119
124
|
hasOutput,
|
|
120
125
|
hasConsoleOutput,
|
|
121
126
|
status,
|
|
127
|
+
blocked,
|
|
122
128
|
} = cell;
|
|
123
129
|
|
|
124
130
|
const toggleDisabled = () => {
|
|
@@ -144,6 +150,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
|
|
|
144
150
|
|
|
145
151
|
// Actions
|
|
146
152
|
const actions: ActionButton[][] = [
|
|
153
|
+
stagedActions,
|
|
147
154
|
[
|
|
148
155
|
{
|
|
149
156
|
icon: <PlayIcon size={13} strokeWidth={1.5} />,
|
|
@@ -152,7 +159,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
|
|
|
152
159
|
hidden:
|
|
153
160
|
status === "running" ||
|
|
154
161
|
status === "queued" ||
|
|
155
|
-
|
|
162
|
+
blocked ||
|
|
156
163
|
config.disabled,
|
|
157
164
|
redundant: true,
|
|
158
165
|
handle: () => runCell(),
|
|
@@ -46,43 +46,6 @@ export const createAiCompletionOnKeydown = (opts: {
|
|
|
46
46
|
};
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
export const CompletionActionsCellFooter: React.FC<{
|
|
50
|
-
isLoading: boolean;
|
|
51
|
-
onAccept: () => void;
|
|
52
|
-
onDecline: () => void;
|
|
53
|
-
size?: "xs" | "sm";
|
|
54
|
-
multipleCompletions?: boolean;
|
|
55
|
-
runCell?: () => void;
|
|
56
|
-
acceptLabel?: string;
|
|
57
|
-
declineLabel?: string;
|
|
58
|
-
}> = ({
|
|
59
|
-
isLoading,
|
|
60
|
-
onAccept,
|
|
61
|
-
onDecline,
|
|
62
|
-
runCell,
|
|
63
|
-
acceptLabel,
|
|
64
|
-
declineLabel,
|
|
65
|
-
size,
|
|
66
|
-
}) => {
|
|
67
|
-
return (
|
|
68
|
-
<>
|
|
69
|
-
<AcceptCompletionButton
|
|
70
|
-
isLoading={isLoading}
|
|
71
|
-
onAccept={onAccept}
|
|
72
|
-
size={size}
|
|
73
|
-
runCell={runCell}
|
|
74
|
-
label={acceptLabel}
|
|
75
|
-
/>
|
|
76
|
-
<RejectCompletionButton
|
|
77
|
-
onDecline={onDecline}
|
|
78
|
-
size={size}
|
|
79
|
-
label={declineLabel}
|
|
80
|
-
disabled={isLoading}
|
|
81
|
-
/>
|
|
82
|
-
</>
|
|
83
|
-
);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
49
|
export const AcceptCompletionButton: React.FC<{
|
|
87
50
|
isLoading: boolean;
|
|
88
51
|
onAccept: () => void;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { atom, useAtomValue } from "jotai";
|
|
4
4
|
import { notebookAtom } from "@/core/cells/cells";
|
|
5
|
+
import { cellExecutionInputsAtom, cellExecutionView } from "@/core/executions";
|
|
5
6
|
import { Logger } from "@/utils/Logger";
|
|
6
7
|
import { Button } from "../../ui/button";
|
|
7
8
|
import { Kbd } from "../../ui/kbd";
|
|
@@ -10,12 +11,13 @@ import { FloatingAlert } from "./floating-alert";
|
|
|
10
11
|
// Atom to check if there is a cell with unresolved stdin
|
|
11
12
|
const hasBlockingStdinAtom = atom((get) => {
|
|
12
13
|
const notebook = get(notebookAtom);
|
|
14
|
+
const executions = get(cellExecutionInputsAtom);
|
|
13
15
|
|
|
14
16
|
// Check each cell in order
|
|
15
17
|
for (const cellId of notebook.cellIds) {
|
|
16
18
|
// Check if the cell is idle and has unresolved stdin
|
|
17
19
|
const runtime = notebook.cellRuntime[cellId];
|
|
18
|
-
if (
|
|
20
|
+
if (cellExecutionView(executions, cellId).status === "idle") {
|
|
19
21
|
const hasUnresolvedStdin = runtime.consoleOutputs.some(
|
|
20
22
|
(output) => output.channel === "stdin" && output.response === undefined,
|
|
21
23
|
);
|
|
@@ -7,24 +7,29 @@ import {
|
|
|
7
7
|
} from "lucide-react";
|
|
8
8
|
import { useDateFormatter } from "react-aria";
|
|
9
9
|
import { MultiIcon } from "@/components/icons/multi-icon";
|
|
10
|
+
import type { CellRunStatus } from "@/core/executions";
|
|
10
11
|
import { Logger } from "@/utils/Logger";
|
|
11
|
-
import type { CellRuntimeState } from "../../../core/cells/types";
|
|
12
12
|
import { useElapsedTime } from "../../../hooks/useElapsedTime";
|
|
13
13
|
import { Tooltip } from "../../ui/tooltip";
|
|
14
14
|
|
|
15
15
|
import "./cell-status.css";
|
|
16
16
|
import { formatDistanceToNow } from "date-fns";
|
|
17
|
-
import { formatElapsedTime, Time } from "@/utils/time";
|
|
17
|
+
import { formatElapsedTime, type Milliseconds, Time } from "@/utils/time";
|
|
18
18
|
|
|
19
|
-
export interface CellStatusComponentProps
|
|
20
|
-
CellRuntimeState,
|
|
21
|
-
"status" | "runStartTimestamp" | "interrupted" | "lastRunStartTimestamp"
|
|
22
|
-
> {
|
|
19
|
+
export interface CellStatusComponentProps {
|
|
23
20
|
editing: boolean;
|
|
24
21
|
edited: boolean;
|
|
25
22
|
disabled: boolean;
|
|
23
|
+
/** Whether a cell it depends on is disabled. */
|
|
24
|
+
blocked: boolean;
|
|
26
25
|
staleInputs: boolean;
|
|
27
|
-
|
|
26
|
+
status: CellRunStatus;
|
|
27
|
+
/** Whether the cell's last run was interrupted. */
|
|
28
|
+
interrupted: boolean;
|
|
29
|
+
/** How long the last run took, once it has ended. */
|
|
30
|
+
elapsedTime: Milliseconds | null;
|
|
31
|
+
/** When the current run started, or the last one, since the epoch. */
|
|
32
|
+
startedAt: Milliseconds | null;
|
|
28
33
|
uninstantiated: boolean;
|
|
29
34
|
}
|
|
30
35
|
|
|
@@ -32,20 +37,21 @@ export const CellStatusComponent: React.FC<CellStatusComponentProps> = ({
|
|
|
32
37
|
editing,
|
|
33
38
|
status,
|
|
34
39
|
disabled,
|
|
40
|
+
blocked,
|
|
35
41
|
staleInputs,
|
|
36
42
|
edited,
|
|
37
43
|
interrupted,
|
|
38
44
|
elapsedTime,
|
|
39
|
-
|
|
40
|
-
lastRunStartTimestamp,
|
|
45
|
+
startedAt,
|
|
41
46
|
uninstantiated,
|
|
42
47
|
}) => {
|
|
43
48
|
if (!editing) {
|
|
44
49
|
return null;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
const
|
|
48
|
-
|
|
52
|
+
const lastRanTime = startedAt ? (
|
|
53
|
+
<LastRanTime lastRanTime={startedAt} />
|
|
54
|
+
) : null;
|
|
49
55
|
|
|
50
56
|
// unexpected states
|
|
51
57
|
if (disabled && status === "running") {
|
|
@@ -103,7 +109,7 @@ export const CellStatusComponent: React.FC<CellStatusComponentProps> = ({
|
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
// disabled from parent
|
|
106
|
-
if (!staleInputs &&
|
|
112
|
+
if (!staleInputs && blocked) {
|
|
107
113
|
return (
|
|
108
114
|
<Tooltip
|
|
109
115
|
content={
|
|
@@ -131,7 +137,7 @@ export const CellStatusComponent: React.FC<CellStatusComponentProps> = ({
|
|
|
131
137
|
}
|
|
132
138
|
|
|
133
139
|
// stale from parent being disabled
|
|
134
|
-
if (staleInputs &&
|
|
140
|
+
if (staleInputs && blocked) {
|
|
135
141
|
return (
|
|
136
142
|
<Tooltip
|
|
137
143
|
content={
|
|
@@ -175,9 +181,7 @@ export const CellStatusComponent: React.FC<CellStatusComponentProps> = ({
|
|
|
175
181
|
data-testid="cell-status"
|
|
176
182
|
data-status="running"
|
|
177
183
|
>
|
|
178
|
-
<CellTimer
|
|
179
|
-
startTime={Time.fromSeconds(runStartTimestamp) || Time.now()}
|
|
180
|
-
/>
|
|
184
|
+
<CellTimer startTime={startedAt ?? Time.now().toMilliseconds()} />
|
|
181
185
|
</div>
|
|
182
186
|
</Tooltip>
|
|
183
187
|
);
|
|
@@ -307,7 +311,7 @@ export const ElapsedTime = (props: { elapsedTime: string }) => {
|
|
|
307
311
|
);
|
|
308
312
|
};
|
|
309
313
|
const LastRanTime = (props: { lastRanTime: number }) => {
|
|
310
|
-
const date = new Date(props.lastRanTime
|
|
314
|
+
const date = new Date(props.lastRanTime);
|
|
311
315
|
const today = new Date();
|
|
312
316
|
|
|
313
317
|
// Looks like HH:MM:SS.SSS AM/PM
|
|
@@ -348,7 +352,7 @@ const LastRanTime = (props: { lastRanTime: number }) => {
|
|
|
348
352
|
);
|
|
349
353
|
};
|
|
350
354
|
|
|
351
|
-
const CellTimer = (props: { startTime:
|
|
352
|
-
const time = useElapsedTime(props.startTime
|
|
355
|
+
const CellTimer = (props: { startTime: Milliseconds }) => {
|
|
356
|
+
const time = useElapsedTime(props.startTime);
|
|
353
357
|
return <span>{formatElapsedTime(time)}</span>;
|
|
354
358
|
};
|