mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): improved starter model buttons & tooltips
This commit is contained in:
@@ -810,7 +810,11 @@
|
||||
"urlUnauthorizedErrorMessage": "You may need to configure an API token to access this model.",
|
||||
"urlUnauthorizedErrorMessage2": "Learn how here.",
|
||||
"imageEncoderModelId": "Image Encoder Model ID",
|
||||
"includesNModels": "Includes {{n}} models and their dependencies",
|
||||
"installedModelsCount": "{{installed}} of {{total}} models installed.",
|
||||
"includesNModels": "Includes {{n}} models and their dependencies.",
|
||||
"allNModelsInstalled": "All {{count}} models installed",
|
||||
"nToInstall": "{{count}} to install",
|
||||
"nAlreadyInstalled": "{{count}} already installed",
|
||||
"installQueue": "Install Queue",
|
||||
"inplaceInstall": "In-place install",
|
||||
"inplaceInstallDesc": "Install models without copying the files. When using the model, it will be loaded from its this location. If disabled, the model file(s) will be copied into the Invoke-managed models directory during installation.",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { S } from 'services/api/types';
|
||||
|
||||
import { useStarterBundleInstall } from './useStarterBundleInstall';
|
||||
|
||||
export const useStarterBundleInstallStatus = (bundle: S['StarterModelBundle']) => {
|
||||
const { getModelsToInstall } = useStarterBundleInstall();
|
||||
const total = useMemo(() => bundle.models.length, [bundle.models.length]);
|
||||
const install = useMemo(() => getModelsToInstall(bundle).install, [bundle, getModelsToInstall]);
|
||||
const skip = useMemo(() => getModelsToInstall(bundle).skip, [bundle, getModelsToInstall]);
|
||||
|
||||
return { total, skip, install };
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Button, Flex, Grid, Heading, Icon, Text } from '@invoke-ai/ui-library';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import { useStarterBundleInstall } from 'features/modelManagerV2/hooks/useStarterBundleInstall';
|
||||
import { map } from 'es-toolkit/compat';
|
||||
import { setInstallModelsTabByName } from 'features/modelManagerV2/store/installModelsStore';
|
||||
import { StarterBundleButton } from 'features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterBundle';
|
||||
import { StarterBundleTooltipContentCompact } from 'features/modelManagerV2/subpanels/AddModelPanel/StarterModels/StarterBundleTooltipContentCompact';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiFolderOpenBold, PiLinkBold, PiStarBold } from 'react-icons/pi';
|
||||
@@ -10,26 +12,8 @@ import { useGetStarterModelsQuery } from 'services/api/endpoints/models';
|
||||
|
||||
export const LaunchpadForm = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const { installBundle } = useStarterBundleInstall();
|
||||
const { data: starterModelsData } = useGetStarterModelsQuery();
|
||||
|
||||
// Function to install models from a bundle
|
||||
const handleBundleInstall = useCallback(
|
||||
(bundleName: string) => {
|
||||
if (!starterModelsData?.starter_bundles) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bundle = starterModelsData.starter_bundles[bundleName];
|
||||
if (!bundle) {
|
||||
return;
|
||||
}
|
||||
|
||||
installBundle(bundle);
|
||||
},
|
||||
[starterModelsData, installBundle]
|
||||
);
|
||||
|
||||
const navigateToUrlTab = useCallback(() => {
|
||||
setInstallModelsTabByName('urlOrLocal');
|
||||
}, []);
|
||||
@@ -46,18 +30,6 @@ export const LaunchpadForm = memo(() => {
|
||||
setInstallModelsTabByName('starterModels');
|
||||
}, []);
|
||||
|
||||
const handleSD15BundleClick = useCallback(() => {
|
||||
handleBundleInstall('sd-1');
|
||||
}, [handleBundleInstall]);
|
||||
|
||||
const handleSDXLBundleClick = useCallback(() => {
|
||||
handleBundleInstall('sdxl');
|
||||
}, [handleBundleInstall]);
|
||||
|
||||
const handleFluxBundleClick = useCallback(() => {
|
||||
handleBundleInstall('flux');
|
||||
}, [handleBundleInstall]);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" height="100%" gap={3}>
|
||||
<ScrollableContent>
|
||||
@@ -145,32 +117,36 @@ export const LaunchpadForm = memo(() => {
|
||||
</Grid>
|
||||
</Flex>
|
||||
{/* Recommended Section */}
|
||||
<Flex flexDir="column" gap={2} alignItems="flex-start">
|
||||
<Heading size="sm">{t('modelManager.launchpad.recommendedModels')}</Heading>
|
||||
{/* Starter Model Bundles - More Prominent */}
|
||||
<Text color="base.300">{t('modelManager.launchpad.bundleDescription')}</Text>
|
||||
<Grid templateColumns="repeat(auto-fit, minmax(180px, 1fr))" gap={2} w="full">
|
||||
<Button onClick={handleSD15BundleClick} variant="outline" p={6}>
|
||||
{t('modelManager.launchpad.stableDiffusion15')}
|
||||
{starterModelsData && (
|
||||
<Flex flexDir="column" gap={2} alignItems="flex-start">
|
||||
<Heading size="sm">{t('modelManager.launchpad.recommendedModels')}</Heading>
|
||||
{/* Starter Model Bundles - More Prominent */}
|
||||
<Text color="base.300">{t('modelManager.launchpad.bundleDescription')}</Text>
|
||||
<Grid templateColumns="repeat(auto-fit, minmax(180px, 1fr))" gap={2} w="full">
|
||||
{map(starterModelsData.starter_bundles, (bundle) => (
|
||||
<StarterBundleButton
|
||||
size="md"
|
||||
tooltip={<StarterBundleTooltipContentCompact bundle={bundle} />}
|
||||
key={bundle.name}
|
||||
bundle={bundle}
|
||||
variant="outline"
|
||||
p={4}
|
||||
h="unset"
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
{/* Browse All - Simple Link */}
|
||||
<Button
|
||||
onClick={navigateToStarterModelsTab}
|
||||
variant="link"
|
||||
size="sm"
|
||||
leftIcon={<PiStarBold />}
|
||||
colorScheme="invokeBlue"
|
||||
>
|
||||
{t('modelManager.launchpad.exploreStarter')}
|
||||
</Button>
|
||||
<Button onClick={handleSDXLBundleClick} variant="outline" p={6}>
|
||||
{t('modelManager.launchpad.sdxl')}
|
||||
</Button>
|
||||
<Button onClick={handleFluxBundleClick} variant="outline" p={6}>
|
||||
{t('modelManager.launchpad.fluxDev')}
|
||||
</Button>
|
||||
</Grid>
|
||||
{/* Browse All - Simple Link */}
|
||||
<Button
|
||||
onClick={navigateToStarterModelsTab}
|
||||
variant="link"
|
||||
size="sm"
|
||||
leftIcon={<PiStarBold />}
|
||||
colorScheme="invokeBlue"
|
||||
>
|
||||
{t('modelManager.launchpad.exploreStarter')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</ScrollableContent>
|
||||
</Flex>
|
||||
|
||||
@@ -1,47 +1,21 @@
|
||||
import { Button, Flex, ListItem, Text, Tooltip, UnorderedList } from '@invoke-ai/ui-library';
|
||||
import type { ButtonProps } from '@invoke-ai/ui-library';
|
||||
import { Button } from '@invoke-ai/ui-library';
|
||||
import { useStarterBundleInstall } from 'features/modelManagerV2/hooks/useStarterBundleInstall';
|
||||
import { isMainModelBase } from 'features/nodes/types/common';
|
||||
import { MODEL_TYPE_SHORT_MAP } from 'features/parameters/types/constants';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useStarterBundleInstallStatus } from 'features/modelManagerV2/hooks/useStarterBundleInstallStatus';
|
||||
import { useCallback } from 'react';
|
||||
import type { S } from 'services/api/types';
|
||||
|
||||
export const StarterBundle = ({ bundle }: { bundle: S['StarterModelBundle'] }) => {
|
||||
const { installBundle, getModelsToInstall } = useStarterBundleInstall();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const modelsToInstall = useMemo(() => getModelsToInstall(bundle), [bundle, getModelsToInstall]);
|
||||
export const StarterBundleButton = ({ bundle, ...rest }: { bundle: S['StarterModelBundle'] } & ButtonProps) => {
|
||||
const { installBundle } = useStarterBundleInstall();
|
||||
const { install } = useStarterBundleInstallStatus(bundle);
|
||||
|
||||
const handleClickBundle = useCallback(() => {
|
||||
installBundle(bundle);
|
||||
}, [installBundle, bundle]);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
label={
|
||||
<Flex flexDir="column" p={1}>
|
||||
<Text>{t('modelManager.includesNModels', { n: bundle.models.length })}:</Text>
|
||||
<UnorderedList>
|
||||
{bundle.models.map((model, index) => (
|
||||
<ListItem key={index} wordBreak="break-all">
|
||||
{model.name}
|
||||
</ListItem>
|
||||
))}
|
||||
</UnorderedList>
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
<Button size="sm" onClick={handleClickBundle} py={6} isDisabled={modelsToInstall.install.length === 0}>
|
||||
<Flex flexDir="column">
|
||||
<Text>{isMainModelBase(bundle.name) ? MODEL_TYPE_SHORT_MAP[bundle.name] : bundle.name}</Text>
|
||||
{modelsToInstall.install.length > 0 && (
|
||||
<Text fontSize="xs">
|
||||
({bundle.models.length} {t('settings.models')})
|
||||
</Text>
|
||||
)}
|
||||
{modelsToInstall.install.length === 0 && <Text fontSize="xs">{t('common.installed')}</Text>}
|
||||
</Flex>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button onClick={handleClickBundle} isDisabled={install.length === 0} {...rest}>
|
||||
{bundle.name}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Flex, ListItem, Text, UnorderedList } from '@invoke-ai/ui-library';
|
||||
import { useStarterBundleInstallStatus } from 'features/modelManagerV2/hooks/useStarterBundleInstallStatus';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { S } from 'services/api/types';
|
||||
|
||||
export const StarterBundleTooltipContent = memo(({ bundle }: { bundle: S['StarterModelBundle'] }) => {
|
||||
const { t } = useTranslation();
|
||||
const { total, install, skip } = useStarterBundleInstallStatus(bundle);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" p={1} gap={2}>
|
||||
<Text>{t('modelManager.includesNModels', { n: total })}</Text>
|
||||
{install.length === 0 && (
|
||||
<Flex flexDir="column">
|
||||
<Text fontWeight="semibold">{t('modelManager.allNModelsInstalled', { count: total })}.</Text>
|
||||
<UnorderedList>
|
||||
{skip.map((model, index) => (
|
||||
<ListItem key={index} wordBreak="break-all">
|
||||
{model.config.name}
|
||||
</ListItem>
|
||||
))}
|
||||
</UnorderedList>
|
||||
</Flex>
|
||||
)}
|
||||
{install.length > 0 && (
|
||||
<>
|
||||
<Flex flexDir="column">
|
||||
<Text fontWeight="semibold">{t('modelManager.nToInstall', { count: install.length })}:</Text>
|
||||
<UnorderedList>
|
||||
{install.map((model, index) => (
|
||||
<ListItem key={index} wordBreak="break-all">
|
||||
{model.config.name}
|
||||
</ListItem>
|
||||
))}
|
||||
</UnorderedList>
|
||||
</Flex>
|
||||
<Flex flexDir="column">
|
||||
<Text fontWeight="semibold">{t('modelManager.nAlreadyInstalled', { count: skip.length })}:</Text>
|
||||
<UnorderedList>
|
||||
{skip.map((model, index) => (
|
||||
<ListItem key={index} wordBreak="break-all">
|
||||
{model.config.name}
|
||||
</ListItem>
|
||||
))}
|
||||
</UnorderedList>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
StarterBundleTooltipContent.displayName = 'StarterBundleTooltipContent';
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Flex, Text } from '@invoke-ai/ui-library';
|
||||
import { useStarterBundleInstallStatus } from 'features/modelManagerV2/hooks/useStarterBundleInstallStatus';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { S } from 'services/api/types';
|
||||
|
||||
export const StarterBundleTooltipContentCompact = memo(({ bundle }: { bundle: S['StarterModelBundle'] }) => {
|
||||
const { t } = useTranslation();
|
||||
const { total, install, skip } = useStarterBundleInstallStatus(bundle);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap={1} p={1}>
|
||||
<Text>{t('modelManager.includesNModels', { n: total })}</Text>
|
||||
{install.length === 0 && (
|
||||
<Text fontWeight="semibold">{t('modelManager.allNModelsInstalled', { count: total })}.</Text>
|
||||
)}
|
||||
{install.length > 0 && (
|
||||
<Text fontWeight="semibold">{t('modelManager.installedModelsCount', { installed: skip.length, total })}</Text>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
StarterBundleTooltipContentCompact.displayName = 'StarterBundleTooltipContentCompact';
|
||||
@@ -7,7 +7,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import { PiInfoBold, PiXBold } from 'react-icons/pi';
|
||||
import type { GetStarterModelsResponse } from 'services/api/endpoints/models';
|
||||
|
||||
import { StarterBundle } from './StarterBundle';
|
||||
import { StarterBundleButton } from './StarterBundle';
|
||||
import { StarterBundleTooltipContent } from './StarterBundleTooltipContent';
|
||||
import { StarterModelsResultItem } from './StarterModelsResultItem';
|
||||
|
||||
type StarterModelsResultsProps = {
|
||||
@@ -62,7 +63,12 @@ export const StarterModelsResults = memo(({ results }: StarterModelsResultsProps
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
{map(results.starter_bundles, (bundle) => (
|
||||
<StarterBundle key={bundle.name} bundle={bundle} />
|
||||
<StarterBundleButton
|
||||
key={bundle.name}
|
||||
bundle={bundle}
|
||||
tooltip={<StarterBundleTooltipContent bundle={bundle} />}
|
||||
size="sm"
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
Reference in New Issue
Block a user