import { useState, useRef } from 'react' import { API_BASE, authHeaders } from '../config' export function useStreaming() { const [loading, setLoading] = useState(false) const [response, setResponse] = useState(null) const [error, setError] = useState(null) const [streamingText, setStreamingText] = useState('') const abortRef = useRef(null) const readNDJSONStream = async (res) => { const reader = res.body.getReader() const decoder = new TextDecoder() let accumulated = '' let buffer = '' let streamError = null while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const lines = buffer.split('\n') buffer = lines.pop() for (const line of lines) { if (!line.trim()) continue try { const chunk = JSON.parse(line) if (chunk.error) { streamError = String(chunk.error) continue } if (chunk.response) { accumulated += chunk.response setStreamingText(accumulated) } } catch { /* skip malformed */ } } } if (streamError) { throw new Error(streamError) } const finalText = accumulated.trim() if (!finalText) { throw new Error('Model returned an empty response. Try again or pick a different model.') } return finalText } const streamFrom = async (endpoint, { json, formData } = {}) => { abortRef.current = new AbortController() const fetchOpts = { method: 'POST', signal: abortRef.current.signal, } if (json) { fetchOpts.headers = authHeaders({ 'Content-Type': 'application/json' }) fetchOpts.body = JSON.stringify(json) } else if (formData) { fetchOpts.headers = authHeaders() fetchOpts.body = formData } const res = await fetch(`${API_BASE}${endpoint}`, fetchOpts) if (!res.ok) { const body = await res.text() let detail = `Backend error (${res.status})` try { detail = JSON.parse(body).detail } catch { /* use default */ } throw new Error(detail) } return readNDJSONStream(res) } const submit = async (activeTab, { youtubeUrl, transcript, selectedFile, selectedModel }) => { setLoading(true) setError(null) setResponse(null) setStreamingText('') try { let summary if (activeTab === 'youtube') { if (!youtubeUrl.trim()) throw new Error('Please enter a YouTube URL') summary = await streamFrom('/summarize/youtube', { json: { url: youtubeUrl, model: selectedModel } }) } else if (activeTab === 'transcript') { if (!transcript.trim()) throw new Error('Please enter some text') summary = await streamFrom('/summarize/transcript', { json: { text: transcript, model: selectedModel } }) } else if (activeTab === 'file') { if (!selectedFile) throw new Error('Please select a file') const fd = new FormData() fd.append('file', selectedFile) summary = await streamFrom(`/summarize/file?model=${encodeURIComponent(selectedModel)}`, { formData: fd }) } setResponse({ summary, success: true, source_type: activeTab, model: selectedModel }) } catch (err) { setError(err.message || 'An error occurred') } finally { setLoading(false) } } return { loading, response, error, streamingText, submit } }