mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-01-16 16:57:58 -05:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efb187ced2 | ||
|
|
ca9150e9b3 |
@@ -0,0 +1,28 @@
|
||||
import type { ButtonProps } from '@invoke-ai/ui-library';
|
||||
import { Button } from '@invoke-ai/ui-library';
|
||||
import { useCancelAllExceptCurrentQueueItemDialog } from 'features/queue/components/CancelAllExceptCurrentQueueItemConfirmationAlertDialog';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiXCircle } from 'react-icons/pi';
|
||||
|
||||
export const CancelAllExceptCurrentButton = memo((props: ButtonProps) => {
|
||||
const { t } = useTranslation();
|
||||
const api = useCancelAllExceptCurrentQueueItemDialog();
|
||||
|
||||
return (
|
||||
<Button
|
||||
isDisabled={api.isDisabled}
|
||||
isLoading={api.isLoading}
|
||||
aria-label={t('queue.clear')}
|
||||
tooltip={t('queue.cancelAllExceptCurrentTooltip')}
|
||||
leftIcon={<PiXCircle />}
|
||||
colorScheme="error"
|
||||
onClick={api.openDialog}
|
||||
{...props}
|
||||
>
|
||||
{t('queue.clear')}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
||||
CancelAllExceptCurrentButton.displayName = 'CancelAllExceptCurrentButton';
|
||||
@@ -1,32 +0,0 @@
|
||||
import type { ButtonProps } from '@invoke-ai/ui-library';
|
||||
import { Button } from '@invoke-ai/ui-library';
|
||||
import { useDeleteAllExceptCurrentQueueItemDialog } from 'features/queue/components/DeleteAllExceptCurrentQueueItemConfirmationAlertDialog';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiXCircle } from 'react-icons/pi';
|
||||
|
||||
type Props = ButtonProps;
|
||||
|
||||
export const DeleteAllExceptCurrentButton = memo((props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const deleteAllExceptCurrent = useDeleteAllExceptCurrentQueueItemDialog();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={deleteAllExceptCurrent.openDialog}
|
||||
isLoading={deleteAllExceptCurrent.isLoading}
|
||||
isDisabled={deleteAllExceptCurrent.isDisabled}
|
||||
tooltip={t('queue.cancelAllExceptCurrentTooltip')}
|
||||
leftIcon={<PiXCircle />}
|
||||
colorScheme="error"
|
||||
data-testid={t('queue.clear')}
|
||||
{...props}
|
||||
>
|
||||
{t('queue.clear')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
DeleteAllExceptCurrentButton.displayName = 'DeleteAllExceptCurrentButton';
|
||||
@@ -3,7 +3,7 @@ import { Badge, ButtonGroup, Collapse, Flex, IconButton, Text } from '@invoke-ai
|
||||
import QueueStatusBadge from 'features/queue/components/common/QueueStatusBadge';
|
||||
import { useDestinationText } from 'features/queue/components/QueueList/useDestinationText';
|
||||
import { useOriginText } from 'features/queue/components/QueueList/useOriginText';
|
||||
import { useDeleteQueueItem } from 'features/queue/hooks/useDeleteQueueItem';
|
||||
import { useCancelQueueItem } from 'features/queue/hooks/useCancelQueueItem';
|
||||
import { useRetryQueueItem } from 'features/queue/hooks/useRetryQueueItem';
|
||||
import { getSecondsFromTimestamps } from 'features/queue/util/getSecondsFromTimestamps';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
@@ -38,13 +38,13 @@ const QueueItemComponent = ({ index, item, context }: InnerItemProps) => {
|
||||
const handleToggle = useCallback(() => {
|
||||
context.toggleQueueItem(item.item_id);
|
||||
}, [context, item.item_id]);
|
||||
const deleteQueueItem = useDeleteQueueItem();
|
||||
const onClickDeleteQueueItem = useCallback(
|
||||
const cancelQueueItem = useCancelQueueItem();
|
||||
const onClickCancelQueueItem = useCallback(
|
||||
(e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
deleteQueueItem.trigger(item.item_id);
|
||||
cancelQueueItem.trigger(item.item_id);
|
||||
},
|
||||
[deleteQueueItem, item.item_id]
|
||||
[cancelQueueItem, item.item_id]
|
||||
);
|
||||
const retryQueueItem = useRetryQueueItem();
|
||||
const onClickRetryQueueItem = useCallback(
|
||||
@@ -135,9 +135,9 @@ const QueueItemComponent = ({ index, item, context }: InnerItemProps) => {
|
||||
<ButtonGroup size="xs" variant="ghost">
|
||||
{(!isFailed || !isRetryEnabled || isValidationRun) && (
|
||||
<IconButton
|
||||
onClick={onClickDeleteQueueItem}
|
||||
onClick={onClickCancelQueueItem}
|
||||
isDisabled={isCanceled}
|
||||
isLoading={deleteQueueItem.isLoading}
|
||||
isLoading={cancelQueueItem.isLoading}
|
||||
aria-label={t('queue.cancelItem')}
|
||||
icon={<PiXBold />}
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useDestinationText } from 'features/queue/components/QueueList/useDesti
|
||||
import { useOriginText } from 'features/queue/components/QueueList/useOriginText';
|
||||
import { useBatchIsCanceled } from 'features/queue/hooks/useBatchIsCanceled';
|
||||
import { useCancelBatch } from 'features/queue/hooks/useCancelBatch';
|
||||
import { useDeleteQueueItem } from 'features/queue/hooks/useDeleteQueueItem';
|
||||
import { useCancelQueueItem } from 'features/queue/hooks/useCancelQueueItem';
|
||||
import { useRetryQueueItem } from 'features/queue/hooks/useRetryQueueItem';
|
||||
import { getSecondsFromTimestamps } from 'features/queue/util/getSecondsFromTimestamps';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
@@ -13,20 +13,22 @@ import type { ReactNode } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiXBold } from 'react-icons/pi';
|
||||
import { useGetQueueItemQuery } from 'services/api/endpoints/queue';
|
||||
import type { S } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
queueItem: S['SessionQueueItem'];
|
||||
};
|
||||
|
||||
const QueueItemComponent = ({ queueItem }: Props) => {
|
||||
const { session_id, batch_id, item_id, origin, destination } = queueItem;
|
||||
const QueueItemComponent = ({ queueItem: queueItemDTO }: Props) => {
|
||||
const { session_id, batch_id, item_id, origin, destination } = queueItemDTO;
|
||||
const { t } = useTranslation();
|
||||
const isRetryEnabled = useFeatureStatus('retryQueueItem');
|
||||
const isBatchCanceled = useBatchIsCanceled(batch_id);
|
||||
const cancelBatch = useCancelBatch();
|
||||
const deleteQueueItem = useDeleteQueueItem();
|
||||
const cancelQueueItem = useCancelQueueItem();
|
||||
const retryQueueItem = useRetryQueueItem();
|
||||
const { data: queueItem } = useGetQueueItemQuery(item_id);
|
||||
|
||||
const originText = useOriginText(origin);
|
||||
const destinationText = useDestinationText(destination);
|
||||
@@ -57,8 +59,8 @@ const QueueItemComponent = ({ queueItem }: Props) => {
|
||||
}, [cancelBatch, batch_id]);
|
||||
|
||||
const onCancelQueueItem = useCallback(() => {
|
||||
deleteQueueItem.trigger(item_id);
|
||||
}, [deleteQueueItem, item_id]);
|
||||
cancelQueueItem.trigger(item_id);
|
||||
}, [cancelQueueItem, item_id]);
|
||||
|
||||
const onRetryQueueItem = useCallback(() => {
|
||||
retryQueueItem.trigger(item_id);
|
||||
@@ -85,8 +87,8 @@ const QueueItemComponent = ({ queueItem }: Props) => {
|
||||
{(!isFailed || !isRetryEnabled) && (
|
||||
<Button
|
||||
onClick={onCancelQueueItem}
|
||||
isLoading={deleteQueueItem.isLoading}
|
||||
isDisabled={deleteQueueItem.isDisabled || queueItem ? isCanceled : true}
|
||||
isLoading={cancelQueueItem.isLoading}
|
||||
isDisabled={cancelQueueItem.isDisabled || queueItem ? isCanceled : true}
|
||||
aria-label={t('queue.cancelItem')}
|
||||
leftIcon={<PiXBold />}
|
||||
colorScheme="error"
|
||||
|
||||
@@ -13,7 +13,7 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { Components, ItemContent } from 'react-virtuoso';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { useListQueueItemsQuery } from 'services/api/endpoints/queue';
|
||||
import { queueItemsAdapterSelectors, useListQueueItemsQuery } from 'services/api/endpoints/queue';
|
||||
import type { S } from 'services/api/types';
|
||||
|
||||
import QueueItemComponent from './QueueItemComponent';
|
||||
@@ -70,7 +70,7 @@ const QueueList = () => {
|
||||
if (!listQueueItemsData) {
|
||||
return [];
|
||||
}
|
||||
return listQueueItemsData.items;
|
||||
return queueItemsAdapterSelectors.selectAll(listQueueItemsData);
|
||||
}, [listQueueItemsData]);
|
||||
|
||||
const handleLoadMore = useCallback(() => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ButtonGroup, Flex } from '@invoke-ai/ui-library';
|
||||
import { DeleteAllExceptCurrentButton } from 'features/queue/components/DeleteAllExceptCurrentButton';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { CancelAllExceptCurrentButton } from './CancelAllExceptCurrentButton';
|
||||
import ClearModelCacheButton from './ClearModelCacheButton';
|
||||
import PauseProcessorButton from './PauseProcessorButton';
|
||||
import PruneQueueButton from './PruneQueueButton';
|
||||
@@ -23,7 +23,7 @@ const QueueTabQueueControls = () => {
|
||||
)}
|
||||
<ButtonGroup w={28} orientation="vertical" size="sm">
|
||||
<PruneQueueButton />
|
||||
<DeleteAllExceptCurrentButton />
|
||||
<CancelAllExceptCurrentButton />
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
<ClearModelCacheButton />
|
||||
|
||||
@@ -33,7 +33,7 @@ export const queueSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
export const { listCursorChanged, listPriorityChanged } = queueSlice.actions;
|
||||
export const { listCursorChanged, listPriorityChanged, listParamsReset } = queueSlice.actions;
|
||||
|
||||
const selectQueueSlice = (state: RootState) => state.queue;
|
||||
const createQueueSelector = <T>(selector: Selector<QueueState, T>) => createSelector(selectQueueSlice, selector);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { EntityState, ThunkDispatch, UnknownAction } from '@reduxjs/toolkit';
|
||||
import { createEntityAdapter } from '@reduxjs/toolkit';
|
||||
import { getSelectorsOptions } from 'app/store/createMemoizedSelector';
|
||||
import { $queueId } from 'app/store/nanostores/queueId';
|
||||
import { listParamsReset } from 'features/queue/store/queueSlice';
|
||||
import queryString from 'query-string';
|
||||
import type { components, paths } from 'services/api/schema';
|
||||
|
||||
@@ -31,6 +35,30 @@ export type SessionQueueItemStatus = NonNullable<
|
||||
NonNullable<paths['/api/v1/queue/{queue_id}/list']['get']['parameters']['query']>['status']
|
||||
>;
|
||||
|
||||
export const queueItemsAdapter = createEntityAdapter<components['schemas']['SessionQueueItem'], string>({
|
||||
selectId: (queueItem) => String(queueItem.item_id),
|
||||
sortComparer: (a, b) => {
|
||||
// Sort by priority in descending order
|
||||
if (a.priority > b.priority) {
|
||||
return -1;
|
||||
}
|
||||
if (a.priority < b.priority) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If priority is the same, sort by id in ascending order
|
||||
if (a.item_id < b.item_id) {
|
||||
return -1;
|
||||
}
|
||||
if (a.item_id > b.item_id) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
});
|
||||
export const queueItemsAdapterSelectors = queueItemsAdapter.getSelectors(undefined, getSelectorsOptions);
|
||||
|
||||
export const queueApi = api.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
enqueueBatch: build.mutation<
|
||||
@@ -50,6 +78,57 @@ export const queueApi = api.injectEndpoints({
|
||||
{ type: 'SessionQueueItem', id: LIST_TAG },
|
||||
{ type: 'SessionQueueItem', id: LIST_ALL_TAG },
|
||||
],
|
||||
onQueryStarted: async (arg, api) => {
|
||||
const { dispatch, queryFulfilled } = api;
|
||||
try {
|
||||
const { data } = await queryFulfilled;
|
||||
resetListQueryData(dispatch);
|
||||
/**
|
||||
* When a batch is enqueued, we need to update the queue status. While it might be templting to invalidate the
|
||||
* `SessionQueueStatus` tag here, this can introduce a race condition when the queue item executes quickly:
|
||||
*
|
||||
* - Enqueue via this query
|
||||
* - On success, we invalidate `SessionQueueStatus` tag - network request sent to server
|
||||
* - The server gets the queue status request and responds, but this takes some time... in the meantime:
|
||||
* - The new queue item starts executing, and we receive a socket queue item status changed event
|
||||
* - We optimistically update the queue status in the queue item status changed socket handler
|
||||
* - At this point, the queue status is correct
|
||||
* - Finally, we get the queue status from the tag invalidation request - but it's reporting the queue status
|
||||
* from _before_ the last queue event
|
||||
* - The queue status is now incorrect!
|
||||
*
|
||||
* Ok, what if we just never did optimistic updates and invalidated the tag in the queue event handlers instead?
|
||||
* It's much simpler that way, but it causes a lot of network requests - 3 per queue item, as it moves from
|
||||
* pending -> in_progress -> completed/failed/canceled.
|
||||
*
|
||||
* We can do a bit of extra work here, incrementing the pending and total counts in the queue status, and do
|
||||
* similar optimistic updates in the socket handler. Because this optimistic update runs immediately after the
|
||||
* enqueue network request, it should always occur _before_ the next queue event, so no race condition:
|
||||
*
|
||||
* - Enqueue batch via this query
|
||||
* - On success, optimistically update - this happens immediately on the HTTP OK - before the next queue event
|
||||
* - At this point, the queue status is correct
|
||||
* - A queue item status changes and we receive a socket event w/ updated status
|
||||
* - Update status optimistically in socket handler
|
||||
* - Queue status is still correct
|
||||
*
|
||||
* This problem occurs most commonly with canvas filters like Canny edge detection, which are single-node
|
||||
* graphs that execute very quickly. Image generation graphs take long enough to not trigger this race
|
||||
* condition - even when all nodes are cached on the server.
|
||||
*/
|
||||
dispatch(
|
||||
queueApi.util.updateQueryData('getQueueStatus', undefined, (draft) => {
|
||||
if (!draft) {
|
||||
return;
|
||||
}
|
||||
draft.queue.pending += data.enqueued;
|
||||
draft.queue.total += data.enqueued;
|
||||
})
|
||||
);
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
},
|
||||
}),
|
||||
resumeProcessor: build.mutation<
|
||||
paths['/api/v1/queue/{queue_id}/processor/resume']['put']['responses']['200']['content']['application/json'],
|
||||
@@ -85,6 +164,15 @@ export const queueApi = api.injectEndpoints({
|
||||
{ type: 'SessionQueueItem', id: LIST_TAG },
|
||||
{ type: 'SessionQueueItem', id: LIST_ALL_TAG },
|
||||
],
|
||||
onQueryStarted: async (arg, api) => {
|
||||
const { dispatch, queryFulfilled } = api;
|
||||
try {
|
||||
await queryFulfilled;
|
||||
resetListQueryData(dispatch);
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
},
|
||||
}),
|
||||
clearQueue: build.mutation<
|
||||
paths['/api/v1/queue/{queue_id}/clear']['put']['responses']['200']['content']['application/json'],
|
||||
@@ -104,6 +192,15 @@ export const queueApi = api.injectEndpoints({
|
||||
{ type: 'SessionQueueItem', id: LIST_TAG },
|
||||
{ type: 'SessionQueueItem', id: LIST_ALL_TAG },
|
||||
],
|
||||
onQueryStarted: async (arg, api) => {
|
||||
const { dispatch, queryFulfilled } = api;
|
||||
try {
|
||||
await queryFulfilled;
|
||||
resetListQueryData(dispatch);
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
},
|
||||
}),
|
||||
getCurrentQueueItem: build.query<
|
||||
paths['/api/v1/queue/{queue_id}/current']['get']['responses']['200']['content']['application/json'],
|
||||
@@ -187,6 +284,25 @@ export const queueApi = api.injectEndpoints({
|
||||
url: buildQueueUrl(`i/${item_id}/cancel`),
|
||||
method: 'PUT',
|
||||
}),
|
||||
onQueryStarted: async (item_id, { dispatch, queryFulfilled }) => {
|
||||
try {
|
||||
const { data } = await queryFulfilled;
|
||||
dispatch(
|
||||
queueApi.util.updateQueryData('listQueueItems', undefined, (draft) => {
|
||||
queueItemsAdapter.updateOne(draft, {
|
||||
id: String(item_id),
|
||||
changes: {
|
||||
status: data.status,
|
||||
completed_at: data.completed_at,
|
||||
updated_at: data.updated_at,
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
},
|
||||
invalidatesTags: (result) => {
|
||||
if (!result) {
|
||||
return [];
|
||||
@@ -210,6 +326,15 @@ export const queueApi = api.injectEndpoints({
|
||||
method: 'PUT',
|
||||
body,
|
||||
}),
|
||||
onQueryStarted: async (arg, api) => {
|
||||
const { dispatch, queryFulfilled } = api;
|
||||
try {
|
||||
await queryFulfilled;
|
||||
resetListQueryData(dispatch);
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
},
|
||||
invalidatesTags: (result, error, { batch_ids }) => {
|
||||
if (!result) {
|
||||
return [];
|
||||
@@ -256,16 +381,6 @@ export const queueApi = api.injectEndpoints({
|
||||
}),
|
||||
invalidatesTags: ['SessionQueueStatus', 'BatchStatus', 'QueueCountsByDestination', 'SessionQueueItem'],
|
||||
}),
|
||||
deleteAllExceptCurrent: build.mutation<
|
||||
paths['/api/v1/queue/{queue_id}/delete_all_except_current']['put']['responses']['200']['content']['application/json'],
|
||||
void
|
||||
>({
|
||||
query: () => ({
|
||||
url: buildQueueUrl('delete_all_except_current'),
|
||||
method: 'PUT',
|
||||
}),
|
||||
invalidatesTags: ['SessionQueueStatus', 'BatchStatus', 'QueueCountsByDestination', 'SessionQueueItem'],
|
||||
}),
|
||||
retryItemsById: build.mutation<
|
||||
paths['/api/v1/queue/{queue_id}/retry_items_by_id']['put']['responses']['200']['content']['application/json'],
|
||||
paths['/api/v1/queue/{queue_id}/retry_items_by_id']['put']['requestBody']['content']['application/json']
|
||||
@@ -275,6 +390,15 @@ export const queueApi = api.injectEndpoints({
|
||||
method: 'PUT',
|
||||
body,
|
||||
}),
|
||||
onQueryStarted: async (arg, api) => {
|
||||
const { dispatch, queryFulfilled } = api;
|
||||
try {
|
||||
await queryFulfilled;
|
||||
resetListQueryData(dispatch);
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
},
|
||||
invalidatesTags: (result, error, item_ids) => {
|
||||
if (!result) {
|
||||
return [];
|
||||
@@ -290,24 +414,31 @@ export const queueApi = api.injectEndpoints({
|
||||
},
|
||||
}),
|
||||
listQueueItems: build.query<
|
||||
components['schemas']['CursorPaginatedResults_SessionQueueItem_'],
|
||||
{ cursor?: number; priority?: number; destination?: string } | undefined
|
||||
EntityState<components['schemas']['SessionQueueItem'], string> & {
|
||||
has_more: boolean;
|
||||
},
|
||||
{ cursor?: number; priority?: number } | undefined
|
||||
>({
|
||||
query: (queryArgs) => ({
|
||||
url: getListQueueItemsUrl(queryArgs),
|
||||
method: 'GET',
|
||||
}),
|
||||
keepUnusedDataFor: 60 * 5, // 5 minutes
|
||||
providesTags: (result, _error, _args) => {
|
||||
if (!result) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
'FetchOnReconnect',
|
||||
{ type: 'SessionQueueItem', id: LIST_TAG },
|
||||
...result.items.map(({ item_id }) => ({ type: 'SessionQueueItem', id: item_id }) satisfies ApiTagDescription),
|
||||
];
|
||||
serializeQueryArgs: () => {
|
||||
return buildQueueUrl('list');
|
||||
},
|
||||
transformResponse: (response: components['schemas']['CursorPaginatedResults_SessionQueueItem_']) =>
|
||||
queueItemsAdapter.addMany(
|
||||
queueItemsAdapter.getInitialState({
|
||||
has_more: response.has_more,
|
||||
}),
|
||||
response.items
|
||||
),
|
||||
merge: (cache, response) => {
|
||||
queueItemsAdapter.addMany(cache, queueItemsAdapterSelectors.selectAll(response));
|
||||
cache.has_more = response.has_more;
|
||||
},
|
||||
forceRefetch: ({ currentArg, previousArg }) => currentArg !== previousArg,
|
||||
keepUnusedDataFor: 60 * 5, // 5 minutes
|
||||
}),
|
||||
listAllQueueItems: build.query<
|
||||
paths['/api/v1/queue/{queue_id}/list_all']['get']['responses']['200']['content']['application/json'],
|
||||
@@ -356,6 +487,16 @@ export const queueApi = api.injectEndpoints({
|
||||
{ type: 'SessionQueueItem', id: LIST_ALL_TAG },
|
||||
],
|
||||
}),
|
||||
deleteAllExceptCurrent: build.mutation<
|
||||
paths['/api/v1/queue/{queue_id}/delete_all_except_current']['put']['responses']['200']['content']['application/json'],
|
||||
void
|
||||
>({
|
||||
query: () => ({
|
||||
url: buildQueueUrl('delete_all_except_current'),
|
||||
method: 'PUT',
|
||||
}),
|
||||
invalidatesTags: ['SessionQueueStatus', 'BatchStatus', 'QueueCountsByDestination', 'SessionQueueItem'],
|
||||
}),
|
||||
getQueueCountsByDestination: build.query<
|
||||
paths['/api/v1/queue/{queue_id}/counts_by_destination']['get']['responses']['200']['content']['application/json'],
|
||||
paths['/api/v1/queue/{queue_id}/counts_by_destination']['get']['parameters']['query']
|
||||
@@ -378,6 +519,7 @@ export const {
|
||||
useClearQueueMutation,
|
||||
usePruneQueueMutation,
|
||||
useGetQueueStatusQuery,
|
||||
useGetQueueItemQuery,
|
||||
useListQueueItemsQuery,
|
||||
useCancelQueueItemMutation,
|
||||
useCancelQueueItemsByDestinationMutation,
|
||||
@@ -392,6 +534,24 @@ export const {
|
||||
|
||||
export const selectQueueStatus = queueApi.endpoints.getQueueStatus.select();
|
||||
|
||||
const resetListQueryData = (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
dispatch: ThunkDispatch<any, any, UnknownAction>
|
||||
) => {
|
||||
dispatch(
|
||||
queueApi.util.updateQueryData('listQueueItems', undefined, (draft) => {
|
||||
// remove all items from the list
|
||||
queueItemsAdapter.removeAll(draft);
|
||||
// reset the has_more flag
|
||||
draft.has_more = false;
|
||||
})
|
||||
);
|
||||
// set the list cursor and priority to undefined
|
||||
dispatch(listParamsReset());
|
||||
// we have to manually kick off another query to get the first page and re-initialize the list
|
||||
dispatch(queueApi.endpoints.listQueueItems.initiate(undefined));
|
||||
};
|
||||
|
||||
export const enqueueMutationFixedCacheKeyOptions = {
|
||||
fixedCacheKey: 'enqueueBatch',
|
||||
} as const;
|
||||
|
||||
@@ -22,7 +22,7 @@ import { t } from 'i18next';
|
||||
import type { ApiTagDescription } from 'services/api';
|
||||
import { api, LIST_ALL_TAG, LIST_TAG } from 'services/api';
|
||||
import { modelsApi } from 'services/api/endpoints/models';
|
||||
import { queueApi } from 'services/api/endpoints/queue';
|
||||
import { queueApi, queueItemsAdapter } from 'services/api/endpoints/queue';
|
||||
import { workflowsApi } from 'services/api/endpoints/workflows';
|
||||
import { buildOnInvocationComplete } from 'services/events/onInvocationComplete';
|
||||
import { buildOnModelInstallError } from 'services/events/onModelInstallError';
|
||||
@@ -343,10 +343,42 @@ export const setEventListeners = ({ socket, store, setIsConnected }: SetEventLis
|
||||
|
||||
socket.on('queue_item_status_changed', (data) => {
|
||||
// we've got new status for the queue item, batch and queue
|
||||
const { item_id, session_id, status, batch_status, error_type, error_message, destination } = data;
|
||||
const {
|
||||
item_id,
|
||||
session_id,
|
||||
status,
|
||||
batch_status,
|
||||
error_type,
|
||||
error_message,
|
||||
destination,
|
||||
started_at,
|
||||
updated_at,
|
||||
completed_at,
|
||||
error_traceback,
|
||||
credits,
|
||||
} = data;
|
||||
|
||||
log.debug({ data }, `Queue item ${item_id} status updated: ${status}`);
|
||||
|
||||
// // Update this specific queue item in the list of queue items
|
||||
dispatch(
|
||||
queueApi.util.updateQueryData('listQueueItems', undefined, (draft) => {
|
||||
queueItemsAdapter.updateOne(draft, {
|
||||
id: String(item_id),
|
||||
changes: {
|
||||
status,
|
||||
started_at,
|
||||
updated_at: updated_at ?? undefined,
|
||||
completed_at: completed_at ?? undefined,
|
||||
error_type,
|
||||
error_message,
|
||||
error_traceback,
|
||||
credits,
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// Invalidate caches for things we cannot easily update
|
||||
const tagsToInvalidate: ApiTagDescription[] = [
|
||||
'SessionQueueStatus',
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "6.0.0rc4"
|
||||
__version__ = "6.0.0rc5"
|
||||
|
||||
Reference in New Issue
Block a user