diff --git a/invokeai/app/api/routers/app_info.py b/invokeai/app/api/routers/app_info.py
index a98c8edc6a..39d570ec99 100644
--- a/invokeai/app/api/routers/app_info.py
+++ b/invokeai/app/api/routers/app_info.py
@@ -7,6 +7,7 @@ from fastapi.routing import APIRouter
from pydantic import BaseModel, Field
from invokeai.app.invocations.upscale import ESRGAN_MODELS
+from invokeai.app.services.invocation_cache.invocation_cache_common import InvocationCacheStatus
from invokeai.backend.image_util.invisible_watermark import InvisibleWatermark
from invokeai.backend.image_util.patchmatch import PatchMatch
from invokeai.backend.image_util.safety_checker import SafetyChecker
@@ -113,3 +114,33 @@ async def set_log_level(
async def clear_invocation_cache() -> None:
"""Clears the invocation cache"""
ApiDependencies.invoker.services.invocation_cache.clear()
+
+
+@app_router.put(
+ "/invocation_cache/enable",
+ operation_id="enable_invocation_cache",
+ responses={200: {"description": "The operation was successful"}},
+)
+async def enable_invocation_cache() -> None:
+ """Clears the invocation cache"""
+ ApiDependencies.invoker.services.invocation_cache.enable()
+
+
+@app_router.put(
+ "/invocation_cache/disable",
+ operation_id="disable_invocation_cache",
+ responses={200: {"description": "The operation was successful"}},
+)
+async def disable_invocation_cache() -> None:
+ """Clears the invocation cache"""
+ ApiDependencies.invoker.services.invocation_cache.disable()
+
+
+@app_router.get(
+ "/invocation_cache/status",
+ operation_id="get_invocation_cache_status",
+ responses={200: {"model": InvocationCacheStatus}},
+)
+async def get_invocation_cache_status() -> InvocationCacheStatus:
+ """Clears the invocation cache"""
+ return ApiDependencies.invoker.services.invocation_cache.get_status()
diff --git a/invokeai/app/services/invocation_cache/invocation_cache_base.py b/invokeai/app/services/invocation_cache/invocation_cache_base.py
index c35a31f851..d913e050ab 100644
--- a/invokeai/app/services/invocation_cache/invocation_cache_base.py
+++ b/invokeai/app/services/invocation_cache/invocation_cache_base.py
@@ -2,6 +2,7 @@ from abc import ABC, abstractmethod
from typing import Optional, Union
from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput
+from invokeai.app.services.invocation_cache.invocation_cache_common import InvocationCacheStatus
class InvocationCacheBase(ABC):
@@ -32,7 +33,7 @@ class InvocationCacheBase(ABC):
@abstractmethod
def delete(self, key: Union[int, str]) -> None:
- """Deleteds an invocation output from the cache"""
+ """Deletes an invocation output from the cache"""
pass
@abstractmethod
@@ -44,3 +45,18 @@ class InvocationCacheBase(ABC):
def create_key(self, invocation: BaseInvocation) -> int:
"""Gets the key for the invocation's cache item"""
pass
+
+ @abstractmethod
+ def disable(self) -> None:
+ """Disables the cache, overriding the max cache size"""
+ pass
+
+ @abstractmethod
+ def enable(self) -> None:
+ """Enables the cache, letting the the max cache size take effect"""
+ pass
+
+ @abstractmethod
+ def get_status(self) -> InvocationCacheStatus:
+ """Returns the status of the cache"""
+ pass
diff --git a/invokeai/app/services/invocation_cache/invocation_cache_common.py b/invokeai/app/services/invocation_cache/invocation_cache_common.py
new file mode 100644
index 0000000000..6ce2d02f3b
--- /dev/null
+++ b/invokeai/app/services/invocation_cache/invocation_cache_common.py
@@ -0,0 +1,9 @@
+from pydantic import BaseModel, Field
+
+
+class InvocationCacheStatus(BaseModel):
+ size: int = Field(description="The current size of the invocation cache")
+ hits: int = Field(description="The number of cache hits")
+ misses: int = Field(description="The number of cache misses")
+ enabled: bool = Field(description="Whether the invocation cache is enabled")
+ max_size: int = Field(description="The maximum size of the invocation cache")
diff --git a/invokeai/app/services/invocation_cache/invocation_cache_memory.py b/invokeai/app/services/invocation_cache/invocation_cache_memory.py
index 4c0eb2106f..a1910a0bd8 100644
--- a/invokeai/app/services/invocation_cache/invocation_cache_memory.py
+++ b/invokeai/app/services/invocation_cache/invocation_cache_memory.py
@@ -3,18 +3,25 @@ from typing import Optional, Union
from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput
from invokeai.app.services.invocation_cache.invocation_cache_base import InvocationCacheBase
+from invokeai.app.services.invocation_cache.invocation_cache_common import InvocationCacheStatus
from invokeai.app.services.invoker import Invoker
class MemoryInvocationCache(InvocationCacheBase):
__cache: dict[Union[int, str], tuple[BaseInvocationOutput, str]]
__max_cache_size: int
+ __disabled: bool
+ __hits: int
+ __misses: int
__cache_ids: Queue
__invoker: Invoker
def __init__(self, max_cache_size: int = 0) -> None:
self.__cache = dict()
self.__max_cache_size = max_cache_size
+ self.__disabled = False
+ self.__hits = 0
+ self.__misses = 0
self.__cache_ids = Queue()
def start(self, invoker: Invoker) -> None:
@@ -25,15 +32,17 @@ class MemoryInvocationCache(InvocationCacheBase):
self.__invoker.services.latents.on_deleted(self._delete_by_match)
def get(self, key: Union[int, str]) -> Optional[BaseInvocationOutput]:
- if self.__max_cache_size == 0:
+ if self.__max_cache_size == 0 or self.__disabled:
return
item = self.__cache.get(key, None)
if item is not None:
+ self.__hits += 1
return item[0]
+ self.__misses += 1
def save(self, key: Union[int, str], invocation_output: BaseInvocationOutput) -> None:
- if self.__max_cache_size == 0:
+ if self.__max_cache_size == 0 or self.__disabled:
return
if key not in self.__cache:
@@ -47,24 +56,41 @@ class MemoryInvocationCache(InvocationCacheBase):
pass
def delete(self, key: Union[int, str]) -> None:
- if self.__max_cache_size == 0:
+ if self.__max_cache_size == 0 or self.__disabled:
return
if key in self.__cache:
del self.__cache[key]
def clear(self, *args, **kwargs) -> None:
- if self.__max_cache_size == 0:
+ if self.__max_cache_size == 0 or self.__disabled:
return
self.__cache.clear()
self.__cache_ids = Queue()
+ self.__misses = 0
+ self.__hits = 0
def create_key(self, invocation: BaseInvocation) -> int:
return hash(invocation.json(exclude={"id"}))
+ def disable(self) -> None:
+ self.__disabled = True
+
+ def enable(self) -> None:
+ self.__disabled = False
+
+ def get_status(self) -> InvocationCacheStatus:
+ return InvocationCacheStatus(
+ hits=self.__hits,
+ misses=self.__misses,
+ enabled=not self.__disabled,
+ size=len(self.__cache),
+ max_size=self.__max_cache_size,
+ )
+
def _delete_by_match(self, to_match: str) -> None:
- if self.__max_cache_size == 0:
+ if self.__max_cache_size == 0 or self.__disabled:
return
keys_to_delete = set()
diff --git a/invokeai/app/services/session_queue/session_queue_common.py b/invokeai/app/services/session_queue/session_queue_common.py
index a7032c7f1f..905e568fa1 100644
--- a/invokeai/app/services/session_queue/session_queue_common.py
+++ b/invokeai/app/services/session_queue/session_queue_common.py
@@ -162,15 +162,15 @@ class SessionQueueItemWithoutGraph(BaseModel):
session_id: str = Field(
description="The ID of the session associated with this queue item. The session doesn't exist in graph_executions until the queue item is executed."
)
- field_values: Optional[list[NodeFieldValue]] = Field(
- default=None, description="The field values that were used for this queue item"
- )
- queue_id: str = Field(description="The id of the queue with which this item is associated")
error: Optional[str] = Field(default=None, description="The error message if this queue item errored")
created_at: Union[datetime.datetime, str] = Field(description="When this queue item was created")
updated_at: Union[datetime.datetime, str] = Field(description="When this queue item was updated")
started_at: Optional[Union[datetime.datetime, str]] = Field(description="When this queue item was started")
completed_at: Optional[Union[datetime.datetime, str]] = Field(description="When this queue item was completed")
+ queue_id: str = Field(description="The id of the queue with which this item is associated")
+ field_values: Optional[list[NodeFieldValue]] = Field(
+ default=None, description="The field values that were used for this queue item"
+ )
@classmethod
def from_dict(cls, queue_item_dict: dict) -> "SessionQueueItemDTO":
diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index f48df3368f..875b611026 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -264,6 +264,22 @@
"graphQueued": "Graph queued",
"graphFailedToQueue": "Failed to queue graph"
},
+ "invocationCache": {
+ "invocationCache": "Invocation Cache",
+ "cacheSize": "Cache Size",
+ "maxCacheSize": "Max Cache Size",
+ "hits": "Cache Hits",
+ "misses": "Cache Misses",
+ "clear": "Clear",
+ "clearSucceeded": "Invocation Cache Cleared",
+ "clearFailed": "Problem Clearing Invocation Cache",
+ "enable": "Enable",
+ "enableSucceeded": "Invocation Cache Enabled",
+ "enableFailed": "Problem Enabling Invocation Cache",
+ "disable": "Disable",
+ "disableSucceeded": "Invocation Cache Disabled",
+ "disableFailed": "Problem Disabling Invocation Cache"
+ },
"gallery": {
"allImagesLoaded": "All Images Loaded",
"assets": "Assets",
diff --git a/invokeai/frontend/web/src/app/types/invokeai.ts b/invokeai/frontend/web/src/app/types/invokeai.ts
index ca2e80535c..2cb2173b19 100644
--- a/invokeai/frontend/web/src/app/types/invokeai.ts
+++ b/invokeai/frontend/web/src/app/types/invokeai.ts
@@ -21,7 +21,8 @@ export type AppFeature =
| 'multiselect'
| 'pauseQueue'
| 'resumeQueue'
- | 'prependQueue';
+ | 'prependQueue'
+ | 'invocationCache';
/**
* A disable-able Stable Diffusion feature
diff --git a/invokeai/frontend/web/src/features/queue/components/ClearInvocationCacheButton.tsx b/invokeai/frontend/web/src/features/queue/components/ClearInvocationCacheButton.tsx
new file mode 100644
index 0000000000..941bc88125
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/components/ClearInvocationCacheButton.tsx
@@ -0,0 +1,22 @@
+import IAIButton from 'common/components/IAIButton';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useClearInvocationCache } from '../hooks/useClearInvocationCache';
+
+const ClearInvocationCacheButton = () => {
+ const { t } = useTranslation();
+ const { clearInvocationCache, isDisabled, isLoading } =
+ useClearInvocationCache();
+
+ return (
+
+ {t('invocationCache.clear')}
+
+ );
+};
+
+export default memo(ClearInvocationCacheButton);
diff --git a/invokeai/frontend/web/src/features/queue/components/InvocationCacheStatus.tsx b/invokeai/frontend/web/src/features/queue/components/InvocationCacheStatus.tsx
new file mode 100644
index 0000000000..7af2c99bcf
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/components/InvocationCacheStatus.tsx
@@ -0,0 +1,45 @@
+import { ButtonGroup } from '@chakra-ui/react';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo';
+import ClearInvocationCacheButton from './ClearInvocationCacheButton';
+import ToggleInvocationCacheButton from './ToggleInvocationCacheButton';
+import StatusStatGroup from './common/StatusStatGroup';
+import StatusStatItem from './common/StatusStatItem';
+
+const InvocationCacheStatus = () => {
+ const { data: cacheStatus } = useGetInvocationCacheStatusQuery(undefined, {
+ pollingInterval: 5000,
+ });
+ const { t } = useTranslation();
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default memo(InvocationCacheStatus);
diff --git a/invokeai/frontend/web/src/features/queue/components/QueueStatus.tsx b/invokeai/frontend/web/src/features/queue/components/QueueStatus.tsx
index 72dfdd77ad..a91d3c4679 100644
--- a/invokeai/frontend/web/src/features/queue/components/QueueStatus.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/QueueStatus.tsx
@@ -1,38 +1,39 @@
-import { Stat, StatGroup, StatLabel, StatNumber } from '@chakra-ui/react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
+import StatusStatGroup from './common/StatusStatGroup';
+import StatusStatItem from './common/StatusStatItem';
const QueueStatus = () => {
const { data: queueStatus } = useGetQueueStatusQuery();
const { t } = useTranslation();
return (
-
-
- {t('queue.in_progress')}
- {queueStatus?.queue.in_progress ?? 0}
-
-
- {t('queue.pending')}
- {queueStatus?.queue.pending ?? 0}
-
-
- {t('queue.completed')}
- {queueStatus?.queue.completed ?? 0}
-
-
- {t('queue.failed')}
- {queueStatus?.queue.failed ?? 0}
-
-
- {t('queue.canceled')}
- {queueStatus?.queue.canceled ?? 0}
-
-
- {t('queue.total')}
- {queueStatus?.queue.total}
-
-
+
+
+
+
+
+
+
+
);
};
diff --git a/invokeai/frontend/web/src/features/queue/components/QueueTabContent.tsx b/invokeai/frontend/web/src/features/queue/components/QueueTabContent.tsx
index 254e34c453..23e2fe399f 100644
--- a/invokeai/frontend/web/src/features/queue/components/QueueTabContent.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/QueueTabContent.tsx
@@ -1,16 +1,14 @@
-import { Box, ButtonGroup, Flex } from '@chakra-ui/react';
+import { Box, Flex } from '@chakra-ui/react';
import { memo } from 'react';
-import ClearQueueButton from './ClearQueueButton';
-import PauseProcessorButton from './PauseProcessorButton';
-import PruneQueueButton from './PruneQueueButton';
+import { useFeatureStatus } from '../../system/hooks/useFeatureStatus';
+import InvocationCacheStatus from './InvocationCacheStatus';
import QueueList from './QueueList/QueueList';
import QueueStatus from './QueueStatus';
-import ResumeProcessorButton from './ResumeProcessorButton';
-import { useFeatureStatus } from '../../system/hooks/useFeatureStatus';
+import QueueTabQueueControls from './QueueTabQueueControls';
const QueueTabContent = () => {
- const isPauseEnabled = useFeatureStatus('pauseQueue').isFeatureEnabled;
- const isResumeEnabled = useFeatureStatus('resumeQueue').isFeatureEnabled;
+ const isInvocationCacheEnabled =
+ useFeatureStatus('invocationCache').isFeatureEnabled;
return (
{
gap={2}
>
-
- {isPauseEnabled || isResumeEnabled ? (
-
- {isResumeEnabled ? : <>>}
- {isPauseEnabled ? : <>>}
-
- ) : (
- <>>
- )}
-
-
-
-
-
-
-
-
- {/*
-
- */}
+
+
+ {isInvocationCacheEnabled && }
diff --git a/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx b/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx
new file mode 100644
index 0000000000..f8bf5d64d5
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx
@@ -0,0 +1,30 @@
+import { ButtonGroup, Flex } from '@chakra-ui/react';
+import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
+import { memo } from 'react';
+import ClearQueueButton from './ClearQueueButton';
+import PauseProcessorButton from './PauseProcessorButton';
+import PruneQueueButton from './PruneQueueButton';
+import ResumeProcessorButton from './ResumeProcessorButton';
+
+const QueueTabQueueControls = () => {
+ const isPauseEnabled = useFeatureStatus('pauseQueue').isFeatureEnabled;
+ const isResumeEnabled = useFeatureStatus('resumeQueue').isFeatureEnabled;
+ return (
+
+ {isPauseEnabled || isResumeEnabled ? (
+
+ {isResumeEnabled ? : <>>}
+ {isPauseEnabled ? : <>>}
+
+ ) : (
+ <>>
+ )}
+
+
+
+
+
+ );
+};
+
+export default memo(QueueTabQueueControls);
diff --git a/invokeai/frontend/web/src/features/queue/components/ToggleInvocationCacheButton.tsx b/invokeai/frontend/web/src/features/queue/components/ToggleInvocationCacheButton.tsx
new file mode 100644
index 0000000000..94b0fc82ae
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/components/ToggleInvocationCacheButton.tsx
@@ -0,0 +1,47 @@
+import IAIButton from 'common/components/IAIButton';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo';
+import { useDisableInvocationCache } from '../hooks/useDisableInvocationCache';
+import { useEnableInvocationCache } from '../hooks/useEnableInvocationCache';
+
+const ToggleInvocationCacheButton = () => {
+ const { t } = useTranslation();
+ const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
+
+ const {
+ enableInvocationCache,
+ isDisabled: isEnableDisabled,
+ isLoading: isEnableLoading,
+ } = useEnableInvocationCache();
+
+ const {
+ disableInvocationCache,
+ isDisabled: isDisableDisabled,
+ isLoading: isDisableLoading,
+ } = useDisableInvocationCache();
+
+ if (cacheStatus?.enabled) {
+ return (
+
+ {t('invocationCache.disable')}
+
+ );
+ }
+
+ return (
+
+ {t('invocationCache.enable')}
+
+ );
+};
+
+export default memo(ToggleInvocationCacheButton);
diff --git a/invokeai/frontend/web/src/features/queue/components/VerticalQueueControls.tsx b/invokeai/frontend/web/src/features/queue/components/VerticalQueueControls.tsx
deleted file mode 100644
index 665eaf190a..0000000000
--- a/invokeai/frontend/web/src/features/queue/components/VerticalQueueControls.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { ButtonGroup, ButtonGroupProps, Flex } from '@chakra-ui/react';
-import { memo } from 'react';
-import ClearQueueButton from './ClearQueueButton';
-import PauseProcessorButton from './PauseProcessorButton';
-import PruneQueueButton from './PruneQueueButton';
-import ResumeProcessorButton from './ResumeProcessorButton';
-
-type Props = ButtonGroupProps & {
- asIconButtons?: boolean;
-};
-
-const VerticalQueueControls = ({ asIconButtons, ...rest }: Props) => {
- return (
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default memo(VerticalQueueControls);
diff --git a/invokeai/frontend/web/src/features/queue/components/common/StatusStatGroup.tsx b/invokeai/frontend/web/src/features/queue/components/common/StatusStatGroup.tsx
new file mode 100644
index 0000000000..b083715472
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/components/common/StatusStatGroup.tsx
@@ -0,0 +1,22 @@
+import { StatGroup, StatGroupProps } from '@chakra-ui/react';
+import { memo } from 'react';
+
+const StatusStatGroup = ({ children, ...rest }: StatGroupProps) => (
+
+ {children}
+
+);
+
+export default memo(StatusStatGroup);
diff --git a/invokeai/frontend/web/src/features/queue/components/common/StatusStatItem.tsx b/invokeai/frontend/web/src/features/queue/components/common/StatusStatItem.tsx
new file mode 100644
index 0000000000..92100d4040
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/components/common/StatusStatItem.tsx
@@ -0,0 +1,47 @@
+import {
+ ChakraProps,
+ Stat,
+ StatLabel,
+ StatNumber,
+ StatProps,
+} from '@chakra-ui/react';
+import { memo } from 'react';
+
+const sx: ChakraProps['sx'] = {
+ '&[aria-disabled="true"]': {
+ color: 'base.400',
+ _dark: {
+ color: 'base.500',
+ },
+ },
+};
+
+type Props = Omit & {
+ label: string;
+ value: string | number;
+ isDisabled?: boolean;
+};
+
+const StatusStatItem = ({
+ label,
+ value,
+ isDisabled = false,
+ ...rest
+}: Props) => (
+
+
+ {label}
+
+ {value}
+
+);
+
+export default memo(StatusStatItem);
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useClearInvocationCache.ts b/invokeai/frontend/web/src/features/queue/hooks/useClearInvocationCache.ts
new file mode 100644
index 0000000000..ce2fc0e46b
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/hooks/useClearInvocationCache.ts
@@ -0,0 +1,48 @@
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { addToast } from 'features/system/store/systemSlice';
+import { useCallback, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ useClearInvocationCacheMutation,
+ useGetInvocationCacheStatusQuery,
+} from 'services/api/endpoints/appInfo';
+
+export const useClearInvocationCache = () => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
+ const isConnected = useAppSelector((state) => state.system.isConnected);
+ const [trigger, { isLoading }] = useClearInvocationCacheMutation({
+ fixedCacheKey: 'clearInvocationCache',
+ });
+
+ const isDisabled = useMemo(
+ () => !cacheStatus?.size || !isConnected,
+ [cacheStatus?.size, isConnected]
+ );
+
+ const clearInvocationCache = useCallback(async () => {
+ if (isDisabled) {
+ return;
+ }
+
+ try {
+ await trigger().unwrap();
+ dispatch(
+ addToast({
+ title: t('invocationCache.clearSucceeded'),
+ status: 'success',
+ })
+ );
+ } catch {
+ dispatch(
+ addToast({
+ title: t('invocationCache.clearFailed'),
+ status: 'error',
+ })
+ );
+ }
+ }, [isDisabled, trigger, dispatch, t]);
+
+ return { clearInvocationCache, isLoading, cacheStatus, isDisabled };
+};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useDisableInvocationCache.ts b/invokeai/frontend/web/src/features/queue/hooks/useDisableInvocationCache.ts
new file mode 100644
index 0000000000..6f0f41498a
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/hooks/useDisableInvocationCache.ts
@@ -0,0 +1,48 @@
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { addToast } from 'features/system/store/systemSlice';
+import { useCallback, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ useDisableInvocationCacheMutation,
+ useGetInvocationCacheStatusQuery,
+} from 'services/api/endpoints/appInfo';
+
+export const useDisableInvocationCache = () => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
+ const isConnected = useAppSelector((state) => state.system.isConnected);
+ const [trigger, { isLoading }] = useDisableInvocationCacheMutation({
+ fixedCacheKey: 'disableInvocationCache',
+ });
+
+ const isDisabled = useMemo(
+ () => !cacheStatus?.enabled || !isConnected,
+ [cacheStatus?.enabled, isConnected]
+ );
+
+ const disableInvocationCache = useCallback(async () => {
+ if (isDisabled) {
+ return;
+ }
+
+ try {
+ await trigger().unwrap();
+ dispatch(
+ addToast({
+ title: t('invocationCache.disableSucceeded'),
+ status: 'success',
+ })
+ );
+ } catch {
+ dispatch(
+ addToast({
+ title: t('invocationCache.disableFailed'),
+ status: 'error',
+ })
+ );
+ }
+ }, [isDisabled, trigger, dispatch, t]);
+
+ return { disableInvocationCache, isLoading, cacheStatus, isDisabled };
+};
diff --git a/invokeai/frontend/web/src/features/queue/hooks/useEnableInvocationCache.ts b/invokeai/frontend/web/src/features/queue/hooks/useEnableInvocationCache.ts
new file mode 100644
index 0000000000..f061dc97ec
--- /dev/null
+++ b/invokeai/frontend/web/src/features/queue/hooks/useEnableInvocationCache.ts
@@ -0,0 +1,48 @@
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { addToast } from 'features/system/store/systemSlice';
+import { useCallback, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ useEnableInvocationCacheMutation,
+ useGetInvocationCacheStatusQuery,
+} from 'services/api/endpoints/appInfo';
+
+export const useEnableInvocationCache = () => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
+ const isConnected = useAppSelector((state) => state.system.isConnected);
+ const [trigger, { isLoading }] = useEnableInvocationCacheMutation({
+ fixedCacheKey: 'enableInvocationCache',
+ });
+
+ const isDisabled = useMemo(
+ () => cacheStatus?.enabled || !isConnected,
+ [cacheStatus?.enabled, isConnected]
+ );
+
+ const enableInvocationCache = useCallback(async () => {
+ if (isDisabled) {
+ return;
+ }
+
+ try {
+ await trigger().unwrap();
+ dispatch(
+ addToast({
+ title: t('invocationCache.enableSucceeded'),
+ status: 'success',
+ })
+ );
+ } catch {
+ dispatch(
+ addToast({
+ title: t('invocationCache.enableFailed'),
+ status: 'error',
+ })
+ );
+ }
+ }, [isDisabled, trigger, dispatch, t]);
+
+ return { enableInvocationCache, isLoading, cacheStatus, isDisabled };
+};
diff --git a/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts b/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts
index 2d3537998d..ffe2afd2f6 100644
--- a/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts
+++ b/invokeai/frontend/web/src/services/api/endpoints/appInfo.ts
@@ -1,4 +1,5 @@
import { api } from '..';
+import { paths } from '../schema';
import { AppConfig, AppVersion } from '../types';
export const appInfoApi = api.injectEndpoints({
@@ -19,7 +20,45 @@ export const appInfoApi = api.injectEndpoints({
providesTags: ['AppConfig'],
keepUnusedDataFor: 86400000, // 1 day
}),
+ getInvocationCacheStatus: build.query<
+ paths['/api/v1/app/invocation_cache/status']['get']['responses']['200']['content']['application/json'],
+ void
+ >({
+ query: () => ({
+ url: `app/invocation_cache/status`,
+ method: 'GET',
+ }),
+ providesTags: ['InvocationCacheStatus'],
+ }),
+ clearInvocationCache: build.mutation({
+ query: () => ({
+ url: `app/invocation_cache`,
+ method: 'DELETE',
+ }),
+ invalidatesTags: ['InvocationCacheStatus'],
+ }),
+ enableInvocationCache: build.mutation({
+ query: () => ({
+ url: `app/invocation_cache/enable`,
+ method: 'PUT',
+ }),
+ invalidatesTags: ['InvocationCacheStatus'],
+ }),
+ disableInvocationCache: build.mutation({
+ query: () => ({
+ url: `app/invocation_cache/disable`,
+ method: 'PUT',
+ }),
+ invalidatesTags: ['InvocationCacheStatus'],
+ }),
}),
});
-export const { useGetAppVersionQuery, useGetAppConfigQuery } = appInfoApi;
+export const {
+ useGetAppVersionQuery,
+ useGetAppConfigQuery,
+ useClearInvocationCacheMutation,
+ useDisableInvocationCacheMutation,
+ useEnableInvocationCacheMutation,
+ useGetInvocationCacheStatusQuery,
+} = appInfoApi;
diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts
index 8b4f886bb0..b39b11af29 100644
--- a/invokeai/frontend/web/src/services/api/index.ts
+++ b/invokeai/frontend/web/src/services/api/index.ts
@@ -24,6 +24,7 @@ export const tagTypes = [
'SessionQueueStatus',
'SessionProcessorStatus',
'BatchStatus',
+ 'InvocationCacheStatus',
];
export type ApiTagDescription = TagDescription<(typeof tagTypes)[number]>;
export const LIST_TAG = 'LIST';
diff --git a/invokeai/frontend/web/src/services/api/schema.d.ts b/invokeai/frontend/web/src/services/api/schema.d.ts
index 26cb798594..87c0c6828d 100644
--- a/invokeai/frontend/web/src/services/api/schema.d.ts
+++ b/invokeai/frontend/web/src/services/api/schema.d.ts
@@ -317,6 +317,34 @@ export type paths = {
*/
post: operations["set_log_level"];
};
+ "/api/v1/app/invocation_cache": {
+ /**
+ * Clear Invocation Cache
+ * @description Clears the invocation cache
+ */
+ delete: operations["clear_invocation_cache"];
+ };
+ "/api/v1/app/invocation_cache/enable": {
+ /**
+ * Enable Invocation Cache
+ * @description Clears the invocation cache
+ */
+ put: operations["enable_invocation_cache"];
+ };
+ "/api/v1/app/invocation_cache/disable": {
+ /**
+ * Disable Invocation Cache
+ * @description Clears the invocation cache
+ */
+ put: operations["disable_invocation_cache"];
+ };
+ "/api/v1/app/invocation_cache/status": {
+ /**
+ * Get Invocation Cache Status
+ * @description Clears the invocation cache
+ */
+ get: operations["get_invocation_cache_status"];
+ };
"/api/v1/queue/{queue_id}/enqueue_graph": {
/**
* Enqueue Graph
@@ -1259,11 +1287,6 @@ export type components = {
* @default true
*/
use_cache?: boolean;
- /**
- * CLIP
- * @description CLIP (tokenizer, text encoder, LoRAs) and skipped layer count
- */
- clip?: components["schemas"]["ClipField"];
/**
* Skipped Layers
* @description Number of layers to skip in text encoder
@@ -1276,6 +1299,11 @@ export type components = {
* @enum {string}
*/
type: "clip_skip";
+ /**
+ * CLIP
+ * @description CLIP (tokenizer, text encoder, LoRAs) and skipped layer count
+ */
+ clip?: components["schemas"]["ClipField"];
};
/**
* ClipSkipInvocationOutput
@@ -2020,7 +2048,7 @@ export type components = {
* Clip Skip
* @description The number of skipped CLIP layers
*/
- clip_skip: number;
+ clip_skip?: number;
/**
* Model
* @description The main model used for inference
@@ -2309,10 +2337,7 @@ export type components = {
* @enum {string}
*/
scheduler?: "ddim" | "ddpm" | "deis" | "lms" | "lms_k" | "pndm" | "heun" | "heun_k" | "euler" | "euler_k" | "euler_a" | "kdpm_2" | "kdpm_2_a" | "dpmpp_2s" | "dpmpp_2s_k" | "dpmpp_2m" | "dpmpp_2m_k" | "dpmpp_2m_sde" | "dpmpp_2m_sde_k" | "dpmpp_sde" | "dpmpp_sde_k" | "unipc";
- /**
- * Control
- * @description ControlNet(s) to apply
- */
+ /** Control */
control?: components["schemas"]["ControlField"] | components["schemas"]["ControlField"][];
/**
* IP-Adapter
@@ -4718,6 +4743,34 @@ export type components = {
*/
type: "integer_output";
};
+ /** InvocationCacheStatus */
+ InvocationCacheStatus: {
+ /**
+ * Size
+ * @description The current size of the invocation cache
+ */
+ size: number;
+ /**
+ * Hits
+ * @description The number of cache hits
+ */
+ hits: number;
+ /**
+ * Misses
+ * @description The number of cache misses
+ */
+ misses: number;
+ /**
+ * Enabled
+ * @description Whether the invocation cache is enabled
+ */
+ enabled: boolean;
+ /**
+ * Max Size
+ * @description The maximum size of the invocation cache
+ */
+ max_size: number;
+ };
/**
* IterateInvocation
* @description Iterates over a list of items
@@ -7497,11 +7550,6 @@ export type components = {
* @default false
*/
use_cache?: boolean;
- /**
- * Image
- * @description The image to load
- */
- image?: components["schemas"]["ImageField"];
/**
* Metadata
* @description Optional core metadata to be written to image
@@ -7513,6 +7561,11 @@ export type components = {
* @enum {string}
*/
type: "save_image";
+ /**
+ * Image
+ * @description The image to load
+ */
+ image?: components["schemas"]["ImageField"];
};
/**
* Scale Latents
@@ -9042,18 +9095,6 @@ export type components = {
/** Ui Order */
ui_order?: number;
};
- /**
- * IPAdapterModelFormat
- * @description An enumeration.
- * @enum {string}
- */
- IPAdapterModelFormat: "invokeai";
- /**
- * ControlNetModelFormat
- * @description An enumeration.
- * @enum {string}
- */
- ControlNetModelFormat: "checkpoint" | "diffusers";
/**
* StableDiffusionOnnxModelFormat
* @description An enumeration.
@@ -9066,24 +9107,36 @@ export type components = {
* @enum {string}
*/
StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
- /**
- * StableDiffusion1ModelFormat
- * @description An enumeration.
- * @enum {string}
- */
- StableDiffusion1ModelFormat: "checkpoint" | "diffusers";
/**
* CLIPVisionModelFormat
* @description An enumeration.
* @enum {string}
*/
CLIPVisionModelFormat: "diffusers";
+ /**
+ * StableDiffusion1ModelFormat
+ * @description An enumeration.
+ * @enum {string}
+ */
+ StableDiffusion1ModelFormat: "checkpoint" | "diffusers";
/**
* StableDiffusionXLModelFormat
* @description An enumeration.
* @enum {string}
*/
StableDiffusionXLModelFormat: "checkpoint" | "diffusers";
+ /**
+ * IPAdapterModelFormat
+ * @description An enumeration.
+ * @enum {string}
+ */
+ IPAdapterModelFormat: "invokeai";
+ /**
+ * ControlNetModelFormat
+ * @description An enumeration.
+ * @enum {string}
+ */
+ ControlNetModelFormat: "checkpoint" | "diffusers";
};
responses: never;
parameters: never;
@@ -10505,6 +10558,62 @@ export type operations = {
};
};
};
+ /**
+ * Clear Invocation Cache
+ * @description Clears the invocation cache
+ */
+ clear_invocation_cache: {
+ responses: {
+ /** @description The operation was successful */
+ 200: {
+ content: {
+ "application/json": unknown;
+ };
+ };
+ };
+ };
+ /**
+ * Enable Invocation Cache
+ * @description Clears the invocation cache
+ */
+ enable_invocation_cache: {
+ responses: {
+ /** @description The operation was successful */
+ 200: {
+ content: {
+ "application/json": unknown;
+ };
+ };
+ };
+ };
+ /**
+ * Disable Invocation Cache
+ * @description Clears the invocation cache
+ */
+ disable_invocation_cache: {
+ responses: {
+ /** @description The operation was successful */
+ 200: {
+ content: {
+ "application/json": unknown;
+ };
+ };
+ };
+ };
+ /**
+ * Get Invocation Cache Status
+ * @description Clears the invocation cache
+ */
+ get_invocation_cache_status: {
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ content: {
+ "application/json": components["schemas"]["InvocationCacheStatus"];
+ };
+ };
+ };
+ };
/**
* Enqueue Graph
* @description Enqueues a graph for single execution.