mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-13 17:14:56 -05:00
wip
This commit is contained in:
@@ -123,6 +123,8 @@ export default [
|
||||
},
|
||||
],
|
||||
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
{
|
||||
|
||||
@@ -5,6 +5,12 @@ import type { StudioInitAction } from 'app/hooks/useStudioInitAction';
|
||||
import { $didStudioInit } from 'app/hooks/useStudioInitAction';
|
||||
import type { LoggingOverrides } from 'app/logging/logger';
|
||||
import { $loggingOverrides, configureLogging } from 'app/logging/logger';
|
||||
import {
|
||||
$resetClientState,
|
||||
buildDriver,
|
||||
buildResetClientState,
|
||||
type StorageDriverApi,
|
||||
} from 'app/store/enhancers/reduxRemember/driver';
|
||||
import { $accountSettingsLink } from 'app/store/nanostores/accountSettingsLink';
|
||||
import { $authToken } from 'app/store/nanostores/authToken';
|
||||
import { $baseUrl } from 'app/store/nanostores/baseUrl';
|
||||
@@ -70,6 +76,7 @@ interface Props extends PropsWithChildren {
|
||||
* If provided, overrides in-app navigation to the model manager
|
||||
*/
|
||||
onClickGoToModelManager?: () => void;
|
||||
storageDriverApi?: StorageDriverApi;
|
||||
}
|
||||
|
||||
const InvokeAIUI = ({
|
||||
@@ -96,6 +103,7 @@ const InvokeAIUI = ({
|
||||
loggingOverrides,
|
||||
onClickGoToModelManager,
|
||||
whatsNew,
|
||||
storageDriverApi,
|
||||
}: Props) => {
|
||||
useLayoutEffect(() => {
|
||||
/*
|
||||
@@ -308,9 +316,18 @@ const InvokeAIUI = ({
|
||||
};
|
||||
}, [isDebugging]);
|
||||
|
||||
useEffect(() => {
|
||||
$resetClientState.set(buildResetClientState(storageDriverApi));
|
||||
|
||||
return () => {
|
||||
$resetClientState.set(() => {});
|
||||
};
|
||||
}, [storageDriverApi]);
|
||||
|
||||
const store = useMemo(() => {
|
||||
return createStore(projectId);
|
||||
}, [projectId]);
|
||||
const driver = buildDriver(storageDriverApi);
|
||||
return createStore(driver);
|
||||
}, [storageDriverApi]);
|
||||
|
||||
useEffect(() => {
|
||||
$store.set(store);
|
||||
|
||||
@@ -3,10 +3,17 @@ import { StorageError } from 'app/store/enhancers/reduxRemember/errors';
|
||||
import { $authToken } from 'app/store/nanostores/authToken';
|
||||
import { $projectId } from 'app/store/nanostores/projectId';
|
||||
import { $queueId } from 'app/store/nanostores/queueId';
|
||||
import { atom } from 'nanostores';
|
||||
import type { Driver } from 'redux-remember';
|
||||
import { getBaseUrl } from 'services/api';
|
||||
import { buildAppInfoUrl } from 'services/api/endpoints/appInfo';
|
||||
|
||||
export type StorageDriverApi = {
|
||||
getItem: (key: string) => Promise<any>;
|
||||
setItem: (key: string, value: any) => Promise<any>;
|
||||
clear: () => Promise<void>;
|
||||
};
|
||||
|
||||
const log = logger('system');
|
||||
|
||||
// Persistence happens per slice. To track when persistence is in progress, maintain a ref count, incrementing
|
||||
@@ -47,77 +54,94 @@ const getHeaders = (extra?: Record<string, string>) => {
|
||||
return headers;
|
||||
};
|
||||
|
||||
export const serverBackedDriver: Driver = {
|
||||
getItem: async (key) => {
|
||||
try {
|
||||
const url = getUrl(key);
|
||||
const headers = getHeaders();
|
||||
const res = await fetch(url, { headers, method: 'GET' });
|
||||
if (!res.ok) {
|
||||
throw new Error(`Response status: ${res.status}`);
|
||||
export const buildDriver = (api?: StorageDriverApi): Driver => {
|
||||
return {
|
||||
getItem: async (key) => {
|
||||
try {
|
||||
if (api) {
|
||||
log.trace(`Using provided API to get item for key "${key}"`);
|
||||
return await api.getItem(key);
|
||||
}
|
||||
const url = getUrl(key);
|
||||
const headers = getHeaders();
|
||||
const res = await fetch(url, { headers, method: 'GET' });
|
||||
if (!res.ok) {
|
||||
throw new Error(`Response status: ${res.status}`);
|
||||
}
|
||||
const json = await res.json();
|
||||
return json;
|
||||
} catch (originalError) {
|
||||
throw new StorageError({
|
||||
key,
|
||||
projectId: $projectId.get(),
|
||||
originalError,
|
||||
});
|
||||
}
|
||||
const json = await res.json();
|
||||
return json;
|
||||
} catch (originalError) {
|
||||
throw new StorageError({
|
||||
key,
|
||||
projectId: $projectId.get(),
|
||||
originalError,
|
||||
});
|
||||
}
|
||||
},
|
||||
setItem: async (key, value) => {
|
||||
try {
|
||||
persistRefCount++;
|
||||
// Deep equality check to avoid noop persist network requests.
|
||||
//
|
||||
// `redux-remember` persists individual slices of state, so we can implicity denylist a slice by not giving it a
|
||||
// persist config.
|
||||
//
|
||||
// However, we may need to avoid persisting individual _fields_ of a slice. `redux-remember` does not provide a
|
||||
// way to do this directly.
|
||||
//
|
||||
// To accomplish this, we add a layer of logic on top of the `redux-remember`. In the state serializer function
|
||||
// provided to `redux-remember`, we can omit certain fields from the state that we do not want to persist. See
|
||||
// the implementation in `store.ts` for this logic.
|
||||
//
|
||||
// This logic is unknown to `redux-remember`. When an omitted field changes, it will still attempt to persist the
|
||||
// whole slice, even if the final, _serialized_ slice value is unchanged.
|
||||
//
|
||||
// To avoid unnecessary network requests, we keep track of the last persisted state for each key. If the value to
|
||||
// be persisted is the same as the last persisted value, we skip the network request.
|
||||
if (lastPersistedState.get(key) === value) {
|
||||
log.trace(`Skipping persist for key "${key}" as value is unchanged.`);
|
||||
},
|
||||
setItem: async (key, value) => {
|
||||
try {
|
||||
persistRefCount++;
|
||||
if (api) {
|
||||
log.trace(`Using provided API to get item for key "${key}"`);
|
||||
return await api.setItem(key, value);
|
||||
}
|
||||
// Deep equality check to avoid noop persist network requests.
|
||||
//
|
||||
// `redux-remember` persists individual slices of state, so we can implicity denylist a slice by not giving it a
|
||||
// persist config.
|
||||
//
|
||||
// However, we may need to avoid persisting individual _fields_ of a slice. `redux-remember` does not provide a
|
||||
// way to do this directly.
|
||||
//
|
||||
// To accomplish this, we add a layer of logic on top of the `redux-remember`. In the state serializer function
|
||||
// provided to `redux-remember`, we can omit certain fields from the state that we do not want to persist. See
|
||||
// the implementation in `store.ts` for this logic.
|
||||
//
|
||||
// This logic is unknown to `redux-remember`. When an omitted field changes, it will still attempt to persist the
|
||||
// whole slice, even if the final, _serialized_ slice value is unchanged.
|
||||
//
|
||||
// To avoid unnecessary network requests, we keep track of the last persisted state for each key. If the value to
|
||||
// be persisted is the same as the last persisted value, we skip the network request.
|
||||
if (lastPersistedState.get(key) === value) {
|
||||
log.trace(`Skipping persist for key "${key}" as value is unchanged.`);
|
||||
return value;
|
||||
}
|
||||
const url = getUrl(key);
|
||||
const headers = getHeaders({ 'content-type': 'application/json' });
|
||||
const res = await fetch(url, { headers, method: 'POST', body: JSON.stringify(value) });
|
||||
if (!res.ok) {
|
||||
throw new Error(`Response status: ${res.status}`);
|
||||
}
|
||||
lastPersistedState.set(key, value);
|
||||
return value;
|
||||
} catch (originalError) {
|
||||
throw new StorageError({
|
||||
key,
|
||||
value,
|
||||
projectId: $projectId.get(),
|
||||
originalError,
|
||||
});
|
||||
} finally {
|
||||
persistRefCount--;
|
||||
if (persistRefCount < 0) {
|
||||
log.warn('Persist ref count is negative, resetting to 0');
|
||||
persistRefCount = 0;
|
||||
}
|
||||
}
|
||||
const url = getUrl(key);
|
||||
const headers = getHeaders({ 'content-type': 'application/json' });
|
||||
const res = await fetch(url, { headers, method: 'POST', body: JSON.stringify(value) });
|
||||
if (!res.ok) {
|
||||
throw new Error(`Response status: ${res.status}`);
|
||||
}
|
||||
lastPersistedState.set(key, value);
|
||||
return value;
|
||||
} catch (originalError) {
|
||||
throw new StorageError({
|
||||
key,
|
||||
value,
|
||||
projectId: $projectId.get(),
|
||||
originalError,
|
||||
});
|
||||
} finally {
|
||||
persistRefCount--;
|
||||
if (persistRefCount < 0) {
|
||||
log.warn('Persist ref count is negative, resetting to 0');
|
||||
persistRefCount = 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const resetClientState = async () => {
|
||||
export const $resetClientState = atom(() => {});
|
||||
|
||||
export const buildResetClientState = (api?: StorageDriverApi) => async () => {
|
||||
try {
|
||||
persistRefCount++;
|
||||
if (api) {
|
||||
log.trace('Using provided API to reset client state');
|
||||
await api.clear();
|
||||
return;
|
||||
}
|
||||
const url = getUrl();
|
||||
const headers = getHeaders();
|
||||
const res = await fetch(url, { headers, method: 'DELETE' });
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ThunkDispatch, TypedStartListening, UnknownAction } from '@reduxjs/toolkit';
|
||||
import { addListener, combineReducers, configureStore, createListenerMiddleware } from '@reduxjs/toolkit';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { serverBackedDriver } from 'app/store/enhancers/reduxRemember/driver';
|
||||
import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
|
||||
import { addAdHocPostProcessingRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener';
|
||||
import { addAnyEnqueuedListener } from 'app/store/middleware/listenerMiddleware/listeners/anyEnqueued';
|
||||
@@ -41,7 +40,7 @@ import { systemSliceConfig } from 'features/system/store/systemSlice';
|
||||
import { uiSliceConfig } from 'features/ui/store/uiSlice';
|
||||
import { diff } from 'jsondiffpatch';
|
||||
import dynamicMiddlewares from 'redux-dynamic-middlewares';
|
||||
import type { SerializeFunction, UnserializeFunction } from 'redux-remember';
|
||||
import type { Driver, SerializeFunction, UnserializeFunction } from 'redux-remember';
|
||||
import { rememberEnhancer, rememberReducer } from 'redux-remember';
|
||||
import undoable, { newHistory } from 'redux-undo';
|
||||
import { serializeError } from 'serialize-error';
|
||||
@@ -185,7 +184,7 @@ const PERSISTED_KEYS = Object.values(SLICE_CONFIGS)
|
||||
.filter((sliceConfig) => !!sliceConfig.persistConfig)
|
||||
.map((sliceConfig) => sliceConfig.slice.reducerPath);
|
||||
|
||||
export const createStore = (uniqueStoreKey?: string, persist = true) =>
|
||||
export const createStore = (driver: Driver, persist = true) =>
|
||||
configureStore({
|
||||
reducer: rememberedRootReducer,
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
@@ -204,7 +203,7 @@ export const createStore = (uniqueStoreKey?: string, persist = true) =>
|
||||
const enhancers = getDefaultEnhancers();
|
||||
if (persist) {
|
||||
const res = enhancers.prepend(
|
||||
rememberEnhancer(serverBackedDriver, PERSISTED_KEYS, {
|
||||
rememberEnhancer(driver, PERSISTED_KEYS, {
|
||||
persistThrottle: 2000,
|
||||
serialize,
|
||||
unserialize,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { resetClientState } from 'app/store/enhancers/reduxRemember/driver';
|
||||
import { $resetClientState } from 'app/store/enhancers/reduxRemember/driver';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useClearStorage = () => {
|
||||
const clearStorage = useCallback(() => {
|
||||
// clearIdbKeyValStore();
|
||||
resetClientState();
|
||||
$resetClientState.get()();
|
||||
localStorage.clear();
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
export { default as InvokeAIUI } from './app/components/InvokeAIUI';
|
||||
export type { StudioInitAction } from './app/hooks/useStudioInitAction';
|
||||
export type { LoggingOverrides } from './app/logging/logger';
|
||||
export type { StorageDriverApi } from './app/store/enhancers/reduxRemember/driver';
|
||||
export type { PartialAppConfig } from './app/types/invokeai';
|
||||
export { default as HotkeysModal } from './features/system/components/HotkeysModal/HotkeysModal';
|
||||
export { default as InvokeAiLogoComponent } from './features/system/components/InvokeAILogoComponent';
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
import type { StorageDriverApi } from 'app/store/enhancers/reduxRemember/driver';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
|
||||
import InvokeAIUI from './app/components/InvokeAIUI';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<InvokeAIUI />);
|
||||
let state: Record<string, any> = {};
|
||||
const storageDriverApi: StorageDriverApi = {
|
||||
getItem: (key: string) => {
|
||||
return Promise.resolve(state[key]);
|
||||
},
|
||||
setItem: (key: string, value: any) => {
|
||||
state[key] = value;
|
||||
return Promise.resolve(value);
|
||||
},
|
||||
clear: () => {
|
||||
state = {};
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<InvokeAIUI storageDriverApi={storageDriverApi} />
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user