journeystuff
This commit is contained in:
@@ -4,49 +4,23 @@
|
||||
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';
|
||||
import type { ComfyBoxWorkflow } from '$lib/stores/workflowState';
|
||||
import workflowState from '$lib/stores/workflowState';
|
||||
import type { WritableJourneyStateStore } from '$lib/stores/journeyStates';
|
||||
import JourneyRenderer from './JourneyRenderer.svelte';
|
||||
|
||||
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' }
|
||||
]
|
||||
let workflow: ComfyBoxWorkflow;
|
||||
let journey: WritableJourneyStateStore
|
||||
|
||||
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' }
|
||||
]
|
||||
$: workflow = $workflowState.activeWorkflow
|
||||
$: journey = workflow?.journey
|
||||
</script>
|
||||
|
||||
<div class="journey-view">
|
||||
<Graph>
|
||||
{#each nodes as node}
|
||||
<GraphNode node={node}/>
|
||||
{/each}
|
||||
|
||||
{#each edges as edge}
|
||||
<GraphEdge edge={edge}/>
|
||||
{/each}
|
||||
</Graph>
|
||||
<JourneyRenderer {workflow} {journey} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
38
src/lib/components/JourneyRenderer.svelte
Normal file
38
src/lib/components/JourneyRenderer.svelte
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import type { WritableJourneyStateStore } from '$lib/stores/journeyStates';
|
||||
import { ComfyBoxWorkflow } from '$lib/stores/workflowState';
|
||||
import Graph from './graph/Graph.svelte'
|
||||
import type { NodeDataDefinition, EdgeDataDefinition } from 'cytoscape';
|
||||
import { NodeDataDefinition } from 'cytoscape';
|
||||
|
||||
export let workflow: ComfyBoxWorkflow | null = null
|
||||
export let journey: WritableJourneyStateStore | null = ull
|
||||
|
||||
const nodes: NodeDataDefinition[] = [
|
||||
{ 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: EdgeDataDefinition[] = [
|
||||
{ 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>
|
||||
|
||||
{#if workflow && journey}
|
||||
<Graph {nodes} {edges} style="background: var(--neutral-900)" />
|
||||
{/if}
|
||||
@@ -7,49 +7,70 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount, setContext } from "svelte"
|
||||
import cytoscape from "cytoscape"
|
||||
import dagre from "cytoscape-dagre"
|
||||
import GraphStyles from "./GraphStyles"
|
||||
import cytoscape from "cytoscape"
|
||||
import dagre from "cytoscape-dagre"
|
||||
import GraphStyles from "./GraphStyles"
|
||||
import type { EdgeDataDefinition } from "cytoscape";
|
||||
import type { NodeDataDefinition } from "cytoscape";
|
||||
|
||||
const graphContext: GraphContext = {
|
||||
getCyInstance: () => cyInstance
|
||||
export let nodes: ReadonlyArray<NodeDataDefinition>;
|
||||
export let edges: ReadonlyArray<EdgeDataDefinition>;
|
||||
|
||||
export let style: string = ""
|
||||
|
||||
$: if (nodes != null && edges != null && refElement != null) {
|
||||
rebuildGraph()
|
||||
}
|
||||
else {
|
||||
cyInstance = null;
|
||||
}
|
||||
setContext(GRAPH_STATE, graphContext)
|
||||
|
||||
let refElement = null
|
||||
let cyInstance: cytoscape.Core = null
|
||||
let _nodes: any[];
|
||||
let _edges: any[];
|
||||
|
||||
onMount(() => {
|
||||
cytoscape.use(dagre)
|
||||
function rebuildGraph() {
|
||||
cytoscape.use(dagre)
|
||||
|
||||
cyInstance = cytoscape({
|
||||
container: refElement,
|
||||
style: GraphStyles,
|
||||
cyInstance = cytoscape({
|
||||
container: refElement,
|
||||
style: GraphStyles,
|
||||
wheelSensitivity: 0.1,
|
||||
})
|
||||
|
||||
})
|
||||
cyInstance.on("add", () => {
|
||||
cyInstance
|
||||
.makeLayout({
|
||||
name: "dagre",
|
||||
rankDir: "TB",
|
||||
nodeSep: 150
|
||||
})
|
||||
.run()
|
||||
})
|
||||
|
||||
cyInstance.on("add", () => {
|
||||
cyInstance
|
||||
.makeLayout({
|
||||
name: "dagre",
|
||||
rankDir: "TB",
|
||||
nodeSep: 150
|
||||
})
|
||||
.run()
|
||||
})
|
||||
})
|
||||
for (const node of nodes) {
|
||||
cyInstance.add({
|
||||
group: 'nodes',
|
||||
data: { ...node }
|
||||
})
|
||||
}
|
||||
|
||||
for (const edge of edges) {
|
||||
cyInstance.add({
|
||||
group: 'edges',
|
||||
data: { ...edge }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let refElement = null
|
||||
let cyInstance: cytoscape.Core = null
|
||||
</script>
|
||||
|
||||
<div class="graph" bind:this={refElement}>
|
||||
{#if cyInstance}
|
||||
<slot></slot>
|
||||
{/if}
|
||||
<div class="cy-graph" {style} bind:this={refElement}>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.graph {
|
||||
.cy-graph {
|
||||
background: white;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { onMount, getContext } from 'svelte'
|
||||
import { GRAPH_STATE, type GraphContext } from './Graph.svelte';
|
||||
import type { EdgeDataDefinition } from 'cytoscape';
|
||||
|
||||
export let edge
|
||||
export let edge: EdgeDataDefinition
|
||||
|
||||
const { getCyInstance } = getContext(GRAPH_STATE) as GraphContext;
|
||||
const cyInstance = getCyInstance()
|
||||
|
||||
cyInstance.add({
|
||||
group: 'edges',
|
||||
id: edge.id,
|
||||
data: { ...edge }
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { onMount, getContext } from 'svelte'
|
||||
import { GRAPH_STATE, type GraphContext } from './Graph.svelte';
|
||||
import { onMount, getContext } from 'svelte'
|
||||
import { GRAPH_STATE, type GraphContext } from './Graph.svelte';
|
||||
import type { NodeDataDefinition } from 'cytoscape';
|
||||
|
||||
export let node
|
||||
export let node: NodeDataDefinition
|
||||
|
||||
const { getCyInstance } = getContext(GRAPH_STATE) as GraphContext
|
||||
const cyInstance = getCyInstance()
|
||||
const { getCyInstance } = getContext(GRAPH_STATE) as GraphContext
|
||||
const cyInstance = getCyInstance()
|
||||
|
||||
cyInstance.add({
|
||||
group: 'nodes',
|
||||
id: node.id,
|
||||
data: { ...node }
|
||||
})
|
||||
cyInstance.add({
|
||||
group: 'nodes',
|
||||
data: { ...node }
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
export default [
|
||||
import type { Stylesheet } from "cytoscape";
|
||||
|
||||
const styles: Stylesheet[] = [
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
'width': '50',
|
||||
'height': '50',
|
||||
'font-family': 'Arial',
|
||||
'font-size': '18',
|
||||
'font-weight': 'bold',
|
||||
'font-weight': 'normal',
|
||||
'content': `data(label)`,
|
||||
'text-valign': 'center',
|
||||
'text-wrap': 'wrap',
|
||||
'text-max-width': '140',
|
||||
'background-color': 'gold',
|
||||
'border-color': 'orange',
|
||||
'background-color': '#60a5fa',
|
||||
'border-color': '#2563eb',
|
||||
'border-width': '3',
|
||||
'color': 'darkred'
|
||||
'color': '#1d3660'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'node:selected',
|
||||
style: {
|
||||
'background-color': 'darkred',
|
||||
'background-color': '#f97316',
|
||||
color: 'white',
|
||||
'border-color': 'darkred',
|
||||
'border-color': '#ea580c',
|
||||
'line-color': '#0e76ba',
|
||||
'target-arrow-color': '#0e76ba'
|
||||
}
|
||||
@@ -36,8 +39,8 @@ export default [
|
||||
'text-background-padding': '3',
|
||||
'width': '3',
|
||||
'target-arrow-shape': 'triangle',
|
||||
'line-color': 'darkred',
|
||||
'target-arrow-color': 'darkred',
|
||||
'line-color': '#1d4ed8',
|
||||
'target-arrow-color': '#1d4ed8',
|
||||
'font-weight': 'bold'
|
||||
}
|
||||
},
|
||||
@@ -55,3 +58,4 @@ export default [
|
||||
}
|
||||
}
|
||||
]
|
||||
export default styles;
|
||||
|
||||
74
src/lib/stores/journeyStates.ts
Normal file
74
src/lib/stores/journeyStates.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
import type { DragItemID, IDragItem } from './layoutStates';
|
||||
import type { LGraphNode, NodeID } from '@litegraph-ts/core';
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export type JourneyState = {
|
||||
/*
|
||||
* Selected drag items.
|
||||
* NOTE: Order is important, for node grouping actions.
|
||||
*/
|
||||
currentJourney: DragItemID[],
|
||||
|
||||
/*
|
||||
* Hovered drag items.
|
||||
*/
|
||||
currentHovered: Set<DragItemID>,
|
||||
|
||||
/*
|
||||
* Selected LGraphNodes inside the litegraph canvas.
|
||||
* NOTE: Order is important, for node grouping actions.
|
||||
*/
|
||||
currentJourneyNodes: LGraphNode[],
|
||||
|
||||
/*
|
||||
* Currently hovered nodes.
|
||||
*/
|
||||
currentHoveredNodes: Set<NodeID>
|
||||
}
|
||||
|
||||
type JourneyStateOps = {
|
||||
clear: () => void,
|
||||
}
|
||||
|
||||
export type WritableJourneyStateStore = Writable<JourneyState> & JourneyStateOps;
|
||||
|
||||
function create() {
|
||||
const store: Writable<JourneyState> = writable(
|
||||
{
|
||||
currentJourney: [],
|
||||
currentJourneyNodes: [],
|
||||
currentHovered: new Set(),
|
||||
currentHoveredNodes: new Set(),
|
||||
})
|
||||
|
||||
function clear() {
|
||||
store.set({
|
||||
currentJourney: [],
|
||||
currentJourneyNodes: [],
|
||||
currentHovered: new Set(),
|
||||
currentHoveredNodes: new Set(),
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...store,
|
||||
clear
|
||||
}
|
||||
}
|
||||
|
||||
export type JourneyStateStaticOps = {
|
||||
create: () => WritableJourneyStateStore
|
||||
}
|
||||
|
||||
// These will be attached to workflows.
|
||||
const ops: JourneyStateStaticOps = {
|
||||
create
|
||||
}
|
||||
|
||||
export default ops
|
||||
@@ -12,6 +12,8 @@ import type { SerializedAppState, SerializedPrompt } from '$lib/components/Comfy
|
||||
import type ComfyReceiveOutputNode from '$lib/nodes/actions/ComfyReceiveOutputNode';
|
||||
import type { ComfyBoxPromptExtraData, PromptID } from '$lib/api';
|
||||
import type { ComfyAPIPromptErrorResponse, ComfyExecutionError } from '$lib/apiErrors';
|
||||
import type { WritableJourneyStateStore } from './journeyState';
|
||||
import journeyStates from './journeyStates';
|
||||
|
||||
type ActiveCanvas = {
|
||||
canvas: LGraphCanvas | null;
|
||||
@@ -115,7 +117,12 @@ export class ComfyBoxWorkflow {
|
||||
/*
|
||||
* Completed queue entry ID that holds the last validation/execution error.
|
||||
*/
|
||||
lastError?: PromptID
|
||||
lastError?: PromptID;
|
||||
|
||||
/*
|
||||
* Saved prompt history ("journey") for this workflow
|
||||
*/
|
||||
journey: WritableJourneyStateStore;
|
||||
|
||||
get layout(): WritableLayoutStateStore | null {
|
||||
return layoutStates.getLayout(this.id)
|
||||
@@ -133,6 +140,7 @@ export class ComfyBoxWorkflow {
|
||||
title,
|
||||
}
|
||||
this.graph = new ComfyGraph(this.id);
|
||||
this.journey = journeyStates.create();
|
||||
}
|
||||
|
||||
notifyModified() {
|
||||
|
||||
Reference in New Issue
Block a user