SuperGradio / frontend /src /OutputNode.js
hadinicknam's picture
The bug for the input won't resolve , the webp I think is not Space friendly
371d180
import React from 'react';
import { Handle, Position } from 'reactflow';
import './OutputNode.css';
const OutputNode = ({ data, isConnectable }) => {
const { connectedType, label, result } = data;
// Log the props received by the component for debugging
console.log(`[OutputNode: ${label}] Received data:`, data);
// Function to download the output content
const downloadOutput = () => {
if (!result) return;
let filename = `${label || 'output'}-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}`;
let blob, url, content;
switch (connectedType) {
case 'textbox':
case 'markdown':
case 'json':
// For text/JSON content
content = typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result);
blob = new Blob([content], { type: 'text/plain' });
filename = `${filename}.txt`;
break;
case 'image':
// For image content - handle if it's a URL
let imageUrl = '';
if (typeof result === 'string') {
imageUrl = result;
} else if (Array.isArray(result) && typeof result[0] === 'string') {
imageUrl = result[0];
} else if (typeof result === 'object' && result !== null && result.url) {
imageUrl = result.url;
}
if (imageUrl) {
// For a URL, open in a new tab for direct download
window.open(imageUrl, '_blank');
return;
}
return; // Can't download if no URL
case 'audio':
case 'video':
case 'file':
// For file content - handle if it's a URL
let fileUrl = '';
if (typeof result === 'string') {
fileUrl = result;
} else if (Array.isArray(result) && typeof result[0] === 'string') {
fileUrl = result[0];
} else if (typeof result === 'object' && result !== null && result.url) {
fileUrl = result.url;
}
if (fileUrl) {
// For a URL, open in a new tab for direct download
window.open(fileUrl, '_blank');
return;
}
return; // Can't download if no URL
default:
// Default case - try to stringify any content
content = typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result);
blob = new Blob([content], { type: 'text/plain' });
filename = `${filename}.txt`;
}
// Create download link and trigger it
if (blob) {
url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
// Clean up
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
}
};
const renderContent = () => {
// Use a more robust check for the existence of a result.
// Checks for null, undefined. Allows 0, false, and empty strings to be displayed.
const hasResult = result !== null && result !== undefined;
if (!hasResult) {
console.log(`[OutputNode: ${label}] No result found. Rendering placeholder.`);
switch (connectedType) {
case 'textbox':
return <div className="output-content placeholder"><p>Text output will appear here.</p></div>;
case 'image':
return <div className="output-content placeholder"><p>Image output will appear here.</p></div>;
case 'audio':
case 'video':
case 'file':
return <div className="output-content placeholder"><p>File output will appear here.</p></div>;
default:
return <div className="output-content placeholder"><p>Connect an output to see the result.</p></div>;
}
}
console.log(`[OutputNode: ${label}] Result found. Rendering content for type: ${connectedType}`);
// --- Robust Result Rendering ---
let content;
if (typeof result === 'object' && result !== null) {
// If the result is an object or array, display it as a formatted JSON string.
// This prevents React from crashing on "Objects are not valid as a React child".
content = <pre>{JSON.stringify(result, null, 2)}</pre>;
} else {
// Otherwise, display the primitive value directly.
content = <p>{String(result)}</p>;
}
switch (connectedType) {
case 'textbox':
case 'markdown': // Handle markdown as text for now
case 'json':
return <div className="output-content text-output">{content}</div>;
case 'image':
// Handle results that are strings, objects with a URL, or arrays containing a URL.
let imageUrl = '';
if (typeof result === 'string') {
imageUrl = result;
} else if (Array.isArray(result) && typeof result[0] === 'string') {
imageUrl = result[0];
} else if (typeof result === 'object' && result !== null && result.url) {
imageUrl = result.url;
}
return <div className="output-content image-output"><img src={imageUrl} alt="Generated" style={{ maxWidth: '100%', maxHeight: '100%' }} /></div>;
case 'audio':
case 'video':
case 'file':
// Handle results that are strings, objects with a URL, or arrays containing a URL.
let fileUrl = '';
if (typeof result === 'string') {
fileUrl = result;
} else if (Array.isArray(result) && typeof result[0] === 'string') {
fileUrl = result[0];
} else if (typeof result === 'object' && result !== null && result.url) {
fileUrl = result.url;
}
return (
<div className="output-content audio-output">
<p>File output:</p>
<a href={fileUrl} target="_blank" rel="noopener noreferrer">{fileUrl}</a>
{connectedType === 'audio' && <audio controls src={fileUrl} style={{ width: '100%', marginTop: '10px' }} />}
{connectedType === 'video' && <video controls src={fileUrl} style={{ width: '100%', marginTop: '10px' }} />}
</div>
);
default:
console.log(`[OutputNode: ${label}] Cannot determine how to render type '${connectedType}'. Displaying raw data.`);
return <div className="output-content text-output">{content}</div>;
}
};
// Download button icon (simple SVG)
const DownloadIcon = () => (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 16L12 8" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
<path d="M9 13L12 16L15 13" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
<path d="M3 20H21" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
</svg>
);
// Get the output type for the handle
const getOutputDataType = () => {
switch (connectedType) {
case 'textbox':
case 'markdown':
return 'string';
case 'json':
return 'object';
case 'image':
return 'image';
case 'audio':
return 'audio';
case 'video':
return 'video';
case 'file':
return 'file';
default:
return 'any';
}
};
return (
<div className={`output-node ${data.status || ''} ${data.isSource ? 'is-source' : ''}`}>
<Handle
type="target"
position={Position.Left}
id="target-any"
isConnectable={isConnectable}
/>
<div className="output-node-header">
<strong>{label || 'Output'}</strong>
{data.status && <span className="node-status">{data.status}</span>}
{data.isSource && <span className="source-indicator">→ Source</span>}
</div>
{renderContent()}
{result !== null && result !== undefined && (
<div className="output-actions">
<button
className="download-button"
onClick={downloadOutput}
title="Download this output"
>
<DownloadIcon /> Download
</button>
</div>
)}
<Handle
type="source"
position={Position.Right}
id="source-result"
isConnectable={isConnectable}
data-type={getOutputDataType()}
/>
</div>
);
};
export default OutputNode;