/* Copyright 2026 Marimo. All rights reserved. */
import { useAtomValue, useSetAtom } from "jotai";
import {
BoxIcon,
ChevronDownIcon,
ChevronRightIcon,
HelpCircleIcon,
} from "lucide-react";
import React from "react";
import { useOpenSettingsToTab } from "@/components/app-config/state";
import { Spinner } from "@/components/icons/spinner";
import { SearchInput } from "@/components/ui/input";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Tooltip } from "@/components/ui/tooltip";
import { toast } from "@/components/ui/use-toast";
import { isConnectedAtom } from "@/core/network/connection";
import type {
DependencyTreeNode,
DependencyTreeResponse,
} from "@/core/network/types";
import { sandboxSyncAtom } from "@/core/packages/sandbox-state";
import { stripPackageManagerPrefix } from "@/core/packages/package-input-utils";
import { usePackageAction } from "@/core/packages/usePackageAction";
import { usePackageDependencies } from "@/core/packages/usePackageDependencies";
import { useInstallPackages } from "@/core/packages/useInstallPackage";
import { isWasm } from "@/core/wasm/utils";
import { ErrorBanner } from "@/plugins/impl/common/error-banner";
import { cn } from "@/utils/cn";
import { copyToClipboard } from "@/utils/copy";
import { Events } from "@/utils/events";
import { RuntimeDetails } from "./runtime-panel";
import { PanelEmptyState } from "./empty-state";
import { PACKAGES_INPUT_ID, packagesToInstallAtom } from "./packages-utils";
type ViewMode = "tree" | "list";
type PackageInstallationContext = DependencyTreeResponse["context"];
const PackageActionButton: React.FC<{
onClick: () => void;
loading: boolean;
children: React.ReactNode;
className?: string;
}> = ({ onClick, loading, children, className }) => {
if (loading) {
return ;
}
return (
);
};
const PackagesPanel: React.FC = () => {
const connected = useAtomValue(isConnectedAtom);
return (
{connected && }
);
};
const PackageContents: React.FC = () => {
const syncing = useAtomValue(sandboxSyncAtom).kind === "running";
const [userViewMode, setUserViewMode] = React.useState(null);
const { data: dependencies, error, isPending } = usePackageDependencies();
// Only show on the first load
if (isPending) {
return (
);
}
if (error) {
return ;
}
const isTreeSupported = dependencies.tree != null;
const name = dependencies.tree?.name;
const version = dependencies?.tree?.version;
const sandboxBackend =
dependencies.context.kind === "sandbox"
? dependencies.context.backend
: null;
const isSandbox = sandboxBackend !== null;
const viewMode = isSandbox
? "tree"
: resolveViewMode(userViewMode, isTreeSupported);
const scopeLabel = name && name !== "" ? "project" : "environment";
return (
{isTreeSupported && !isSandbox && (
{scopeLabel}
{name && (
{name}
{version && ` v${version}`}
)}
)}
);
};
export default PackagesPanel;
const InstallPackageForm: React.FC<{
context: PackageInstallationContext;
}> = ({ context }) => {
const [input, setInput] = React.useState("");
const { handleClick: openSettings } = useOpenSettingsToTab();
const isSandbox = context.kind === "sandbox";
const packageManager = isSandbox ? context.backend : context.name;
// Get the packages to install from the atom
const packagesToInstall = useAtomValue(packagesToInstallAtom);
const setPackagesToInstall = useSetAtom(packagesToInstallAtom);
// Set the input value when packagesToInstall changes
React.useEffect(() => {
if (packagesToInstall) {
setInput(packagesToInstall);
// Clear the atom after setting the input
setPackagesToInstall(null);
}
}, [packagesToInstall, setPackagesToInstall]);
const { loading, handleInstallPackages } = useInstallPackages();
const onSuccessInstallPackages = () => {
setInput("");
};
const installPackages = () => {
const cleanedInput = stripPackageManagerPrefix(input);
handleInstallPackages(
[cleanedInput], // the backend will handle splitting the packages
onSuccessInstallPackages,
);
};
return (
) : isSandbox ? (
) : (
)
}
rootClassName="flex-1 border-none"
value={input}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
installPackages();
}
}}
onChange={(e) => setInput(e.target.value)}
/>
{isSandbox ? (
Packages are recorded in this notebook's inline metadata
and synchronized with its {packageManager} sandbox. To switch
backends, restart marimo with a different --sandbox option.
) : (
Packages are installed using {packageManager}, selected in your
user configuration.
)}
You can install packages with various formats:
Package name: A
package name; this will install the latest version.
Example: httpx
Package and version:
{" "}
A package with a specific version or version range.
{"Examples: httpx==0.27.0, httpx>=0.27.0"}
Git: A Git
repository
Example: git+https://github.com/encode/httpx
URL: A remote
wheel or source distribution.
Example: https://example.com/httpx-0.27.0.tar.gz
Path: A local
wheel, source distribution, or project directory.