diff --git a/src/areas/generate/components/GenerationOptions.tsx b/src/areas/generate/components/GenerationOptions.tsx
index 2a91d6d..7638f67 100644
--- a/src/areas/generate/components/GenerationOptions.tsx
+++ b/src/areas/generate/components/GenerationOptions.tsx
@@ -84,24 +84,42 @@ function FloatParam({ schema, value, onChange }: { schema: ParamSchema; value: a
)
}
+function IntInput({ value, onChange, placeholder, className }: { value: number; onChange: (v: number) => void; placeholder?: string; className: string }) {
+ const [text, setText] = useState(String(value))
+ const prevValue = useRef(value)
+ if (prevValue.current !== value && parseInt(text, 10) !== value) {
+ prevValue.current = value
+ setText(String(value))
+ }
+ return (
+ {
+ const raw = e.target.value
+ if (raw !== '' && raw !== '-' && !/^-?\d+$/.test(raw)) return
+ setText(raw)
+ const n = parseInt(raw, 10)
+ if (!isNaN(n)) { prevValue.current = n; onChange(n) }
+ }}
+ className={className}
+ />
+ )
+}
+
function IntParam({ schema, value, onChange }: { schema: ParamSchema; value: any; onChange: (v: any) => void }): JSX.Element {
const isSeed = schema.id === 'seed'
return (
- {
- const v = parseInt(e.target.value)
- onChange(isNaN(v) ? schema.default : v)
- }}
- className="w-full px-3 py-1.5 text-xs rounded-lg bg-zinc-900 border border-zinc-700/60 text-zinc-200 focus:outline-none focus:border-zinc-500 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
+ className="w-full px-3 py-1.5 text-xs rounded-lg bg-zinc-900 border border-zinc-700/60 text-zinc-200 focus:outline-none focus:border-zinc-500"
/>
{isSeed && (
)
}
- return (
-
onChange(param.type === 'float' ? parseFloat(e.target.value) : parseInt(e.target.value, 10))}
- className={inputCls} />
- )
+ if (param.type === 'float') {
+ return
onChange(v)} className={inputCls} />
+ }
+ // int
+ return onChange(v)} className={inputCls} />
}
// ─── Workflow dropdown ────────────────────────────────────────────────────────
@@ -362,8 +409,8 @@ function EmbeddedCanvas({ workflow, allExtensions }: {
))
}, [setNodes])
- const { setCurrentJob, updateCurrentJob, selectedImagePath, selectedImageData } = useAppStore()
- const { runState, run, cancel } = useWorkflowRunner(allExtensions)
+ const { setCurrentJob } = useAppStore()
+ const { runState, run, cancel } = useWorkflowRunStore()
const isRunning = runState.status === 'running'
// Update AddToScene node when run completes
@@ -373,21 +420,6 @@ function EmbeddedCanvas({ workflow, allExtensions }: {
if (out) updateNodeData(out.id, { params: { outputUrl: runState.outputUrl } })
}, [runState.status, runState.outputUrl])
- // Sync runState → currentJob
- useEffect(() => {
- if (runState.status === 'running') {
- const total = runState.blockTotal
- const overall = total > 0
- ? Math.round((runState.blockIndex / total) * 100 + runState.blockProgress / total)
- : runState.blockProgress
- updateCurrentJob({ status: 'generating', progress: overall, step: runState.blockStep })
- } else if (runState.status === 'done') {
- updateCurrentJob({ status: 'done', progress: 100, outputUrl: runState.outputUrl })
- } else if (runState.status === 'error') {
- updateCurrentJob({ status: 'error', error: runState.error })
- }
- }, [runState])
-
// Type mismatch detection
const typeMismatch = useMemo(() => {
const sorted = topoSortNodes(workflow.nodes, workflow.edges)
@@ -418,28 +450,9 @@ function EmbeddedCanvas({ workflow, allExtensions }: {
)
const handleGenerate = useCallback(() => {
- const imageNode = nodes.find((n) => n.type === 'imageNode')
- const imagePath = (imageNode?.data?.params?.filePath as string | undefined) ?? selectedImagePath ?? ''
- const imageData = selectedImageData ?? undefined
-
- // Capture the current mesh URL *before* setCurrentJob overwrites it
- const currentMeshUrl = useAppStore.getState().currentJob?.outputUrl
-
- setCurrentJob({
- id: crypto.randomUUID(),
- imageFile: imagePath,
- status: 'uploading',
- progress: 0,
- createdAt: Date.now(),
- })
-
- run(
- { ...workflow, nodes: nodes as WFNode[], edges: edges as WFEdge[] },
- imagePath,
- imageData,
- currentMeshUrl,
- )
- }, [nodes, edges, workflow, selectedImagePath, selectedImageData, allExtensions, setCurrentJob, run])
+ const wf: Workflow = { ...workflow, nodes: nodes as WFNode[], edges: edges as WFEdge[] }
+ run(wf, allExtensions)
+ }, [nodes, edges, workflow, allExtensions, run])
return (
@@ -495,7 +508,7 @@ function EmbeddedCanvas({ workflow, allExtensions }: {
)}
{isRunning ? (
-
)
}
- return (
- onChange(param.type === 'float' ? parseFloat(e.target.value) : parseInt(e.target.value, 10))}
- className={inputCls} />
- )
+ if (param.type === 'float') {
+ return onChange(v)} className={inputCls} />
+ }
+ // int
+ return onChange(v)} className={inputCls} />
}
// ─── ExtensionNode ────────────────────────────────────────────────────────────
diff --git a/src/areas/workflows/workflowRunStore.ts b/src/areas/workflows/workflowRunStore.ts
index e9ff51c..1930907 100644
--- a/src/areas/workflows/workflowRunStore.ts
+++ b/src/areas/workflows/workflowRunStore.ts
@@ -76,6 +76,11 @@ export const useWorkflowRunStore = create((set) => ({
const ordered = topoSort(workflow.nodes, workflow.edges)
const execNodes = ordered.filter((n) => n.type === 'extensionNode' && n.data.enabled)
+ // Capture before setCurrentJob overwrites currentJob
+ const selectedImagePath = appState.selectedImagePath ?? ''
+ const selectedImageData = appState.selectedImageData ?? undefined
+ const currentMeshUrl = appState.currentJob?.outputUrl
+
set({
activeWorkflowId: workflow.id,
runState: { status: 'running', blockIndex: 0, blockTotal: execNodes.length, blockProgress: 0, blockStep: 'Starting…' },
@@ -83,7 +88,7 @@ export const useWorkflowRunStore = create((set) => ({
appState.setCurrentJob({
id: crypto.randomUUID(),
- imageFile: '',
+ imageFile: selectedImagePath,
status: 'generating',
progress: 0,
createdAt: Date.now(),
@@ -94,10 +99,7 @@ export const useWorkflowRunStore = create((set) => ({
const settings = await window.electron.settings.get()
const workspaceDir = settings.workspaceDir.replace(/\\/g, '/')
- const nodeOutputs = new Map()
- const selectedImagePath = useAppStore.getState().selectedImagePath ?? ''
- const selectedImageData = useAppStore.getState().selectedImageData ?? undefined
- const currentMeshUrl = useAppStore.getState().currentJob?.outputUrl
+ const nodeOutputs = new Map()
// Pre-populate source nodes
for (const node of ordered) {