mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): crude node outputs display
Resets on invoke. Nothing fancy for the UI yet, just simple text (for numbers and strings) or image. For other output types, the output in JSON.
This commit is contained in:
parent
f952f8f685
commit
2514af79a0
@ -0,0 +1,17 @@
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import { memo } from 'react';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import { ImageOutput } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
output: ImageOutput;
|
||||
};
|
||||
|
||||
const ImageOutputPreview = ({ output }: Props) => {
|
||||
const { image, width, height } = output;
|
||||
const { data: imageDTO } = useGetImageDTOQuery(image.image_name);
|
||||
|
||||
return <IAIDndImage imageDTO={imageDTO} />;
|
||||
};
|
||||
|
||||
export default memo(ImageOutputPreview);
|
@ -8,6 +8,7 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import NodeDataInspector from './NodeDataInspector';
|
||||
import NodeResultsInspector from './NodeResultsInspector';
|
||||
import NodeTemplateInspector from './NodeTemplateInspector';
|
||||
|
||||
const InspectorPanel = () => {
|
||||
@ -15,10 +16,12 @@ const InspectorPanel = () => {
|
||||
<Flex
|
||||
layerStyle="first"
|
||||
sx={{
|
||||
flexDir: 'column',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
borderRadius: 'base',
|
||||
p: 4,
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
@ -26,17 +29,21 @@ const InspectorPanel = () => {
|
||||
sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }}
|
||||
>
|
||||
<TabList>
|
||||
<Tab>Node Template</Tab>
|
||||
<Tab>Node Outputs</Tab>
|
||||
<Tab>Node Data</Tab>
|
||||
<Tab>Node Template</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<NodeTemplateInspector />
|
||||
<NodeResultsInspector />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<NodeDataInspector />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<NodeTemplateInspector />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</Flex>
|
||||
|
@ -0,0 +1,101 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import { memo } from 'react';
|
||||
import ImageOutputPreview from './ImageOutputPreview';
|
||||
import NumberOutputPreview from './NumberOutputPreview';
|
||||
import ScrollableContent from './ScrollableContent';
|
||||
import StringOutputPreview from './StringOutputPreview';
|
||||
import { AnyResult } from 'services/events/types';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
({ nodes }) => {
|
||||
const lastSelectedNodeId =
|
||||
nodes.selectedNodes[nodes.selectedNodes.length - 1];
|
||||
|
||||
const lastSelectedNode = nodes.nodes.find(
|
||||
(node) => node.id === lastSelectedNodeId
|
||||
);
|
||||
|
||||
const nes =
|
||||
nodes.nodeExecutionStates[lastSelectedNodeId ?? '__UNKNOWN_NODE__'];
|
||||
|
||||
return {
|
||||
node: lastSelectedNode,
|
||||
nes,
|
||||
};
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const NodeResultsInspector = () => {
|
||||
const { node, nes } = useAppSelector(selector);
|
||||
|
||||
if (!node || !nes) {
|
||||
return <IAINoContentFallback label="No node selected" icon={null} />;
|
||||
}
|
||||
|
||||
if (nes.outputs.length === 0) {
|
||||
return <IAINoContentFallback label="No outputs recorded" icon={null} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'relative',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
}}
|
||||
>
|
||||
<ScrollableContent>
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'relative',
|
||||
flexDir: 'column',
|
||||
alignItems: 'flex-start',
|
||||
p: 1,
|
||||
gap: 2,
|
||||
h: 'full',
|
||||
w: 'full',
|
||||
}}
|
||||
>
|
||||
{nes.outputs.map((result, i) => {
|
||||
if (result.type === 'string_output') {
|
||||
return (
|
||||
<StringOutputPreview key={getKey(result, i)} output={result} />
|
||||
);
|
||||
}
|
||||
if (result.type === 'float_output') {
|
||||
return (
|
||||
<NumberOutputPreview key={getKey(result, i)} output={result} />
|
||||
);
|
||||
}
|
||||
if (result.type === 'integer_output') {
|
||||
return (
|
||||
<NumberOutputPreview key={getKey(result, i)} output={result} />
|
||||
);
|
||||
}
|
||||
if (result.type === 'image_output') {
|
||||
return (
|
||||
<ImageOutputPreview key={getKey(result, i)} output={result} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<pre key={getKey(result, i)}>
|
||||
{JSON.stringify(result, null, 2)}
|
||||
</pre>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</ScrollableContent>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(NodeResultsInspector);
|
||||
|
||||
const getKey = (result: AnyResult, i: number) => `${result.type}-${i}`;
|
@ -0,0 +1,13 @@
|
||||
import { Text } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import { FloatOutput, IntegerOutput } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
output: IntegerOutput | FloatOutput;
|
||||
};
|
||||
|
||||
const NumberOutputPreview = ({ output }: Props) => {
|
||||
return <Text>{output.value}</Text>;
|
||||
};
|
||||
|
||||
export default memo(NumberOutputPreview);
|
@ -0,0 +1,13 @@
|
||||
import { Text } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import { StringOutput } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
output: StringOutput;
|
||||
};
|
||||
|
||||
const StringOutputPreview = ({ output }: Props) => {
|
||||
return <Text>{output.value}</Text>;
|
||||
};
|
||||
|
||||
export default memo(StringOutputPreview);
|
@ -44,6 +44,7 @@ import {
|
||||
isNotesNode,
|
||||
LoRAModelInputFieldValue,
|
||||
MainModelInputFieldValue,
|
||||
NodeExecutionState,
|
||||
NodeStatus,
|
||||
NotesNodeData,
|
||||
SchedulerInputFieldValue,
|
||||
@ -55,6 +56,14 @@ import {
|
||||
import { NodesState } from './types';
|
||||
import { findUnoccupiedPosition } from './util/findUnoccupiedPosition';
|
||||
|
||||
const initialNodeExecutionState: Omit<NodeExecutionState, 'nodeId'> = {
|
||||
status: NodeStatus.PENDING,
|
||||
error: null,
|
||||
progress: null,
|
||||
progressImage: null,
|
||||
outputs: [],
|
||||
};
|
||||
|
||||
export const initialNodesState: NodesState = {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
@ -67,7 +76,7 @@ export const initialNodesState: NodesState = {
|
||||
shouldShowMinimapPanel: true,
|
||||
shouldValidateGraph: true,
|
||||
shouldAnimateEdges: true,
|
||||
shouldSnapToGrid: true,
|
||||
shouldSnapToGrid: false,
|
||||
shouldColorEdges: true,
|
||||
nodeOpacity: 1,
|
||||
selectedNodes: [],
|
||||
@ -141,10 +150,8 @@ const nodesSlice = createSlice({
|
||||
}
|
||||
|
||||
state.nodeExecutionStates[node.id] = {
|
||||
status: NodeStatus.PENDING,
|
||||
error: null,
|
||||
progress: null,
|
||||
progressImage: null,
|
||||
nodeId: node.id,
|
||||
...initialNodeExecutionState,
|
||||
};
|
||||
},
|
||||
edgesChanged: (state, action: PayloadAction<EdgeChange[]>) => {
|
||||
@ -677,10 +684,8 @@ const nodesSlice = createSlice({
|
||||
|
||||
newNodes.forEach((node) => {
|
||||
state.nodeExecutionStates[node.id] = {
|
||||
status: NodeStatus.PENDING,
|
||||
error: null,
|
||||
progress: null,
|
||||
progressImage: null,
|
||||
nodeId: node.id,
|
||||
...initialNodeExecutionState,
|
||||
};
|
||||
});
|
||||
},
|
||||
@ -700,13 +705,14 @@ const nodesSlice = createSlice({
|
||||
}
|
||||
});
|
||||
builder.addCase(appSocketInvocationComplete, (state, action) => {
|
||||
const { source_node_id } = action.payload.data;
|
||||
const node = state.nodeExecutionStates[source_node_id];
|
||||
if (node) {
|
||||
node.status = NodeStatus.COMPLETED;
|
||||
if (node.progress !== null) {
|
||||
node.progress = 1;
|
||||
const { source_node_id, result } = action.payload.data;
|
||||
const nes = state.nodeExecutionStates[source_node_id];
|
||||
if (nes) {
|
||||
nes.status = NodeStatus.COMPLETED;
|
||||
if (nes.progress !== null) {
|
||||
nes.progress = 1;
|
||||
}
|
||||
nes.outputs.push(result);
|
||||
}
|
||||
});
|
||||
builder.addCase(appSocketInvocationError, (state, action) => {
|
||||
@ -735,6 +741,7 @@ const nodesSlice = createSlice({
|
||||
nes.error = null;
|
||||
nes.progress = null;
|
||||
nes.progressImage = null;
|
||||
nes.outputs = [];
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -9,14 +9,20 @@ import {
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
import { RgbaColor } from 'react-colorful';
|
||||
import { Edge, Node } from 'reactflow';
|
||||
import { components } from 'services/api/schema';
|
||||
import {
|
||||
Graph,
|
||||
GraphExecutionState,
|
||||
ImageDTO,
|
||||
ImageField,
|
||||
_InputField,
|
||||
_OutputField,
|
||||
} from 'services/api/types';
|
||||
import { AnyInvocationType, ProgressImage } from 'services/events/types';
|
||||
import {
|
||||
AnyInvocationType,
|
||||
AnyResult,
|
||||
ProgressImage,
|
||||
} from 'services/events/types';
|
||||
import { O } from 'ts-toolbelt';
|
||||
import { z } from 'zod';
|
||||
|
||||
@ -671,11 +677,32 @@ export enum NodeStatus {
|
||||
FAILED,
|
||||
}
|
||||
|
||||
type SavedOutput =
|
||||
| components['schemas']['StringOutput']
|
||||
| components['schemas']['IntegerOutput']
|
||||
| components['schemas']['FloatOutput']
|
||||
| components['schemas']['ImageOutput'];
|
||||
|
||||
export const isSavedOutput = (
|
||||
output: GraphExecutionState['results'][string]
|
||||
): output is SavedOutput =>
|
||||
Boolean(
|
||||
output &&
|
||||
[
|
||||
'string_output',
|
||||
'integer_output',
|
||||
'float_output',
|
||||
'image_output',
|
||||
].includes(output?.type)
|
||||
);
|
||||
|
||||
export type NodeExecutionState = {
|
||||
nodeId: string;
|
||||
status: NodeStatus;
|
||||
progress: number | null;
|
||||
progressImage: ProgressImage | null;
|
||||
error: string | null;
|
||||
outputs: AnyResult[];
|
||||
};
|
||||
|
||||
export type FieldIdentifier = {
|
||||
|
@ -155,6 +155,9 @@ export type ZoeDepthImageProcessorInvocation =
|
||||
|
||||
// Node Outputs
|
||||
export type ImageOutput = s['ImageOutput'];
|
||||
export type StringOutput = s['StringOutput'];
|
||||
export type FloatOutput = s['FloatOutput'];
|
||||
export type IntegerOutput = s['IntegerOutput'];
|
||||
export type IterateInvocationOutput = s['IterateInvocationOutput'];
|
||||
export type CollectInvocationOutput = s['CollectInvocationOutput'];
|
||||
export type LatentsOutput = s['LatentsOutput'];
|
||||
|
Loading…
Reference in New Issue
Block a user