journeystuff

This commit is contained in:
space-nuko
2023-06-02 15:51:53 -05:00
parent 2197994317
commit c8a3276336
8 changed files with 206 additions and 87 deletions

View File

@@ -4,49 +4,23 @@
jump between past and present sets of parameters. jump between past and present sets of parameters.
--> -->
<script lang="ts"> <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 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; export let app: ComfyApp;
const nodes = [ let workflow: ComfyBoxWorkflow;
{ id: 'N1', label: 'Start' }, let journey: WritableJourneyStateStore
{ 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 = [ $: workflow = $workflowState.activeWorkflow
{ id: 'E1', source: 'N1', target: 'N2' }, $: journey = workflow?.journey
{ 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> </script>
<div class="journey-view"> <div class="journey-view">
<Graph> <JourneyRenderer {workflow} {journey} />
{#each nodes as node}
<GraphNode node={node}/>
{/each}
{#each edges as edge}
<GraphEdge edge={edge}/>
{/each}
</Graph>
</div> </div>
<style lang="scss"> <style lang="scss">

View 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}

View File

@@ -7,26 +7,34 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { onMount, setContext } from "svelte"
import cytoscape from "cytoscape" import cytoscape from "cytoscape"
import dagre from "cytoscape-dagre" import dagre from "cytoscape-dagre"
import GraphStyles from "./GraphStyles" import GraphStyles from "./GraphStyles"
import type { EdgeDataDefinition } from "cytoscape";
import type { NodeDataDefinition } from "cytoscape";
const graphContext: GraphContext = { export let nodes: ReadonlyArray<NodeDataDefinition>;
getCyInstance: () => cyInstance 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 _nodes: any[];
let cyInstance: cytoscape.Core = null let _edges: any[];
onMount(() => { function rebuildGraph() {
cytoscape.use(dagre) cytoscape.use(dagre)
cyInstance = cytoscape({ cyInstance = cytoscape({
container: refElement, container: refElement,
style: GraphStyles, style: GraphStyles,
wheelSensitivity: 0.1,
}) })
cyInstance.on("add", () => { cyInstance.on("add", () => {
@@ -38,18 +46,31 @@
}) })
.run() .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> </script>
<div class="graph" bind:this={refElement}> <div class="cy-graph" {style} bind:this={refElement}>
{#if cyInstance}
<slot></slot>
{/if}
</div> </div>
<style lang="scss"> <style lang="scss">
.graph { .cy-graph {
background: white; background: white;
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@@ -1,15 +1,15 @@
<script lang="ts"> <script lang="ts">
import { onMount, getContext } from 'svelte' import { onMount, getContext } from 'svelte'
import { GRAPH_STATE, type GraphContext } from './Graph.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 { getCyInstance } = getContext(GRAPH_STATE) as GraphContext;
const cyInstance = getCyInstance() const cyInstance = getCyInstance()
cyInstance.add({ cyInstance.add({
group: 'edges', group: 'edges',
id: edge.id,
data: { ...edge } data: { ...edge }
}) })
</script> </script>

View File

@@ -1,15 +1,15 @@
<script lang="ts"> <script lang="ts">
import { onMount, getContext } from 'svelte' import { onMount, getContext } from 'svelte'
import { GRAPH_STATE, type GraphContext } from './Graph.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 { getCyInstance } = getContext(GRAPH_STATE) as GraphContext
const cyInstance = getCyInstance() const cyInstance = getCyInstance()
cyInstance.add({ cyInstance.add({
group: 'nodes', group: 'nodes',
id: node.id,
data: { ...node } data: { ...node }
}) })
</script> </script>

View File

@@ -1,27 +1,30 @@
export default [ import type { Stylesheet } from "cytoscape";
const styles: Stylesheet[] = [
{ {
selector: 'node', selector: 'node',
style: { style: {
'width': '50', 'width': '50',
'height': '50', 'height': '50',
'font-family': 'Arial',
'font-size': '18', 'font-size': '18',
'font-weight': 'bold', 'font-weight': 'normal',
'content': `data(label)`, 'content': `data(label)`,
'text-valign': 'center', 'text-valign': 'center',
'text-wrap': 'wrap', 'text-wrap': 'wrap',
'text-max-width': '140', 'text-max-width': '140',
'background-color': 'gold', 'background-color': '#60a5fa',
'border-color': 'orange', 'border-color': '#2563eb',
'border-width': '3', 'border-width': '3',
'color': 'darkred' 'color': '#1d3660'
} }
}, },
{ {
selector: 'node:selected', selector: 'node:selected',
style: { style: {
'background-color': 'darkred', 'background-color': '#f97316',
color: 'white', color: 'white',
'border-color': 'darkred', 'border-color': '#ea580c',
'line-color': '#0e76ba', 'line-color': '#0e76ba',
'target-arrow-color': '#0e76ba' 'target-arrow-color': '#0e76ba'
} }
@@ -36,8 +39,8 @@ export default [
'text-background-padding': '3', 'text-background-padding': '3',
'width': '3', 'width': '3',
'target-arrow-shape': 'triangle', 'target-arrow-shape': 'triangle',
'line-color': 'darkred', 'line-color': '#1d4ed8',
'target-arrow-color': 'darkred', 'target-arrow-color': '#1d4ed8',
'font-weight': 'bold' 'font-weight': 'bold'
} }
}, },
@@ -55,3 +58,4 @@ export default [
} }
} }
] ]
export default styles;

View 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

View File

@@ -12,6 +12,8 @@ import type { SerializedAppState, SerializedPrompt } from '$lib/components/Comfy
import type ComfyReceiveOutputNode from '$lib/nodes/actions/ComfyReceiveOutputNode'; import type ComfyReceiveOutputNode from '$lib/nodes/actions/ComfyReceiveOutputNode';
import type { ComfyBoxPromptExtraData, PromptID } from '$lib/api'; import type { ComfyBoxPromptExtraData, PromptID } from '$lib/api';
import type { ComfyAPIPromptErrorResponse, ComfyExecutionError } from '$lib/apiErrors'; import type { ComfyAPIPromptErrorResponse, ComfyExecutionError } from '$lib/apiErrors';
import type { WritableJourneyStateStore } from './journeyState';
import journeyStates from './journeyStates';
type ActiveCanvas = { type ActiveCanvas = {
canvas: LGraphCanvas | null; canvas: LGraphCanvas | null;
@@ -115,7 +117,12 @@ export class ComfyBoxWorkflow {
/* /*
* Completed queue entry ID that holds the last validation/execution error. * 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 { get layout(): WritableLayoutStateStore | null {
return layoutStates.getLayout(this.id) return layoutStates.getLayout(this.id)
@@ -133,6 +140,7 @@ export class ComfyBoxWorkflow {
title, title,
} }
this.graph = new ComfyGraph(this.id); this.graph = new ComfyGraph(this.id);
this.journey = journeyStates.create();
} }
notifyModified() { notifyModified() {