mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
wip
This commit is contained in:
parent
11085783ef
commit
46905175a9
@ -34,7 +34,7 @@ async def get_workflow(
|
||||
"/i/{workflow_id}",
|
||||
operation_id="update_workflow",
|
||||
responses={
|
||||
200: {"model": Workflow},
|
||||
200: {"model": WorkflowRecordDTO},
|
||||
},
|
||||
)
|
||||
async def update_workflow(
|
||||
|
@ -48,7 +48,7 @@ WorkflowWithoutIDValidator = TypeAdapter(WorkflowWithoutID)
|
||||
|
||||
|
||||
class Workflow(WorkflowWithoutID):
|
||||
workflow_id: str = Field(default_factory=uuid_string, description="The id of the workflow.")
|
||||
id: str = Field(default_factory=uuid_string, description="The id of the workflow.")
|
||||
|
||||
|
||||
WorkflowValidator = TypeAdapter(Workflow)
|
||||
|
@ -58,7 +58,7 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
VALUES (?, ?);
|
||||
""",
|
||||
(
|
||||
workflow_with_id.workflow_id,
|
||||
workflow_with_id.id,
|
||||
workflow_with_id.model_dump_json(),
|
||||
),
|
||||
)
|
||||
@ -68,7 +68,7 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
raise
|
||||
finally:
|
||||
self._lock.release()
|
||||
return self.get(workflow_with_id.workflow_id)
|
||||
return self.get(workflow_with_id.id)
|
||||
|
||||
def update(self, workflow: Workflow) -> WorkflowRecordDTO:
|
||||
try:
|
||||
@ -79,7 +79,7 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
SET workflow = ?
|
||||
WHERE workflow_id = ?;
|
||||
""",
|
||||
(workflow.model_dump_json(), workflow.workflow_id),
|
||||
(workflow.model_dump_json(), workflow.id),
|
||||
)
|
||||
self._conn.commit()
|
||||
except Exception:
|
||||
@ -87,7 +87,7 @@ class SqliteWorkflowRecordsStorage(WorkflowRecordsStorageBase):
|
||||
raise
|
||||
finally:
|
||||
self._lock.release()
|
||||
return self.get(workflow.workflow_id)
|
||||
return self.get(workflow.id)
|
||||
|
||||
def delete(self, workflow_id: str) -> None:
|
||||
try:
|
||||
|
@ -1613,6 +1613,8 @@
|
||||
"systemCategory": "System",
|
||||
"loadWorkflow": "$t(nodes.loadWorkflow)",
|
||||
"deleteWorkflow": "Delete Workflow",
|
||||
"unnamedWorkflow": "Unnamed Workflow"
|
||||
"unnamedWorkflow": "Unnamed Workflow",
|
||||
"downloadWorkflow": "Download Workflow",
|
||||
"saveWorkflow": "Save Workflow"
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { FaDownload, FaPlus } from 'react-icons/fa';
|
||||
import { useBoardName } from 'services/api/hooks/useBoardName';
|
||||
import { BoardDTO } from 'services/api/types';
|
||||
import { menuListMotionProps } from 'theme/components/menu';
|
||||
import { MENU_LIST_MOTION_PROPS } from 'theme/components/menu';
|
||||
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
|
||||
import NoBoardContextMenuItems from './NoBoardContextMenuItems';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
@ -94,7 +94,7 @@ const BoardContextMenu = ({
|
||||
() => (
|
||||
<MenuList
|
||||
sx={{ visibility: 'visible !important' }}
|
||||
motionProps={menuListMotionProps}
|
||||
motionProps={MENU_LIST_MOTION_PROPS}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<MenuGroup title={boardName}>
|
||||
|
@ -46,7 +46,7 @@ import {
|
||||
useLazyGetImageWorkflowQuery,
|
||||
} from 'services/api/endpoints/images';
|
||||
import { useDebouncedMetadata } from 'services/api/hooks/useDebouncedMetadata';
|
||||
import { menuListMotionProps } from 'theme/components/menu';
|
||||
import { MENU_LIST_MOTION_PROPS } from 'theme/components/menu';
|
||||
|
||||
const currentImageButtonsSelector = createSelector(
|
||||
[stateSelector, activeTabNameSelector],
|
||||
@ -248,7 +248,7 @@ const CurrentImageButtons = () => {
|
||||
isDisabled={!imageDTO}
|
||||
icon={<FaEllipsis />}
|
||||
/>
|
||||
<MenuList motionProps={menuListMotionProps}>
|
||||
<MenuList motionProps={MENU_LIST_MOTION_PROPS}>
|
||||
{imageDTO && <SingleSelectionMenuItems imageDTO={imageDTO} />}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
} from 'common/components/IAIContextMenu';
|
||||
import { MouseEvent, memo, useCallback } from 'react';
|
||||
import { ImageDTO } from 'services/api/types';
|
||||
import { menuListMotionProps } from 'theme/components/menu';
|
||||
import { MENU_LIST_MOTION_PROPS } from 'theme/components/menu';
|
||||
import SingleSelectionMenuItems from './SingleSelectionMenuItems';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
@ -44,7 +44,7 @@ const ImageContextMenu = ({ imageDTO, children }: Props) => {
|
||||
return (
|
||||
<MenuList
|
||||
sx={{ visibility: 'visible !important' }}
|
||||
motionProps={menuListMotionProps}
|
||||
motionProps={MENU_LIST_MOTION_PROPS}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<MultipleSelectionMenuItems />
|
||||
@ -55,7 +55,7 @@ const ImageContextMenu = ({ imageDTO, children }: Props) => {
|
||||
return (
|
||||
<MenuList
|
||||
sx={{ visibility: 'visible !important' }}
|
||||
motionProps={menuListMotionProps}
|
||||
motionProps={MENU_LIST_MOTION_PROPS}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<SingleSelectionMenuItems imageDTO={imageDTO} />
|
||||
|
@ -11,13 +11,10 @@ const PER_PAGE = 10;
|
||||
const WorkflowLibraryContent = () => {
|
||||
const [page, setPage] = useState(0);
|
||||
const [category, setCategory] = useState<WorkflowCategory>('user');
|
||||
const { data } = useListWorkflowsQuery(
|
||||
{
|
||||
page,
|
||||
per_page: PER_PAGE,
|
||||
},
|
||||
{ refetchOnMountOrArgChange: true }
|
||||
);
|
||||
const { data } = useListWorkflowsQuery({
|
||||
page,
|
||||
per_page: PER_PAGE,
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
} from 'features/nodes/store/nodesSlice';
|
||||
import { MouseEvent, ReactNode, memo, useCallback, useMemo } from 'react';
|
||||
import { FaMinus, FaPlus } from 'react-icons/fa';
|
||||
import { menuListMotionProps } from 'theme/components/menu';
|
||||
import { MENU_LIST_MOTION_PROPS } from 'theme/components/menu';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
@ -110,7 +110,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
||||
!menuItems.length ? null : (
|
||||
<MenuList
|
||||
sx={{ visibility: 'visible !important' }}
|
||||
motionProps={menuListMotionProps}
|
||||
motionProps={MENU_LIST_MOTION_PROPS}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<MenuGroup
|
||||
|
@ -0,0 +1,30 @@
|
||||
import { FileButton } from '@mantine/core';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { useLoadWorkflowFromFile } from 'features/nodes/hooks/useLoadWorkflowFromFile';
|
||||
import { memo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaSave, FaUpload } from 'react-icons/fa';
|
||||
|
||||
const LoadWorkflowButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const resetRef = useRef<() => void>(null);
|
||||
const loadWorkflowFromFile = useLoadWorkflowFromFile(resetRef);
|
||||
return (
|
||||
<FileButton
|
||||
resetRef={resetRef}
|
||||
accept="application/json"
|
||||
onChange={loadWorkflowFromFile}
|
||||
>
|
||||
{(props) => (
|
||||
<IAIIconButton
|
||||
icon={<FaSave />}
|
||||
tooltip={t('nodes.loadWorkflow')}
|
||||
aria-label={t('nodes.loadWorkflow')}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
</FileButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(LoadWorkflowButton);
|
@ -3,6 +3,7 @@ import { memo } from 'react';
|
||||
import DownloadWorkflowButton from './DownloadWorkflowButton';
|
||||
import LoadWorkflowButton from './LoadWorkflowButton';
|
||||
import ResetWorkflowButton from './ResetWorkflowButton';
|
||||
import SaveWorkflowButton from 'features/nodes/components/flow/panels/TopCenterPanel/SaveWorkflowButton';
|
||||
|
||||
const TopCenterPanel = () => {
|
||||
return (
|
||||
@ -17,6 +18,7 @@ const TopCenterPanel = () => {
|
||||
>
|
||||
<DownloadWorkflowButton />
|
||||
<LoadWorkflowButton />
|
||||
<SaveWorkflowButton />
|
||||
<ResetWorkflowButton />
|
||||
</Flex>
|
||||
);
|
||||
|
@ -1,13 +1,53 @@
|
||||
import { Flex } from '@chakra-ui/layout';
|
||||
import { memo } from 'react';
|
||||
import WorkflowEditorSettings from './WorkflowEditorSettings';
|
||||
import {
|
||||
Flex,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuGroup,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
} from '@chakra-ui/react';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import WorkflowLibraryButton from 'features/nodes/components/flow/WorkflowLibrary/WorkflowLibraryButton';
|
||||
import { memo } from 'react';
|
||||
import { FaEllipsis } from 'react-icons/fa6';
|
||||
import WorkflowEditorSettings from './WorkflowEditorSettings';
|
||||
import { MENU_LIST_MOTION_PROPS as MENU_LIST_MOTION_PROPS } from 'theme/components/menu';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDownloadWorkflow } from 'features/nodes/hooks/useDownloadWorkflow';
|
||||
import { FaDownload, FaSave } from 'react-icons/fa';
|
||||
import { useSaveWorkflow } from 'features/nodes/hooks/useSaveWorkflow';
|
||||
|
||||
const TopRightPanel = () => {
|
||||
const { t } = useTranslation();
|
||||
const downloadWorkflow = useDownloadWorkflow();
|
||||
const saveWorkflow = useSaveWorkflow();
|
||||
return (
|
||||
<Flex sx={{ gap: 2, position: 'absolute', top: 2, insetInlineEnd: 2 }}>
|
||||
<WorkflowEditorSettings />
|
||||
<WorkflowLibraryButton />
|
||||
<Menu>
|
||||
<MenuButton as={IAIIconButton} icon={<FaEllipsis />} />
|
||||
<MenuList motionProps={MENU_LIST_MOTION_PROPS}>
|
||||
<MenuItem onClick={saveWorkflow} icon={<FaSave />}>
|
||||
{t('workflows.saveWorkflow')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={downloadWorkflow} icon={<FaDownload />}>
|
||||
{t('workflows.downloadWorkflow')}
|
||||
</MenuItem>
|
||||
{/* <MenuGroup title={t('common.settingsLabel')}>
|
||||
<HotkeysModal>
|
||||
<MenuItem as="button" icon={<FaKeyboard />}>
|
||||
{t('common.hotkeysLabel')}
|
||||
</MenuItem>
|
||||
</HotkeysModal>
|
||||
<SettingsModal>
|
||||
<MenuItem as="button" icon={<FaCog />}>
|
||||
{t('common.settingsLabel')}
|
||||
</MenuItem>
|
||||
</SettingsModal>
|
||||
</MenuGroup> */}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,17 @@
|
||||
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useDownloadWorkflow = () => {
|
||||
const workflow = useWorkflow();
|
||||
const downloadWorkflow = useCallback(() => {
|
||||
const blob = new Blob([JSON.stringify(workflow, null, 2)]);
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `${workflow.name || 'My Workflow'}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
}, [workflow]);
|
||||
|
||||
return downloadWorkflow;
|
||||
};
|
@ -0,0 +1,46 @@
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
||||
import { workflowLoaded } from 'features/nodes/store/nodesSlice';
|
||||
import { zWorkflowV2 } from 'features/nodes/types/workflow';
|
||||
import { useCallback } from 'react';
|
||||
import {
|
||||
useCreateWorkflowMutation,
|
||||
useUpdateWorkflowMutation,
|
||||
} from 'services/api/endpoints/workflows';
|
||||
|
||||
export const useSaveWorkflow = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const workflow = useWorkflow();
|
||||
const [updateWorkflow, updateWorkflowResult] = useUpdateWorkflowMutation();
|
||||
const [createWorkflow, createWorkflowResult] = useCreateWorkflowMutation();
|
||||
const toaster = useAppToaster();
|
||||
const saveWorkflow = useCallback(async () => {
|
||||
try {
|
||||
if (workflow.id) {
|
||||
console.log('update workflow');
|
||||
const data = await updateWorkflow(workflow).unwrap();
|
||||
const updatedWorkflow = zWorkflowV2.parse(data.workflow);
|
||||
dispatch(workflowLoaded(updatedWorkflow));
|
||||
} else {
|
||||
console.log('create workflow');
|
||||
const data = await createWorkflow(workflow).unwrap();
|
||||
const createdWorkflow = zWorkflowV2.parse(data.workflow);
|
||||
dispatch(workflowLoaded(createdWorkflow));
|
||||
}
|
||||
toaster({
|
||||
title: 'Workflow saved',
|
||||
status: 'success',
|
||||
duration: 3000,
|
||||
});
|
||||
} catch (e) {
|
||||
toaster({
|
||||
title: 'Failed to save workflow',
|
||||
// description: e.message,
|
||||
status: 'error',
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
}, [workflow, toaster, updateWorkflow, dispatch, createWorkflow]);
|
||||
return saveWorkflow;
|
||||
};
|
@ -711,6 +711,9 @@ const nodesSlice = createSlice({
|
||||
workflowContactChanged: (state, action: PayloadAction<string>) => {
|
||||
state.workflow.contact = action.payload;
|
||||
},
|
||||
workflowIDChanged: (state, action: PayloadAction<string>) => {
|
||||
state.workflow.id = action.payload;
|
||||
},
|
||||
workflowLoaded: (state, action: PayloadAction<WorkflowV2>) => {
|
||||
const { nodes, edges, ...workflow } = action.payload;
|
||||
state.workflow = workflow;
|
||||
@ -1003,6 +1006,7 @@ export const {
|
||||
workflowNotesChanged,
|
||||
workflowTagsChanged,
|
||||
workflowVersionChanged,
|
||||
workflowIDChanged,
|
||||
edgeAdded,
|
||||
} = nodesSlice.actions;
|
||||
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
FaGithub,
|
||||
FaKeyboard,
|
||||
} from 'react-icons/fa';
|
||||
import { menuListMotionProps } from 'theme/components/menu';
|
||||
import { MENU_LIST_MOTION_PROPS } from 'theme/components/menu';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import HotkeysModal from './HotkeysModal/HotkeysModal';
|
||||
import InvokeAILogoComponent from './InvokeAILogoComponent';
|
||||
@ -54,7 +54,7 @@ const SiteHeader = () => {
|
||||
icon={<FaBars />}
|
||||
sx={{ boxSize: 8 }}
|
||||
/>
|
||||
<MenuList motionProps={menuListMotionProps}>
|
||||
<MenuList motionProps={MENU_LIST_MOTION_PROPS}>
|
||||
<MenuGroup title={t('common.communityLabel')}>
|
||||
{isGithubLinkEnabled && (
|
||||
<MenuItem
|
||||
|
@ -18,11 +18,14 @@ export const workflowsApi = api.injectEndpoints({
|
||||
url: `workflows/i/${workflow_id}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
invalidatesTags: [{ type: 'Workflow', id: LIST_TAG }],
|
||||
invalidatesTags: (result, error, workflow_id) => [
|
||||
{ type: 'Workflow', id: LIST_TAG },
|
||||
{ type: 'Workflow', id: workflow_id },
|
||||
],
|
||||
}),
|
||||
createWorkflow: build.mutation<
|
||||
paths['/api/v1/workflows/']['post']['responses']['200']['content']['application/json'],
|
||||
WorkflowV2
|
||||
paths['/api/v1/workflows/']['post']['requestBody']['content']['application/json']['workflow']
|
||||
>({
|
||||
query: (workflow) => ({
|
||||
url: 'workflows',
|
||||
@ -33,16 +36,16 @@ export const workflowsApi = api.injectEndpoints({
|
||||
}),
|
||||
updateWorkflow: build.mutation<
|
||||
paths['/api/v1/workflows/i/{workflow_id}']['patch']['responses']['200']['content']['application/json'],
|
||||
WorkflowV2
|
||||
paths['/api/v1/workflows/i/{workflow_id}']['patch']['requestBody']['content']['application/json']['workflow']
|
||||
>({
|
||||
query: (workflow) => ({
|
||||
url: `workflows/i/${workflow.id}`,
|
||||
method: 'PATCH',
|
||||
body: workflow,
|
||||
}),
|
||||
invalidatesTags: (response, error, arg) => [
|
||||
invalidatesTags: (response, error, workflow) => [
|
||||
{ type: 'Workflow', id: LIST_TAG },
|
||||
{ type: 'Workflow', id: arg.id },
|
||||
{ type: 'Workflow', id: workflow.id },
|
||||
],
|
||||
}),
|
||||
listWorkflows: build.query<
|
||||
@ -53,10 +56,7 @@ export const workflowsApi = api.injectEndpoints({
|
||||
url: 'workflows/',
|
||||
params,
|
||||
}),
|
||||
providesTags: (result, error, params) => [
|
||||
{ type: 'Workflow', id: LIST_TAG },
|
||||
{ type: 'Workflow', id: params?.page },
|
||||
],
|
||||
providesTags: [{ type: 'Workflow', id: LIST_TAG }],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
File diff suppressed because one or more lines are too long
@ -56,7 +56,7 @@ export const menuTheme = defineMultiStyleConfig({
|
||||
},
|
||||
});
|
||||
|
||||
export const menuListMotionProps: MotionProps = {
|
||||
export const MENU_LIST_MOTION_PROPS: MotionProps = {
|
||||
variants: {
|
||||
enter: {
|
||||
visibility: 'visible',
|
||||
|
Loading…
Reference in New Issue
Block a user