@marimo-team/islands 0.23.11-dev3 → 0.23.11-dev30
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-Z3RP7Vmm.js → ConnectedDataExplorerComponent-WqG-xX4l.js} +13 -13
- package/dist/{ErrorBoundary-DpbaKVv7.js → ErrorBoundary-BNx_OSVo.js} +2 -2
- package/dist/{any-language-editor-CAgFD4Kd.js → any-language-editor-rPSlOll9.js} +5 -5
- package/dist/assets/__vite-browser-external-DAm_YW43.js +1 -0
- package/dist/assets/{worker-CC0Oul9k.js → worker-DEDLIQQV.js} +2 -2
- package/dist/{button-C5K9fIPF.js → button-vQhauTmO.js} +20 -0
- package/dist/{capabilities-BceAxrAW.js → capabilities-BEHzIS99.js} +1 -1
- package/dist/{chat-ui-c1FdlK-C.js → chat-ui-k2kqhCv5.js} +135 -106
- package/dist/{check-BCaJeT-J.js → check-nrzHDi45.js} +1 -1
- package/dist/{code-visibility-Csek3T1q.js → code-visibility-CnZ5SJV5.js} +81 -68
- package/dist/{copy-UqRYxiOg.js → copy-UhDed7D4.js} +2 -2
- package/dist/{dist-Dk6PV_d3.js → dist-DYGLrbYQ.js} +2 -2
- package/dist/{error-banner-CJXYJ6Sb.js → error-banner-BHAkVFc2.js} +2 -2
- package/dist/{esm-BaH2eg5-.js → esm-Bqu9AE2K.js} +1 -1
- package/dist/{extends-D_hDsj6R.js → extends-9Yl5BEcg.js} +4 -4
- package/dist/{formats-C4wO47tk.js → formats-BV4bOfMI.js} +3 -3
- package/dist/{glide-data-editor-BPkCPs7L.js → glide-data-editor-BDTq6YUb.js} +9 -9
- package/dist/{html-to-image-D5-EpALB.js → html-to-image-C86pQALH.js} +16 -16
- package/dist/{input-OdWHkobi.js → input-AKkGXdyV.js} +6 -6
- package/dist/{label-CC4ytI1X.js → label-E3ZJXHu8.js} +2 -2
- package/dist/{loader-BWLPpjKK.js → loader-YPuQvn1Y.js} +1 -1
- package/dist/main.js +981 -978
- package/dist/{mermaid-BotvIKg9.js → mermaid-QFAR9YgY.js} +5 -5
- package/dist/{process-output-WDZE0cyS.js → process-output-nNw4OpSj.js} +3 -3
- package/dist/{reveal-component-D7GJ7KT7.js → reveal-component-O3jTcQna.js} +10 -10
- package/dist/{spec-X7FwLJni.js → spec-B45_YCNI.js} +4 -4
- package/dist/{strings-J57tzLr3.js → strings-Cq2s9_EQ.js} +4 -4
- package/dist/style.css +1 -1
- package/dist/{swiper-component-B2t5sN1q.js → swiper-component-BNa_4kh2.js} +2 -2
- package/dist/{toDate-d8RCRrRd.js → toDate-Do1xRzAo.js} +3 -3
- package/dist/{tooltip-DpcyNkQ2.js → tooltip-Bz3OAwrU.js} +3 -3
- package/dist/{types-ChtMFmZ2.js → types-D8gEGs4R.js} +1 -1
- package/dist/{useAsyncData-PonK__yh.js → useAsyncData-CL3o2p4i.js} +1 -1
- package/dist/{useDateFormatter-QB-3MpYr.js → useDateFormatter-BC6iSz9g.js} +2 -2
- package/dist/{useDeepCompareMemoize-D3NGWke6.js → useDeepCompareMemoize-BPx2MuOK.js} +1 -1
- package/dist/{useIframeCapabilities-C4JTXTIh.js → useIframeCapabilities-C6Ta3EyP.js} +1 -1
- package/dist/{useLifecycle-00mO3OSS.js → useLifecycle-C3Ec71q0.js} +3 -3
- package/dist/{useTheme-DEhDzATN.js → useTheme-ZhT6uIu3.js} +4 -3
- package/dist/{vega-component-DGPUhbDs.js → vega-component-C3AWYGAL.js} +10 -10
- package/dist/{zod-aLSua2NL.js → zod-DXqkaI_w.js} +1 -1
- package/package.json +1 -1
- package/src/components/ai/ai-model-dropdown.tsx +6 -0
- package/src/components/app-config/ai-config.tsx +73 -55
- package/src/components/app-config/data-form.tsx +33 -43
- package/src/components/app-config/is-overridden.tsx +125 -58
- package/src/components/app-config/user-config-form.tsx +178 -227
- package/src/components/chat/chat-display.tsx +5 -0
- package/src/components/chat/chat-panel.tsx +21 -8
- package/src/components/chat/tool-call/tool-call-view.tsx +4 -0
- package/src/components/chat/tool-call/tool-history-row.tsx +26 -5
- package/src/components/data-table/TableTopBar.tsx +4 -4
- package/src/components/data-table/__tests__/columns.test.tsx +20 -0
- package/src/components/data-table/__tests__/data-table.test.tsx +28 -0
- package/src/components/data-table/columns.tsx +16 -1
- package/src/components/data-table/data-table.tsx +3 -3
- package/src/components/data-table/filters/types.ts +1 -0
- package/src/components/data-table/renderers.tsx +5 -1
- package/src/components/datasources/__tests__/column-preview.test.tsx +97 -0
- package/src/components/datasources/__tests__/install-package-button.test.tsx +11 -0
- package/src/components/datasources/__tests__/utils.test.ts +127 -2
- package/src/components/datasources/column-preview.tsx +2 -4
- package/src/components/datasources/datasources.tsx +44 -17
- package/src/components/datasources/install-package-button.tsx +3 -1
- package/src/components/datasources/utils.ts +45 -0
- package/src/components/editor/actions/useNotebookActions.tsx +62 -21
- package/src/components/editor/actions/useSetCodeVisibility.ts +57 -0
- package/src/components/editor/connections/components.tsx +13 -0
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +4 -4
- package/src/components/editor/connections/storage/as-code.ts +11 -4
- package/src/components/editor/header/__tests__/status.test.tsx +0 -4
- package/src/components/editor/header/status.tsx +2 -3
- package/src/core/cells/__tests__/cells.test.ts +33 -0
- package/src/core/cells/cells.ts +1 -1
- package/src/core/config/config-schema.ts +6 -1
- package/src/core/edit-app.tsx +14 -0
- package/src/core/hotkeys/hotkeys.ts +20 -0
- package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +0 -13
- package/src/core/websocket/types.ts +0 -1
- package/src/core/websocket/useMarimoKernelConnection.tsx +0 -14
- package/src/css/md.css +12 -0
- package/src/plugins/impl/DataTablePlugin.tsx +12 -5
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +1 -1
- package/src/plugins/impl/chat/chat-ui.tsx +1 -0
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -1
- package/dist/assets/__vite-browser-external-eshhtsgZ.js +0 -1
- package/src/components/editor/actions/useHideAllMarkdownCode.ts +0 -53
|
@@ -29,7 +29,7 @@ import { Button } from "../ui/button";
|
|
|
29
29
|
import { Tooltip } from "../ui/tooltip";
|
|
30
30
|
import { ColumnPreviewContainer } from "./components";
|
|
31
31
|
import { InstallPackageButton } from "./install-package-button";
|
|
32
|
-
import { convertStatsName, sqlCode } from "./utils";
|
|
32
|
+
import { convertStatsName, sqlCode, tableUniqueId } from "./utils";
|
|
33
33
|
|
|
34
34
|
export const DatasetColumnPreview: React.FC<{
|
|
35
35
|
table: DataTable;
|
|
@@ -48,9 +48,7 @@ export const DatasetColumnPreview: React.FC<{
|
|
|
48
48
|
tableName: table.name,
|
|
49
49
|
columnName: column.name,
|
|
50
50
|
sourceType: table.source_type,
|
|
51
|
-
fullyQualifiedTableName: sqlTableContext
|
|
52
|
-
? `${sqlTableContext.database}.${sqlTableContext.schema}.${table.name}`
|
|
53
|
-
: table.name,
|
|
51
|
+
fullyQualifiedTableName: tableUniqueId(sqlTableContext, table.name),
|
|
54
52
|
});
|
|
55
53
|
};
|
|
56
54
|
|
|
@@ -83,6 +83,8 @@ import {
|
|
|
83
83
|
areSchemasResolved,
|
|
84
84
|
areTablesResolved,
|
|
85
85
|
isSchemaless,
|
|
86
|
+
schemaSubtreeMatchesSearch,
|
|
87
|
+
shouldExpandDatabaseForSearch,
|
|
86
88
|
sqlCode,
|
|
87
89
|
tableUniqueId,
|
|
88
90
|
} from "./utils";
|
|
@@ -360,7 +362,6 @@ export const DataSources: React.FC = () => {
|
|
|
360
362
|
key={database.name}
|
|
361
363
|
connection={connection}
|
|
362
364
|
database={database}
|
|
363
|
-
hasSearch={hasSearch}
|
|
364
365
|
searchValue={searchValue}
|
|
365
366
|
/>
|
|
366
367
|
))}
|
|
@@ -435,7 +436,6 @@ interface DataSourceTree {
|
|
|
435
436
|
dialect: string;
|
|
436
437
|
engineName: string;
|
|
437
438
|
databaseName: string;
|
|
438
|
-
hasSearch: boolean;
|
|
439
439
|
searchValue?: string;
|
|
440
440
|
}
|
|
441
441
|
|
|
@@ -470,9 +470,8 @@ function buildSqlTableContext(
|
|
|
470
470
|
const DatabaseTree: React.FC<{
|
|
471
471
|
connection: DataSourceConnection;
|
|
472
472
|
database: Database;
|
|
473
|
-
hasSearch: boolean;
|
|
474
473
|
searchValue?: string;
|
|
475
|
-
}> = ({ connection, database,
|
|
474
|
+
}> = ({ connection, database, searchValue }) => {
|
|
476
475
|
const tree = React.useMemo<DataSourceTree>(
|
|
477
476
|
() => ({
|
|
478
477
|
engineName: connection.name,
|
|
@@ -480,7 +479,6 @@ const DatabaseTree: React.FC<{
|
|
|
480
479
|
dialect: connection.dialect,
|
|
481
480
|
defaultSchema: connection.default_schema,
|
|
482
481
|
defaultDatabase: connection.default_database,
|
|
483
|
-
hasSearch,
|
|
484
482
|
searchValue,
|
|
485
483
|
}),
|
|
486
484
|
[
|
|
@@ -489,7 +487,6 @@ const DatabaseTree: React.FC<{
|
|
|
489
487
|
connection.default_schema,
|
|
490
488
|
connection.default_database,
|
|
491
489
|
database.name,
|
|
492
|
-
hasSearch,
|
|
493
490
|
searchValue,
|
|
494
491
|
],
|
|
495
492
|
);
|
|
@@ -498,7 +495,7 @@ const DatabaseTree: React.FC<{
|
|
|
498
495
|
<DatabaseItem
|
|
499
496
|
engineName={connection.name}
|
|
500
497
|
database={database}
|
|
501
|
-
|
|
498
|
+
searchValue={searchValue}
|
|
502
499
|
>
|
|
503
500
|
<DataSourceTreeContext.Provider value={tree}>
|
|
504
501
|
<SchemaList
|
|
@@ -513,18 +510,30 @@ const DatabaseTree: React.FC<{
|
|
|
513
510
|
};
|
|
514
511
|
|
|
515
512
|
const DatabaseItem: React.FC<{
|
|
516
|
-
|
|
513
|
+
searchValue?: string;
|
|
517
514
|
engineName: string;
|
|
518
515
|
database: Database;
|
|
519
516
|
children: React.ReactNode;
|
|
520
|
-
}> = ({
|
|
521
|
-
|
|
517
|
+
}> = ({ searchValue, engineName, database, children }) => {
|
|
518
|
+
// Auto-expand during search only when a loaded schema under this database
|
|
519
|
+
// contains a matching table.
|
|
520
|
+
const expandForSearch = shouldExpandDatabaseForSearch(database, searchValue);
|
|
521
|
+
const [isExpanded, setIsExpanded] = React.useState(expandForSearch);
|
|
522
522
|
const [isSelected, setIsSelected] = React.useState(false);
|
|
523
|
-
const [
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
523
|
+
const [prevSearchValue, setPrevSearchValue] = React.useState(searchValue);
|
|
524
|
+
const [prevExpandForSearch, setPrevExpandForSearch] =
|
|
525
|
+
React.useState(expandForSearch);
|
|
526
|
+
|
|
527
|
+
// Re-sync when the query changes or when newly resolved data turns this
|
|
528
|
+
// branch into a match under the same query (deferred subtrees resolve
|
|
529
|
+
// asynchronously, so the decision can flip without the query changing).
|
|
530
|
+
if (
|
|
531
|
+
prevSearchValue !== searchValue ||
|
|
532
|
+
prevExpandForSearch !== expandForSearch
|
|
533
|
+
) {
|
|
534
|
+
setPrevSearchValue(searchValue);
|
|
535
|
+
setPrevExpandForSearch(expandForSearch);
|
|
536
|
+
setIsExpanded(expandForSearch);
|
|
528
537
|
}
|
|
529
538
|
|
|
530
539
|
return (
|
|
@@ -657,12 +666,30 @@ interface SchemaNodeProps {
|
|
|
657
666
|
const SchemaNode: React.FC<SchemaNodeProps> = (props) => {
|
|
658
667
|
const { schema, schemaPath, depth } = props;
|
|
659
668
|
const tree = useDataSourceTree();
|
|
660
|
-
const { databaseName,
|
|
661
|
-
|
|
669
|
+
const { databaseName, searchValue } = tree;
|
|
670
|
+
// Auto-expand during search only when this schema's loaded subtree contains a
|
|
671
|
+
// matching table.
|
|
672
|
+
const expandForSearch = schemaSubtreeMatchesSearch(schema, searchValue);
|
|
673
|
+
const [isExpanded, setIsExpanded] = React.useState(expandForSearch);
|
|
662
674
|
const [isSelected, setIsSelected] = React.useState(false);
|
|
675
|
+
const [prevSearchValue, setPrevSearchValue] = React.useState(searchValue);
|
|
676
|
+
const [prevExpandForSearch, setPrevExpandForSearch] =
|
|
677
|
+
React.useState(expandForSearch);
|
|
663
678
|
const uniqueValue = `${databaseName}:${schemaPath.join(".")}`;
|
|
664
679
|
const childSchemas = schema.child_schemas ?? [];
|
|
665
680
|
|
|
681
|
+
// Re-sync when the query changes or when newly resolved data turns this
|
|
682
|
+
// subtree into a match under the same query (deferred tables/child schemas
|
|
683
|
+
// resolve asynchronously, so the decision can flip without the query changing).
|
|
684
|
+
if (
|
|
685
|
+
prevSearchValue !== searchValue ||
|
|
686
|
+
prevExpandForSearch !== expandForSearch
|
|
687
|
+
) {
|
|
688
|
+
setPrevSearchValue(searchValue);
|
|
689
|
+
setPrevExpandForSearch(expandForSearch);
|
|
690
|
+
setIsExpanded(expandForSearch);
|
|
691
|
+
}
|
|
692
|
+
|
|
666
693
|
return (
|
|
667
694
|
<>
|
|
668
695
|
<CommandItem
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { useInstallAllowed } from "@/core/mode";
|
|
5
6
|
import { useInstallPackages } from "@/core/packages/useInstallPackage";
|
|
6
7
|
import { cn } from "@/utils/cn";
|
|
7
8
|
|
|
@@ -23,8 +24,9 @@ export const InstallPackageButton: React.FC<InstallPackageButtonProps> = ({
|
|
|
23
24
|
onInstall,
|
|
24
25
|
}) => {
|
|
25
26
|
const { handleInstallPackages } = useInstallPackages();
|
|
27
|
+
const installAllowed = useInstallAllowed();
|
|
26
28
|
|
|
27
|
-
if (!packages || packages.length === 0) {
|
|
29
|
+
if (!packages || packages.length === 0 || !installAllowed) {
|
|
28
30
|
return null;
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -52,6 +52,51 @@ export function areChildSchemasResolved(schema: DatabaseSchema): boolean {
|
|
|
52
52
|
return schema.child_schemas_resolved !== false;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Whether a loaded schema subtree contains a table whose name matches
|
|
57
|
+
* `searchValue`. Deferred (not-yet-fetched) tables and child schemas are
|
|
58
|
+
* treated as non-matching, so searching never triggers a lazy catalog fetch on
|
|
59
|
+
* large/remote connections.
|
|
60
|
+
*/
|
|
61
|
+
export function schemaSubtreeMatchesSearch(
|
|
62
|
+
schema: DatabaseSchema,
|
|
63
|
+
searchValue: string | undefined,
|
|
64
|
+
): boolean {
|
|
65
|
+
const query = searchValue?.trim().toLowerCase();
|
|
66
|
+
if (!query) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
areTablesResolved(schema) &&
|
|
71
|
+
schema.tables.some((table) => table.name.toLowerCase().includes(query))
|
|
72
|
+
) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (areChildSchemasResolved(schema)) {
|
|
76
|
+
return (schema.child_schemas ?? []).some((child) =>
|
|
77
|
+
schemaSubtreeMatchesSearch(child, query),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Auto-expand a database row during search only when one of its loaded schemas
|
|
85
|
+
* contains a matching table. Returns false when the schema list itself is
|
|
86
|
+
* deferred.
|
|
87
|
+
*/
|
|
88
|
+
export function shouldExpandDatabaseForSearch(
|
|
89
|
+
database: Database,
|
|
90
|
+
searchValue: string | undefined,
|
|
91
|
+
): boolean {
|
|
92
|
+
if (!searchValue?.trim() || !areSchemasResolved(database)) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return database.schemas.some((schema) =>
|
|
96
|
+
schemaSubtreeMatchesSearch(schema, searchValue),
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
55
100
|
interface SqlCodeFormatter {
|
|
56
101
|
/**
|
|
57
102
|
* Format the table path based on dialect-specific rules
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
DownloadIcon,
|
|
15
15
|
EditIcon,
|
|
16
16
|
ExternalLinkIcon,
|
|
17
|
+
EyeIcon,
|
|
17
18
|
EyeOffIcon,
|
|
18
19
|
FastForwardIcon,
|
|
19
20
|
FileIcon,
|
|
@@ -41,7 +42,10 @@ import {
|
|
|
41
42
|
YoutubeIcon,
|
|
42
43
|
ZapIcon,
|
|
43
44
|
} from "lucide-react";
|
|
44
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
settingDialogAtom,
|
|
47
|
+
useOpenSettingsToTab,
|
|
48
|
+
} from "@/components/app-config/state";
|
|
45
49
|
import { MarkdownIcon } from "@/components/editor/cell/code/icons";
|
|
46
50
|
import { MarimoPlusIcon } from "@/components/icons/marimo-icons";
|
|
47
51
|
import { useImperativeModal } from "@/components/modal/ImperativeModal";
|
|
@@ -57,7 +61,8 @@ import {
|
|
|
57
61
|
useCellActions,
|
|
58
62
|
} from "@/core/cells/cells";
|
|
59
63
|
import { disabledCellIds } from "@/core/cells/utils";
|
|
60
|
-
import {
|
|
64
|
+
import { capabilitiesAtom } from "@/core/config/capabilities";
|
|
65
|
+
import { aiEnabledAtom, useResolvedMarimoConfig } from "@/core/config/config";
|
|
61
66
|
import { Constants } from "@/core/constants";
|
|
62
67
|
import {
|
|
63
68
|
updateCellOutputsWithScreenshots,
|
|
@@ -86,7 +91,7 @@ import { Strings } from "@/utils/strings";
|
|
|
86
91
|
import { newNotebookURL } from "@/utils/urls";
|
|
87
92
|
import { useRunAllCells } from "../cell/useRunCells";
|
|
88
93
|
import { useChromeActions, useChromeState } from "../chrome/state";
|
|
89
|
-
import { PANELS } from "../chrome/types";
|
|
94
|
+
import { isPanelHidden, PANELS } from "../chrome/types";
|
|
90
95
|
import { AddConnectionDialogContent } from "../connections/add-connection-dialog";
|
|
91
96
|
import { keyboardShortcutsAtom } from "../controls/keyboard-shortcuts";
|
|
92
97
|
import { commandPaletteAtom } from "../controls/state";
|
|
@@ -95,8 +100,8 @@ import { LAYOUT_TYPES } from "../renderers/types";
|
|
|
95
100
|
import { runServerSidePDFDownload } from "./pdf-export";
|
|
96
101
|
import type { ActionButton } from "./types";
|
|
97
102
|
import { useCopyNotebook } from "./useCopyNotebook";
|
|
98
|
-
import { useHideAllMarkdownCode } from "./useHideAllMarkdownCode";
|
|
99
103
|
import { useRestartKernel } from "./useRestartKernel";
|
|
104
|
+
import { useSetCodeVisibility } from "./useSetCodeVisibility";
|
|
100
105
|
|
|
101
106
|
const NOOP_HANDLER = (event?: Event) => {
|
|
102
107
|
event?.preventDefault();
|
|
@@ -110,8 +115,10 @@ export function useNotebookActions() {
|
|
|
110
115
|
const { selectedPanel } = useChromeState();
|
|
111
116
|
const [viewState] = useAtom(viewStateAtom);
|
|
112
117
|
const kioskMode = useAtomValue(kioskModeAtom);
|
|
113
|
-
const
|
|
118
|
+
const setCodeVisibility = useSetCodeVisibility();
|
|
114
119
|
const [resolvedConfig] = useResolvedMarimoConfig();
|
|
120
|
+
const capabilities = useAtomValue(capabilitiesAtom);
|
|
121
|
+
const aiEnabled = useAtomValue(aiEnabledAtom);
|
|
115
122
|
|
|
116
123
|
const {
|
|
117
124
|
updateCellConfig,
|
|
@@ -126,6 +133,7 @@ export function useNotebookActions() {
|
|
|
126
133
|
const copyNotebook = useCopyNotebook(filename);
|
|
127
134
|
const setCommandPaletteOpen = useSetAtom(commandPaletteAtom);
|
|
128
135
|
const setSettingsDialogOpen = useSetAtom(settingDialogAtom);
|
|
136
|
+
const { handleClick: openSettings } = useOpenSettingsToTab();
|
|
129
137
|
const setKeyboardShortcutsOpen = useSetAtom(keyboardShortcutsAtom);
|
|
130
138
|
const {
|
|
131
139
|
exportAsIPYNB,
|
|
@@ -407,20 +415,31 @@ export function useNotebookActions() {
|
|
|
407
415
|
label: "Helper panel",
|
|
408
416
|
redundant: true,
|
|
409
417
|
handle: NOOP_HANDLER,
|
|
410
|
-
dropdown: PANELS.flatMap(
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
418
|
+
dropdown: PANELS.flatMap((panel) => {
|
|
419
|
+
// Still show the AI panel in the command palette so users can try AI
|
|
420
|
+
// features. When AI is disabled, open settings instead of the panel.
|
|
421
|
+
const openAiSettingsWhenDisabled = panel.type === "ai" && !aiEnabled;
|
|
422
|
+
if (
|
|
423
|
+
isPanelHidden({ panel, capabilities, aiEnabled }) &&
|
|
424
|
+
!openAiSettingsWhenDisabled
|
|
425
|
+
) {
|
|
426
|
+
return [];
|
|
427
|
+
}
|
|
428
|
+
const { type: id, Icon, additionalKeywords } = panel;
|
|
429
|
+
return {
|
|
430
|
+
label: Strings.startCase(id),
|
|
431
|
+
rightElement: renderCheckboxElement(selectedPanel === id),
|
|
432
|
+
icon: <Icon size={14} strokeWidth={1.5} />,
|
|
433
|
+
handle: () => {
|
|
434
|
+
if (openAiSettingsWhenDisabled) {
|
|
435
|
+
openSettings("ai", "ai-features");
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
toggleApplication(id);
|
|
439
|
+
},
|
|
440
|
+
additionalKeywords,
|
|
441
|
+
};
|
|
442
|
+
}),
|
|
424
443
|
},
|
|
425
444
|
|
|
426
445
|
{
|
|
@@ -560,11 +579,33 @@ export function useNotebookActions() {
|
|
|
560
579
|
clearAllCellOutputs();
|
|
561
580
|
},
|
|
562
581
|
},
|
|
582
|
+
{
|
|
583
|
+
icon: <EyeIcon size={14} strokeWidth={1.5} />,
|
|
584
|
+
label: "Show all code",
|
|
585
|
+
hotkey: "global.showAllCode",
|
|
586
|
+
handle: () => setCodeVisibility(false, "code"),
|
|
587
|
+
redundant: true,
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
icon: <EyeOffIcon size={14} strokeWidth={1.5} />,
|
|
591
|
+
label: "Hide all code",
|
|
592
|
+
hotkey: "global.hideAllCode",
|
|
593
|
+
handle: () => setCodeVisibility(true, "code"),
|
|
594
|
+
redundant: true,
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
icon: <EyeIcon size={14} strokeWidth={1.5} />,
|
|
598
|
+
label: "Show all markdown code",
|
|
599
|
+
hotkey: "global.showAllMarkdownCode",
|
|
600
|
+
handle: () => setCodeVisibility(false, "markdown"),
|
|
601
|
+
redundant: true,
|
|
602
|
+
},
|
|
563
603
|
{
|
|
564
604
|
icon: <EyeOffIcon size={14} strokeWidth={1.5} />,
|
|
565
605
|
label: "Hide all markdown code",
|
|
566
|
-
|
|
567
|
-
|
|
606
|
+
hotkey: "global.hideAllMarkdownCode",
|
|
607
|
+
handle: () => setCodeVisibility(true, "markdown"),
|
|
608
|
+
redundant: true,
|
|
568
609
|
},
|
|
569
610
|
{
|
|
570
611
|
icon: <ChevronRightCircleIcon size={14} strokeWidth={1.5} />,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
import { getNotebook, useCellActions } from "@/core/cells/cells";
|
|
5
|
+
import type { CellId } from "@/core/cells/ids";
|
|
6
|
+
import { MarkdownLanguageAdapter } from "@/core/codemirror/language/languages/markdown";
|
|
7
|
+
import { useRequestClient } from "@/core/network/requests";
|
|
8
|
+
import type { CellConfig } from "@/core/network/types";
|
|
9
|
+
import { Objects } from "@/utils/objects";
|
|
10
|
+
|
|
11
|
+
const markdownAdapter = new MarkdownLanguageAdapter();
|
|
12
|
+
|
|
13
|
+
/** The kind of cell a visibility change applies to. */
|
|
14
|
+
export type CellCodeKind = "code" | "markdown";
|
|
15
|
+
|
|
16
|
+
const cellCodeKind = (code: string): CellCodeKind =>
|
|
17
|
+
markdownAdapter.isSupported(code) ? "markdown" : "code";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Hook returning a callback that hides or shows the code editor for every
|
|
21
|
+
* cell of the given kind.
|
|
22
|
+
*/
|
|
23
|
+
export const useSetCodeVisibility = () => {
|
|
24
|
+
const { updateCellConfig } = useCellActions();
|
|
25
|
+
const { saveCellConfig } = useRequestClient();
|
|
26
|
+
|
|
27
|
+
return useCallback(
|
|
28
|
+
async (hidden: boolean, kind: CellCodeKind) => {
|
|
29
|
+
const notebook = getNotebook();
|
|
30
|
+
|
|
31
|
+
const newConfigs: Record<CellId, Partial<CellConfig>> = {};
|
|
32
|
+
|
|
33
|
+
for (const cellId of notebook.cellIds.inOrderIds) {
|
|
34
|
+
const cell = notebook.cellData[cellId];
|
|
35
|
+
if (cell === undefined || cell.config.hide_code === hidden) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (cellCodeKind(cell.code) !== kind) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
newConfigs[cellId] = { hide_code: hidden };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const entries = Objects.entries(newConfigs);
|
|
45
|
+
if (entries.length === 0) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await saveCellConfig({ configs: newConfigs });
|
|
50
|
+
|
|
51
|
+
for (const [cellId, config] of entries) {
|
|
52
|
+
updateCellConfig({ cellId, config });
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
[updateCellConfig, saveCellConfig],
|
|
56
|
+
);
|
|
57
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
|
+
import { useAtomValue } from "jotai";
|
|
4
5
|
import React from "react";
|
|
5
6
|
import { type DefaultValues, type FieldValues, useForm } from "react-hook-form";
|
|
6
7
|
import type { z } from "zod";
|
|
@@ -16,8 +17,10 @@ import {
|
|
|
16
17
|
SelectTrigger,
|
|
17
18
|
SelectValue,
|
|
18
19
|
} from "@/components/ui/select";
|
|
20
|
+
import { maybeAddMarimoImport } from "@/core/cells/add-missing-import";
|
|
19
21
|
import { useCellActions } from "@/core/cells/cells";
|
|
20
22
|
import { useLastFocusedCellId } from "@/core/cells/focus";
|
|
23
|
+
import { autoInstantiateAtom } from "@/core/config/config";
|
|
21
24
|
import { ENV_RENDERER, SecretsProvider } from "./form-renderers";
|
|
22
25
|
|
|
23
26
|
const RENDERERS: FormRenderer[] = [ENV_RENDERER];
|
|
@@ -106,8 +109,18 @@ export const ConnectionFormFooter = <L extends string>({
|
|
|
106
109
|
export function useInsertCode() {
|
|
107
110
|
const { createNewCell } = useCellActions();
|
|
108
111
|
const lastFocusedCellId = useLastFocusedCellId();
|
|
112
|
+
const autoInstantiate = useAtomValue(autoInstantiateAtom);
|
|
109
113
|
|
|
110
114
|
return (code: string) => {
|
|
115
|
+
// Ensure `mo` is importable when the generated code references it
|
|
116
|
+
if (/\bmo\./.test(code)) {
|
|
117
|
+
maybeAddMarimoImport({
|
|
118
|
+
autoInstantiate,
|
|
119
|
+
createNewCell,
|
|
120
|
+
fromCellId: lastFocusedCellId,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
111
124
|
createNewCell({
|
|
112
125
|
code,
|
|
113
126
|
before: false,
|
package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap
CHANGED
|
@@ -85,14 +85,14 @@ store = GCSStore("my-bucket")"
|
|
|
85
85
|
exports[`generateStorageCode > Google Drive > with default auth (no credentials) 1`] = `
|
|
86
86
|
"from gdrive_fsspec import GoogleDriveFileSystem
|
|
87
87
|
|
|
88
|
-
fs = GoogleDriveFileSystem(use_listings_cache=False)"
|
|
88
|
+
fs = GoogleDriveFileSystem(use_listings_cache=False, skip_instance_cache=True)"
|
|
89
89
|
`;
|
|
90
90
|
|
|
91
91
|
exports[`generateStorageCode > Google Drive > with embedded auth (no credentials) 1`] = `
|
|
92
92
|
"from gdrive_fsspec import GoogleDriveFileSystem
|
|
93
93
|
|
|
94
|
-
fs = GoogleDriveFileSystem(use_listings_cache=False, auth_kwargs={"use_local_webserver": False})
|
|
95
|
-
|
|
94
|
+
fs = GoogleDriveFileSystem(use_listings_cache=False, skip_instance_cache=True, auth_kwargs={"use_local_webserver": False})
|
|
95
|
+
mo.output.clear_console()"
|
|
96
96
|
`;
|
|
97
97
|
|
|
98
98
|
exports[`generateStorageCode > Google Drive > with service account credentials 1`] = `
|
|
@@ -100,7 +100,7 @@ exports[`generateStorageCode > Google Drive > with service account credentials 1
|
|
|
100
100
|
import json
|
|
101
101
|
|
|
102
102
|
_creds = json.loads("""{"type": "service_account", "client_email": "test@test.iam.gserviceaccount.com"}""")
|
|
103
|
-
fs = GoogleDriveFileSystem(creds=_creds, token="service_account", use_listings_cache=False)"
|
|
103
|
+
fs = GoogleDriveFileSystem(creds=_creds, token="service_account", use_listings_cache=False, skip_instance_cache=True)"
|
|
104
104
|
`;
|
|
105
105
|
|
|
106
106
|
exports[`generateStorageCode > S3 > basic connection with all fields 1`] = `
|
|
@@ -168,6 +168,10 @@ function generateGDriveCode(
|
|
|
168
168
|
connection: Extract<StorageConnection, { type: "gdrive" }>,
|
|
169
169
|
options: { secrets: SecretContainer; isEmbedded?: boolean },
|
|
170
170
|
): { imports: Set<string>; code: string } {
|
|
171
|
+
/**
|
|
172
|
+
* Skip instance cache True so you can create multiple connections which don't reference the same creds.
|
|
173
|
+
* Use listings cache False so we don't get stale reads.
|
|
174
|
+
*/
|
|
171
175
|
const { secrets, isEmbedded = false } = options;
|
|
172
176
|
const imports = new Set(["from gdrive_fsspec import GoogleDriveFileSystem"]);
|
|
173
177
|
|
|
@@ -179,18 +183,21 @@ function generateGDriveCode(
|
|
|
179
183
|
);
|
|
180
184
|
const code = dedent(`
|
|
181
185
|
_creds = json.loads("""${connection.credentials_json?.startsWith("ENV:") ? `{${creds}}` : connection.credentials_json}""")
|
|
182
|
-
fs = GoogleDriveFileSystem(creds=_creds, token="service_account", use_listings_cache=False)
|
|
186
|
+
fs = GoogleDriveFileSystem(creds=_creds, token="service_account", use_listings_cache=False, skip_instance_cache=True)
|
|
183
187
|
`);
|
|
184
188
|
return { imports, code };
|
|
185
189
|
}
|
|
186
190
|
|
|
191
|
+
// In the iframe (embedded) flow we authenticate via the console-based OOB
|
|
192
|
+
// flow, which prints an auth URL and reads the code from stdin. Clear the
|
|
193
|
+
// console afterwards so the (single-use) auth code doesn't linger.
|
|
187
194
|
const code = isEmbedded
|
|
188
195
|
? dedent(`
|
|
189
|
-
fs = GoogleDriveFileSystem(use_listings_cache=False, auth_kwargs={"use_local_webserver": False})
|
|
190
|
-
|
|
196
|
+
fs = GoogleDriveFileSystem(use_listings_cache=False, skip_instance_cache=True, auth_kwargs={"use_local_webserver": False})
|
|
197
|
+
mo.output.clear_console()
|
|
191
198
|
`)
|
|
192
199
|
: dedent(`
|
|
193
|
-
fs = GoogleDriveFileSystem(use_listings_cache=False)
|
|
200
|
+
fs = GoogleDriveFileSystem(use_listings_cache=False, skip_instance_cache=True)
|
|
194
201
|
`);
|
|
195
202
|
return { imports, code };
|
|
196
203
|
}
|
|
@@ -67,10 +67,6 @@ describe("StatusOverlay disconnect indicator", () => {
|
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
it.each([
|
|
70
|
-
[
|
|
71
|
-
WebSocketClosedReason.MALFORMED_QUERY,
|
|
72
|
-
"the kernel did not recognize a request; please file a bug with marimo",
|
|
73
|
-
],
|
|
74
70
|
[
|
|
75
71
|
WebSocketClosedReason.KERNEL_STARTUP_ERROR,
|
|
76
72
|
"Failed to start kernel sandbox",
|
|
@@ -22,9 +22,8 @@ export const StatusOverlay: React.FC<{
|
|
|
22
22
|
const { mode } = useAtomValue(viewStateAtom);
|
|
23
23
|
const isClosed = connection.state === WebSocketState.CLOSED;
|
|
24
24
|
const isOpen = connection.state === WebSocketState.OPEN;
|
|
25
|
-
// Only KERNEL_DISCONNECTED is recoverable by a retry.
|
|
26
|
-
//
|
|
27
|
-
// fail the same way.
|
|
25
|
+
// Only KERNEL_DISCONNECTED is recoverable by a retry. KERNEL_STARTUP_ERROR
|
|
26
|
+
// would deterministically fail the same way.
|
|
28
27
|
const canReconnect =
|
|
29
28
|
isClosed && connection.code === WebSocketClosedReason.KERNEL_DISCONNECTED;
|
|
30
29
|
|
|
@@ -2920,6 +2920,39 @@ describe("cell reducer", () => {
|
|
|
2920
2920
|
expect(state.untouchedNewCells.has(newCellId)).toBe(true);
|
|
2921
2921
|
});
|
|
2922
2922
|
|
|
2923
|
+
it("adds interactively-created hidden cell with boilerplate code to untouchedNewCells", () => {
|
|
2924
|
+
// Markdown cells are created with hideCode and non-empty default code
|
|
2925
|
+
// (e.g. `mo.md(r"""\n""")`). They are user-initiated, so their editor
|
|
2926
|
+
// should be shown until first blur.
|
|
2927
|
+
actions.createNewCell({
|
|
2928
|
+
cellId: "__end__",
|
|
2929
|
+
before: false,
|
|
2930
|
+
code: 'mo.md(r"""\n""")',
|
|
2931
|
+
hideCode: true,
|
|
2932
|
+
autoFocus: true,
|
|
2933
|
+
});
|
|
2934
|
+
|
|
2935
|
+
const newCellId =
|
|
2936
|
+
state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
|
|
2937
|
+
expect(state.untouchedNewCells.has(newCellId)).toBe(true);
|
|
2938
|
+
});
|
|
2939
|
+
|
|
2940
|
+
it("does not add programmatically-created hidden cell with code to untouchedNewCells", () => {
|
|
2941
|
+
// Cells created by the kernel (e.g. via code_mode) carry code and
|
|
2942
|
+
// autoFocus=false; their hide_code must take effect immediately.
|
|
2943
|
+
actions.createNewCell({
|
|
2944
|
+
cellId: "__end__",
|
|
2945
|
+
before: false,
|
|
2946
|
+
code: "x = 1",
|
|
2947
|
+
hideCode: true,
|
|
2948
|
+
autoFocus: false,
|
|
2949
|
+
});
|
|
2950
|
+
|
|
2951
|
+
const newCellId =
|
|
2952
|
+
state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
|
|
2953
|
+
expect(state.untouchedNewCells.has(newCellId)).toBe(false);
|
|
2954
|
+
});
|
|
2955
|
+
|
|
2923
2956
|
it("does not add cell to untouchedNewCells when hideCode is false", () => {
|
|
2924
2957
|
actions.createNewCell({
|
|
2925
2958
|
cellId: "__end__",
|
package/src/core/cells/cells.ts
CHANGED
|
@@ -45,7 +45,12 @@ export const DEFAULT_AI_MODEL = "openai/gpt-4o";
|
|
|
45
45
|
const AUTO_DOWNLOAD_FORMATS = ["html", "markdown", "ipynb"] as const;
|
|
46
46
|
|
|
47
47
|
export type CopilotMode = NonNullable<schemas["AiConfig"]["mode"]>;
|
|
48
|
-
export const COPILOT_MODES: CopilotMode[] = [
|
|
48
|
+
export const COPILOT_MODES: CopilotMode[] = [
|
|
49
|
+
"manual",
|
|
50
|
+
"ask",
|
|
51
|
+
"agent",
|
|
52
|
+
"code_mode",
|
|
53
|
+
];
|
|
49
54
|
|
|
50
55
|
const AiConfigSchema = z
|
|
51
56
|
.object({
|
package/src/core/edit-app.tsx
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
useRunAllCells,
|
|
21
21
|
useRunStaleCells,
|
|
22
22
|
} from "../components/editor/cell/useRunCells";
|
|
23
|
+
import { useSetCodeVisibility } from "../components/editor/actions/useSetCodeVisibility";
|
|
23
24
|
import { CellArray } from "../components/editor/renderers/cell-array";
|
|
24
25
|
import { CellsRenderer } from "../components/editor/renderers/cells-renderer";
|
|
25
26
|
import { useHotkey } from "../hooks/useHotkey";
|
|
@@ -112,6 +113,7 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
112
113
|
const runStaleCells = useRunStaleCells();
|
|
113
114
|
const runAllCells = useRunAllCells();
|
|
114
115
|
const togglePresenting = useTogglePresenting();
|
|
116
|
+
const setCodeVisibility = useSetCodeVisibility();
|
|
115
117
|
|
|
116
118
|
// HOTKEYS
|
|
117
119
|
useHotkey("global.runStale", () => {
|
|
@@ -126,6 +128,18 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
126
128
|
useHotkey("global.runAll", () => {
|
|
127
129
|
runAllCells();
|
|
128
130
|
});
|
|
131
|
+
useHotkey("global.showAllCode", () => {
|
|
132
|
+
setCodeVisibility(false, "code");
|
|
133
|
+
});
|
|
134
|
+
useHotkey("global.hideAllCode", () => {
|
|
135
|
+
setCodeVisibility(true, "code");
|
|
136
|
+
});
|
|
137
|
+
useHotkey("global.showAllMarkdownCode", () => {
|
|
138
|
+
setCodeVisibility(false, "markdown");
|
|
139
|
+
});
|
|
140
|
+
useHotkey("global.hideAllMarkdownCode", () => {
|
|
141
|
+
setCodeVisibility(true, "markdown");
|
|
142
|
+
});
|
|
129
143
|
useHotkey("global.collapseAllSections", () => {
|
|
130
144
|
collapseAllCells();
|
|
131
145
|
});
|