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:
psychedelicious 2023-08-18 20:27:40 +10:00
parent f952f8f685
commit 2514af79a0
8 changed files with 206 additions and 18 deletions

View File

@ -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);

View File

@ -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>

View File

@ -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}`;

View File

@ -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);

View File

@ -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);

View File

@ -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 = [];
});
});
},

View File

@ -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 = {

View File

@ -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'];