create-substrate 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/index.js +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +68 -4
- package/dist/scaffold.js.map +1 -1
- package/dist/surfaces/3d-scene.d.ts.map +1 -1
- package/dist/surfaces/3d-scene.js +414 -67
- package/dist/surfaces/3d-scene.js.map +1 -1
- package/dist/surfaces/animation.d.ts.map +1 -1
- package/dist/surfaces/animation.js +95 -103
- package/dist/surfaces/animation.js.map +1 -1
- package/dist/surfaces/blank.d.ts.map +1 -1
- package/dist/surfaces/blank.js +28 -16
- package/dist/surfaces/blank.js.map +1 -1
- package/dist/surfaces/canvas-2d.d.ts.map +1 -1
- package/dist/surfaces/canvas-2d.js +369 -92
- package/dist/surfaces/canvas-2d.js.map +1 -1
- package/dist/surfaces/data-vis.d.ts.map +1 -1
- package/dist/surfaces/data-vis.js +262 -98
- package/dist/surfaces/data-vis.js.map +1 -1
- package/dist/surfaces/image-gen.d.ts.map +1 -1
- package/dist/surfaces/image-gen.js +354 -92
- package/dist/surfaces/image-gen.js.map +1 -1
- package/dist/surfaces/node-editor.d.ts.map +1 -1
- package/dist/surfaces/node-editor.js +559 -114
- package/dist/surfaces/node-editor.js.map +1 -1
- package/package.json +4 -2
- package/skills/3d-scene/SKILL.md +6 -0
- package/skills/animation/SKILL.md +6 -0
- package/skills/canvas-2d/SKILL.md +7 -1
- package/skills/composing-panels/SKILL.md +14 -8
- package/skills/create-custom-tool/SKILL.md +6 -0
- package/skills/data-visualisation/SKILL.md +6 -0
- package/skills/image-generation/SKILL.md +6 -0
- package/skills/install-components/SKILL.md +199 -0
- package/skills/scaffold-playground/SKILL.md +12 -6
- package/skills/substrate-controls/SKILL.md +1 -3
- package/skills/theming/SKILL.md +6 -0
- package/skills/wire-interactions/SKILL.md +6 -0
|
@@ -3,16 +3,19 @@ export const imageGen = {
|
|
|
3
3
|
"toolbar",
|
|
4
4
|
"panel",
|
|
5
5
|
"pane",
|
|
6
|
+
"action",
|
|
6
7
|
"gallery",
|
|
7
8
|
"text-area",
|
|
8
9
|
"slider",
|
|
9
10
|
"number-input",
|
|
10
11
|
"progress-bar",
|
|
11
12
|
"status-bar",
|
|
13
|
+
"empty-state",
|
|
12
14
|
],
|
|
13
15
|
app: () => `import { useState } from "react"
|
|
14
16
|
import {
|
|
15
17
|
Toolbar,
|
|
18
|
+
ToolbarSeparator,
|
|
16
19
|
ToolbarToggleGroup,
|
|
17
20
|
ToolbarToggle,
|
|
18
21
|
} from "@/components/substrate/toolbar"
|
|
@@ -21,167 +24,426 @@ import {
|
|
|
21
24
|
Pane,
|
|
22
25
|
PaneHeader,
|
|
23
26
|
PaneLabel,
|
|
27
|
+
PaneAction,
|
|
24
28
|
PaneContent,
|
|
25
29
|
} from "@/components/substrate/pane"
|
|
26
|
-
import {
|
|
30
|
+
import { Action, ActionLabel, ActionControls } from "@/components/substrate/action"
|
|
31
|
+
import {
|
|
32
|
+
Gallery,
|
|
33
|
+
GalleryItem,
|
|
34
|
+
GalleryImage,
|
|
35
|
+
GalleryAction,
|
|
36
|
+
GalleryEmpty,
|
|
37
|
+
} from "@/components/substrate/gallery"
|
|
27
38
|
import { TextArea } from "@/components/substrate/text-area"
|
|
28
39
|
import { Slider } from "@/components/substrate/slider"
|
|
29
40
|
import { NumberInput } from "@/components/substrate/number-input"
|
|
30
41
|
import { ProgressBar } from "@/components/substrate/progress-bar"
|
|
31
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
StatusBar,
|
|
44
|
+
StatusBarSection,
|
|
45
|
+
StatusBarItem,
|
|
46
|
+
StatusBarSeparator,
|
|
47
|
+
} from "@/components/substrate/status-bar"
|
|
48
|
+
import {
|
|
49
|
+
EmptyState,
|
|
50
|
+
EmptyStateIcon,
|
|
51
|
+
EmptyStateTitle,
|
|
52
|
+
EmptyStateDescription,
|
|
53
|
+
} from "@/components/substrate/empty-state"
|
|
32
54
|
import {
|
|
33
55
|
RiSparklingLine,
|
|
34
56
|
RiImageLine,
|
|
35
|
-
|
|
57
|
+
RiShuffleLine,
|
|
58
|
+
RiDeleteBinLine,
|
|
36
59
|
} from "@remixicon/react"
|
|
37
60
|
|
|
38
|
-
|
|
39
|
-
|
|
61
|
+
type AspectRatio = "1:1" | "3:2" | "2:3"
|
|
62
|
+
|
|
63
|
+
interface Settings {
|
|
40
64
|
prompt: string
|
|
41
|
-
|
|
42
|
-
|
|
65
|
+
steps: number
|
|
66
|
+
cfg: number
|
|
67
|
+
seed: number
|
|
68
|
+
aspect: AspectRatio
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface Generation extends Settings {
|
|
72
|
+
id: string
|
|
73
|
+
src: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const ASPECT_SIZES: Record<AspectRatio, { width: number; height: number }> = {
|
|
77
|
+
"1:1": { width: 768, height: 768 },
|
|
78
|
+
"3:2": { width: 960, height: 640 },
|
|
79
|
+
"2:3": { width: 640, height: 960 },
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const ASPECT_RATIOS: AspectRatio[] = ["1:1", "3:2", "2:3"]
|
|
83
|
+
|
|
84
|
+
const MAX_SEED = 999999
|
|
85
|
+
|
|
86
|
+
function isAspectRatio(value: string): value is AspectRatio {
|
|
87
|
+
return ASPECT_RATIOS.some((ratio) => ratio === value)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function hashString(text: string) {
|
|
91
|
+
let hash = 0
|
|
92
|
+
for (let i = 0; i < text.length; i++) {
|
|
93
|
+
hash = (hash * 31 + text.charCodeAt(i)) | 0
|
|
94
|
+
}
|
|
95
|
+
return hash
|
|
43
96
|
}
|
|
44
97
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
98
|
+
// Small deterministic PRNG so the same settings always give the same image
|
|
99
|
+
function createRandom(seed: number) {
|
|
100
|
+
let state = seed | 0
|
|
101
|
+
return () => {
|
|
102
|
+
state = (state + 0x6d2b79f5) | 0
|
|
103
|
+
let t = Math.imul(state ^ (state >>> 15), 1 | state)
|
|
104
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
|
|
105
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Placeholder renderer: draws an abstract SVG from the settings and returns
|
|
110
|
+
// it as a data URL. Swap this for a call to your image model.
|
|
111
|
+
function renderPlaceholder({ prompt, steps, cfg, seed, aspect }: Settings) {
|
|
112
|
+
const { width, height } = ASPECT_SIZES[aspect]
|
|
113
|
+
const random = createRandom(seed + hashString(prompt))
|
|
114
|
+
const hue = Math.floor(random() * 360)
|
|
115
|
+
const saturation = Math.round(30 + cfg * 3)
|
|
116
|
+
const count = 3 + Math.round(steps / 8)
|
|
117
|
+
|
|
118
|
+
let shapes = ""
|
|
119
|
+
for (let i = 0; i < count; i++) {
|
|
120
|
+
const cx = Math.round(random() * width)
|
|
121
|
+
const cy = Math.round(random() * height)
|
|
122
|
+
const r = Math.round((0.08 + random() * 0.3) * Math.min(width, height))
|
|
123
|
+
const shapeHue = (hue + Math.round(random() * 120) - 60 + 360) % 360
|
|
124
|
+
const lightness = Math.round(40 + random() * 35)
|
|
125
|
+
const opacity = (0.25 + random() * 0.45).toFixed(2)
|
|
126
|
+
shapes += \`<circle cx="\${cx}" cy="\${cy}" r="\${r}" fill="hsl(\${shapeHue} \${saturation}% \${lightness}%)" opacity="\${opacity}"/>\`
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const svg =
|
|
130
|
+
\`<svg xmlns="http://www.w3.org/2000/svg" width="\${width}" height="\${height}" viewBox="0 0 \${width} \${height}">\` +
|
|
131
|
+
\`<defs><linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">\` +
|
|
132
|
+
\`<stop offset="0" stop-color="hsl(\${hue} \${saturation}% 22%)"/>\` +
|
|
133
|
+
\`<stop offset="1" stop-color="hsl(\${(hue + 60) % 360} \${saturation}% 12%)"/>\` +
|
|
134
|
+
\`</linearGradient><filter id="blur"><feGaussianBlur stdDeviation="24"/></filter></defs>\` +
|
|
135
|
+
\`<rect width="\${width}" height="\${height}" fill="url(#bg)"/>\` +
|
|
136
|
+
\`<g filter="url(#blur)">\${shapes}</g></svg>\`
|
|
137
|
+
|
|
138
|
+
return "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function createGeneration(id: string, settings: Settings): Generation {
|
|
142
|
+
return { ...settings, id, src: renderPlaceholder(settings) }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function randomSeed() {
|
|
146
|
+
return Math.floor(Math.random() * (MAX_SEED + 1))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const INITIAL_GENERATIONS: Generation[] = [
|
|
150
|
+
createGeneration("sample-1", {
|
|
151
|
+
prompt: "Aurora over a frozen lake, long exposure",
|
|
152
|
+
steps: 40,
|
|
153
|
+
cfg: 8,
|
|
154
|
+
seed: 1204,
|
|
155
|
+
aspect: "3:2",
|
|
156
|
+
}),
|
|
157
|
+
createGeneration("sample-2", {
|
|
158
|
+
prompt: "Soft studio portrait of a ceramic vase",
|
|
159
|
+
steps: 24,
|
|
160
|
+
cfg: 5,
|
|
161
|
+
seed: 77,
|
|
162
|
+
aspect: "1:1",
|
|
163
|
+
}),
|
|
164
|
+
createGeneration("sample-3", {
|
|
165
|
+
prompt: "Neon city street in the rain at night",
|
|
166
|
+
steps: 60,
|
|
167
|
+
cfg: 12,
|
|
168
|
+
seed: 5150,
|
|
169
|
+
aspect: "2:3",
|
|
170
|
+
}),
|
|
50
171
|
]
|
|
51
172
|
|
|
173
|
+
const TOOLBAR_BUTTON =
|
|
174
|
+
"inline-flex h-7 items-center justify-center gap-1.5 rounded-md px-2 text-xs font-medium outline-none transition-all hover:bg-muted hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50"
|
|
175
|
+
|
|
52
176
|
export default function App() {
|
|
53
|
-
const [prompt, setPrompt] = useState("")
|
|
177
|
+
const [prompt, setPrompt] = useState("Misty mountain valley at sunrise")
|
|
54
178
|
const [steps, setSteps] = useState(30)
|
|
55
179
|
const [cfg, setCfg] = useState(7)
|
|
56
180
|
const [seed, setSeed] = useState(-1)
|
|
181
|
+
const [aspect, setAspect] = useState<AspectRatio>("1:1")
|
|
57
182
|
const [generating, setGenerating] = useState(false)
|
|
58
183
|
const [progress, setProgress] = useState(0)
|
|
59
|
-
const [generations, setGenerations] = useState<Generation[]>(
|
|
60
|
-
const [selectedId, setSelectedId] = useState<string | null>(
|
|
184
|
+
const [generations, setGenerations] = useState<Generation[]>(INITIAL_GENERATIONS)
|
|
185
|
+
const [selectedId, setSelectedId] = useState<string | null>(
|
|
186
|
+
INITIAL_GENERATIONS[0]?.id ?? null,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
const canGenerate = !generating && prompt.trim().length > 0
|
|
190
|
+
const selected = generations.find((g) => g.id === selectedId)
|
|
61
191
|
|
|
62
192
|
async function handleGenerate() {
|
|
63
|
-
if (!
|
|
193
|
+
if (!canGenerate) return
|
|
194
|
+
const settings: Settings = {
|
|
195
|
+
prompt: prompt.trim(),
|
|
196
|
+
steps,
|
|
197
|
+
cfg,
|
|
198
|
+
seed: seed < 0 ? randomSeed() : seed,
|
|
199
|
+
aspect,
|
|
200
|
+
}
|
|
201
|
+
|
|
64
202
|
setGenerating(true)
|
|
65
203
|
setProgress(0)
|
|
66
204
|
|
|
67
|
-
//
|
|
68
|
-
for (let
|
|
69
|
-
await new Promise((
|
|
70
|
-
setProgress(
|
|
205
|
+
// Simulated progress. Replace with your model's progress events.
|
|
206
|
+
for (let step = 1; step <= 10; step++) {
|
|
207
|
+
await new Promise((resolve) => setTimeout(resolve, 120))
|
|
208
|
+
setProgress(step * 10)
|
|
71
209
|
}
|
|
72
210
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
timestamp: Date.now(),
|
|
77
|
-
placeholder: PLACEHOLDER_COLOURS[generations.length % PLACEHOLDER_COLOURS.length],
|
|
78
|
-
}
|
|
79
|
-
setGenerations((prev) => [gen, ...prev])
|
|
80
|
-
setSelectedId(gen.id)
|
|
211
|
+
const generation = createGeneration(crypto.randomUUID(), settings)
|
|
212
|
+
setGenerations((prev) => [generation, ...prev])
|
|
213
|
+
setSelectedId(generation.id)
|
|
81
214
|
setGenerating(false)
|
|
82
215
|
setProgress(0)
|
|
83
216
|
}
|
|
84
217
|
|
|
85
|
-
|
|
218
|
+
function handleRemove(id: string) {
|
|
219
|
+
const remaining = generations.filter((g) => g.id !== id)
|
|
220
|
+
setGenerations(remaining)
|
|
221
|
+
if (selectedId === id) setSelectedId(remaining[0]?.id ?? null)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function handleClear() {
|
|
225
|
+
setGenerations([])
|
|
226
|
+
setSelectedId(null)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Load a previous result's settings back into the controls
|
|
230
|
+
function handleSelect(generation: Generation) {
|
|
231
|
+
setSelectedId(generation.id)
|
|
232
|
+
setPrompt(generation.prompt)
|
|
233
|
+
setSteps(generation.steps)
|
|
234
|
+
setCfg(generation.cfg)
|
|
235
|
+
setSeed(generation.seed)
|
|
236
|
+
setAspect(generation.aspect)
|
|
237
|
+
}
|
|
86
238
|
|
|
87
239
|
return (
|
|
88
240
|
<div className="flex h-screen flex-col bg-background text-foreground">
|
|
89
|
-
<div className="
|
|
90
|
-
<Panel position="left">
|
|
241
|
+
<div className="flex min-h-0 flex-1">
|
|
242
|
+
<Panel position="left" className="shrink-0">
|
|
91
243
|
<Pane>
|
|
92
244
|
<PaneHeader>
|
|
93
245
|
<PaneLabel>Prompt</PaneLabel>
|
|
94
246
|
</PaneHeader>
|
|
95
|
-
<PaneContent>
|
|
247
|
+
<PaneContent className="space-y-2">
|
|
96
248
|
<TextArea
|
|
97
249
|
value={prompt}
|
|
98
250
|
onChange={(e) => setPrompt(e.target.value)}
|
|
251
|
+
onKeyDown={(e) => {
|
|
252
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
253
|
+
e.preventDefault()
|
|
254
|
+
void handleGenerate()
|
|
255
|
+
}
|
|
256
|
+
}}
|
|
99
257
|
placeholder="Describe the image you want to generate..."
|
|
100
|
-
|
|
258
|
+
minRows={4}
|
|
259
|
+
aria-label="Prompt"
|
|
101
260
|
/>
|
|
261
|
+
<button
|
|
262
|
+
type="button"
|
|
263
|
+
onClick={() => void handleGenerate()}
|
|
264
|
+
disabled={!canGenerate}
|
|
265
|
+
className="flex h-7 w-full items-center justify-center gap-1.5 rounded-md bg-primary text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50"
|
|
266
|
+
>
|
|
267
|
+
<RiSparklingLine className="size-3.5" />
|
|
268
|
+
{generating ? "Generating..." : "Generate"}
|
|
269
|
+
</button>
|
|
270
|
+
{generating && <ProgressBar value={progress} aria-label="Generation progress" />}
|
|
102
271
|
</PaneContent>
|
|
103
272
|
</Pane>
|
|
104
273
|
<Pane>
|
|
105
274
|
<PaneHeader>
|
|
106
275
|
<PaneLabel>Parameters</PaneLabel>
|
|
276
|
+
<PaneAction
|
|
277
|
+
type="button"
|
|
278
|
+
title="Random seed"
|
|
279
|
+
aria-label="Random seed"
|
|
280
|
+
onClick={() => setSeed(randomSeed())}
|
|
281
|
+
>
|
|
282
|
+
<RiShuffleLine />
|
|
283
|
+
</PaneAction>
|
|
107
284
|
</PaneHeader>
|
|
108
|
-
<PaneContent className="space-y-
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
285
|
+
<PaneContent className="space-y-1">
|
|
286
|
+
<Action>
|
|
287
|
+
<ActionLabel>Steps</ActionLabel>
|
|
288
|
+
<ActionControls>
|
|
289
|
+
<Slider
|
|
290
|
+
aria-label="Steps"
|
|
291
|
+
min={1}
|
|
292
|
+
max={100}
|
|
293
|
+
value={[steps]}
|
|
294
|
+
onValueChange={(value) => setSteps(value[0] ?? steps)}
|
|
295
|
+
/>
|
|
296
|
+
<span className="w-8 text-right text-xs tabular-nums">{steps}</span>
|
|
297
|
+
</ActionControls>
|
|
298
|
+
</Action>
|
|
299
|
+
<Action>
|
|
300
|
+
<ActionLabel>CFG scale</ActionLabel>
|
|
301
|
+
<ActionControls>
|
|
302
|
+
<Slider
|
|
303
|
+
aria-label="CFG scale"
|
|
304
|
+
min={1}
|
|
305
|
+
max={20}
|
|
306
|
+
step={0.5}
|
|
307
|
+
value={[cfg]}
|
|
308
|
+
onValueChange={(value) => setCfg(value[0] ?? cfg)}
|
|
309
|
+
/>
|
|
310
|
+
<span className="w-8 text-right text-xs tabular-nums">{cfg}</span>
|
|
311
|
+
</ActionControls>
|
|
312
|
+
</Action>
|
|
313
|
+
<Action>
|
|
314
|
+
<ActionLabel>Seed</ActionLabel>
|
|
315
|
+
<ActionControls>
|
|
316
|
+
<NumberInput
|
|
317
|
+
className="flex-1"
|
|
318
|
+
aria-label="Seed"
|
|
319
|
+
value={seed}
|
|
320
|
+
onChange={(value) => setSeed(Math.round(value))}
|
|
321
|
+
min={-1}
|
|
322
|
+
max={MAX_SEED}
|
|
323
|
+
precision={0}
|
|
324
|
+
/>
|
|
325
|
+
</ActionControls>
|
|
326
|
+
</Action>
|
|
327
|
+
<p className="pt-1 text-[11px] text-muted-foreground">
|
|
328
|
+
A seed of -1 picks a random seed for each generation.
|
|
329
|
+
</p>
|
|
125
330
|
</PaneContent>
|
|
126
331
|
</Pane>
|
|
127
332
|
</Panel>
|
|
128
333
|
|
|
129
334
|
{/* Preview area */}
|
|
130
|
-
<div className="flex flex-1
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
</div>
|
|
148
|
-
)}
|
|
149
|
-
|
|
150
|
-
{/* Gallery history */}
|
|
151
|
-
{generations.length > 0 && (
|
|
152
|
-
<div className="border-t border-border p-3">
|
|
153
|
-
<Gallery
|
|
154
|
-
items={generations.map((g) => ({
|
|
155
|
-
id: g.id,
|
|
156
|
-
label: g.prompt.slice(0, 40),
|
|
157
|
-
}))}
|
|
158
|
-
selectedId={selectedId}
|
|
159
|
-
onSelect={setSelectedId}
|
|
160
|
-
/>
|
|
161
|
-
</div>
|
|
335
|
+
<div className="flex min-w-0 flex-1 items-center justify-center bg-canvas p-8 pb-28">
|
|
336
|
+
{selected ? (
|
|
337
|
+
<img
|
|
338
|
+
src={selected.src}
|
|
339
|
+
alt={selected.prompt}
|
|
340
|
+
className="max-h-full max-w-full rounded-lg object-contain shadow-lg"
|
|
341
|
+
/>
|
|
342
|
+
) : (
|
|
343
|
+
<EmptyState>
|
|
344
|
+
<EmptyStateIcon>
|
|
345
|
+
<RiImageLine />
|
|
346
|
+
</EmptyStateIcon>
|
|
347
|
+
<EmptyStateTitle>No image selected</EmptyStateTitle>
|
|
348
|
+
<EmptyStateDescription>
|
|
349
|
+
Enter a prompt and press Generate to create one.
|
|
350
|
+
</EmptyStateDescription>
|
|
351
|
+
</EmptyState>
|
|
162
352
|
)}
|
|
163
353
|
</div>
|
|
354
|
+
|
|
355
|
+
<Panel position="right" className="shrink-0">
|
|
356
|
+
<Pane>
|
|
357
|
+
<PaneHeader>
|
|
358
|
+
<PaneLabel>History</PaneLabel>
|
|
359
|
+
<PaneAction
|
|
360
|
+
type="button"
|
|
361
|
+
title="Clear history"
|
|
362
|
+
aria-label="Clear history"
|
|
363
|
+
onClick={handleClear}
|
|
364
|
+
disabled={generations.length === 0}
|
|
365
|
+
>
|
|
366
|
+
<RiDeleteBinLine />
|
|
367
|
+
</PaneAction>
|
|
368
|
+
</PaneHeader>
|
|
369
|
+
<PaneContent>
|
|
370
|
+
<Gallery columns={2}>
|
|
371
|
+
{generations.length === 0 && <GalleryEmpty>No generations yet</GalleryEmpty>}
|
|
372
|
+
{generations.map((generation) => (
|
|
373
|
+
<GalleryItem
|
|
374
|
+
key={generation.id}
|
|
375
|
+
selected={generation.id === selectedId}
|
|
376
|
+
onSelect={() => handleSelect(generation)}
|
|
377
|
+
title={generation.prompt}
|
|
378
|
+
>
|
|
379
|
+
<GalleryImage src={generation.src} alt={generation.prompt} />
|
|
380
|
+
<GalleryAction
|
|
381
|
+
type="button"
|
|
382
|
+
aria-label="Remove"
|
|
383
|
+
onClick={(e) => {
|
|
384
|
+
e.stopPropagation()
|
|
385
|
+
handleRemove(generation.id)
|
|
386
|
+
}}
|
|
387
|
+
>
|
|
388
|
+
<RiDeleteBinLine className="size-3.5" />
|
|
389
|
+
</GalleryAction>
|
|
390
|
+
</GalleryItem>
|
|
391
|
+
))}
|
|
392
|
+
</Gallery>
|
|
393
|
+
</PaneContent>
|
|
394
|
+
</Pane>
|
|
395
|
+
</Panel>
|
|
164
396
|
</div>
|
|
165
397
|
|
|
166
398
|
<Toolbar>
|
|
167
|
-
<
|
|
168
|
-
value=
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
399
|
+
<ToolbarToggleGroup
|
|
400
|
+
value={aspect}
|
|
401
|
+
onValueChange={(value) => {
|
|
402
|
+
if (isAspectRatio(value)) setAspect(value)
|
|
403
|
+
}}
|
|
404
|
+
>
|
|
405
|
+
{ASPECT_RATIOS.map((ratio) => (
|
|
406
|
+
<ToolbarToggle key={ratio} value={ratio} tooltip={"Aspect ratio " + ratio}>
|
|
407
|
+
{ratio}
|
|
408
|
+
</ToolbarToggle>
|
|
409
|
+
))}
|
|
410
|
+
</ToolbarToggleGroup>
|
|
411
|
+
<ToolbarSeparator />
|
|
412
|
+
<button
|
|
413
|
+
type="button"
|
|
414
|
+
className={TOOLBAR_BUTTON}
|
|
415
|
+
onClick={() => void handleGenerate()}
|
|
416
|
+
disabled={!canGenerate}
|
|
417
|
+
title="Generate (Ctrl/Cmd + Enter in the prompt)"
|
|
173
418
|
>
|
|
174
419
|
<RiSparklingLine className="size-4" />
|
|
175
|
-
|
|
420
|
+
Generate
|
|
421
|
+
</button>
|
|
176
422
|
</Toolbar>
|
|
177
423
|
|
|
178
424
|
<StatusBar>
|
|
179
|
-
<StatusBarSection>
|
|
425
|
+
<StatusBarSection align="left">
|
|
180
426
|
<StatusBarItem>Generations: {generations.length}</StatusBarItem>
|
|
181
|
-
{generating &&
|
|
427
|
+
{generating && (
|
|
428
|
+
<>
|
|
429
|
+
<StatusBarSeparator />
|
|
430
|
+
<StatusBarItem>Progress: {progress}%</StatusBarItem>
|
|
431
|
+
</>
|
|
432
|
+
)}
|
|
182
433
|
</StatusBarSection>
|
|
183
|
-
<StatusBarSection>
|
|
434
|
+
<StatusBarSection align="right">
|
|
435
|
+
{selected && (
|
|
436
|
+
<>
|
|
437
|
+
<StatusBarItem>
|
|
438
|
+
{ASPECT_SIZES[selected.aspect].width} × {ASPECT_SIZES[selected.aspect].height}
|
|
439
|
+
</StatusBarItem>
|
|
440
|
+
<StatusBarSeparator />
|
|
441
|
+
<StatusBarItem>Seed: {selected.seed}</StatusBarItem>
|
|
442
|
+
<StatusBarSeparator />
|
|
443
|
+
</>
|
|
444
|
+
)}
|
|
184
445
|
<StatusBarItem>Steps: {steps}</StatusBarItem>
|
|
446
|
+
<StatusBarSeparator />
|
|
185
447
|
<StatusBarItem>CFG: {cfg}</StatusBarItem>
|
|
186
448
|
</StatusBarSection>
|
|
187
449
|
</StatusBar>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-gen.js","sourceRoot":"","sources":["../../src/surfaces/image-gen.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,QAAQ,GAAkB;IACrC,UAAU,EAAE;QACV,SAAS;QACT,OAAO;QACP,MAAM;QACN,SAAS;QACT,WAAW;QACX,QAAQ;QACR,cAAc;QACd,cAAc;QACd,YAAY;
|
|
1
|
+
{"version":3,"file":"image-gen.js","sourceRoot":"","sources":["../../src/surfaces/image-gen.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,QAAQ,GAAkB;IACrC,UAAU,EAAE;QACV,SAAS;QACT,OAAO;QACP,MAAM;QACN,QAAQ;QACR,SAAS;QACT,WAAW;QACX,QAAQ;QACR,cAAc;QACd,cAAc;QACd,YAAY;QACZ,aAAa;KACd;IACD,GAAG,EAAE,GAAG,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsbZ;CACA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-editor.d.ts","sourceRoot":"","sources":["../../src/surfaces/node-editor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,eAAO,MAAM,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"node-editor.d.ts","sourceRoot":"","sources":["../../src/surfaces/node-editor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,eAAO,MAAM,UAAU,EAAE,aAkpBxB,CAAA"}
|