This guide shows how to handle file uploads in your HyperFlow-powered application, including displaying uploaded files and accessing them via URLs.

Overview

HyperFlow supports file uploads through the Control API. When a flow-graph requires file input, it returns a fileUpload interaction spec. The SDK provides:

  1. uploadFile() method to send files
  2. getFileURL() helper to generate download/display URLs
  3. Automatic URL generation in chat history

Basic File Upload Flow

import { useHyperFlow } from '@hyperflow/client-sdk/react';

function ChatBot() {
    const {
        fileUploadInteractionSpec,
        uploadFile,
        getFileURL,
        messages
    } = useHyperFlow({
        baseURL: '<https://hyperflow-ai.com>',
        apiKey: 'hf_...',
        flowGraphID: 'your-flow-graph-id'
    });

    const handleFileSelect = async (event) => {
        const file = event.target.files[0];
        if (!file) return;

        // Upload to HyperFlow (returns file ID in uploads array)
        const formData = new FormData();
        formData.append('file', file);

        const uploadResponse = await fetch(`${baseURL}/api/hyperflow/file/upload`, {
            method: 'POST',
            headers: { 'X-API-Key': apiKey },
            body: formData
        });

        const { fileID } = await uploadResponse.json();

        // Send to flow-graph
        await uploadFile(file, [{ data: fileID }], file.name);
    };

    return (
        <div>
            {fileUploadInteractionSpec && (
                <input type="file" onChange={handleFileSelect} />
            )}
        </div>
    );
}

Displaying Uploaded Files

Option 1: Using Chat History (Automatic)

The SDK automatically adds uploaded files to chat history with URLs:

function MessageList({ messages, getFileURL }) {
    return (
        <div>
            {messages.map((msg, idx) => {
                if (msg.type === 'user' && msg.uploads) {
                    return (
                        <div key={idx}>
                            <p>{msg.text}</p>
                            {msg.uploads.map((upload, i) => (
                                <div key={i}>
                                    {upload.mimetype?.startsWith('image/') ? (
                                        <img src={upload.url} alt={upload.fileName} />
                                    ) : (
                                        <a href={upload.url} download>
                                            Download {upload.fileName}
                                        </a>
                                    )}
                                </div>
                            ))}
                        </div>
                    );
                }
                // ... other message types
            })}
        </div>
    );
}

function ChatBot() {
    const { messages, getFileURL } = useHyperFlow({...});
    return <MessageList messages={messages} getFileURL={getFileURL} />;
}

Option 2: Using getFileURL() Helper

For custom handling or non-chat displays:

function FileGallery({ uploads, getFileURL }) {
    return (
        <div className="gallery">
            {uploads.map((upload) => {
                const url = getFileURL(upload.data);
                return (
                    <div key={upload.data}>
                        <img src={url} alt={upload.fileName} />
                        <a href={url} download={upload.fileName}>
                            Download
                        </a>
                    </div>
                );
            })}
        </div>
    );
}

Complete Example: Image Upload with Preview

import { useHyperFlow } from '@hyperflow/client-sdk/react';
import { useState } from 'react';

function ImageChatBot() {
    const {
        messages,
        fileUploadInteractionSpec,
        uploadFile,
        sendMessage,
        isReady,
        getFileURL
    } = useHyperFlow({
        baseURL: '<https://hyperflow-ai.com>',
        apiKey: 'hf_...',
        flowGraphID: 'image-analysis-bot'
    });

    const [previewURL, setPreviewURL] = useState(null);

    const handleFileSelect = async (event) => {
        const file = event.target.files[0];
        if (!file || !file.type.startsWith('image/')) {
            alert('Please select an image file');
            return;
        }

        // Show local preview
        const localURL = URL.createObjectURL(file);
        setPreviewURL(localURL);

        try {
            // Upload to HyperFlow
            const formData = new FormData();
            formData.append('file', file);

            const response = await fetch(
                `${baseURL}/api/hyperflow/file/upload`,
                {
                    method: 'POST',
                    headers: { 'X-API-Key': apiKey },
                    body: formData
                }
            );

            const { fileID } = await response.json();

            // Send to flow-graph
            // SDK will automatically add URL to chat history
            await uploadFile(file, [{
                data: fileID,
                fileName: file.name,
                mimetype: file.type,
                size: file.size
            }], file.name);

        } catch (error) {
            console.error('Upload failed:', error);
            alert('Failed to upload file');
        } finally {
            // Clean up preview
            URL.revokeObjectURL(localURL);
            setPreviewURL(null);
        }
    };

    return (
        <div className="chat-container">
            {/* Messages */}
            <div className="messages">
                {messages.map((msg, idx) => (
                    <div key={idx} className={`message ${msg.type}`}>
                        {msg.text && <p>{msg.text}</p>}

                        {/* Display uploaded images */}
                        {msg.uploads && msg.uploads.map((upload, i) => (
                            <div key={i} className="upload-preview">
                                {upload.mimetype?.startsWith('image/') ? (
                                    <img
                                        src={upload.url}
                                        alt={upload.fileName}
                                        style={{ maxWidth: '300px' }}
                                    />
                                ) : (
                                    <a href={upload.url} download={upload.fileName}>
                                        Download {upload.fileName}
                                    </a>
                                )}
                            </div>
                        ))}

                        {/* Display media responses from LLM */}
                        {msg.media && (
                            <img src={msg.media.data} alt="Generated image" />
                        )}
                    </div>
                ))}
            </div>

            {/* File upload input */}
            {fileUploadInteractionSpec && (
                <div className="file-input">
                    {previewURL && (
                        <img
                            src={previewURL}
                            alt="Preview"
                            style={{ maxWidth: '200px' }}
                        />
                    )}
                    <input
                        type="file"
                        accept="image/*"
                        onChange={handleFileSelect}
                    />
                </div>
            )}

            {/* Text input */}
            {isReady && (
                <input
                    type="text"
                    placeholder="Type a message..."
                    onKeyPress={(e) => {
                        if (e.key === 'Enter') {
                            sendMessage(e.currentTarget.value);
                            e.currentTarget.value = '';
                        }
                    }}
                />
            )}
        </div>
    );
}

Optional vs Required Uploads

The SDK handles both upload types automatically:

Required Upload (uploaded immediately)