temp journey view
This commit is contained in:
@@ -34,6 +34,8 @@
|
||||
"svelte-check": "^3.2.0",
|
||||
"svelte-dnd-action": "^0.9.22",
|
||||
"typescript": "^5.0.3",
|
||||
"@types/cytoscape": "^3.19.9",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
"vite": "^4.3.8",
|
||||
"vite-plugin-glsl": "^1.1.2",
|
||||
"vite-plugin-static-copy": "^0.14.0",
|
||||
@@ -74,13 +76,14 @@
|
||||
"@litegraph-ts/tsconfig": "workspace:*",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.1.1",
|
||||
"@tsconfig/svelte": "^4.0.1",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
"canvas-to-svg": "^1.0.3",
|
||||
"cm6-theme-basic-dark": "^0.2.0",
|
||||
"cm6-theme-basic-light": "^0.2.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"csv": "^6.3.0",
|
||||
"csv-parse": "^5.3.10",
|
||||
"cytoscape": "^3.25.0",
|
||||
"cytoscape-dagre": "^2.5.0",
|
||||
"dompurify": "^3.0.3",
|
||||
"events": "^3.3.0",
|
||||
"framework7": "^8.0.3",
|
||||
|
||||
57
pnpm-lock.yaml
generated
57
pnpm-lock.yaml
generated
@@ -97,9 +97,6 @@ importers:
|
||||
'@tsconfig/svelte':
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
'@types/dompurify':
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
canvas-to-svg:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
@@ -118,6 +115,12 @@ importers:
|
||||
csv-parse:
|
||||
specifier: ^5.3.10
|
||||
version: 5.3.10
|
||||
cytoscape:
|
||||
specifier: ^3.25.0
|
||||
version: 3.25.0
|
||||
cytoscape-dagre:
|
||||
specifier: ^2.5.0
|
||||
version: 2.5.0(cytoscape@3.25.0)
|
||||
dompurify:
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3
|
||||
@@ -188,6 +191,12 @@ importers:
|
||||
'@floating-ui/dom':
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8
|
||||
'@types/cytoscape':
|
||||
specifier: ^3.19.9
|
||||
version: 3.19.9
|
||||
'@types/dompurify':
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
'@zerodevx/svelte-toast':
|
||||
specifier: ^0.9.3
|
||||
version: 0.9.3(svelte@3.59.1)
|
||||
@@ -2368,6 +2377,10 @@ packages:
|
||||
resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==}
|
||||
dev: true
|
||||
|
||||
/@types/cytoscape@3.19.9:
|
||||
resolution: {integrity: sha512-oqCx0ZGiBO0UESbjgq052vjDAy2X53lZpMrWqiweMpvVwKw/2IiYDdzPFK6+f4tMfdv9YKEM9raO5bAZc3UYBg==}
|
||||
dev: true
|
||||
|
||||
/@types/d3-dsv@3.0.0:
|
||||
resolution: {integrity: sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==}
|
||||
|
||||
@@ -2395,7 +2408,7 @@ packages:
|
||||
resolution: {integrity: sha512-YBL4ziFebbbfQfH5mlC+QTJsvh0oJUrWbmxKMyEdL7emlHJqGR2Qb34TEFKj+VCayBvjKy3xczMFNhugThUsfQ==}
|
||||
dependencies:
|
||||
'@types/trusted-types': 2.0.3
|
||||
dev: false
|
||||
dev: true
|
||||
|
||||
/@types/estree@1.0.1:
|
||||
resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
|
||||
@@ -2482,7 +2495,7 @@ packages:
|
||||
|
||||
/@types/trusted-types@2.0.3:
|
||||
resolution: {integrity: sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==}
|
||||
dev: false
|
||||
dev: true
|
||||
|
||||
/@types/uuid@9.0.1:
|
||||
resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==}
|
||||
@@ -3324,6 +3337,23 @@ packages:
|
||||
stream-transform: 3.2.6
|
||||
dev: false
|
||||
|
||||
/cytoscape-dagre@2.5.0(cytoscape@3.25.0):
|
||||
resolution: {integrity: sha512-VG2Knemmshop4kh5fpLO27rYcyUaaDkRw+6PiX4bstpB+QFt0p2oauMrsjVbUamGWQ6YNavh7x2em2uZlzV44g==}
|
||||
peerDependencies:
|
||||
cytoscape: ^3.2.22
|
||||
dependencies:
|
||||
cytoscape: 3.25.0
|
||||
dagre: 0.8.5
|
||||
dev: false
|
||||
|
||||
/cytoscape@3.25.0:
|
||||
resolution: {integrity: sha512-7MW3Iz57mCUo6JQCho6CmPBCbTlJr7LzyEtIkutG255HLVd4XuBg2I9BkTZLI/e4HoaOB/BiAzXuQybQ95+r9Q==}
|
||||
engines: {node: '>=0.10'}
|
||||
dependencies:
|
||||
heap: 0.2.7
|
||||
lodash: 4.17.21
|
||||
dev: false
|
||||
|
||||
/d3-array@3.2.2:
|
||||
resolution: {integrity: sha512-yEEyEAbDrF8C6Ob2myOBLjwBLck1Z89jMGFee0oPsn95GqjerpaOA4ch+vc2l0FNFFwMD5N7OCSEN5eAlsUbgQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -3474,6 +3504,13 @@ packages:
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/dagre@0.8.5:
|
||||
resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==}
|
||||
dependencies:
|
||||
graphlib: 2.1.8
|
||||
lodash: 4.17.21
|
||||
dev: false
|
||||
|
||||
/dashdash@1.14.1:
|
||||
resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
|
||||
engines: {node: '>=0.10'}
|
||||
@@ -4465,6 +4502,12 @@ packages:
|
||||
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
|
||||
dev: true
|
||||
|
||||
/graphlib@2.1.8:
|
||||
resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==}
|
||||
dependencies:
|
||||
lodash: 4.17.21
|
||||
dev: false
|
||||
|
||||
/happy-dom@9.18.3:
|
||||
resolution: {integrity: sha512-b7iMGYeIXvUryNultA0AHEVU0FPpb2djJ/xSVlMDfP7HG4z7FomdqkCEpWtSv1zDL+t1gRUoBbpqFCoUBvjYtg==}
|
||||
dependencies:
|
||||
@@ -4511,6 +4554,10 @@ packages:
|
||||
dependencies:
|
||||
function-bind: 1.1.1
|
||||
|
||||
/heap@0.2.7:
|
||||
resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==}
|
||||
dev: false
|
||||
|
||||
/htm@3.1.1:
|
||||
resolution: {integrity: sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==}
|
||||
dev: false
|
||||
|
||||
57
src/lib/components/ComfyJourneyView.svelte
Normal file
57
src/lib/components/ComfyJourneyView.svelte
Normal file
@@ -0,0 +1,57 @@
|
||||
<!--
|
||||
A "journey" is like browser history for prompts, except organized in a
|
||||
tree-like graph. It lets you save incremental changes to your workflow and
|
||||
jump between past and present sets of parameters.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import Graph from './graph/Graph.svelte'
|
||||
import GraphNode from './graph/GraphNode.svelte'
|
||||
import GraphEdge from './graph/GraphEdge.svelte'
|
||||
|
||||
import type ComfyApp from './ComfyApp';
|
||||
|
||||
export let app: ComfyApp;
|
||||
|
||||
const nodes = [
|
||||
{ id: 'N1', label: 'Start' },
|
||||
{ id: 'N2', label: '4' },
|
||||
{ id: 'N4', label: '8' },
|
||||
{ id: 'N5', label: '15' },
|
||||
{ id: 'N3', label: '16' },
|
||||
{ id: 'N6', label: '23' },
|
||||
{ id: 'N7', label: '42' },
|
||||
{ id: 'N8', label: 'End' }
|
||||
]
|
||||
|
||||
const edges = [
|
||||
{ id: 'E1', source: 'N1', target: 'N2' },
|
||||
{ id: 'E2', source: 'N2', target: 'N3' },
|
||||
{ id: 'E3', source: 'N3', target: 'N6' },
|
||||
{ id: 'E4', source: 'N2', target: 'N4' },
|
||||
{ id: 'E5', source: 'N4', target: 'N5' },
|
||||
{ id: 'E6', source: 'N5', target: 'N4', label: '2' },
|
||||
{ id: 'E7', source: 'N5', target: 'N6' },
|
||||
{ id: 'E8', source: 'N6', target: 'N7' },
|
||||
{ id: 'E9', source: 'N7', target: 'N7', label: '3' },
|
||||
{ id: 'E10', source: 'N7', target: 'N8' }
|
||||
]
|
||||
</script>
|
||||
|
||||
<div class="journey-view">
|
||||
<Graph>
|
||||
{#each nodes as node}
|
||||
<GraphNode node={node}/>
|
||||
{/each}
|
||||
|
||||
{#each edges as edge}
|
||||
<GraphEdge edge={edge}/>
|
||||
{/each}
|
||||
</Graph>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.journey-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script context="module" lang="ts">
|
||||
export type ComfyPaneMode = "none" | "activeWorkflow" | "graph" | "properties" | "templates" | "queue"
|
||||
export type ComfyPaneMode = "none" | "activeWorkflow" | "graph" | "properties" | "templates" | "queue" | "journey"
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -8,15 +8,17 @@
|
||||
*/
|
||||
import workflowState from "$lib/stores/workflowState";
|
||||
import type ComfyApp from "./ComfyApp";
|
||||
import { Sliders2, BoxSeam, LayoutTextSidebarReverse } from "svelte-bootstrap-icons";
|
||||
import { SvelteComponent } from "svelte";
|
||||
import { capitalize } from "$lib/utils";
|
||||
|
||||
import { Sliders2, BoxSeam, LayoutTextSidebarReverse, Signpost2 } from "svelte-bootstrap-icons";
|
||||
|
||||
import ComfyBoxWorkflowView from "./ComfyBoxWorkflowView.svelte";
|
||||
import ComfyGraphView from "./ComfyGraphView.svelte";
|
||||
import ComfyProperties from "./ComfyProperties.svelte";
|
||||
import ComfyQueue from "./ComfyQueue.svelte";
|
||||
import ComfyTemplates from "./ComfyTemplates.svelte";
|
||||
import { SvelteComponent } from "svelte";
|
||||
import { capitalize } from "$lib/utils";
|
||||
import ComfyJourneyView from "./ComfyJourneyView.svelte";
|
||||
|
||||
export let app: ComfyApp
|
||||
export let mode: ComfyPaneMode = "none";
|
||||
@@ -25,6 +27,7 @@
|
||||
const MODES: [ComfyPaneMode, typeof SvelteComponent][] = [
|
||||
["properties", Sliders2],
|
||||
["templates", BoxSeam],
|
||||
["journey", Signpost2],
|
||||
["queue", LayoutTextSidebarReverse]
|
||||
]
|
||||
|
||||
@@ -46,8 +49,10 @@
|
||||
<ComfyTemplates {app} />
|
||||
{:else if mode === "queue"}
|
||||
<ComfyQueue {app} />
|
||||
{:else if mode === "journey"}
|
||||
<ComfyJourneyView {app} />
|
||||
{:else}
|
||||
<div class="blank-panel">(Blank)</div>
|
||||
<div class="blank-panel">(Blank: {mode})</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if showSwitcher}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import Textbox from "@gradio/form/src/Textbox.svelte";
|
||||
import type { ModalData } from "$lib/stores/modalState";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import { negmod } from "$lib/utils";
|
||||
import { negmod } from "$lib/utils";
|
||||
const DOMPurify = createDOMPurify(window);
|
||||
|
||||
export let templateAndSvg: SerializedComfyBoxTemplate;
|
||||
|
||||
@@ -7,7 +7,18 @@ import type { ComfyBoxWorkflow } from "./stores/workflowState";
|
||||
import { isSerializedPromptInputLink } from "./utils";
|
||||
import ComfyBoxStdPromptSerializer from "./ComfyBoxStdPromptSerializer";
|
||||
|
||||
interface RestoreParamSource {
|
||||
export type RestoreParamType = "workflow" | "backend" | "stdPrompt";
|
||||
|
||||
/*
|
||||
* Data of a parameter that can be restored. Paired with a parameter name.
|
||||
*/
|
||||
export interface RestoreParamSource<T extends RestoreParamType = any> {
|
||||
type: T,
|
||||
|
||||
/*
|
||||
* The actual value to copy to the widget after all conversions have been
|
||||
* applied.
|
||||
*/
|
||||
finalValue: any
|
||||
}
|
||||
|
||||
@@ -16,7 +27,9 @@ interface RestoreParamSource {
|
||||
* *exactly* to a node with the same ID in the current workflow. Easiest case
|
||||
* since the parameter value can just be copied without much fuss.
|
||||
*/
|
||||
interface RestoreParamSourceWorkflowNode extends RestoreParamSource {
|
||||
export interface RestoreParamSourceWorkflowNode extends RestoreParamSource<"workflow"> {
|
||||
type: "workflow",
|
||||
|
||||
sourceNode: SerializedComfyWidgetNode
|
||||
}
|
||||
|
||||
@@ -31,7 +44,9 @@ interface RestoreParamSourceWorkflowNode extends RestoreParamSource {
|
||||
* prompt endpoint. Hence this parameter source won't account for those kinds of
|
||||
* values.)
|
||||
*/
|
||||
interface RestoreParamSourceBackendNodeInput extends RestoreParamSource {
|
||||
export interface RestoreParamSourceBackendNodeInput extends RestoreParamSource<"backend"> {
|
||||
type: "backend",
|
||||
|
||||
backendNode: SerializedComfyWidgetNode,
|
||||
|
||||
/*
|
||||
@@ -45,7 +60,9 @@ interface RestoreParamSourceBackendNodeInput extends RestoreParamSource {
|
||||
/*
|
||||
* A value contained in the standard prompt extracted from the saved workflow.
|
||||
*/
|
||||
interface RestoreParamSourceStdPrompt<T, K extends keyof T> extends RestoreParamSource {
|
||||
export interface RestoreParamSourceStdPrompt<T, K extends keyof T> extends RestoreParamSource<"stdPrompt"> {
|
||||
type: "stdPrompt",
|
||||
|
||||
/*
|
||||
* Name of the group containing the value to pass
|
||||
*
|
||||
@@ -128,35 +145,48 @@ function findUpstreamSerializedWidgetNode(prompt: SerializedPrompt, input: INode
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
export default function restoreParameters(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets {
|
||||
const result = {}
|
||||
const addSource = (result: RestoreParamTargets, targetNode: ComfyWidgetNode, source: RestoreParamSource) => {
|
||||
result[targetNode.id] ||= { targetNode, sources: [] }
|
||||
result[targetNode.id].sources.push(source);
|
||||
}
|
||||
|
||||
const addSource = (targetNode: ComfyWidgetNode, source: RestoreParamSource) => {
|
||||
result[targetNode.id] ||= { targetNode, sources: [] }
|
||||
result[targetNode.id].sources.push(source);
|
||||
const mergeSources = (a: RestoreParamTargets, b: RestoreParamTargets) => {
|
||||
for (const [k, vs] of Object.entries(b)) {
|
||||
a[vs.targetNode.id] ||= { targetNode: vs.targetNode, sources: [] }
|
||||
for (const source of vs.sources) {
|
||||
a[vs.targetNode.id].sources.push(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getWorkflowRestoreParams(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets {
|
||||
const result = {}
|
||||
|
||||
const graph = workflow.graph;
|
||||
|
||||
// Step 1: Find nodes that correspond to *this* workflow exactly, since we
|
||||
// can easily match up the nodes between each (their IDs will be the same)
|
||||
for (const serNode of prompt.workflow.nodes) {
|
||||
const foundNode = graph.getNodeByIdRecursive(serNode.id);
|
||||
if (isComfyWidgetNode(foundNode) && foundNode.type === serNode.type) {
|
||||
const finalValue = (serNode as SerializedComfyWidgetNode).comfyValue;
|
||||
if (finalValue != null) {
|
||||
const source: RestoreParamSourceWorkflowNode = {
|
||||
type: "workflow",
|
||||
finalValue,
|
||||
sourceNode: serNode
|
||||
}
|
||||
addSource(foundNode, source)
|
||||
addSource(result, foundNode, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Figure out what parameters the backend received. If there was a
|
||||
// widget node attached to a backend node's input upstream, then we can
|
||||
// use that value.
|
||||
return result
|
||||
}
|
||||
|
||||
export function getBackendRestoreParams(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets {
|
||||
const result = {}
|
||||
|
||||
const graph = workflow.graph;
|
||||
|
||||
for (const [serNodeID, inputs] of Object.entries(prompt.output)) {
|
||||
const serNode = prompt.workflow.nodes.find(sn => sn.id === serNodeID)
|
||||
if (serNode == null)
|
||||
@@ -176,27 +206,47 @@ export default function restoreParameters(workflow: ComfyBoxWorkflow, prompt: Se
|
||||
const foundNode = graph.getNodeByIdRecursive(serNode.id);
|
||||
if (isComfyWidgetNode(foundNode) && foundNode.type === serNode.type) {
|
||||
const source: RestoreParamSourceBackendNodeInput = {
|
||||
type: "backend",
|
||||
finalValue: inputValue,
|
||||
backendNode: serNode,
|
||||
isDirectAttachment
|
||||
}
|
||||
addSource(foundNode, source)
|
||||
addSource(result, foundNode, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export default function restoreParameters(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets {
|
||||
const result = {}
|
||||
|
||||
// Step 1: Find nodes that correspond to *this* workflow exactly, since we
|
||||
// can easily match up the nodes between each (their IDs will be the same)
|
||||
const workflowParams = getWorkflowRestoreParams(workflow, prompt);
|
||||
mergeSources(result, workflowParams);
|
||||
|
||||
// Step 2: Figure out what parameters the backend received. If there was a
|
||||
// widget node attached to a backend node's input upstream, then we can
|
||||
// use that value.
|
||||
const backendParams = getBackendRestoreParams(workflow, prompt);
|
||||
mergeSources(result, backendParams);
|
||||
|
||||
// Step 3: Extract the standard prompt from the workflow and use that to
|
||||
// infer parameter types
|
||||
|
||||
const serializer = new ComfyBoxStdPromptSerializer();
|
||||
const stdPrompt = serializer.serialize(prompt);
|
||||
// TODO
|
||||
|
||||
const allWidgetNodes = Array.from(graph.iterateNodesInOrderRecursive()).filter(isComfyWidgetNode);
|
||||
// const serializer = new ComfyBoxStdPromptSerializer();
|
||||
// const stdPrompt = serializer.serialize(prompt);
|
||||
|
||||
for (const widgetNode of allWidgetNodes) {
|
||||
// const allWidgetNodes = Array.from(graph.iterateNodesInOrderRecursive()).filter(isComfyWidgetNode);
|
||||
|
||||
}
|
||||
// for (const widgetNode of allWidgetNodes) {
|
||||
|
||||
// }
|
||||
|
||||
// for (const [groupName, groups] of Object.entries(stdPrompt)) {
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user