diff --git a/src/hooks/useAudioMixing.ts b/src/hooks/useAudioMixing.ts index b2ab336c..b762c8f7 100644 --- a/src/hooks/useAudioMixing.ts +++ b/src/hooks/useAudioMixing.ts @@ -80,13 +80,17 @@ async function decodeWithMediaBunny( return decodedBuffers; } finally { - if (sink && typeof sink.close === 'function') { + // Defensively close resources — types may not declare close() but implementations may have it + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const closeable = (obj: unknown): obj is { close(): Promise } => + obj != null && typeof (obj as any).close === 'function'; + if (closeable(sink)) { try { await sink.close(); } catch (e) { console.warn('Failed to close sink:', e); } } - if (input && typeof input.close === 'function') { + if (closeable(input)) { try { await input.close(); } catch (e) { console.warn('Failed to close input:', e); } } - if (blobSource && typeof blobSource.close === 'function') { + if (closeable(blobSource)) { try { await blobSource.close(); } catch (e) { console.warn('Failed to close blobSource:', e); } } } diff --git a/src/store/workflowStore.ts b/src/store/workflowStore.ts index ad0d8bdd..34aeb9f7 100644 --- a/src/store/workflowStore.ts +++ b/src/store/workflowStore.ts @@ -1166,75 +1166,93 @@ export const useWorkflowStore = create((set, get) => ({ break; case "annotation": { - // Get connected image and set as source (use first image) - const { images } = getConnectedInputs(node.id); - const image = images[0] || null; - if (image) { - updateNodeData(node.id, { sourceImage: image }); - // If no annotations, pass through the image - const nodeData = node.data as AnnotationNodeData; - if (!nodeData.outputImage) { - updateNodeData(node.id, { outputImage: image }); + try { + // Get connected image and set as source (use first image) + const { images } = getConnectedInputs(node.id); + const image = images[0] || null; + if (image) { + updateNodeData(node.id, { sourceImage: image }); + // If no annotations, pass through the image + const nodeData = node.data as AnnotationNodeData; + if (!nodeData.outputImage) { + updateNodeData(node.id, { outputImage: image }); + } } + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`[Workflow] Annotation node ${node.id} failed:`, message); + updateNodeData(node.id, { error: message }); } break; } case "prompt": { - // Check for connected text input and update prompt if connected - const { text: connectedText } = getConnectedInputs(node.id); - if (connectedText !== null) { - updateNodeData(node.id, { prompt: connectedText }); + try { + // Check for connected text input and update prompt if connected + const { text: connectedText } = getConnectedInputs(node.id); + if (connectedText !== null) { + updateNodeData(node.id, { prompt: connectedText }); + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`[Workflow] Prompt node ${node.id} failed:`, message); + updateNodeData(node.id, { error: message }); } break; } case "promptConstructor": { - // Get fresh node data from store - const freshNode = get().nodes.find((n) => n.id === node.id); - const nodeData = (freshNode?.data || node.data) as PromptConstructorNodeData; - const template = nodeData.template; - - // Find connected prompt nodes via text edges - const connectedPromptNodes = edges - .filter((e) => e.target === node.id && e.targetHandle === "text") - .map((e) => nodes.find((n) => n.id === e.source)) - .filter((n): n is WorkflowNode => n !== undefined && n.type === "prompt"); - - // Build variable map from connected prompt nodes - const variableMap: Record = {}; - connectedPromptNodes.forEach((promptNode) => { - const promptData = promptNode.data as PromptNodeData; - if (promptData.variableName) { - variableMap[promptData.variableName] = promptData.prompt; - } - }); + try { + // Get fresh node data from store + const freshNode = get().nodes.find((n) => n.id === node.id); + const nodeData = (freshNode?.data || node.data) as PromptConstructorNodeData; + const template = nodeData.template; + + // Find connected prompt nodes via text edges + const connectedPromptNodes = edges + .filter((e) => e.target === node.id && e.targetHandle === "text") + .map((e) => nodes.find((n) => n.id === e.source)) + .filter((n): n is WorkflowNode => n !== undefined && n.type === "prompt"); + + // Build variable map from connected prompt nodes + const variableMap: Record = {}; + connectedPromptNodes.forEach((promptNode) => { + const promptData = promptNode.data as PromptNodeData; + if (promptData.variableName) { + variableMap[promptData.variableName] = promptData.prompt; + } + }); - // Find all @variable patterns in template - const varPattern = /@(\w+)/g; - const unresolvedVars: string[] = []; - let resolvedText = template; - - // Replace @variables with values or track unresolved - const matches = template.matchAll(varPattern); - for (const match of matches) { - const varName = match[1]; - if (variableMap[varName] !== undefined) { - // Replace with actual value - resolvedText = resolvedText.replace(new RegExp(`@${varName}`, 'g'), variableMap[varName]); - } else { - // Track unresolved variable - if (!unresolvedVars.includes(varName)) { - unresolvedVars.push(varName); + // Find all @variable patterns in template + const varPattern = /@(\w+)/g; + const unresolvedVars: string[] = []; + let resolvedText = template; + + // Replace @variables with values or track unresolved + const matches = template.matchAll(varPattern); + for (const match of matches) { + const varName = match[1]; + if (variableMap[varName] !== undefined) { + // Replace with actual value + resolvedText = resolvedText.replaceAll(`@${varName}`, variableMap[varName]); + } else { + // Track unresolved variable + if (!unresolvedVars.includes(varName)) { + unresolvedVars.push(varName); + } } } - } - // Update node with resolved text and unresolved vars list - updateNodeData(node.id, { - outputText: resolvedText, - unresolvedVars, - }); + // Update node with resolved text and unresolved vars list + updateNodeData(node.id, { + outputText: resolvedText, + unresolvedVars, + }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`[Workflow] PromptConstructor node ${node.id} failed:`, message); + updateNodeData(node.id, { error: message }); + } break; }