precis / frontend /src /hooks /useStreaming.js
compendious's picture
Such slow progress...
2f317f9
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 }
}