feat: add ComfyUI integration for image generation and management
- Implemented ComfyGenerateRequest type and associated functions for generating images using various architectures and modes. - Added functions for listing generation options and handling image uploads. - Created workflows for different generation modes including SDXL, Z-Image, Z-Image Turbo, and Anima. - Introduced GenerationJobStatus component to display the status of ongoing generation jobs. - Developed MaskControls for managing mask operations and displaying mask analysis. - Created palette items for tool selection, layer management, and generation settings.
This commit is contained in:
61
view/App.tsx
61
view/App.tsx
@@ -1,23 +1,24 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { DownloadSimple, FolderOpen, Sparkle, Stack } from "@phosphor-icons/react";
|
||||
import type { ImageStudioApp } from "@app/app";
|
||||
import { commandIds } from "@commands/ids";
|
||||
import { BottomControlsIsland } from "./BottomControlsIsland";
|
||||
import { brushUnavailableHint } from "./canvas/brush";
|
||||
import { brushUnavailableHint } from "@operations/paint/brush";
|
||||
import { CanvasViewport } from "./CanvasViewport";
|
||||
import { CommandPalette } from "./CommandPalette";
|
||||
import { GenerateSheet } from "./GenerateSheet";
|
||||
import { GenerationJobStatus } from "./GenerationJobStatus";
|
||||
import { LayersSheet } from "./LayersSheet";
|
||||
import { ShortcutsDisplay } from "./ShortcutsDisplay";
|
||||
import { ToolOverlay } from "./ToolOverlay";
|
||||
import { resolveTransformTargetBounds, selectedTransformTarget } from "@editor/transform-targets";
|
||||
import type { AppState } from "@editor/state";
|
||||
import type { ToolId } from "@editor/tools";
|
||||
import { handleCommandPaletteKey, handleDeleteSelectionKey, handleHistoryKey, handleToolKey, keybindEventFromKeyboardEvent } from "@input/index";
|
||||
import { shallowEqual, useAppState } from "./useAppState";
|
||||
import { downloadArtboardPng } from "./exportArtboardPng";
|
||||
import { downloadArtboardPng } from "@operations/export/downloadArtboard";
|
||||
import { useImageImport } from "./useImageImport";
|
||||
import { useViewportActivityIsland } from "./useViewportActivityIsland";
|
||||
import { loadGenerationResources } from "@operations/generation/loadResources";
|
||||
import "./index.css";
|
||||
|
||||
export type AppProps = {
|
||||
@@ -26,40 +27,25 @@ export type AppProps = {
|
||||
|
||||
export function App({ app }: AppProps) {
|
||||
const shellState = useAppState(app.store, selectAppShellState, shallowEqual);
|
||||
const { document, selection, viewport, tools, generation, commandPalette, transformSession, maskEdit } = shellState;
|
||||
const { document, selection, viewport, tools, generation, commandPalette, transformSession, maskEdit, workspace } = shellState;
|
||||
const viewportActivityIsland = useViewportActivityIsland(viewport);
|
||||
const imageImport = useImageImport(app.store);
|
||||
const [layersOpen, setLayersOpen] = useState(false);
|
||||
const previousGenerateTool = useRef<ToolId>("select");
|
||||
const transformTarget = transformSession?.target ?? selectedTransformTarget(document, selection);
|
||||
const activeArtboard = document.artboards.find((artboard) => artboard.id === selection.artboardId) ?? document.artboards[0];
|
||||
const generateOpen = tools.activeTool === "generate";
|
||||
const generateOpen = workspace.panel === "generate";
|
||||
const layersOpen = workspace.panel === "layers";
|
||||
|
||||
useEffect(() => {
|
||||
if (tools.activeTool !== "generate") {
|
||||
previousGenerateTool.current = tools.activeTool;
|
||||
return;
|
||||
}
|
||||
|
||||
setLayersOpen(false);
|
||||
}, [tools.activeTool]);
|
||||
|
||||
const getGenerateReturnTool = useCallback((): ToolId => {
|
||||
return previousGenerateTool.current === "generate" ? "select" : previousGenerateTool.current;
|
||||
}, []);
|
||||
if (generateOpen) void loadGenerationResources(app.store);
|
||||
}, [app.store, generateOpen]);
|
||||
|
||||
const openGenerate = useCallback(() => {
|
||||
const activeTool = app.store.getState().editor.tools.activeTool;
|
||||
if (activeTool !== "generate") previousGenerateTool.current = activeTool;
|
||||
app.store.dispatch(commandIds.toolSetActive, { tool: "generate" });
|
||||
setLayersOpen(false);
|
||||
app.store.dispatch(commandIds.workspaceSetPanel, { panel: "generate" });
|
||||
}, [app.store]);
|
||||
|
||||
const closeGenerate = useCallback(() => {
|
||||
if (app.store.getState().editor.tools.activeTool === "generate") {
|
||||
app.store.dispatch(commandIds.toolSetActive, { tool: getGenerateReturnTool() });
|
||||
}
|
||||
}, [app.store, getGenerateReturnTool]);
|
||||
app.store.dispatch(commandIds.workspaceSetPanel, { panel: "none" });
|
||||
}, [app.store]);
|
||||
|
||||
const toggleGenerate = useCallback(() => {
|
||||
if (generateOpen) closeGenerate();
|
||||
@@ -67,19 +53,16 @@ export function App({ app }: AppProps) {
|
||||
}, [closeGenerate, generateOpen, openGenerate]);
|
||||
|
||||
const openLayers = useCallback(() => {
|
||||
closeGenerate();
|
||||
setLayersOpen(true);
|
||||
}, [closeGenerate]);
|
||||
app.store.dispatch(commandIds.workspaceSetPanel, { panel: "layers" });
|
||||
}, [app.store]);
|
||||
|
||||
const closeLayers = useCallback(() => {
|
||||
setLayersOpen(false);
|
||||
}, []);
|
||||
app.store.dispatch(commandIds.workspaceSetPanel, { panel: "none" });
|
||||
}, [app.store]);
|
||||
|
||||
const toggleLayers = useCallback(() => {
|
||||
const nextOpen = !layersOpen;
|
||||
if (nextOpen) closeGenerate();
|
||||
setLayersOpen(nextOpen);
|
||||
}, [closeGenerate, layersOpen]);
|
||||
app.store.dispatch(commandIds.workspaceSetPanel, { panel: layersOpen ? "none" : "layers" });
|
||||
}, [app.store, layersOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
@@ -167,6 +150,9 @@ export function App({ app }: AppProps) {
|
||||
/>
|
||||
<header className="pointer-events-none absolute inset-x-3 top-3 z-10 flex h-20 items-center justify-end gap-4 rounded-full px-4 text-white backdrop-blur-xl">
|
||||
<div className="pointer-events-auto flex items-center gap-2">
|
||||
<button type="button" className="border-0 bg-transparent p-0" onClick={openGenerate} title="Open generation activity">
|
||||
<GenerationJobStatus generation={generation} compact />
|
||||
</button>
|
||||
<button type="button" className={topBarButtonClass()} onClick={imageImport.openFilePicker}>
|
||||
<FolderOpen size={24} />
|
||||
</button>
|
||||
@@ -190,6 +176,7 @@ export function App({ app }: AppProps) {
|
||||
</div>
|
||||
<GenerateSheet
|
||||
settings={tools.generate}
|
||||
resources={generation.resources}
|
||||
open={generateOpen}
|
||||
dispatch={app.store.dispatch}
|
||||
/>
|
||||
@@ -243,6 +230,7 @@ type AppShellState = {
|
||||
commandPalette: AppState["editor"]["commandPalette"];
|
||||
transformSession: AppState["editor"]["transformSession"];
|
||||
maskEdit: AppState["editor"]["maskEdit"];
|
||||
workspace: AppState["editor"]["workspace"];
|
||||
};
|
||||
|
||||
function selectAppShellState(state: AppState): AppShellState {
|
||||
@@ -255,6 +243,7 @@ function selectAppShellState(state: AppState): AppShellState {
|
||||
commandPalette: state.editor.commandPalette,
|
||||
transformSession: state.editor.transformSession,
|
||||
maskEdit: state.editor.maskEdit,
|
||||
workspace: state.editor.workspace,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user