mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
merge with main
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import { forEach, size } from 'lodash-es';
|
||||
import { ImageField, LatentsField } from 'services/api';
|
||||
import { ImageField, LatentsField, ConditioningField } from 'services/api';
|
||||
|
||||
const OBJECT_TYPESTRING = '[object Object]';
|
||||
const STRING_TYPESTRING = '[object String]';
|
||||
@ -74,8 +74,38 @@ const parseLatentsField = (latentsField: unknown): LatentsField | undefined => {
|
||||
};
|
||||
};
|
||||
|
||||
const parseConditioningField = (
|
||||
conditioningField: unknown
|
||||
): ConditioningField | undefined => {
|
||||
// Must be an object
|
||||
if (!isObject(conditioningField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A ConditioningField must have a `conditioning_name`
|
||||
if (!('conditioning_name' in conditioningField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A ConditioningField's `conditioning_name` must be a string
|
||||
if (typeof conditioningField.conditioning_name !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build a valid ConditioningField
|
||||
return {
|
||||
conditioning_name: conditioningField.conditioning_name,
|
||||
};
|
||||
};
|
||||
|
||||
type NodeMetadata = {
|
||||
[key: string]: string | number | boolean | ImageField | LatentsField;
|
||||
[key: string]:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| ImageField
|
||||
| LatentsField
|
||||
| ConditioningField;
|
||||
};
|
||||
|
||||
type InvokeAIMetadata = {
|
||||
@ -101,7 +131,7 @@ export const parseNodeMetadata = (
|
||||
return;
|
||||
}
|
||||
|
||||
// the only valid object types are ImageField and LatentsField
|
||||
// the only valid object types are ImageField, LatentsField and ConditioningField
|
||||
if (isObject(nodeItem)) {
|
||||
if ('image_name' in nodeItem || 'image_type' in nodeItem) {
|
||||
const imageField = parseImageField(nodeItem);
|
||||
@ -118,6 +148,14 @@ export const parseNodeMetadata = (
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ('conditioning_name' in nodeItem) {
|
||||
const conditioningField = parseConditioningField(nodeItem);
|
||||
if (conditioningField) {
|
||||
parsed[nodeKey] = conditioningField;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise we accept any string, number or boolean
|
||||
|
@ -6,9 +6,11 @@ import BooleanInputFieldComponent from './fields/BooleanInputFieldComponent';
|
||||
import EnumInputFieldComponent from './fields/EnumInputFieldComponent';
|
||||
import ImageInputFieldComponent from './fields/ImageInputFieldComponent';
|
||||
import LatentsInputFieldComponent from './fields/LatentsInputFieldComponent';
|
||||
import ConditioningInputFieldComponent from './fields/ConditioningInputFieldComponent';
|
||||
import ModelInputFieldComponent from './fields/ModelInputFieldComponent';
|
||||
import NumberInputFieldComponent from './fields/NumberInputFieldComponent';
|
||||
import StringInputFieldComponent from './fields/StringInputFieldComponent';
|
||||
import ItemInputFieldComponent from './fields/ItemInputFieldComponent';
|
||||
|
||||
type InputFieldComponentProps = {
|
||||
nodeId: string;
|
||||
@ -84,6 +86,16 @@ const InputFieldComponent = (props: InputFieldComponentProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'conditioning' && template.type === 'conditioning') {
|
||||
return (
|
||||
<ConditioningInputFieldComponent
|
||||
nodeId={nodeId}
|
||||
field={field}
|
||||
template={template}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'model' && template.type === 'model') {
|
||||
return (
|
||||
<ModelInputFieldComponent
|
||||
@ -104,6 +116,16 @@ const InputFieldComponent = (props: InputFieldComponentProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'item' && template.type === 'item') {
|
||||
return (
|
||||
<ItemInputFieldComponent
|
||||
nodeId={nodeId}
|
||||
field={field}
|
||||
template={template}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <Box p={2}>Unknown field type: {type}</Box>;
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,19 @@
|
||||
import {
|
||||
ConditioningInputFieldTemplate,
|
||||
ConditioningInputFieldValue,
|
||||
} from 'features/nodes/types/types';
|
||||
import { memo } from 'react';
|
||||
import { FieldComponentProps } from './types';
|
||||
|
||||
const ConditioningInputFieldComponent = (
|
||||
props: FieldComponentProps<
|
||||
ConditioningInputFieldValue,
|
||||
ConditioningInputFieldTemplate
|
||||
>
|
||||
) => {
|
||||
const { nodeId, field } = props;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default memo(ConditioningInputFieldComponent);
|
@ -0,0 +1,17 @@
|
||||
import {
|
||||
ItemInputFieldTemplate,
|
||||
ItemInputFieldValue,
|
||||
} from 'features/nodes/types/types';
|
||||
import { memo } from 'react';
|
||||
import { FaAddressCard, FaList } from 'react-icons/fa';
|
||||
import { FieldComponentProps } from './types';
|
||||
|
||||
const ItemInputFieldComponent = (
|
||||
props: FieldComponentProps<ItemInputFieldValue, ItemInputFieldTemplate>
|
||||
) => {
|
||||
const { nodeId, field } = props;
|
||||
|
||||
return <FaAddressCard />;
|
||||
};
|
||||
|
||||
export default memo(ItemInputFieldComponent);
|
@ -11,8 +11,10 @@ export const FIELD_TYPE_MAP: Record<string, FieldType> = {
|
||||
enum: 'enum',
|
||||
ImageField: 'image',
|
||||
LatentsField: 'latents',
|
||||
ConditioningField: 'conditioning',
|
||||
model: 'model',
|
||||
array: 'array',
|
||||
item: 'item',
|
||||
};
|
||||
|
||||
const COLOR_TOKEN_VALUE = 500;
|
||||
@ -63,6 +65,12 @@ export const FIELDS: Record<FieldType, FieldUIConfig> = {
|
||||
title: 'Latents',
|
||||
description: 'Latents may be passed between nodes.',
|
||||
},
|
||||
conditioning: {
|
||||
color: 'cyan',
|
||||
colorCssVar: getColorTokenCssVariable('cyan'),
|
||||
title: 'Conditioning',
|
||||
description: 'Conditioning may be passed between nodes.',
|
||||
},
|
||||
model: {
|
||||
color: 'teal',
|
||||
colorCssVar: getColorTokenCssVariable('teal'),
|
||||
@ -75,4 +83,10 @@ export const FIELDS: Record<FieldType, FieldUIConfig> = {
|
||||
title: 'Array',
|
||||
description: 'TODO: Array type description.',
|
||||
},
|
||||
item: {
|
||||
color: 'gray',
|
||||
colorCssVar: getColorTokenCssVariable('gray'),
|
||||
title: 'Collection Item',
|
||||
description: 'TODO: Collection Item type description.',
|
||||
},
|
||||
};
|
||||
|
@ -56,8 +56,10 @@ export type FieldType =
|
||||
| 'enum'
|
||||
| 'image'
|
||||
| 'latents'
|
||||
| 'conditioning'
|
||||
| 'model'
|
||||
| 'array';
|
||||
| 'array'
|
||||
| 'item';
|
||||
|
||||
/**
|
||||
* An input field is persisted across reloads as part of the user's local state.
|
||||
@ -74,9 +76,11 @@ export type InputFieldValue =
|
||||
| BooleanInputFieldValue
|
||||
| ImageInputFieldValue
|
||||
| LatentsInputFieldValue
|
||||
| ConditioningInputFieldValue
|
||||
| EnumInputFieldValue
|
||||
| ModelInputFieldValue
|
||||
| ArrayInputFieldValue;
|
||||
| ArrayInputFieldValue
|
||||
| ItemInputFieldValue;
|
||||
|
||||
/**
|
||||
* An input field template is generated on each page load from the OpenAPI schema.
|
||||
@ -91,9 +95,11 @@ export type InputFieldTemplate =
|
||||
| BooleanInputFieldTemplate
|
||||
| ImageInputFieldTemplate
|
||||
| LatentsInputFieldTemplate
|
||||
| ConditioningInputFieldTemplate
|
||||
| EnumInputFieldTemplate
|
||||
| ModelInputFieldTemplate
|
||||
| ArrayInputFieldTemplate;
|
||||
| ArrayInputFieldTemplate
|
||||
| ItemInputFieldTemplate;
|
||||
|
||||
/**
|
||||
* An output field is persisted across as part of the user's local state.
|
||||
@ -162,6 +168,11 @@ export type LatentsInputFieldValue = FieldValueBase & {
|
||||
value?: undefined;
|
||||
};
|
||||
|
||||
export type ConditioningInputFieldValue = FieldValueBase & {
|
||||
type: 'conditioning';
|
||||
value?: undefined;
|
||||
};
|
||||
|
||||
export type ImageInputFieldValue = FieldValueBase & {
|
||||
type: 'image';
|
||||
value?: Pick<ImageField, 'image_name' | 'image_type'>;
|
||||
@ -177,6 +188,11 @@ export type ArrayInputFieldValue = FieldValueBase & {
|
||||
value?: (string | number)[];
|
||||
};
|
||||
|
||||
export type ItemInputFieldValue = FieldValueBase & {
|
||||
type: 'item';
|
||||
value?: undefined;
|
||||
};
|
||||
|
||||
export type InputFieldTemplateBase = {
|
||||
name: string;
|
||||
title: string;
|
||||
@ -229,6 +245,11 @@ export type LatentsInputFieldTemplate = InputFieldTemplateBase & {
|
||||
type: 'latents';
|
||||
};
|
||||
|
||||
export type ConditioningInputFieldTemplate = InputFieldTemplateBase & {
|
||||
default: undefined;
|
||||
type: 'conditioning';
|
||||
};
|
||||
|
||||
export type EnumInputFieldTemplate = InputFieldTemplateBase & {
|
||||
default: string | number;
|
||||
type: 'enum';
|
||||
@ -242,10 +263,15 @@ export type ModelInputFieldTemplate = InputFieldTemplateBase & {
|
||||
};
|
||||
|
||||
export type ArrayInputFieldTemplate = InputFieldTemplateBase & {
|
||||
default: (string | number)[];
|
||||
default: [];
|
||||
type: 'array';
|
||||
};
|
||||
|
||||
export type ItemInputFieldTemplate = InputFieldTemplateBase & {
|
||||
default: undefined;
|
||||
type: 'item';
|
||||
};
|
||||
|
||||
/**
|
||||
* JANKY CUSTOMISATION OF OpenAPI SCHEMA TYPES
|
||||
*/
|
||||
|
@ -9,12 +9,15 @@ import {
|
||||
ImageInputFieldTemplate,
|
||||
IntegerInputFieldTemplate,
|
||||
LatentsInputFieldTemplate,
|
||||
ConditioningInputFieldTemplate,
|
||||
StringInputFieldTemplate,
|
||||
ModelInputFieldTemplate,
|
||||
InputFieldTemplateBase,
|
||||
OutputFieldTemplate,
|
||||
TypeHints,
|
||||
FieldType,
|
||||
ArrayInputFieldTemplate,
|
||||
ItemInputFieldTemplate,
|
||||
} from '../types/types';
|
||||
|
||||
export type BaseFieldProperties = 'name' | 'title' | 'description';
|
||||
@ -196,6 +199,21 @@ const buildLatentsInputFieldTemplate = ({
|
||||
return template;
|
||||
};
|
||||
|
||||
const buildConditioningInputFieldTemplate = ({
|
||||
schemaObject,
|
||||
baseField,
|
||||
}: BuildInputFieldArg): ConditioningInputFieldTemplate => {
|
||||
const template: ConditioningInputFieldTemplate = {
|
||||
...baseField,
|
||||
type: 'conditioning',
|
||||
inputRequirement: 'always',
|
||||
inputKind: 'connection',
|
||||
default: schemaObject.default ?? undefined,
|
||||
};
|
||||
|
||||
return template;
|
||||
};
|
||||
|
||||
const buildEnumInputFieldTemplate = ({
|
||||
schemaObject,
|
||||
baseField,
|
||||
@ -214,6 +232,36 @@ const buildEnumInputFieldTemplate = ({
|
||||
return template;
|
||||
};
|
||||
|
||||
const buildArrayInputFieldTemplate = ({
|
||||
schemaObject,
|
||||
baseField,
|
||||
}: BuildInputFieldArg): ArrayInputFieldTemplate => {
|
||||
const template: ArrayInputFieldTemplate = {
|
||||
...baseField,
|
||||
type: 'array',
|
||||
inputRequirement: 'always',
|
||||
inputKind: 'direct',
|
||||
default: [],
|
||||
};
|
||||
|
||||
return template;
|
||||
};
|
||||
|
||||
const buildItemInputFieldTemplate = ({
|
||||
schemaObject,
|
||||
baseField,
|
||||
}: BuildInputFieldArg): ItemInputFieldTemplate => {
|
||||
const template: ItemInputFieldTemplate = {
|
||||
...baseField,
|
||||
type: 'item',
|
||||
inputRequirement: 'always',
|
||||
inputKind: 'direct',
|
||||
default: undefined,
|
||||
};
|
||||
|
||||
return template;
|
||||
};
|
||||
|
||||
export const getFieldType = (
|
||||
schemaObject: OpenAPIV3.SchemaObject,
|
||||
name: string,
|
||||
@ -266,6 +314,9 @@ export const buildInputFieldTemplate = (
|
||||
if (['latents'].includes(fieldType)) {
|
||||
return buildLatentsInputFieldTemplate({ schemaObject, baseField });
|
||||
}
|
||||
if (['conditioning'].includes(fieldType)) {
|
||||
return buildConditioningInputFieldTemplate({ schemaObject, baseField });
|
||||
}
|
||||
if (['model'].includes(fieldType)) {
|
||||
return buildModelInputFieldTemplate({ schemaObject, baseField });
|
||||
}
|
||||
@ -284,6 +335,12 @@ export const buildInputFieldTemplate = (
|
||||
if (['boolean'].includes(fieldType)) {
|
||||
return buildBooleanInputFieldTemplate({ schemaObject, baseField });
|
||||
}
|
||||
if (['array'].includes(fieldType)) {
|
||||
return buildArrayInputFieldTemplate({ schemaObject, baseField });
|
||||
}
|
||||
if (['item'].includes(fieldType)) {
|
||||
return buildItemInputFieldTemplate({ schemaObject, baseField });
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
@ -48,6 +48,10 @@ export const buildInputFieldValue = (
|
||||
fieldValue.value = undefined;
|
||||
}
|
||||
|
||||
if (template.type === 'conditioning') {
|
||||
fieldValue.value = undefined;
|
||||
}
|
||||
|
||||
if (template.type === 'model') {
|
||||
fieldValue.value = undefined;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ export const buildIterateNode = (): IterateInvocation => {
|
||||
return {
|
||||
id: nodeId,
|
||||
type: 'iterate',
|
||||
collection: [],
|
||||
index: 0,
|
||||
// collection: [],
|
||||
// index: 0,
|
||||
};
|
||||
};
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
buildOutputFieldTemplates,
|
||||
} from './fieldTemplateBuilders';
|
||||
|
||||
const invocationDenylist = ['Graph', 'Collect', 'LoadImage'];
|
||||
const invocationDenylist = ['Graph', 'LoadImage'];
|
||||
|
||||
export const parseSchema = (openAPI: OpenAPIV3.Document) => {
|
||||
// filter out non-invocation schemas, plus some tricky invocations for now
|
||||
@ -32,49 +32,62 @@ export const parseSchema = (openAPI: OpenAPIV3.Document) => {
|
||||
if (isInvocationSchemaObject(schema)) {
|
||||
const type = schema.properties.type.default;
|
||||
|
||||
const title =
|
||||
schema.ui?.title ??
|
||||
schema.title
|
||||
.replace('Invocation', '')
|
||||
.split(/(?=[A-Z])/) // split PascalCase into array
|
||||
.join(' ');
|
||||
const title = schema.ui?.title ?? schema.title.replace('Invocation', '');
|
||||
|
||||
const typeHints = schema.ui?.type_hints;
|
||||
|
||||
const inputs = reduce(
|
||||
schema.properties,
|
||||
(inputsAccumulator, property, propertyName) => {
|
||||
if (
|
||||
// `type` and `id` are not valid inputs/outputs
|
||||
!['type', 'id'].includes(propertyName) &&
|
||||
isSchemaObject(property)
|
||||
) {
|
||||
let field: InputFieldTemplate | undefined;
|
||||
if (propertyName === 'collection') {
|
||||
field = {
|
||||
default: property.default ?? [],
|
||||
name: 'collection',
|
||||
title: property.title ?? '',
|
||||
description: property.description ?? '',
|
||||
type: 'array',
|
||||
inputRequirement: 'always',
|
||||
inputKind: 'connection',
|
||||
};
|
||||
} else {
|
||||
field = buildInputFieldTemplate(
|
||||
property,
|
||||
propertyName,
|
||||
typeHints
|
||||
);
|
||||
const inputs: Record<string, InputFieldTemplate> = {};
|
||||
|
||||
if (type === 'collect') {
|
||||
const itemProperty = schema.properties[
|
||||
'item'
|
||||
] as InvocationSchemaObject;
|
||||
// Handle the special Collect node
|
||||
inputs.item = {
|
||||
type: 'item',
|
||||
name: 'item',
|
||||
description: itemProperty.description ?? '',
|
||||
title: 'Collection Item',
|
||||
inputKind: 'connection',
|
||||
inputRequirement: 'always',
|
||||
default: undefined,
|
||||
};
|
||||
} else if (type === 'iterate') {
|
||||
const itemProperty = schema.properties[
|
||||
'collection'
|
||||
] as InvocationSchemaObject;
|
||||
|
||||
inputs.collection = {
|
||||
type: 'array',
|
||||
name: 'collection',
|
||||
title: itemProperty.title ?? '',
|
||||
default: [],
|
||||
description: itemProperty.description ?? '',
|
||||
inputRequirement: 'always',
|
||||
inputKind: 'connection',
|
||||
};
|
||||
} else {
|
||||
// All other nodes
|
||||
reduce(
|
||||
schema.properties,
|
||||
(inputsAccumulator, property, propertyName) => {
|
||||
if (
|
||||
// `type` and `id` are not valid inputs/outputs
|
||||
!['type', 'id'].includes(propertyName) &&
|
||||
isSchemaObject(property)
|
||||
) {
|
||||
const field: InputFieldTemplate | undefined =
|
||||
buildInputFieldTemplate(property, propertyName, typeHints);
|
||||
|
||||
if (field) {
|
||||
inputsAccumulator[propertyName] = field;
|
||||
}
|
||||
}
|
||||
if (field) {
|
||||
inputsAccumulator[propertyName] = field;
|
||||
}
|
||||
}
|
||||
return inputsAccumulator;
|
||||
},
|
||||
{} as Record<string, InputFieldTemplate>
|
||||
);
|
||||
return inputsAccumulator;
|
||||
},
|
||||
inputs
|
||||
);
|
||||
}
|
||||
|
||||
const rawOutput = (schema as InvocationSchemaObject).output;
|
||||
|
||||
|
@ -107,7 +107,7 @@ const initialSystemState: SystemState = {
|
||||
subscribedNodeIds: [],
|
||||
wereModelsReceived: false,
|
||||
wasSchemaParsed: false,
|
||||
consoleLogLevel: 'error',
|
||||
consoleLogLevel: 'debug',
|
||||
shouldLogToConsole: true,
|
||||
statusTranslationKey: 'common.statusDisconnected',
|
||||
canceledSession: '',
|
||||
@ -384,6 +384,13 @@ export const systemSlice = createSlice({
|
||||
state.statusTranslationKey = 'common.statusPreparing';
|
||||
});
|
||||
|
||||
builder.addCase(sessionInvoked.rejected, (state, action) => {
|
||||
const error = action.payload as string | undefined;
|
||||
state.toastQueue.push(
|
||||
makeToast({ title: error || t('toast.serverError'), status: 'error' })
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Session Canceled
|
||||
*/
|
||||
|
@ -46,6 +46,8 @@ export const socketMiddleware = () => {
|
||||
// TODO: handle providing jwt to socket.io
|
||||
socketOptions.auth = { token: OpenAPI.TOKEN };
|
||||
}
|
||||
|
||||
socketOptions.transports = ['websocket', 'polling'];
|
||||
}
|
||||
|
||||
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
|
||||
|
@ -22,6 +22,8 @@ import {
|
||||
} from 'services/thunks/gallery';
|
||||
import { receivedModels } from 'services/thunks/model';
|
||||
import { receivedOpenAPISchema } from 'services/thunks/schema';
|
||||
import { makeToast } from '../../../features/system/hooks/useToastWatcher';
|
||||
import { addToast } from '../../../features/system/store/systemSlice';
|
||||
|
||||
type SetEventListenersArg = {
|
||||
socket: Socket<ServerToClientEvents, ClientToServerEvents>;
|
||||
@ -78,6 +80,16 @@ export const setEventListeners = (arg: SetEventListenersArg) => {
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('connect_error', (error) => {
|
||||
if (error && error.message) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({ title: error.message, status: 'error', duration: 10000 })
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Disconnect
|
||||
*/
|
||||
|
@ -101,17 +101,24 @@ export const nodeAdded = createAppAsyncThunk(
|
||||
*/
|
||||
export const sessionInvoked = createAppAsyncThunk(
|
||||
'api/sessionInvoked',
|
||||
async (arg: { sessionId: string }, _thunkApi) => {
|
||||
async (arg: { sessionId: string }, { rejectWithValue }) => {
|
||||
const { sessionId } = arg;
|
||||
|
||||
const response = await SessionsService.invokeSession({
|
||||
sessionId,
|
||||
all: true,
|
||||
});
|
||||
try {
|
||||
const response = await SessionsService.invokeSession({
|
||||
sessionId,
|
||||
all: true,
|
||||
});
|
||||
sessionLog.info({ arg, response }, `Session invoked (${sessionId})`);
|
||||
|
||||
sessionLog.info({ arg, response }, `Session invoked (${sessionId})`);
|
||||
|
||||
return response;
|
||||
return response;
|
||||
} catch (error) {
|
||||
const err = error as any;
|
||||
if (err.status === 403) {
|
||||
return rejectWithValue(err.body.detail);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
Reference in New Issue
Block a user