File size: 4,399 Bytes
fb57ab3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import type { CanvasEdge, CanvasNode, NodeDef, WidgetValue, WorkflowTemplate } from "./types";
import { defaultValues, getDef } from "./registry";

let counter = 0;
export const uid = (prefix = "n") => `${prefix}_${Date.now().toString(36)}${(counter++).toString(36)}`;

export function makeNode(
  id: string,
  type: string,
  x: number,
  y: number,
  values: Record<string, WidgetValue> = {},
  extra: Partial<CanvasNode["data"]> = {},
): CanvasNode {
  const def = getDef(type);
  return {
    id,
    type: "space",
    position: { x, y },
    width: def.width ?? 260,
    data: {
      defType: type,
      values: { ...defaultValues(def), ...values },
      mode: "always",
      status: "idle",
      progress: 0,
      ...extra,
    },
  };
}

export function makeEdge(source: string, sourceHandle: string, target: string, targetHandle: string): CanvasEdge {
  return {
    id: `e_${source}.${sourceHandle}->${target}.${targetHandle}`,
    type: "typed",
    source,
    sourceHandle,
    target,
    targetHandle,
  };
}

/** Edge with its data type filled in from the source def. */
export function typedEdge(nodes: CanvasNode[], source: string, sourceHandle: string, target: string, targetHandle: string): CanvasEdge {
  const src = nodes.find((n) => n.id === source);
  const tgt = nodes.find((n) => n.id === target);
  const sPort = src && getDef(src.data.defType).outputs.find((p) => p.id === sourceHandle);
  const tPort = tgt && getDef(tgt.data.defType).inputs.find((p) => p.id === targetHandle);
  const type = sPort?.type === "any" || !sPort ? (tPort?.type ?? "any") : sPort.type;
  return { ...makeEdge(source, sourceHandle, target, targetHandle), data: { type } };
}

export const FLUX = "black-forest-labs/FLUX.1-schnell";
export const ENHANCER = "finegrain/finegrain-image-enhancer";
export const WHISPER = "hf-audio/whisper-large-v3";

const endpoint = (defs: NodeDef[], apiName: string) => defs.find((d) => d.space?.apiName === apiName) ?? defs[0];
const firstInput = (def: NodeDef | undefined, type: string, fallback: string) => def?.inputs.find((p) => p.type === type)?.id ?? fallback;
const firstOutput = (def: NodeDef | undefined, type: string, fallback: string) => def?.outputs.find((p) => p.type === type)?.id ?? fallback;

const PROMPT = "cinematic photo of a lighthouse on a basalt cliff at blue hour, sea mist, 35mm film";

export const TEMPLATES: WorkflowTemplate[] = [
  {
    id: "txt2img",
    name: "Text to image",
    description: "A prompt into FLUX.1 schnell on ZeroGPU.",
    spaces: [FLUX],
    build: (defsOf) => {
      const flux = endpoint(defsOf(FLUX), "/infer");
      if (!flux) return { nodes: [], edges: [] };
      const nodes = [makeNode("prompt", "in:text", 0, 40, { value: PROMPT }, { title: "Prompt" }), makeNode("flux", flux.type, 380, 0)];
      const edges = [typedEdge(nodes, "prompt", "value", "flux", firstInput(flux, "text", "prompt"))];
      return { nodes, edges };
    },
  },
  {
    id: "enhance",
    name: "Generate & enhance",
    description: "FLUX.1 schnell, then Finegrain's upscaler on the result.",
    spaces: [FLUX, ENHANCER],
    build: (defsOf) => {
      const flux = endpoint(defsOf(FLUX), "/infer");
      const enh = endpoint(defsOf(ENHANCER), "/process");
      if (!flux || !enh) return { nodes: [], edges: [] };
      const nodes = [
        makeNode("prompt", "in:text", 0, 40, { value: PROMPT }, { title: "Prompt" }),
        makeNode("flux", flux.type, 380, 0),
        makeNode("enh", enh.type, 760, 0),
      ];
      const edges = [
        typedEdge(nodes, "prompt", "value", "flux", firstInput(flux, "text", "prompt")),
        typedEdge(nodes, "flux", firstOutput(flux, "image", "result"), "enh", firstInput(enh, "image", "input_image")),
      ];
      return { nodes, edges };
    },
  },
  {
    id: "transcribe",
    name: "Transcribe audio",
    description: "An audio file into Whisper large v3.",
    spaces: [WHISPER],
    build: (defsOf) => {
      const w = endpoint(defsOf(WHISPER), "/transcribe");
      if (!w) return { nodes: [], edges: [] };
      const nodes = [makeNode("audio", "in:audio", 0, 40), makeNode("whisper", w.type, 340, 0)];
      const edges = [typedEdge(nodes, "audio", "value", "whisper", firstInput(w, "audio", "inputs"))];
      return { nodes, edges };
    },
  },
];

export const getTemplate = (id: string) => TEMPLATES.find((t) => t.id === id) ?? TEMPLATES[0];