mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): add debug mode & socketOptions
This commit is contained in:
parent
317b5ebae1
commit
591b601fd3
@ -20,6 +20,7 @@ import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
|
|||||||
import GlobalHotkeys from './GlobalHotkeys';
|
import GlobalHotkeys from './GlobalHotkeys';
|
||||||
import PreselectedImage from './PreselectedImage';
|
import PreselectedImage from './PreselectedImage';
|
||||||
import Toaster from './Toaster';
|
import Toaster from './Toaster';
|
||||||
|
import { useSocketIO } from '../hooks/useSocketIO';
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {};
|
const DEFAULT_CONFIG = {};
|
||||||
|
|
||||||
@ -33,10 +34,12 @@ interface Props {
|
|||||||
|
|
||||||
const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
||||||
const language = useAppSelector(languageSelector);
|
const language = useAppSelector(languageSelector);
|
||||||
|
|
||||||
const logger = useLogger('system');
|
const logger = useLogger('system');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
// singleton!
|
||||||
|
useSocketIO();
|
||||||
|
|
||||||
const handleReset = useCallback(() => {
|
const handleReset = useCallback(() => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
location.reload();
|
location.reload();
|
||||||
|
@ -1,26 +1,27 @@
|
|||||||
import { Middleware } from '@reduxjs/toolkit';
|
import { Middleware } from '@reduxjs/toolkit';
|
||||||
import { store } from 'app/store/store';
|
|
||||||
import { PartialAppConfig } from 'app/types/invokeai';
|
|
||||||
import React, {
|
|
||||||
lazy,
|
|
||||||
memo,
|
|
||||||
PropsWithChildren,
|
|
||||||
ReactNode,
|
|
||||||
useEffect,
|
|
||||||
} from 'react';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
|
|
||||||
import { $authToken, $baseUrl, $projectId } from 'services/api/client';
|
|
||||||
import { socketMiddleware } from 'services/events/middleware';
|
|
||||||
import Loading from '../../common/components/Loading/Loading';
|
|
||||||
import '../../i18n';
|
|
||||||
import AppDndContext from '../../features/dnd/components/AppDndContext';
|
|
||||||
import { $customStarUI, CustomStarUi } from 'app/store/nanostores/customStarUI';
|
import { $customStarUI, CustomStarUi } from 'app/store/nanostores/customStarUI';
|
||||||
import { $headerComponent } from 'app/store/nanostores/headerComponent';
|
import { $headerComponent } from 'app/store/nanostores/headerComponent';
|
||||||
|
import { $isDebugging } from 'app/store/nanostores/isDebugging';
|
||||||
|
import { store } from 'app/store/store';
|
||||||
|
import { PartialAppConfig } from 'app/types/invokeai';
|
||||||
import {
|
import {
|
||||||
$queueId,
|
$queueId,
|
||||||
DEFAULT_QUEUE_ID,
|
DEFAULT_QUEUE_ID,
|
||||||
} from 'features/queue/store/queueNanoStore';
|
} from 'features/queue/store/queueNanoStore';
|
||||||
|
import React, {
|
||||||
|
PropsWithChildren,
|
||||||
|
ReactNode,
|
||||||
|
lazy,
|
||||||
|
memo,
|
||||||
|
useEffect,
|
||||||
|
} from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { $authToken, $baseUrl, $projectId } from 'services/api/client';
|
||||||
|
import { ManagerOptions, SocketOptions } from 'socket.io-client';
|
||||||
|
import Loading from '../../common/components/Loading/Loading';
|
||||||
|
import AppDndContext from '../../features/dnd/components/AppDndContext';
|
||||||
|
import '../../i18n';
|
||||||
|
import { $socketOptions } from '../hooks/useSocketIO';
|
||||||
|
|
||||||
const App = lazy(() => import('./App'));
|
const App = lazy(() => import('./App'));
|
||||||
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
||||||
@ -38,6 +39,8 @@ interface Props extends PropsWithChildren {
|
|||||||
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
||||||
};
|
};
|
||||||
customStarUi?: CustomStarUi;
|
customStarUi?: CustomStarUi;
|
||||||
|
socketOptions?: Partial<ManagerOptions & SocketOptions>;
|
||||||
|
isDebugging?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InvokeAIUI = ({
|
const InvokeAIUI = ({
|
||||||
@ -50,6 +53,8 @@ const InvokeAIUI = ({
|
|||||||
queueId,
|
queueId,
|
||||||
selectedImage,
|
selectedImage,
|
||||||
customStarUi,
|
customStarUi,
|
||||||
|
socketOptions,
|
||||||
|
isDebugging = false,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// configure API client token
|
// configure API client token
|
||||||
@ -72,21 +77,6 @@ const InvokeAIUI = ({
|
|||||||
$queueId.set(queueId);
|
$queueId.set(queueId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset dynamically added middlewares
|
|
||||||
resetMiddlewares();
|
|
||||||
|
|
||||||
// TODO: at this point, after resetting the middleware, we really ought to clean up the socket
|
|
||||||
// stuff by calling `dispatch(socketReset())`. but we cannot dispatch from here as we are
|
|
||||||
// outside the provider. it's not needed until there is the possibility that we will change
|
|
||||||
// the `apiUrl`/`token` dynamically.
|
|
||||||
|
|
||||||
// rebuild socket middleware with token and apiUrl
|
|
||||||
if (middleware && middleware.length > 0) {
|
|
||||||
addMiddleware(socketMiddleware(), ...middleware);
|
|
||||||
} else {
|
|
||||||
addMiddleware(socketMiddleware());
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// Reset the API client token and base url on unmount
|
// Reset the API client token and base url on unmount
|
||||||
$baseUrl.set(undefined);
|
$baseUrl.set(undefined);
|
||||||
@ -116,6 +106,24 @@ const InvokeAIUI = ({
|
|||||||
};
|
};
|
||||||
}, [headerComponent]);
|
}, [headerComponent]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (socketOptions) {
|
||||||
|
$socketOptions.set(socketOptions);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
$socketOptions.set({});
|
||||||
|
};
|
||||||
|
}, [socketOptions]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDebugging) {
|
||||||
|
$isDebugging.set(isDebugging);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
$isDebugging.set(false);
|
||||||
|
};
|
||||||
|
}, [isDebugging]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
96
invokeai/frontend/web/src/app/hooks/useSocketIO.ts
Normal file
96
invokeai/frontend/web/src/app/hooks/useSocketIO.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { $isDebugging } from 'app/store/nanostores/isDebugging';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { MapStore, WritableAtom, atom, map } from 'nanostores';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { $authToken, $baseUrl } from 'services/api/client';
|
||||||
|
import {
|
||||||
|
ClientToServerEvents,
|
||||||
|
ServerToClientEvents,
|
||||||
|
} from 'services/events/types';
|
||||||
|
import { setEventListeners } from 'services/events/util/setEventListeners';
|
||||||
|
import { ManagerOptions, Socket, SocketOptions, io } from 'socket.io-client';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
$socketOptions?: MapStore<Partial<ManagerOptions & SocketOptions>>;
|
||||||
|
$socketUrl?: WritableAtom<string>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const makeSocketOptions = (): Partial<ManagerOptions & SocketOptions> => {
|
||||||
|
const socketOptions: Parameters<typeof io>[0] = {
|
||||||
|
timeout: 60000,
|
||||||
|
path: '/ws/socket.io',
|
||||||
|
autoConnect: false, // achtung! removing this breaks the dynamic middleware
|
||||||
|
forceNew: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// if building in package mode, replace socket url with open api base url minus the http protocol
|
||||||
|
if (['nodes', 'package'].includes(import.meta.env.MODE)) {
|
||||||
|
const authToken = $authToken.get();
|
||||||
|
if (authToken) {
|
||||||
|
// TODO: handle providing jwt to socket.io
|
||||||
|
socketOptions.auth = { token: authToken };
|
||||||
|
}
|
||||||
|
|
||||||
|
socketOptions.transports = ['websocket', 'polling'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return socketOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeSocketUrl = (): string => {
|
||||||
|
const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
|
let socketUrl = `${wsProtocol}://${window.location.host}`;
|
||||||
|
if (['nodes', 'package'].includes(import.meta.env.MODE)) {
|
||||||
|
const baseUrl = $baseUrl.get();
|
||||||
|
if (baseUrl) {
|
||||||
|
//eslint-disable-next-line
|
||||||
|
socketUrl = baseUrl.replace(/^https?\:\/\//i, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return socketUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeSocket = (): Socket<ServerToClientEvents, ClientToServerEvents> => {
|
||||||
|
const socketOptions = makeSocketOptions();
|
||||||
|
const socketUrl = $socketUrl.get();
|
||||||
|
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
|
||||||
|
socketUrl,
|
||||||
|
{ ...socketOptions, ...$socketOptions.get() }
|
||||||
|
);
|
||||||
|
return socket;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const $socketOptions = map<Partial<ManagerOptions & SocketOptions>>({});
|
||||||
|
export const $socketUrl = atom<string>(makeSocketUrl());
|
||||||
|
|
||||||
|
export const useSocketIO = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const socketOptions = useStore($socketOptions);
|
||||||
|
const socketUrl = useStore($socketUrl);
|
||||||
|
const baseUrl = useStore($baseUrl);
|
||||||
|
const authToken = useStore($authToken);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const socket = makeSocket();
|
||||||
|
setEventListeners({ dispatch, socket });
|
||||||
|
socket.connect();
|
||||||
|
|
||||||
|
if ($isDebugging.get()) {
|
||||||
|
window.$socketOptions = $socketOptions;
|
||||||
|
window.$socketUrl = $socketUrl;
|
||||||
|
console.log('Socket initialized', socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if ($isDebugging.get()) {
|
||||||
|
window.$socketOptions = undefined;
|
||||||
|
window.$socketUrl = undefined;
|
||||||
|
console.log('Socket teardown', socket);
|
||||||
|
}
|
||||||
|
socket.disconnect();
|
||||||
|
};
|
||||||
|
}, [dispatch, socketOptions, socketUrl, baseUrl, authToken]);
|
||||||
|
};
|
@ -0,0 +1,3 @@
|
|||||||
|
import { atom } from 'nanostores';
|
||||||
|
|
||||||
|
export const $isDebugging = atom<boolean>(false);
|
@ -12,17 +12,16 @@ import deleteImageModalReducer from 'features/deleteImageModal/store/slice';
|
|||||||
import dynamicPromptsReducer from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
import dynamicPromptsReducer from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||||
import galleryReducer from 'features/gallery/store/gallerySlice';
|
import galleryReducer from 'features/gallery/store/gallerySlice';
|
||||||
import loraReducer from 'features/lora/store/loraSlice';
|
import loraReducer from 'features/lora/store/loraSlice';
|
||||||
|
import modelmanagerReducer from 'features/modelManager/store/modelManagerSlice';
|
||||||
import nodesReducer from 'features/nodes/store/nodesSlice';
|
import nodesReducer from 'features/nodes/store/nodesSlice';
|
||||||
import generationReducer from 'features/parameters/store/generationSlice';
|
import generationReducer from 'features/parameters/store/generationSlice';
|
||||||
import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
|
import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
|
||||||
|
import queueReducer from 'features/queue/store/queueSlice';
|
||||||
import sdxlReducer from 'features/sdxl/store/sdxlSlice';
|
import sdxlReducer from 'features/sdxl/store/sdxlSlice';
|
||||||
import configReducer from 'features/system/store/configSlice';
|
import configReducer from 'features/system/store/configSlice';
|
||||||
import systemReducer from 'features/system/store/systemSlice';
|
import systemReducer from 'features/system/store/systemSlice';
|
||||||
import queueReducer from 'features/queue/store/queueSlice';
|
|
||||||
import modelmanagerReducer from 'features/modelManager/store/modelManagerSlice';
|
|
||||||
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
|
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
|
||||||
import uiReducer from 'features/ui/store/uiSlice';
|
import uiReducer from 'features/ui/store/uiSlice';
|
||||||
import dynamicMiddlewares from 'redux-dynamic-middlewares';
|
|
||||||
import { rememberEnhancer, rememberReducer } from 'redux-remember';
|
import { rememberEnhancer, rememberReducer } from 'redux-remember';
|
||||||
import { api } from 'services/api';
|
import { api } from 'services/api';
|
||||||
import { LOCALSTORAGE_PREFIX } from './constants';
|
import { LOCALSTORAGE_PREFIX } from './constants';
|
||||||
@ -94,7 +93,6 @@ export const store = configureStore({
|
|||||||
immutableCheck: false,
|
immutableCheck: false,
|
||||||
})
|
})
|
||||||
.concat(api.middleware)
|
.concat(api.middleware)
|
||||||
.concat(dynamicMiddlewares)
|
|
||||||
.prepend(listenerMiddleware.middleware),
|
.prepend(listenerMiddleware.middleware),
|
||||||
devTools: {
|
devTools: {
|
||||||
actionSanitizer,
|
actionSanitizer,
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
import { Middleware, MiddlewareAPI } from '@reduxjs/toolkit';
|
|
||||||
import { AppThunkDispatch, RootState } from 'app/store/store';
|
|
||||||
import { $authToken, $baseUrl } from 'services/api/client';
|
|
||||||
import {
|
|
||||||
ClientToServerEvents,
|
|
||||||
ServerToClientEvents,
|
|
||||||
} from 'services/events/types';
|
|
||||||
import { setEventListeners } from 'services/events/util/setEventListeners';
|
|
||||||
import { Socket, io } from 'socket.io-client';
|
|
||||||
|
|
||||||
export const socketMiddleware = () => {
|
|
||||||
let areListenersSet = false;
|
|
||||||
|
|
||||||
const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
|
||||||
let socketUrl = `${wsProtocol}://${window.location.host}`;
|
|
||||||
|
|
||||||
const socketOptions: Parameters<typeof io>[0] = {
|
|
||||||
timeout: 60000,
|
|
||||||
path: '/ws/socket.io',
|
|
||||||
autoConnect: false, // achtung! removing this breaks the dynamic middleware
|
|
||||||
};
|
|
||||||
|
|
||||||
// if building in package mode, replace socket url with open api base url minus the http protocol
|
|
||||||
if (['nodes', 'package'].includes(import.meta.env.MODE)) {
|
|
||||||
const baseUrl = $baseUrl.get();
|
|
||||||
if (baseUrl) {
|
|
||||||
//eslint-disable-next-line
|
|
||||||
socketUrl = baseUrl.replace(/^https?\:\/\//i, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
const authToken = $authToken.get();
|
|
||||||
if (authToken) {
|
|
||||||
// TODO: handle providing jwt to socket.io
|
|
||||||
socketOptions.auth = { token: authToken };
|
|
||||||
}
|
|
||||||
|
|
||||||
socketOptions.transports = ['websocket', 'polling'];
|
|
||||||
}
|
|
||||||
|
|
||||||
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
|
|
||||||
socketUrl,
|
|
||||||
socketOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
const middleware: Middleware =
|
|
||||||
(storeApi: MiddlewareAPI<AppThunkDispatch, RootState>) =>
|
|
||||||
(next) =>
|
|
||||||
(action) => {
|
|
||||||
// Set listeners for `connect` and `disconnect` events once
|
|
||||||
// Must happen in middleware to get access to `dispatch`
|
|
||||||
if (!areListenersSet) {
|
|
||||||
setEventListeners({ storeApi, socket });
|
|
||||||
|
|
||||||
areListenersSet = true;
|
|
||||||
|
|
||||||
socket.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
next(action);
|
|
||||||
};
|
|
||||||
|
|
||||||
return middleware;
|
|
||||||
};
|
|
@ -1,6 +1,4 @@
|
|||||||
import { MiddlewareAPI } from '@reduxjs/toolkit';
|
import { AppDispatch } from 'app/store/store';
|
||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import { AppDispatch, RootState } from 'app/store/store';
|
|
||||||
import { $queueId } from 'features/queue/store/queueNanoStore';
|
import { $queueId } from 'features/queue/store/queueNanoStore';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { makeToast } from 'features/system/util/makeToast';
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
@ -23,20 +21,16 @@ import { ClientToServerEvents, ServerToClientEvents } from '../types';
|
|||||||
|
|
||||||
type SetEventListenersArg = {
|
type SetEventListenersArg = {
|
||||||
socket: Socket<ServerToClientEvents, ClientToServerEvents>;
|
socket: Socket<ServerToClientEvents, ClientToServerEvents>;
|
||||||
storeApi: MiddlewareAPI<AppDispatch, RootState>;
|
dispatch: AppDispatch;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setEventListeners = (arg: SetEventListenersArg) => {
|
export const setEventListeners = (arg: SetEventListenersArg) => {
|
||||||
const { socket, storeApi } = arg;
|
const { socket, dispatch } = arg;
|
||||||
const { dispatch } = storeApi;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect
|
* Connect
|
||||||
*/
|
*/
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
const log = logger('socketio');
|
|
||||||
log.debug('Connected');
|
|
||||||
|
|
||||||
dispatch(socketConnected());
|
dispatch(socketConnected());
|
||||||
const queue_id = $queueId.get();
|
const queue_id = $queueId.get();
|
||||||
socket.emit('subscribe_queue', { queue_id });
|
socket.emit('subscribe_queue', { queue_id });
|
||||||
|
Loading…
x
Reference in New Issue
Block a user