diff --git a/invokeai/frontend/web/src/app/hooks/useSocketIO.ts b/invokeai/frontend/web/src/app/hooks/useSocketIO.ts index dc2a8947fc..ad331bc385 100644 --- a/invokeai/frontend/web/src/app/hooks/useSocketIO.ts +++ b/invokeai/frontend/web/src/app/hooks/useSocketIO.ts @@ -45,7 +45,7 @@ export const useSocketIO = () => { const socketOptions = useMemo(() => { const options: Partial = { timeout: 60000, - path: '/ws/socket.io', + path: `${window.location.pathname}ws/socket.io`, autoConnect: false, // achtung! removing this breaks the dynamic middleware forceNew: true, }; diff --git a/invokeai/frontend/web/src/features/nodes/util/workflow/buildWorkflow.ts b/invokeai/frontend/web/src/features/nodes/util/workflow/buildWorkflow.ts index 35e189e246..1eaacb7827 100644 --- a/invokeai/frontend/web/src/features/nodes/util/workflow/buildWorkflow.ts +++ b/invokeai/frontend/web/src/features/nodes/util/workflow/buildWorkflow.ts @@ -24,6 +24,7 @@ const workflowKeys = [ 'notes', 'exposedFields', 'meta', + 'id', ] satisfies (keyof WorkflowV2)[]; export type BuildWorkflowFunction = (arg: BuildWorkflowArg) => WorkflowV2; diff --git a/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryList.tsx b/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryList.tsx index 3ff519f672..9e8adb6503 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryList.tsx +++ b/invokeai/frontend/web/src/features/workflowLibrary/components/WorkflowLibraryList.tsx @@ -61,13 +61,21 @@ const WorkflowLibraryList = () => { const [category, setCategory] = useState('user'); const [page, setPage] = useState(0); const [query, setQuery] = useState(''); - const [order_by, setOrderBy] = useState('opened_at'); + const projectId = useStore($projectId); + const orderByOptions = useMemo(() => { + return projectId + ? ORDER_BY_OPTIONS.filter((option) => option.value !== 'opened_at') + : ORDER_BY_OPTIONS; + }, [projectId]); + + const [order_by, setOrderBy] = useState( + orderByOptions[0]?.value as WorkflowRecordOrderBy + ); const [direction, setDirection] = useState('ASC'); const [debouncedQuery] = useDebounce(query, 500); - const projectId = useStore($projectId); const queryArg = useMemo[0]>(() => { - if (category === 'user') { + if (category !== 'default') { return { page, per_page: PER_PAGE, @@ -101,8 +109,8 @@ const WorkflowLibraryList = () => { [order_by] ); const valueOrderBy = useMemo( - () => ORDER_BY_OPTIONS.find((o) => o.value === order_by), - [order_by] + () => orderByOptions.find((o) => o.value === order_by), + [order_by, orderByOptions] ); const onChangeDirection = useCallback( @@ -186,7 +194,7 @@ const WorkflowLibraryList = () => { {t('common.orderBy')} diff --git a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useDeleteLibraryWorkflow.ts b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useDeleteLibraryWorkflow.ts index bf518fe409..ae541c8157 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useDeleteLibraryWorkflow.ts +++ b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useDeleteLibraryWorkflow.ts @@ -1,7 +1,11 @@ +import { useToast } from '@invoke-ai/ui'; import { useAppToaster } from 'app/components/Toaster'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDeleteWorkflowMutation } from 'services/api/endpoints/workflows'; +import { + useDeleteWorkflowMutation, + workflowsApi, +} from 'services/api/endpoints/workflows'; type UseDeleteLibraryWorkflowOptions = { onSuccess?: () => void; @@ -22,6 +26,7 @@ export const useDeleteLibraryWorkflow: UseDeleteLibraryWorkflow = ({ onError, }) => { const toaster = useAppToaster(); + const toast = useToast(); const { t } = useTranslation(); const [_deleteWorkflow, deleteWorkflowResult] = useDeleteWorkflowMutation(); @@ -34,14 +39,20 @@ export const useDeleteLibraryWorkflow: UseDeleteLibraryWorkflow = ({ }); onSuccess && onSuccess(); } catch { - toaster({ - title: t('toast.problemDeletingWorkflow'), - status: 'error', - }); + if ( + !toast.isActive( + `auth-error-toast-${workflowsApi.endpoints.deleteWorkflow.name}` + ) + ) { + toaster({ + title: t('toast.problemDeletingWorkflow'), + status: 'error', + }); + } onError && onError(); } }, - [_deleteWorkflow, toaster, t, onSuccess, onError] + [_deleteWorkflow, toaster, t, onSuccess, onError, toast] ); return { deleteWorkflow, deleteWorkflowResult }; diff --git a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow.ts b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow.ts index 27de12789a..85a0a6c757 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow.ts +++ b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow.ts @@ -1,9 +1,13 @@ +import { useToast } from '@invoke-ai/ui'; import { useAppToaster } from 'app/components/Toaster'; import { useAppDispatch } from 'app/store/storeHooks'; import { workflowLoadRequested } from 'features/nodes/store/actions'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { useLazyGetWorkflowQuery } from 'services/api/endpoints/workflows'; +import { + useLazyGetWorkflowQuery, + workflowsApi, +} from 'services/api/endpoints/workflows'; type UseGetAndLoadLibraryWorkflowOptions = { onSuccess?: () => void; @@ -25,6 +29,7 @@ export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = ({ }) => { const dispatch = useAppDispatch(); const toaster = useAppToaster(); + const toast = useToast(); const { t } = useTranslation(); const [_getAndLoadWorkflow, getAndLoadWorkflowResult] = useLazyGetWorkflowQuery(); @@ -38,14 +43,20 @@ export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = ({ // No toast - the listener for this action does that after the workflow is loaded onSuccess && onSuccess(); } catch { - toaster({ - title: t('toast.problemRetrievingWorkflow'), - status: 'error', - }); + if ( + !toast.isActive( + `auth-error-toast-${workflowsApi.endpoints.getWorkflow.name}` + ) + ) { + toaster({ + title: t('toast.problemRetrievingWorkflow'), + status: 'error', + }); + } onError && onError(); } }, - [_getAndLoadWorkflow, dispatch, onSuccess, toaster, t, onError] + [_getAndLoadWorkflow, dispatch, onSuccess, toaster, t, onError, toast] ); return { getAndLoadWorkflow, getAndLoadWorkflowResult }; diff --git a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflow.ts b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflow.ts index 6417084312..85589c6694 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflow.ts +++ b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflow.ts @@ -12,6 +12,7 @@ import { useTranslation } from 'react-i18next'; import { useCreateWorkflowMutation, useUpdateWorkflowMutation, + workflowsApi, } from 'services/api/endpoints/workflows'; import type { O } from 'ts-toolbelt'; @@ -60,12 +61,23 @@ export const useSaveLibraryWorkflow: UseSaveLibraryWorkflow = () => { isClosable: true, }); } catch (e) { - toast.update(toastRef.current, { - title: t('workflows.problemSavingWorkflow'), - status: 'error', - duration: 1000, - isClosable: true, - }); + if ( + !toast.isActive( + `auth-error-toast-${workflowsApi.endpoints.createWorkflow.name}` + ) && + !toast.isActive( + `auth-error-toast-${workflowsApi.endpoints.updateWorkflow.name}` + ) + ) { + toast.update(toastRef.current, { + title: t('workflows.problemSavingWorkflow'), + status: 'error', + duration: 1000, + isClosable: true, + }); + } else { + toast.close(toastRef.current); + } } }, [updateWorkflow, dispatch, toast, t, createWorkflow]); return { diff --git a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflowAs.ts b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflowAs.ts index bd0b091346..cb827f2195 100644 --- a/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflowAs.ts +++ b/invokeai/frontend/web/src/features/workflowLibrary/hooks/useSaveWorkflowAs.ts @@ -9,7 +9,10 @@ import { } from 'features/nodes/store/workflowSlice'; import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { useCreateWorkflowMutation } from 'services/api/endpoints/workflows'; +import { + useCreateWorkflowMutation, + workflowsApi, +} from 'services/api/endpoints/workflows'; type SaveWorkflowAsArg = { name: string; @@ -59,12 +62,20 @@ export const useSaveWorkflowAs: UseSaveWorkflowAs = () => { }); } catch (e) { onError && onError(); - toast.update(toastRef.current, { - title: t('workflows.problemSavingWorkflow'), - status: 'error', - duration: 1000, - isClosable: true, - }); + if ( + !toast.isActive( + `auth-error-toast-${workflowsApi.endpoints.createWorkflow.name}` + ) + ) { + toast.update(toastRef.current, { + title: t('workflows.problemSavingWorkflow'), + status: 'error', + duration: 1000, + isClosable: true, + }); + } else { + toast.close(toastRef.current); + } } }, [toast, createWorkflow, dispatch, t] diff --git a/invokeai/frontend/web/src/i18n.ts b/invokeai/frontend/web/src/i18n.ts index f0cbe40a75..89c855bcd0 100644 --- a/invokeai/frontend/web/src/i18n.ts +++ b/invokeai/frontend/web/src/i18n.ts @@ -32,7 +32,7 @@ if (import.meta.env.MODE === 'package') { fallbackLng: 'en', debug: false, backend: { - loadPath: '/locales/{{lng}}.json', + loadPath: `${window.location.href.replace(/\/$/, '')}/locales/{{lng}}.json`, }, interpolation: { escapeValue: false, diff --git a/invokeai/frontend/web/src/services/api/authToastMiddleware.ts b/invokeai/frontend/web/src/services/api/authToastMiddleware.ts index 366f4d06c4..f1e5862c57 100644 --- a/invokeai/frontend/web/src/services/api/authToastMiddleware.ts +++ b/invokeai/frontend/web/src/services/api/authToastMiddleware.ts @@ -27,7 +27,8 @@ export const authToastMiddleware: Middleware = if (isRejectedWithValue(action)) { try { const parsed = zRejectedForbiddenAction.parse(action); - if (parsed.meta?.arg?.endpointName === 'getImageDTO') { + const endpointName = parsed.meta?.arg?.endpointName; + if (endpointName === 'getImageDTO') { // do not show toast if problem is image access return; } @@ -39,6 +40,7 @@ export const authToastMiddleware: Middleware = : undefined; dispatch( addToast({ + id: `auth-error-toast-${endpointName}`, title: t('common.somethingWentWrong'), status: 'error', description: customMessage, diff --git a/invokeai/frontend/web/src/services/api/index.ts b/invokeai/frontend/web/src/services/api/index.ts index 1c76838db1..9679fabc3f 100644 --- a/invokeai/frontend/web/src/services/api/index.ts +++ b/invokeai/frontend/web/src/services/api/index.ts @@ -57,7 +57,9 @@ const dynamicBaseQuery: BaseQueryFn< const projectId = $projectId.get(); const rawBaseQuery = fetchBaseQuery({ - baseUrl: `${baseUrl ?? ''}/api/v1`, + baseUrl: baseUrl + ? `${baseUrl}/api/v1` + : `${window.location.href.replace(/\/$/, '')}/api/v1`, prepareHeaders: (headers) => { if (authToken) { headers.set('Authorization', `Bearer ${authToken}`); diff --git a/invokeai/frontend/web/src/services/api/thunks/schema.ts b/invokeai/frontend/web/src/services/api/thunks/schema.ts index c209469e02..ed36ac4cfb 100644 --- a/invokeai/frontend/web/src/services/api/thunks/schema.ts +++ b/invokeai/frontend/web/src/services/api/thunks/schema.ts @@ -26,8 +26,9 @@ export const receivedOpenAPISchema = createAsyncThunk( 'nodes/receivedOpenAPISchema', async (_, { rejectWithValue }) => { try { - const url = [window.location.origin, 'openapi.json'].join('/'); - const response = await fetch(url); + const response = await fetch( + `${window.location.href.replace(/\/$/, '')}/openapi.json` + ); const openAPISchema = await response.json(); const schemaJSON = JSON.parse( diff --git a/invokeai/version/invokeai_version.py b/invokeai/version/invokeai_version.py index b202327a86..4f8219395a 100644 --- a/invokeai/version/invokeai_version.py +++ b/invokeai/version/invokeai_version.py @@ -1 +1 @@ -__version__ = "3.6.1" +__version__ = "3.6.2"