mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat: Improve Scanned / Model Lists layout
- Now inside ScrollArea - Now displays installed models
This commit is contained in:
parent
72c1a8db08
commit
715e3217d0
@ -8,19 +8,34 @@ import {
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { stopPastePropagation } from 'common/util/stopPastePropagation';
|
import { stopPastePropagation } from 'common/util/stopPastePropagation';
|
||||||
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
|
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
|
||||||
import { ChangeEvent, KeyboardEvent, memo, useCallback } from 'react';
|
import {
|
||||||
|
CSSProperties,
|
||||||
|
ChangeEvent,
|
||||||
|
KeyboardEvent,
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
interface IAIInputProps extends InputProps {
|
interface IAIInputProps extends InputProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
|
labelPos?: 'top' | 'side';
|
||||||
value?: string;
|
value?: string;
|
||||||
size?: string;
|
size?: string;
|
||||||
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
|
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||||
formControlProps?: Omit<FormControlProps, 'isInvalid' | 'isDisabled'>;
|
formControlProps?: Omit<FormControlProps, 'isInvalid' | 'isDisabled'>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const labelPosVerticalStyle: CSSProperties = {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 10,
|
||||||
|
};
|
||||||
|
|
||||||
const IAIInput = (props: IAIInputProps) => {
|
const IAIInput = (props: IAIInputProps) => {
|
||||||
const {
|
const {
|
||||||
label = '',
|
label = '',
|
||||||
|
labelPos = 'top',
|
||||||
isDisabled = false,
|
isDisabled = false,
|
||||||
isInvalid,
|
isInvalid,
|
||||||
formControlProps,
|
formControlProps,
|
||||||
@ -51,6 +66,7 @@ const IAIInput = (props: IAIInputProps) => {
|
|||||||
isInvalid={isInvalid}
|
isInvalid={isInvalid}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
{...formControlProps}
|
{...formControlProps}
|
||||||
|
style={labelPos === 'side' ? labelPosVerticalStyle : undefined}
|
||||||
>
|
>
|
||||||
{label !== '' && <FormLabel>{label}</FormLabel>}
|
{label !== '' && <FormLabel>{label}</FormLabel>}
|
||||||
<Input
|
<Input
|
||||||
|
@ -4,8 +4,9 @@ import { RootState } from 'app/store/store';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
import IAIInput from 'common/components/IAIInput';
|
import IAIInput from 'common/components/IAIInput';
|
||||||
|
import IAIScrollArea from 'common/components/IAIScrollArea';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { difference, forEach, map, values } from 'lodash-es';
|
import { difference, forEach, intersection, map, values } from 'lodash-es';
|
||||||
import { ChangeEvent, MouseEvent, useCallback, useState } from 'react';
|
import { ChangeEvent, MouseEvent, useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -26,7 +27,7 @@ export default function FoundModelsList() {
|
|||||||
const { data: installedModels } = useGetMainModelsQuery();
|
const { data: installedModels } = useGetMainModelsQuery();
|
||||||
|
|
||||||
// Get all model paths from a given directory
|
// Get all model paths from a given directory
|
||||||
const { foundModels, notInstalledModels, filteredModels } =
|
const { foundModels, alreadyInstalled, filteredModels } =
|
||||||
useGetModelsInFolderQuery(
|
useGetModelsInFolderQuery(
|
||||||
{
|
{
|
||||||
search_path: searchFolder ? searchFolder : '',
|
search_path: searchFolder ? searchFolder : '',
|
||||||
@ -37,9 +38,10 @@ export default function FoundModelsList() {
|
|||||||
const installedModelPaths = map(installedModelValues, 'path');
|
const installedModelPaths = map(installedModelValues, 'path');
|
||||||
// Only select models those that aren't already installed to Invoke
|
// Only select models those that aren't already installed to Invoke
|
||||||
const notInstalledModels = difference(data, installedModelPaths);
|
const notInstalledModels = difference(data, installedModelPaths);
|
||||||
|
const alreadyInstalled = intersection(data, installedModelPaths);
|
||||||
return {
|
return {
|
||||||
foundModels: data,
|
foundModels: data,
|
||||||
notInstalledModels: notInstalledModels,
|
alreadyInstalled: foundModelsFilter(alreadyInstalled, nameFilter),
|
||||||
filteredModels: foundModelsFilter(notInstalledModels, nameFilter),
|
filteredModels: foundModelsFilter(notInstalledModels, nameFilter),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -89,6 +91,82 @@ export default function FoundModelsList() {
|
|||||||
setNameFilter(e.target.value);
|
setNameFilter(e.target.value);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const renderModels = ({
|
||||||
|
models,
|
||||||
|
showActions = true,
|
||||||
|
}: {
|
||||||
|
models: string[];
|
||||||
|
showActions?: boolean;
|
||||||
|
}) => {
|
||||||
|
return models.map((model) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
p: 4,
|
||||||
|
gap: 4,
|
||||||
|
alignItems: 'center',
|
||||||
|
borderRadius: 4,
|
||||||
|
bg: 'base.200',
|
||||||
|
_dark: {
|
||||||
|
bg: 'base.800',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
key={model}
|
||||||
|
>
|
||||||
|
<Flex w="100%" sx={{ flexDirection: 'column', minW: '25%' }}>
|
||||||
|
<Text sx={{ fontWeight: 600 }}>
|
||||||
|
{model.split('\\').slice(-1)[0]}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
sx={{
|
||||||
|
fontSize: 'sm',
|
||||||
|
color: 'base.600',
|
||||||
|
_dark: {
|
||||||
|
color: 'base.400',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{model}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
{showActions ? (
|
||||||
|
<Flex gap={2}>
|
||||||
|
<IAIButton
|
||||||
|
id={model}
|
||||||
|
onClick={quickAddHandler}
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
Quick Add
|
||||||
|
</IAIButton>
|
||||||
|
<IAIButton
|
||||||
|
onClick={() => dispatch(setAdvancedAddScanModel(model))}
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
Advanced
|
||||||
|
</IAIButton>
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<Text
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
p: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
color: 'accent.50',
|
||||||
|
bg: 'accent.400',
|
||||||
|
_dark: {
|
||||||
|
color: 'accent.100',
|
||||||
|
bg: 'accent.600',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Installed
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const renderFoundModels = () => {
|
const renderFoundModels = () => {
|
||||||
if (!searchFolder) return;
|
if (!searchFolder) return;
|
||||||
|
|
||||||
@ -125,8 +203,12 @@ export default function FoundModelsList() {
|
|||||||
<IAIInput
|
<IAIInput
|
||||||
onChange={handleSearchFilter}
|
onChange={handleSearchFilter}
|
||||||
label={t('modelManager.search')}
|
label={t('modelManager.search')}
|
||||||
|
labelPos="side"
|
||||||
/>
|
/>
|
||||||
<Flex p={2} gap={2}>
|
<Flex p={2} gap={2}>
|
||||||
|
<Text sx={{ fontWeight: 600 }}>
|
||||||
|
Models Found: {foundModels.length}
|
||||||
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@ -136,60 +218,16 @@ export default function FoundModelsList() {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Found Models: {foundModels.length}
|
Not Installed: {filteredModels.length}
|
||||||
</Text>
|
|
||||||
<Text sx={{ fontWeight: 600 }}>
|
|
||||||
Not Installed: {notInstalledModels.length}
|
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{filteredModels.map((model) => (
|
<IAIScrollArea offsetScrollbars>
|
||||||
<Flex
|
<Flex gap={2} flexDirection="column">
|
||||||
sx={{
|
{renderModels({ models: filteredModels })}
|
||||||
p: 4,
|
{renderModels({ models: alreadyInstalled, showActions: false })}
|
||||||
gap: 4,
|
|
||||||
alignItems: 'center',
|
|
||||||
borderRadius: 4,
|
|
||||||
bg: 'base.200',
|
|
||||||
_dark: {
|
|
||||||
bg: 'base.800',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
key={model}
|
|
||||||
>
|
|
||||||
<Flex w="100%" sx={{ flexDirection: 'column', minW: '25%' }}>
|
|
||||||
<Text sx={{ fontWeight: 600 }}>
|
|
||||||
{model.split('\\').slice(-1)[0]}
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
sx={{
|
|
||||||
fontSize: 'sm',
|
|
||||||
color: 'base.600',
|
|
||||||
_dark: {
|
|
||||||
color: 'base.400',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{model}
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={2}>
|
|
||||||
<IAIButton
|
|
||||||
id={model}
|
|
||||||
onClick={quickAddHandler}
|
|
||||||
isLoading={isLoading}
|
|
||||||
>
|
|
||||||
Quick Add
|
|
||||||
</IAIButton>
|
|
||||||
<IAIButton
|
|
||||||
onClick={() => dispatch(setAdvancedAddScanModel(model))}
|
|
||||||
isLoading={isLoading}
|
|
||||||
>
|
|
||||||
Advanced
|
|
||||||
</IAIButton>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
</IAIScrollArea>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -43,8 +43,8 @@ export default function ScanAdvancedAddModels() {
|
|||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
minWidth: '50%',
|
minWidth: '40%',
|
||||||
maxHeight: window.innerHeight - 330,
|
maxHeight: window.innerHeight - 300,
|
||||||
overflow: 'scroll',
|
overflow: 'scroll',
|
||||||
p: 4,
|
p: 4,
|
||||||
gap: 4,
|
gap: 4,
|
||||||
|
@ -10,7 +10,7 @@ export default function ScanModels() {
|
|||||||
<Flex gap={4}>
|
<Flex gap={4}>
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
maxHeight: window.innerHeight - 330,
|
maxHeight: window.innerHeight - 300,
|
||||||
overflow: 'scroll',
|
overflow: 'scroll',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
w: '100%',
|
w: '100%',
|
||||||
|
@ -48,13 +48,8 @@ function SearchFolderForm() {
|
|||||||
sx={{
|
sx={{
|
||||||
w: '100%',
|
w: '100%',
|
||||||
gap: 2,
|
gap: 2,
|
||||||
p: 4,
|
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
background: 'base.200',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
_dark: {
|
|
||||||
background: 'base.800',
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex w="100%" alignItems="center" gap={4} minH={12}>
|
<Flex w="100%" alignItems="center" gap={4} minH={12}>
|
||||||
@ -67,7 +62,7 @@ function SearchFolderForm() {
|
|||||||
_dark: { color: 'base.300' },
|
_dark: { color: 'base.300' },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Search Folder
|
Folder
|
||||||
</Text>
|
</Text>
|
||||||
{!searchFolder ? (
|
{!searchFolder ? (
|
||||||
<IAIInput
|
<IAIInput
|
||||||
|
@ -44,10 +44,6 @@ const ModelList = (props: ModelListProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDirection="column" rowGap={4} width="50%" minWidth="50%">
|
<Flex flexDirection="column" rowGap={4} width="50%" minWidth="50%">
|
||||||
<IAIInput
|
|
||||||
onChange={handleSearchFilter}
|
|
||||||
label={t('modelManager.search')}
|
|
||||||
/>
|
|
||||||
<Flex
|
<Flex
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
gap={4}
|
gap={4}
|
||||||
@ -79,6 +75,12 @@ const ModelList = (props: ModelListProps) => {
|
|||||||
</IAIButton>
|
</IAIButton>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<IAIInput
|
||||||
|
onChange={handleSearchFilter}
|
||||||
|
label={t('modelManager.search')}
|
||||||
|
labelPos="side"
|
||||||
|
/>
|
||||||
|
|
||||||
{['all', 'diffusers'].includes(modelFormatFilter) &&
|
{['all', 'diffusers'].includes(modelFormatFilter) &&
|
||||||
filteredDiffusersModels.length > 0 && (
|
filteredDiffusersModels.length > 0 && (
|
||||||
<Flex sx={{ gap: 2, flexDir: 'column' }}>
|
<Flex sx={{ gap: 2, flexDir: 'column' }}>
|
||||||
|
Loading…
Reference in New Issue
Block a user