feat(ui): add conditionally-enabled workflow publishing ui

This is a squash of a lot of scattered commits that became very difficult to clean up and make individually. Sorry.

Besides the new UI, there are a number of notable changes:
- Publishing logic is disabled in OSS by default. To enable it, provided a `disabledFeatures` prop _without_ "publishWorkflow".
- Enqueuing a workflow is no longer handled in a redux listener. It was  hard to track the state of the enqueue logic in the listener. It is now in a hook. I did not migrate the canvas and upscaling tabs - their enqueue logic is still in the listener.
- When queueing a validation run, the new `useEnqueueWorkflows()` hook will update the payload with the required data for the run.
- Some logic is added to the socket event listeners to handle workflow publish runs completing.
- The workflow library side nav has a new "published" view. It is hidden when the "publishWorkflow" feature is disabled.
- I've added `Safe` and `OrThrow` versions of some workflows hooks. These hooks typically retrieve some data from redux. For example, a node. The `Safe` hooks return the node or null if it cannot be found, while the `OrThrow` hooks return the node or raise if it cannot be found. The `OrThrow` hooks should be used within one of the gate components. These components use the `Safe` hooks and render a fallback if e.g. the node isn't found. This change is required for some of the publish flow UI.
- Add support for locking the workflow editor. When locked, you can pan and zoom but that's it. Currently, it is only locked during publish flow and if a published workflow is opened.
This commit is contained in:
psychedelicious
2025-04-02 16:21:58 +10:00
parent d66fdfde71
commit e4678201cb
101 changed files with 1410 additions and 341 deletions

View File

@@ -22,6 +22,7 @@ import {
import type { DynamicPromptsState } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import { $isInPublishFlow } from 'features/nodes/components/sidePanel/workflow/publish';
import { $templates } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import type { NodesState, Templates } from 'features/nodes/store/types';
@@ -84,7 +85,8 @@ const debouncedUpdateReasons = debounce(
templates: Templates,
upscale: UpscaleState,
config: AppConfig,
store: AppStore
store: AppStore,
isInPublishFlow: boolean
) => {
if (tab === 'canvas') {
const model = selectMainModelConfig(store.getState());
@@ -108,6 +110,7 @@ const debouncedUpdateReasons = debounce(
workflowSettingsState: workflowSettings,
isConnected,
templates,
isInPublishFlow,
});
$reasonsWhyCannotEnqueue.set(reasons);
} else if (tab === 'upscaling') {
@@ -144,6 +147,7 @@ export const useReadinessWatcher = () => {
const canvasIsRasterizing = useStore(canvasManager?.stateApi.$isRasterizing ?? $true);
const canvasIsSelectingObject = useStore(canvasManager?.stateApi.$isSegmenting ?? $true);
const canvasIsCompositing = useStore(canvasManager?.compositor.$isBusy ?? $true);
const isInPublishFlow = useStore($isInPublishFlow);
useEffect(() => {
debouncedUpdateReasons(
@@ -162,7 +166,8 @@ export const useReadinessWatcher = () => {
templates,
upscale,
config,
store
store,
isInPublishFlow
);
}, [
store,
@@ -181,6 +186,7 @@ export const useReadinessWatcher = () => {
templates,
upscale,
workflowSettings,
isInPublishFlow,
]);
};
@@ -192,15 +198,16 @@ const getReasonsWhyCannotEnqueueWorkflowsTab = async (arg: {
workflowSettingsState: WorkflowSettingsState;
isConnected: boolean;
templates: Templates;
isInPublishFlow: boolean;
}): Promise<Reason[]> => {
const { dispatch, nodesState, workflowSettingsState, isConnected, templates } = arg;
const { dispatch, nodesState, workflowSettingsState, isConnected, templates, isInPublishFlow } = arg;
const reasons: Reason[] = [];
if (!isConnected) {
reasons.push(disconnectedReason(i18n.t));
}
if (workflowSettingsState.shouldValidateGraph) {
if (workflowSettingsState.shouldValidateGraph || isInPublishFlow) {
const { nodes, edges } = nodesState;
const invocationNodes = nodes.filter(isInvocationNode);
const batchNodes = invocationNodes.filter(isBatchNode);