Spaces:
Running
Running
import { useState, useCallback, useEffect } from "react"; | |
import { useMutation } from "@tanstack/react-query"; | |
import { v4 as uuidv4 } from "uuid"; | |
// Generate a conversation ID to help backend keep track of the chat history | |
const getConversationId = () => { | |
const storedId = localStorage.getItem("chat_conversation_id"); | |
if (storedId) return storedId; | |
const newId = uuidv4(); | |
localStorage.setItem("chat_conversation_id", newId); | |
return newId; | |
}; | |
export interface ChatMessage { | |
sender: string; | |
text: string; | |
imageUrl?: string; | |
} | |
export const useChatApi = () => { | |
const [conversationId] = useState(getConversationId); | |
const [isStreaming, setIsStreaming] = useState(false); | |
// Use mutation for the API call | |
const chatMutation = useMutation({ | |
mutationFn: async (prompt: string) => { | |
const controller = new AbortController(); | |
const signal = controller.signal; | |
const response = await fetch("http://127.0.0.1:8000/prompt", { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
}, | |
body: JSON.stringify({ | |
session_id: conversationId, | |
prompt: prompt, | |
}), | |
signal, | |
}); | |
if (!response.ok) { | |
throw new Error("Network response was not ok"); | |
} | |
return { response, controller }; | |
}, | |
}); | |
// Process the streaming response | |
const streamResponse = useCallback( | |
async ( | |
response: Response, | |
onChunk: (chunk: string) => void, | |
onImage?: (imageUrl: string) => void | |
) => { | |
if (!response.body) { | |
throw new Error("Response body is null"); | |
} | |
setIsStreaming(true); | |
const reader = response.body.getReader(); | |
const decoder = new TextDecoder(); | |
let buffer = ""; | |
try { | |
while (true) { | |
const { done, value } = await reader.read(); | |
if (done) { | |
break; | |
} | |
// Decode the chunk | |
const text = decoder.decode(value, { stream: true }); | |
buffer += text; | |
// Process SSE format: "data: {json}\n\n" | |
const lines = buffer.split("\n\n"); | |
buffer = lines.pop() || ""; | |
for (const line of lines) { | |
if (line.startsWith("data: ")) { | |
const jsonStr = line.slice(6); | |
try { | |
const data = JSON.parse(jsonStr); | |
// Check if there's text content | |
if (data.text) { | |
onChunk(data.text); | |
} | |
// Check if there's an image URL | |
if (data.image_url) { | |
onImage?.(data.image_url); | |
} | |
} catch (e) { | |
console.error("Error parsing SSE JSON:", e); | |
} | |
} | |
} | |
} | |
} catch (error) { | |
console.error("Error reading stream:", error); | |
} finally { | |
setIsStreaming(false); | |
} | |
}, | |
[] | |
); | |
// Cleanup function to abort any pending requests | |
useEffect(() => { | |
return () => { | |
if (chatMutation.data?.controller) { | |
chatMutation.data.controller.abort(); | |
} | |
}; | |
}, [chatMutation.data]); | |
return { | |
sendMessage: chatMutation.mutate, | |
streamResponse, | |
isLoading: chatMutation.isPending || isStreaming, | |
error: chatMutation.error, | |
}; | |
}; | |