diff --git a/src/common/pip.ts b/src/common/pip.ts index e9d99e301..5765e18d0 100644 --- a/src/common/pip.ts +++ b/src/common/pip.ts @@ -57,7 +57,7 @@ export const runPip = async ( }); }; -const getFindLinks = (dependencies: readonly PyPiPackage[]): string[] => { +export const getFindLinks = (dependencies: readonly PyPiPackage[]): string[] => { const links = new Set(); for (const p of dependencies) { if (p.findLink) { diff --git a/src/renderer/contexts/DependencyContext.tsx b/src/renderer/contexts/DependencyContext.tsx index 75bd58145..e6a30eca0 100644 --- a/src/renderer/contexts/DependencyContext.tsx +++ b/src/renderer/contexts/DependencyContext.tsx @@ -20,10 +20,17 @@ import { ModalFooter, ModalHeader, ModalOverlay, + Popover, + PopoverArrow, + PopoverBody, + PopoverCloseButton, + PopoverContent, + PopoverHeader, + PopoverTrigger, Progress, + Select, Spacer, Spinner, - Switch, Tag, Text, Textarea, @@ -31,6 +38,7 @@ import { VStack, useDisclosure, } from '@chakra-ui/react'; +import { clipboard } from 'electron'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { BsQuestionCircle, BsTerminalFill } from 'react-icons/bs'; import { HiOutlineRefresh } from 'react-icons/hi'; @@ -46,7 +54,7 @@ import { Version, } from '../../common/common-types'; import { log } from '../../common/log'; -import { OnStdio, runPipInstall, runPipUninstall } from '../../common/pip'; +import { OnStdio, getFindLinks, runPipInstall, runPipUninstall } from '../../common/pip'; import { noop } from '../../common/util'; import { versionGt } from '../../common/version'; import { Markdown } from '../components/Markdown'; @@ -56,6 +64,12 @@ import { BackendContext } from './BackendContext'; import { GlobalContext } from './GlobalNodeState'; import { SettingsContext } from './SettingsContext'; +const installModes = { + NORMAL: 'Normal', + DIRECT_PIP: 'Direct Pip', + MANUAL_COPY: 'Manual/Copy', +}; + export interface DependencyContextValue { openDependencyManager: () => void; availableUpdates: number; @@ -382,7 +396,7 @@ export const DependencyProvider = memo(({ children }: React.PropsWithChildren { - setInstallingPackage(p); + const { + isOpen: isPopoverOpen, + onToggle: onPopoverToggle, + onClose: onPopoverClose, + } = useDisclosure(); + const copyCommandToClipboard = (command: string) => { + clipboard.writeText(command); + onPopoverToggle(); + setTimeout(() => { + onPopoverClose(); + }, 5000); + }; + + const installPackage = (pkg: Package) => { + if (installMode === installModes.MANUAL_COPY) { + const deps = pkg.dependencies.map((p) => `${p.pypiName}==${p.version}`); + const findLinks = getFindLinks(pkg.dependencies).flatMap((l) => [ + '--extra-index-url', + l, + ]); + const args = [ + pythonInfo.python, + '-m', + 'pip', + 'install', + '--upgrade', + ...deps, + ...findLinks, + ]; + const cmd = args.join(' '); + copyCommandToClipboard(cmd); + return; + } + setInstallingPackage(pkg); changePackages(() => runPipInstall( pythonInfo, - p.dependencies, - usePipDirectly ? undefined : setProgress, + pkg.dependencies, + installMode === installModes.NORMAL ? setProgress : undefined, onStdio ) ); }; - const uninstallPackage = (p: Package) => { - setUninstallingPackage(p); + const uninstallPackage = (pkg: Package) => { + if (installMode === installModes.MANUAL_COPY) { + const deps = pkg.dependencies.map((p) => p.pypiName); + const args = [pythonInfo.python, '-m', 'pip', 'uninstall', ...deps]; + const cmd = args.join(' '); + copyCommandToClipboard(cmd); + return; + } + setUninstallingPackage(pkg); changePackages(() => runPipUninstall( pythonInfo, - p.dependencies, - usePipDirectly ? undefined : setProgress, + pkg.dependencies, + installMode === installModes.NORMAL ? setProgress : undefined, onStdio ) ); @@ -531,29 +584,65 @@ export const DependencyProvider = memo(({ children }: React.PropsWithChildren - + - { - setUsePipDirectly(!usePipDirectly); - }} - /> - Use Pip Directly - -
- -
-
+ + + + + + + + Command copied to clipboard. + + + + + Open up an external terminal, paste the + command, and run it. When it is done + running, manually restart chaiNNer. + + + + + + {'The dependency install mode. ChaiNNer supports 3 ways of installing packages:\n\n' + + '- Normal: This is the default installation mode. This mode manually downloads packages in order to display download progress.\n' + + '- Direct Pip: This will invoke pip more directly, like installing python packages normally. Use this setting when having issues with Normal. Note: this makes it impossible to show installation progress.\n' + + '- Manual/Copy: Copy the pip install command to your clipboard for you to run in your own terminal. You will have to manually restart chaiNner afterwards.'} + + } + openDelay={500} + px={2} + py={0} + > +
+ +
+
+