@marimo-team/islands 0.19.8-dev5 → 0.19.8-dev50
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/{Combination-Bg-xN8JV.js → Combination-BTMrlhzT.js} +11 -10
- package/dist/{ConnectedDataExplorerComponent-DewsKLl2.js → ConnectedDataExplorerComponent-BAeQ8DWw.js} +11 -11
- package/dist/{ImageComparisonComponent-Bijp8beW.js → ImageComparisonComponent-DkEXPki_.js} +2 -2
- package/dist/{any-language-editor-DZc6NCTp.js → any-language-editor-D0UQItkS.js} +6 -6
- package/dist/{architectureDiagram-VXUJARFQ--NkyBn9Y.js → architectureDiagram-VXUJARFQ-DPPYVq8H.js} +4 -4
- package/dist/assets/__vite-browser-external-WSlCcXn_.js +1 -0
- package/dist/assets/worker-DUYMdbtA.js +73 -0
- package/dist/{blockDiagram-VD42YOAC-DEZZaTW0.js → blockDiagram-VD42YOAC-BA5N05Y9.js} +4 -4
- package/dist/{button-BWvsJ2Wr.js → button-Cy0ElmIm.js} +2 -2
- package/dist/{c4Diagram-YG6GDRKO-Bj7hwWCO.js → c4Diagram-YG6GDRKO-DJLzuGJJ.js} +3 -3
- package/dist/{channel-B_QrFrGg.js → channel-Dob5kWXR.js} +1 -1
- package/dist/{check-CM_kewwn.js → check-DkNR52Mm.js} +1 -1
- package/dist/{chunk-5FQGJX7Z-D5VFKHmt.js → chunk-5FQGJX7Z-BEb20Lzt.js} +3 -3
- package/dist/{chunk-ABZYJK2D-SZPYmRzN.js → chunk-ABZYJK2D-BXTC53mt.js} +1 -1
- package/dist/{chunk-ATLVNIR6-BI_WwH1o.js → chunk-ATLVNIR6-BJDjUR_c.js} +1 -1
- package/dist/{chunk-B4BG7PRW-BlI9Gm1l.js → chunk-B4BG7PRW-DzmUUpfH.js} +4 -4
- package/dist/{chunk-DI55MBZ5-BXxemMn5.js → chunk-DI55MBZ5-gTd3J8Tu.js} +4 -4
- package/dist/{chunk-EXTU4WIE-CzWtDV99.js → chunk-EXTU4WIE-DyoOs5QX.js} +1 -1
- package/dist/{chunk-JA3XYJ7Z-DQ-2ARfa.js → chunk-JA3XYJ7Z-BGnAIbOP.js} +2 -2
- package/dist/{chunk-JZLCHNYA-CVfjf2vv.js → chunk-JZLCHNYA-CIRgweVQ.js} +4 -4
- package/dist/{chunk-N4CR4FBY-BCZvQ7Jq.js → chunk-N4CR4FBY-DKSvXAIS.js} +5 -5
- package/dist/{chunk-QN33PNHL-DY_2Q2zl.js → chunk-QN33PNHL-B6zC8BTi.js} +1 -1
- package/dist/{chunk-QXUST7PY-BMCjAVR_.js → chunk-QXUST7PY-C7750n_u.js} +5 -5
- package/dist/{chunk-S3R3BYOJ-Ddu0H4Qa.js → chunk-S3R3BYOJ-CBkH6JZZ.js} +1 -1
- package/dist/{chunk-TZMSLE5B-C2wVlbMl.js → chunk-TZMSLE5B-DObGL7xi.js} +1 -1
- package/dist/{classDiagram-2ON5EDUG-D-g7zbyO.js → classDiagram-2ON5EDUG-B9pkKjjc.js} +9 -9
- package/dist/{classDiagram-v2-WZHVMYZB-C7v5zNRD.js → classDiagram-v2-WZHVMYZB-CRhhA0tV.js} +9 -9
- package/dist/{click-outside-container-BCN5BtVO.js → click-outside-container-DNfggvIW.js} +1 -1
- package/dist/{code-block-37QAKDTI-eUgXqGNG.js → code-block-37QAKDTI-u5kgjqmr.js} +2 -2
- package/dist/{compiler-runtime-DHFVbq0b.js → compiler-runtime-B_OLMU9S.js} +1 -1
- package/dist/{copy-B59Bw3-w.js → copy-DRaXIb_a.js} +3 -3
- package/dist/{dagre-6UL2VRFP-DKIPL74O.js → dagre-6UL2VRFP-C2C2XxsB.js} +6 -6
- package/dist/{data-grid-overlay-editor-COyFwFmE.js → data-grid-overlay-editor-BXqtz1ia.js} +4 -4
- package/dist/{diagram-PSM6KHXK-CVTrAZaP.js → diagram-PSM6KHXK-DHBY-94p.js} +5 -5
- package/dist/{diagram-QEK2KX5R-BqHBzu3x.js → diagram-QEK2KX5R-CgMshOwn.js} +3 -3
- package/dist/{diagram-S2PKOQOG-CJD6owcg.js → diagram-S2PKOQOG-F1KPva3Y.js} +3 -3
- package/dist/{dist-Co5PD8Fb.js → dist-BBYTEAvO.js} +1 -1
- package/dist/{erDiagram-Q2GNP2WA-CqOceSf9.js → erDiagram-Q2GNP2WA-18gGng8V.js} +9 -9
- package/dist/{error-banner-C7KLpECd.js → error-banner-D2zjeN_a.js} +5 -5
- package/dist/{esm-D4WO8J3G.js → esm-CgRNPmz8.js} +6 -6
- package/dist/{flowDiagram-NV44I4VS-K7-DUifo.js → flowDiagram-NV44I4VS-iHFiHYe0.js} +9 -9
- package/dist/{ganttDiagram-JELNMOA3-BwUFY9Nu.js → ganttDiagram-JELNMOA3-D7GixxiF.js} +2 -2
- package/dist/{gitGraphDiagram-NY62KEGX-CjGRtLb1.js → gitGraphDiagram-NY62KEGX-CJFHytRK.js} +2 -2
- package/dist/{glide-data-editor-C3T7HsLi.js → glide-data-editor-BYwb17Bf.js} +13 -13
- package/dist/{infoDiagram-WHAUD3N6-DNhmDn-6.js → infoDiagram-WHAUD3N6-B5Lkh3A9.js} +2 -2
- package/dist/{journeyDiagram-XKPGCS4Q-BOdK47P8.js → journeyDiagram-XKPGCS4Q-CV_9R9iP.js} +2 -2
- package/dist/{kanban-definition-3W4ZIXB7-A0JC9d0g.js → kanban-definition-3W4ZIXB7-Dp21D5Ym.js} +6 -6
- package/dist/{katex-DJyOeQ91.js → katex-CX2BKujk.js} +1 -1
- package/dist/{katex-Dm9nZf6A.js → katex-Db0k5oV_.js} +1 -1
- package/dist/{label-C4PtQcza.js → label-CxU5JNBW.js} +6 -6
- package/dist/main.js +2000 -1887
- package/dist/mermaid-4DMBBIKO-BhDCqnO1.js +6 -0
- package/dist/{mermaid-Bqp2Xw99.js → mermaid-B__BZSXU.js} +39 -39
- package/dist/{mhchem-BqdXeZVX.js → mhchem-w1tkUnWr.js} +1 -1
- package/dist/{mindmap-definition-VGOIOE7T-CS6nKN_L.js → mindmap-definition-VGOIOE7T-B_5mfdYp.js} +8 -8
- package/dist/{number-overlay-editor-Bz_bDJQb.js → number-overlay-editor-D-4WQAGX.js} +2 -2
- package/dist/{pieDiagram-ADFJNKIX-DSa60Grk.js → pieDiagram-ADFJNKIX-B-DGEopK.js} +3 -3
- package/dist/{quadrantDiagram-AYHSOK5B-CFnMbP2J.js → quadrantDiagram-AYHSOK5B-M_yRSIZn.js} +1 -1
- package/dist/{react-DdA8EBol.js → react-Bs6Z0kvn.js} +1 -1
- package/dist/{react-dom-DJW8xUDg.js → react-dom-CqtLRVZP.js} +2 -2
- package/dist/{react-plotly-jVjTu07w.js → react-plotly-BuRa9xtI.js} +1 -1
- package/dist/{react-vega-DgHpnZ04.js → react-vega-3WcLHYC7.js} +2 -2
- package/dist/{react-vega-CjiPWyw0.js → react-vega-DLFvGrpJ.js} +1 -1
- package/dist/{requirementDiagram-UZGBJVZJ-ytLQrFTk.js → requirementDiagram-UZGBJVZJ-9Wt82hOZ.js} +8 -8
- package/dist/{sankeyDiagram-TZEHDZUN-KQqXDoky.js → sankeyDiagram-TZEHDZUN-x_aTXZeN.js} +1 -1
- package/dist/{sequenceDiagram-WL72ISMW-ByLI04T5.js → sequenceDiagram-WL72ISMW-CXXmJqiQ.js} +3 -3
- package/dist/{slides-component-BVjvNo92.js → slides-component-Dp-y50K9.js} +4 -4
- package/dist/{spec-Dmb1KfK3.js → spec-HoYHAQo2.js} +6 -6
- package/dist/{stateDiagram-FKZM4ZOC-Dfz8vBbP.js → stateDiagram-FKZM4ZOC-CiSKS_Mx.js} +9 -9
- package/dist/{stateDiagram-v2-4FDKWEC3-DRYoLdT5.js → stateDiagram-v2-4FDKWEC3-A43Itnjp.js} +9 -9
- package/dist/style.css +1 -1
- package/dist/{timeline-definition-IT6M3QCI-CO48XU1B.js → timeline-definition-IT6M3QCI-DR26eWb4.js} +1 -1
- package/dist/{types-CzEZ3EWT.js → types-Bb-6p8hv.js} +8 -8
- package/dist/{useAsyncData-BjNwqCfS.js → useAsyncData-Dyq3DyOF.js} +3 -3
- package/dist/{useDeepCompareMemoize-CfoxVor3.js → useDeepCompareMemoize-BhZZsis0.js} +12 -8
- package/dist/{useIframeCapabilities-BBO_R0ww.js → useIframeCapabilities-DurI5SJh.js} +2 -2
- package/dist/{useTheme-BYG2SH8J.js → useTheme-SlKl8MlS.js} +5 -6
- package/dist/{vega-component-rDX7xwxH.js → vega-component-DCxUyPnb.js} +10 -10
- package/dist/{xychartDiagram-PRI3JC2R-CUIfjNVD.js → xychartDiagram-PRI3JC2R-BcVxCRox.js} +4 -4
- package/dist/{zod-DITCj31F.js → zod-bjADtMKr.js} +3 -3
- package/package.json +18 -18
- package/src/components/app-config/ai-config.tsx +11 -2
- package/src/components/app-config/optional-features.tsx +1 -1
- package/src/components/app-config/user-config-form.tsx +0 -54
- package/src/components/chat/__tests__/useFileState.test.tsx +93 -0
- package/src/components/chat/acp/__tests__/state.test.ts +69 -0
- package/src/components/chat/acp/agent-panel.tsx +26 -77
- package/src/components/chat/acp/state.ts +6 -6
- package/src/components/chat/chat-components.tsx +114 -1
- package/src/components/chat/chat-panel.tsx +79 -134
- package/src/components/chat/chat-utils.ts +42 -0
- package/src/components/data-table/__tests__/data-table.test.tsx +94 -2
- package/src/components/editor/actions/useCellActionButton.tsx +14 -1
- package/src/components/editor/ai/add-cell-with-ai.tsx +85 -53
- package/src/components/editor/ai/ai-completion-editor.tsx +15 -38
- package/src/components/editor/cell/CreateCellButton.tsx +2 -1
- package/src/components/editor/cell/code/cell-editor.tsx +12 -0
- package/src/components/editor/chrome/panels/packages-panel.tsx +12 -9
- package/src/components/editor/database/__tests__/__snapshots__/as-code.test.ts.snap +15 -0
- package/src/components/editor/database/__tests__/as-code.test.ts +8 -0
- package/src/components/editor/database/as-code.ts +3 -0
- package/src/components/editor/database/schemas.ts +9 -0
- package/src/components/editor/renderers/cell-array.tsx +2 -1
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +12 -0
- package/src/components/pages/gallery-page.tsx +37 -6
- package/src/core/MarimoApp.tsx +12 -8
- package/src/core/ai/context/providers/file.ts +1 -1
- package/src/core/cells/__tests__/cells.test.ts +120 -0
- package/src/core/cells/__tests__/session.test.ts +37 -1
- package/src/core/cells/cells.ts +14 -0
- package/src/core/cells/session.ts +20 -8
- package/src/core/codemirror/language/languages/markdown.ts +7 -0
- package/src/core/config/feature-flag.tsx +0 -4
- package/src/core/dom/uiregistry.ts +4 -1
- package/src/core/islands/__tests__/bridge.test.ts +7 -2
- package/src/core/islands/bridge.ts +1 -1
- package/src/core/islands/main.ts +7 -0
- package/src/core/network/types.ts +2 -2
- package/src/core/run-app.tsx +11 -4
- package/src/core/static/__tests__/files.test.ts +195 -1
- package/src/core/static/files.ts +39 -9
- package/src/core/wasm/bridge.ts +1 -1
- package/src/core/websocket/useMarimoKernelConnection.tsx +5 -15
- package/src/plugins/core/registerReactComponent.tsx +9 -1
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +164 -0
- package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +93 -168
- package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +37 -123
- package/src/plugins/impl/anywidget/__tests__/model.test.ts +128 -122
- package/src/{utils/__tests__/data-views.test.ts → plugins/impl/anywidget/__tests__/serialization.test.ts} +42 -96
- package/src/plugins/impl/anywidget/model.ts +348 -223
- package/src/plugins/impl/anywidget/schemas.ts +32 -0
- package/src/{utils/data-views.ts → plugins/impl/anywidget/serialization.ts} +13 -36
- package/src/plugins/impl/anywidget/types.ts +27 -0
- package/src/plugins/impl/chat/chat-ui.tsx +22 -20
- package/src/utils/Deferred.ts +21 -0
- package/src/utils/__tests__/blob.test.ts +3 -3
- package/src/utils/__tests__/id-tree.test.ts +22 -7
- package/src/utils/__tests__/mime-types.test.ts +8 -10
- package/src/utils/__tests__/url-parser.test.ts +22 -0
- package/src/utils/blob.ts +14 -27
- package/src/utils/id-tree.tsx +11 -19
- package/src/utils/json/base64.ts +38 -8
- package/src/utils/mime-types.ts +5 -5
- package/src/utils/url-parser.ts +1 -1
- package/dist/assets/__vite-browser-external-DRa9CT_O.js +0 -1
- package/dist/assets/worker-SqntmiwV.js +0 -73
- package/dist/mermaid-4DMBBIKO-o3xNphpD.js +0 -6
|
@@ -7,14 +7,14 @@ import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
|
|
7
7
|
import { DefaultChatTransport, type FileUIPart, type TextUIPart } from "ai";
|
|
8
8
|
import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai";
|
|
9
9
|
import {
|
|
10
|
-
AtSignIcon,
|
|
11
10
|
BotMessageSquareIcon,
|
|
11
|
+
HatGlasses,
|
|
12
12
|
Loader2,
|
|
13
|
-
|
|
13
|
+
type LucideIcon,
|
|
14
|
+
MessageCircleIcon,
|
|
15
|
+
NotebookText,
|
|
14
16
|
PlusIcon,
|
|
15
|
-
SendIcon,
|
|
16
17
|
SettingsIcon,
|
|
17
|
-
SquareIcon,
|
|
18
18
|
} from "lucide-react";
|
|
19
19
|
import { memo, useEffect, useRef, useState } from "react";
|
|
20
20
|
import useEvent from "react-use-event-hook";
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
} from "@/components/ui/select";
|
|
30
30
|
import { replaceMessagesInChat } from "@/core/ai/chat-utils";
|
|
31
31
|
import { useModelChange } from "@/core/ai/config";
|
|
32
|
-
import { AiModelId
|
|
32
|
+
import { AiModelId } from "@/core/ai/ids/ids";
|
|
33
33
|
import { useStagedAICellsActions } from "@/core/ai/staged-cells";
|
|
34
34
|
import {
|
|
35
35
|
activeChatAtom,
|
|
@@ -45,7 +45,6 @@ import {
|
|
|
45
45
|
import { useCellActions } from "@/core/cells/cells";
|
|
46
46
|
import { aiAtom, aiEnabledAtom } from "@/core/config/config";
|
|
47
47
|
import { DEFAULT_AI_MODEL } from "@/core/config/config-schema";
|
|
48
|
-
import { FeatureFlagged } from "@/core/config/feature-flag";
|
|
49
48
|
import { useRequestClient } from "@/core/network/requests";
|
|
50
49
|
import { useRuntimeManager } from "@/core/runtime/config";
|
|
51
50
|
import { ErrorBanner } from "@/plugins/impl/common/error-banner";
|
|
@@ -61,10 +60,14 @@ import {
|
|
|
61
60
|
import { PanelEmptyState } from "../editor/chrome/panels/empty-state";
|
|
62
61
|
import { CopyClipboardIcon } from "../icons/copy-icon";
|
|
63
62
|
import { MCPStatusIndicator } from "../mcp/mcp-status-indicator";
|
|
64
|
-
import { Input } from "../ui/input";
|
|
65
63
|
import { Tooltip, TooltipProvider } from "../ui/tooltip";
|
|
66
|
-
import {
|
|
67
|
-
|
|
64
|
+
import {
|
|
65
|
+
AddContextButton,
|
|
66
|
+
AttachFileButton,
|
|
67
|
+
AttachmentRenderer,
|
|
68
|
+
FileAttachmentPill,
|
|
69
|
+
SendButton,
|
|
70
|
+
} from "./chat-components";
|
|
68
71
|
import { renderUIMessage } from "./chat-display";
|
|
69
72
|
import { ChatHistoryPopover } from "./chat-history-popover";
|
|
70
73
|
import {
|
|
@@ -74,21 +77,13 @@ import {
|
|
|
74
77
|
handleToolCall,
|
|
75
78
|
hasPendingToolCalls,
|
|
76
79
|
isLastMessageReasoning,
|
|
80
|
+
PROVIDERS_THAT_SUPPORT_ATTACHMENTS,
|
|
81
|
+
useFileState,
|
|
77
82
|
} from "./chat-utils";
|
|
78
83
|
|
|
79
84
|
// Default mode for the AI
|
|
80
85
|
const DEFAULT_MODE = "manual";
|
|
81
86
|
|
|
82
|
-
// We need to modify the backend to support attachments for other providers
|
|
83
|
-
// And other types
|
|
84
|
-
const PROVIDERS_THAT_SUPPORT_ATTACHMENTS = new Set<ProviderId>([
|
|
85
|
-
"openai",
|
|
86
|
-
"google",
|
|
87
|
-
"anthropic",
|
|
88
|
-
]);
|
|
89
|
-
const SUPPORTED_ATTACHMENT_TYPES = ["image/*", "text/*"];
|
|
90
|
-
const MAX_ATTACHMENT_SIZE = 1024 * 1024 * 50; // 50MB
|
|
91
|
-
|
|
92
87
|
interface ChatHeaderProps {
|
|
93
88
|
onNewChat: () => void;
|
|
94
89
|
activeChatId: ChatId | undefined;
|
|
@@ -238,58 +233,72 @@ const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
|
|
|
238
233
|
value: CopilotMode;
|
|
239
234
|
label: string;
|
|
240
235
|
subtitle: string;
|
|
236
|
+
Icon: LucideIcon;
|
|
241
237
|
}[] = [
|
|
238
|
+
{
|
|
239
|
+
value: "manual",
|
|
240
|
+
label: "Manual",
|
|
241
|
+
subtitle: "Pure chat, no tool usage",
|
|
242
|
+
Icon: MessageCircleIcon,
|
|
243
|
+
},
|
|
242
244
|
{
|
|
243
245
|
value: "ask",
|
|
244
246
|
label: "Ask",
|
|
245
247
|
subtitle:
|
|
246
248
|
"Use AI with access to read-only tools like documentation search",
|
|
247
|
-
|
|
248
|
-
{
|
|
249
|
-
value: "manual",
|
|
250
|
-
label: "Manual",
|
|
251
|
-
subtitle: "Pure chat, no tool usage",
|
|
249
|
+
Icon: NotebookText,
|
|
252
250
|
},
|
|
253
251
|
{
|
|
254
252
|
value: "agent",
|
|
255
253
|
label: "Agent (beta)",
|
|
256
254
|
subtitle: "Use AI with access to read and write tools",
|
|
255
|
+
Icon: HatGlasses,
|
|
257
256
|
},
|
|
258
257
|
];
|
|
259
258
|
|
|
260
259
|
const isAttachmentSupported =
|
|
261
260
|
PROVIDERS_THAT_SUPPORT_ATTACHMENTS.has(currentProvider);
|
|
262
261
|
|
|
262
|
+
const CurrentModeIcon = modeOptions.find(
|
|
263
|
+
(o) => o.value === currentMode,
|
|
264
|
+
)?.Icon;
|
|
265
|
+
|
|
263
266
|
return (
|
|
264
267
|
<TooltipProvider>
|
|
265
268
|
<div className="px-3 py-2 border-t border-border/20 flex flex-row flex-wrap items-center justify-between gap-1">
|
|
266
269
|
<div className="flex items-center gap-2">
|
|
267
|
-
<
|
|
268
|
-
<
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
270
|
+
<Select value={currentMode} onValueChange={saveModeChange}>
|
|
271
|
+
<SelectTrigger className="h-6 text-xs border-border shadow-none! ring-0! bg-muted hover:bg-muted/30 py-0 px-2 gap-1.5">
|
|
272
|
+
{CurrentModeIcon && <CurrentModeIcon className="h-3 w-3" />}
|
|
273
|
+
<span className="capitalize">{currentMode}</span>
|
|
274
|
+
</SelectTrigger>
|
|
275
|
+
<SelectContent>
|
|
276
|
+
<SelectGroup>
|
|
277
|
+
<SelectLabel className="text-xs uppercase tracking-wider text-muted-foreground/70 font-medium">
|
|
278
|
+
AI Mode
|
|
279
|
+
</SelectLabel>
|
|
280
|
+
{modeOptions.map((option) => (
|
|
281
|
+
<SelectItem
|
|
282
|
+
key={option.value}
|
|
283
|
+
value={option.value}
|
|
284
|
+
className="text-xs py-1"
|
|
285
|
+
>
|
|
286
|
+
<div className="flex items-start gap-2.5">
|
|
287
|
+
<span className="mt-1 text-muted-foreground">
|
|
288
|
+
<option.Icon className="h-3 w-3" />
|
|
289
|
+
</span>
|
|
290
|
+
<div className="flex flex-col gap-0.5">
|
|
291
|
+
<span className="font-semibold">{option.label}</span>
|
|
292
|
+
<span className="text-muted-foreground">
|
|
284
293
|
{option.subtitle}
|
|
285
|
-
</
|
|
294
|
+
</span>
|
|
286
295
|
</div>
|
|
287
|
-
</
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
</
|
|
291
|
-
</
|
|
292
|
-
</
|
|
296
|
+
</div>
|
|
297
|
+
</SelectItem>
|
|
298
|
+
))}
|
|
299
|
+
</SelectGroup>
|
|
300
|
+
</SelectContent>
|
|
301
|
+
</Select>
|
|
293
302
|
<AIModelDropdown
|
|
294
303
|
placeholder="Model"
|
|
295
304
|
triggerClassName="h-6 text-xs shadow-none! ring-0! bg-muted hover:bg-muted/30 rounded-sm"
|
|
@@ -299,60 +308,23 @@ const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
|
|
|
299
308
|
/>
|
|
300
309
|
</div>
|
|
301
310
|
<div className="flex flex-row">
|
|
302
|
-
<
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
onClick={onAddContext}
|
|
307
|
-
disabled={isLoading}
|
|
308
|
-
>
|
|
309
|
-
<AtSignIcon className="h-3.5 w-3.5" />
|
|
310
|
-
</Button>
|
|
311
|
-
</Tooltip>
|
|
311
|
+
<AddContextButton
|
|
312
|
+
handleAddContext={onAddContext}
|
|
313
|
+
isLoading={isLoading}
|
|
314
|
+
/>
|
|
312
315
|
{isAttachmentSupported && (
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
className="cursor-pointer"
|
|
319
|
-
onClick={() => fileInputRef.current?.click()}
|
|
320
|
-
title="Attach a file"
|
|
321
|
-
disabled={isLoading}
|
|
322
|
-
>
|
|
323
|
-
<PaperclipIcon className="h-3.5 w-3.5" />
|
|
324
|
-
</Button>
|
|
325
|
-
</Tooltip>
|
|
326
|
-
<Input
|
|
327
|
-
ref={fileInputRef}
|
|
328
|
-
type="file"
|
|
329
|
-
multiple={true}
|
|
330
|
-
hidden={true}
|
|
331
|
-
onChange={(event) => {
|
|
332
|
-
if (event.target.files) {
|
|
333
|
-
onAddFiles([...event.target.files]);
|
|
334
|
-
}
|
|
335
|
-
}}
|
|
336
|
-
accept={SUPPORTED_ATTACHMENT_TYPES.join(",")}
|
|
337
|
-
/>
|
|
338
|
-
</>
|
|
316
|
+
<AttachFileButton
|
|
317
|
+
fileInputRef={fileInputRef}
|
|
318
|
+
isLoading={isLoading}
|
|
319
|
+
onAddFiles={onAddFiles}
|
|
320
|
+
/>
|
|
339
321
|
)}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
onClick={isLoading ? onStop : onSendClick}
|
|
347
|
-
disabled={isLoading ? false : isEmpty}
|
|
348
|
-
>
|
|
349
|
-
{isLoading ? (
|
|
350
|
-
<SquareIcon className="h-3 w-3 fill-current" />
|
|
351
|
-
) : (
|
|
352
|
-
<SendIcon className="h-3 w-3" />
|
|
353
|
-
)}
|
|
354
|
-
</Button>
|
|
355
|
-
</Tooltip>
|
|
322
|
+
<SendButton
|
|
323
|
+
isLoading={isLoading}
|
|
324
|
+
onStop={onStop}
|
|
325
|
+
onSendClick={onSendClick}
|
|
326
|
+
isEmpty={isEmpty}
|
|
327
|
+
/>
|
|
356
328
|
</div>
|
|
357
329
|
</div>
|
|
358
330
|
</TooltipProvider>
|
|
@@ -456,7 +428,7 @@ const ChatPanelBody = () => {
|
|
|
456
428
|
const [activeChat, setActiveChat] = useAtom(activeChatAtom);
|
|
457
429
|
const [input, setInput] = useState("");
|
|
458
430
|
const [newThreadInput, setNewThreadInput] = useState("");
|
|
459
|
-
const
|
|
431
|
+
const { files, addFiles, clearFiles, removeFile } = useFileState();
|
|
460
432
|
const newThreadInputRef = useRef<ReactCodeMirrorRef>(null);
|
|
461
433
|
const newMessageInputRef = useRef<ReactCodeMirrorRef>(null);
|
|
462
434
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
@@ -545,33 +517,6 @@ const ChatPanelBody = () => {
|
|
|
545
517
|
},
|
|
546
518
|
});
|
|
547
519
|
|
|
548
|
-
const onAddFiles = useEvent((files: File[]) => {
|
|
549
|
-
if (files.length === 0) {
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
let fileSize = 0;
|
|
554
|
-
for (const file of files) {
|
|
555
|
-
fileSize += file.size;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
if (fileSize > MAX_ATTACHMENT_SIZE) {
|
|
559
|
-
toast({
|
|
560
|
-
title: "File size exceeds 50MB limit",
|
|
561
|
-
description: "Please remove some files and try again.",
|
|
562
|
-
});
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
setFiles((prev) => [...(prev ?? []), ...files]);
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
const removeFile = useEvent((file: File) => {
|
|
570
|
-
if (files) {
|
|
571
|
-
setFiles(files.filter((f) => f !== file));
|
|
572
|
-
}
|
|
573
|
-
});
|
|
574
|
-
|
|
575
520
|
const isLoading = status === "submitted" || status === "streaming";
|
|
576
521
|
|
|
577
522
|
// Check if we're currently streaming reasoning in the latest message
|
|
@@ -631,7 +576,7 @@ const ChatPanelBody = () => {
|
|
|
631
576
|
...(fileParts ?? []),
|
|
632
577
|
],
|
|
633
578
|
});
|
|
634
|
-
|
|
579
|
+
clearFiles();
|
|
635
580
|
setInput("");
|
|
636
581
|
};
|
|
637
582
|
|
|
@@ -639,7 +584,7 @@ const ChatPanelBody = () => {
|
|
|
639
584
|
setActiveChat(null);
|
|
640
585
|
setInput("");
|
|
641
586
|
setNewThreadInput("");
|
|
642
|
-
|
|
587
|
+
clearFiles();
|
|
643
588
|
});
|
|
644
589
|
|
|
645
590
|
const handleMessageEdit = useEvent((index: number, newValue: string) => {
|
|
@@ -670,7 +615,7 @@ const ChatPanelBody = () => {
|
|
|
670
615
|
files: fileParts,
|
|
671
616
|
});
|
|
672
617
|
setInput("");
|
|
673
|
-
|
|
618
|
+
clearFiles();
|
|
674
619
|
},
|
|
675
620
|
);
|
|
676
621
|
|
|
@@ -703,7 +648,7 @@ const ChatPanelBody = () => {
|
|
|
703
648
|
isLoading={isLoading}
|
|
704
649
|
onStop={stop}
|
|
705
650
|
fileInputRef={fileInputRef}
|
|
706
|
-
onAddFiles={
|
|
651
|
+
onAddFiles={addFiles}
|
|
707
652
|
onClose={handleOnCloseThread}
|
|
708
653
|
/>
|
|
709
654
|
) : (
|
|
@@ -716,7 +661,7 @@ const ChatPanelBody = () => {
|
|
|
716
661
|
onStop={stop}
|
|
717
662
|
onClose={() => newMessageInputRef.current?.editor?.blur()}
|
|
718
663
|
fileInputRef={fileInputRef}
|
|
719
|
-
onAddFiles={
|
|
664
|
+
onAddFiles={addFiles}
|
|
720
665
|
/>
|
|
721
666
|
);
|
|
722
667
|
|
|
@@ -777,7 +722,7 @@ const ChatPanelBody = () => {
|
|
|
777
722
|
|
|
778
723
|
{error && (
|
|
779
724
|
<div className="flex items-center justify-center space-x-2 mb-4">
|
|
780
|
-
<ErrorBanner error={error} />
|
|
725
|
+
<ErrorBanner error={error || new Error("Unknown error")} />
|
|
781
726
|
<Button variant="outline" size="sm" onClick={handleReload}>
|
|
782
727
|
Retry
|
|
783
728
|
</Button>
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import type { components } from "@marimo-team/marimo-api";
|
|
4
4
|
import type { FileUIPart, ToolUIPart, UIMessage } from "ai";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import useEvent from "react-use-event-hook";
|
|
7
|
+
import type { ProviderId } from "@/core/ai/ids/ids";
|
|
5
8
|
import type { ToolNotebookContext } from "@/core/ai/tools/base";
|
|
6
9
|
import { FRONTEND_TOOL_REGISTRY } from "@/core/ai/tools/registry";
|
|
7
10
|
import type {
|
|
@@ -11,6 +14,17 @@ import type {
|
|
|
11
14
|
import { blobToString } from "@/utils/fileToBase64";
|
|
12
15
|
import { Logger } from "@/utils/Logger";
|
|
13
16
|
import { getAICompletionBodyWithAttachments } from "../editor/ai/completion-utils";
|
|
17
|
+
import { toast } from "../ui/use-toast";
|
|
18
|
+
|
|
19
|
+
// We need to modify the backend to support attachments for other providers
|
|
20
|
+
// And other types
|
|
21
|
+
export const PROVIDERS_THAT_SUPPORT_ATTACHMENTS = new Set<ProviderId>([
|
|
22
|
+
"openai",
|
|
23
|
+
"google",
|
|
24
|
+
"anthropic",
|
|
25
|
+
]);
|
|
26
|
+
export const SUPPORTED_ATTACHMENT_TYPES = ["image/*", "text/*"];
|
|
27
|
+
const MAX_ATTACHMENT_SIZE = 1024 * 1024 * 50; // 50MB
|
|
14
28
|
|
|
15
29
|
export function generateChatTitle(message: string): string {
|
|
16
30
|
return message.length > 50 ? `${message.slice(0, 50)}...` : message;
|
|
@@ -198,3 +212,31 @@ export function hasPendingToolCalls(messages: UIMessage[]): boolean {
|
|
|
198
212
|
// Only auto-send if we have completed tool calls and there is no reply yet
|
|
199
213
|
return allToolCallsCompleted && !hasTextContent;
|
|
200
214
|
}
|
|
215
|
+
|
|
216
|
+
export function useFileState() {
|
|
217
|
+
const [files, setFiles] = useState<File[]>([]);
|
|
218
|
+
|
|
219
|
+
const addFiles = useEvent((newFiles: File[]) => {
|
|
220
|
+
if (newFiles.length === 0) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const totalSize = newFiles.reduce((size, file) => size + file.size, 0);
|
|
225
|
+
if (totalSize > MAX_ATTACHMENT_SIZE) {
|
|
226
|
+
toast({
|
|
227
|
+
title: "File size exceeded",
|
|
228
|
+
description: "Attachments must be under 50 MB",
|
|
229
|
+
variant: "danger",
|
|
230
|
+
});
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
setFiles((prev) => [...prev, ...newFiles]);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const clearFiles = () => setFiles([]);
|
|
238
|
+
const removeFile = (fileToRemove: File) =>
|
|
239
|
+
setFiles((prev) => prev.filter((f) => f !== fileToRemove));
|
|
240
|
+
|
|
241
|
+
return { files, addFiles, clearFiles, removeFile };
|
|
242
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type {
|
|
3
|
+
ColumnDef,
|
|
4
|
+
PaginationState,
|
|
5
|
+
RowSelectionState,
|
|
6
|
+
SortingState,
|
|
7
|
+
} from "@tanstack/react-table";
|
|
8
|
+
import { render, screen, within } from "@testing-library/react";
|
|
4
9
|
import { describe, expect, it, vi } from "vitest";
|
|
5
10
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
6
11
|
import { DataTable } from "../data-table";
|
|
@@ -95,4 +100,91 @@ describe("DataTable", () => {
|
|
|
95
100
|
expect(rows[1]).toHaveAttribute("title", "Michael Scott");
|
|
96
101
|
expect(rows[2]).toHaveAttribute("title", "Jim Halpert");
|
|
97
102
|
});
|
|
103
|
+
|
|
104
|
+
it("should display updated data after rerender with manual sorting and pagination", () => {
|
|
105
|
+
// Simulates the bug from issue #8023:
|
|
106
|
+
// When a user sorts a table, rows that moved from page 2 to page 1
|
|
107
|
+
// don't visually refresh after the underlying data is updated.
|
|
108
|
+
|
|
109
|
+
interface RowData {
|
|
110
|
+
id: number;
|
|
111
|
+
status: string;
|
|
112
|
+
value: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Initial data: 4 rows, page_size=3
|
|
116
|
+
const initialData: RowData[] = [
|
|
117
|
+
{ id: 4, status: "pending", value: 40 },
|
|
118
|
+
{ id: 3, status: "pending", value: 30 },
|
|
119
|
+
{ id: 2, status: "pending", value: 20 },
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
const columns: ColumnDef<RowData>[] = [
|
|
123
|
+
{ id: "id", accessorFn: (row) => row.id, header: "id" },
|
|
124
|
+
{ id: "status", accessorFn: (row) => row.status, header: "status" },
|
|
125
|
+
{ id: "value", accessorFn: (row) => row.value, header: "value" },
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
// Simulate sorted state (value descending) - manual sorting means
|
|
129
|
+
// data comes pre-sorted from backend
|
|
130
|
+
const sorting: SortingState = [{ id: "value", desc: true }];
|
|
131
|
+
const setSorting = vi.fn();
|
|
132
|
+
|
|
133
|
+
const paginationState: PaginationState = { pageIndex: 0, pageSize: 3 };
|
|
134
|
+
const setPaginationState = vi.fn();
|
|
135
|
+
|
|
136
|
+
const commonProps = {
|
|
137
|
+
columns,
|
|
138
|
+
selection: null as "single" | "multi" | null,
|
|
139
|
+
totalRows: 4,
|
|
140
|
+
totalColumns: 3,
|
|
141
|
+
pagination: true,
|
|
142
|
+
manualPagination: true,
|
|
143
|
+
paginationState,
|
|
144
|
+
setPaginationState,
|
|
145
|
+
manualSorting: true,
|
|
146
|
+
sorting,
|
|
147
|
+
setSorting,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const { rerender } = render(
|
|
151
|
+
<TooltipProvider>
|
|
152
|
+
<DataTable {...commonProps} data={initialData} />
|
|
153
|
+
</TooltipProvider>,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Verify initial data is displayed - look for "pending" in cells
|
|
157
|
+
const rows = screen.getAllByRole("row");
|
|
158
|
+
// Row 0 is header, rows 1-3 are data rows
|
|
159
|
+
expect(rows).toHaveLength(4); // 1 header + 3 data rows
|
|
160
|
+
// All rows should show "pending"
|
|
161
|
+
expect(within(rows[1]).getByText("pending")).toBeTruthy();
|
|
162
|
+
expect(within(rows[2]).getByText("pending")).toBeTruthy();
|
|
163
|
+
expect(within(rows[3]).getByText("pending")).toBeTruthy();
|
|
164
|
+
|
|
165
|
+
// Now simulate data update: row with id=4 is now "approved"
|
|
166
|
+
// Backend returns sorted data with the update applied
|
|
167
|
+
const updatedData: RowData[] = [
|
|
168
|
+
{ id: 4, status: "approved", value: 40 },
|
|
169
|
+
{ id: 3, status: "pending", value: 30 },
|
|
170
|
+
{ id: 2, status: "pending", value: 20 },
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
// Rerender with updated data (same sorting, same pagination)
|
|
174
|
+
rerender(
|
|
175
|
+
<TooltipProvider>
|
|
176
|
+
<DataTable {...commonProps} data={updatedData} />
|
|
177
|
+
</TooltipProvider>,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// BUG: The row should show "approved" but might show stale "pending"
|
|
181
|
+
const updatedRows = screen.getAllByRole("row");
|
|
182
|
+
expect(updatedRows).toHaveLength(4);
|
|
183
|
+
|
|
184
|
+
// The first data row (id=4) should now show "approved"
|
|
185
|
+
expect(within(updatedRows[1]).getByText("approved")).toBeTruthy();
|
|
186
|
+
// Other rows should still show "pending"
|
|
187
|
+
expect(within(updatedRows[2]).getByText("pending")).toBeTruthy();
|
|
188
|
+
expect(within(updatedRows[3]).getByText("pending")).toBeTruthy();
|
|
189
|
+
});
|
|
98
190
|
});
|
|
@@ -43,6 +43,7 @@ import type { CellData } from "@/core/cells/types";
|
|
|
43
43
|
import { formatEditorViews } from "@/core/codemirror/format";
|
|
44
44
|
import { toggleToLanguage } from "@/core/codemirror/language/commands";
|
|
45
45
|
import { switchLanguage } from "@/core/codemirror/language/extension";
|
|
46
|
+
import { MARKDOWN_INITIAL_HIDE_CODE } from "@/core/codemirror/language/languages/markdown";
|
|
46
47
|
import {
|
|
47
48
|
aiEnabledAtom,
|
|
48
49
|
appWidthAtom,
|
|
@@ -85,6 +86,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
|
|
|
85
86
|
sendToBottom,
|
|
86
87
|
addColumnBreakpoint,
|
|
87
88
|
clearCellOutput,
|
|
89
|
+
markUntouched,
|
|
88
90
|
} = useCellActions();
|
|
89
91
|
const splitCell = useSplitCellCallback();
|
|
90
92
|
const runCell = useRunCell(cell?.cellId);
|
|
@@ -209,7 +211,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
|
|
|
209
211
|
icon: <MarkdownIcon />,
|
|
210
212
|
label: "Convert to Markdown",
|
|
211
213
|
hotkey: "cell.viewAsMarkdown",
|
|
212
|
-
handle: () => {
|
|
214
|
+
handle: async () => {
|
|
213
215
|
const editorView = getEditorView();
|
|
214
216
|
if (!editorView) {
|
|
215
217
|
return;
|
|
@@ -219,6 +221,17 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
|
|
|
219
221
|
language: "markdown",
|
|
220
222
|
keepCodeAsIs: false,
|
|
221
223
|
});
|
|
224
|
+
// Code stays visible until the user blurs the cell
|
|
225
|
+
if (!config.hide_code && MARKDOWN_INITIAL_HIDE_CODE) {
|
|
226
|
+
await saveCellConfig({
|
|
227
|
+
configs: { [cellId]: { hide_code: MARKDOWN_INITIAL_HIDE_CODE } },
|
|
228
|
+
});
|
|
229
|
+
updateCellConfig({
|
|
230
|
+
cellId,
|
|
231
|
+
config: { hide_code: MARKDOWN_INITIAL_HIDE_CODE },
|
|
232
|
+
});
|
|
233
|
+
markUntouched({ cellId });
|
|
234
|
+
}
|
|
222
235
|
},
|
|
223
236
|
hidden: isSetupCell,
|
|
224
237
|
},
|