File size: 8,348 Bytes
371d180
 
2303095
 
371d180
2303095
 
33c6d9a
 
 
2dc2fbc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2303095
33c6d9a
 
 
 
 
 
2303095
 
 
 
 
 
 
 
 
 
 
 
 
 
33c6d9a
 
 
 
 
 
 
 
 
 
 
 
 
2303095
 
33c6d9a
 
 
 
2303095
1acafa4
 
 
 
 
 
 
 
 
33c6d9a
 
2303095
 
 
1acafa4
 
 
 
 
 
 
 
 
2303095
 
33c6d9a
 
 
 
2303095
 
33c6d9a
2303095
33c6d9a
 
2303095
 
 
2dc2fbc
 
 
 
 
 
 
 
 
371d180
d57a1c0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2303095
371d180
2303095
 
 
 
 
 
 
 
 
371d180
2303095
 
2dc2fbc
 
 
 
 
 
 
 
 
 
 
7203e64
 
 
 
 
d57a1c0
7203e64
2303095
 
 
 
2dc2fbc
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
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;