Spaces:
Running
Running
| "use client"; | |
| import { useState, useCallback, useRef, useEffect } from "react"; | |
| import { v4 as uuidv4 } from "uuid"; | |
| import { Message, ChatResponse, EmailActionOptions } from "@/types/chat"; | |
| export function useChat() { | |
| const [messages, setMessages] = useState<Message[]>([]); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [error, setError] = useState<string | null>(null); | |
| const [preferredModel, setPreferredModel] = useState<string>("llama-3.1-8b-instant"); | |
| const sessionId = useRef<string>(uuidv4()); | |
| useEffect(() => { | |
| if (typeof window !== "undefined") { | |
| const saved = localStorage.getItem("preferred_model"); | |
| if (saved) { | |
| setPreferredModel(saved); | |
| } | |
| } | |
| }, []); | |
| const selectModel = useCallback((model: string) => { | |
| setPreferredModel(model); | |
| if (typeof window !== "undefined") { | |
| localStorage.setItem("preferred_model", model); | |
| } | |
| }, []); | |
| const sendMessage = useCallback(async (content: string, options?: EmailActionOptions) => { | |
| const trimmed = content.trim(); | |
| // Allow action-only turns (e.g. Send/Cancel buttons) even with empty text. | |
| if ((!trimmed && !options?.emailAction) || isLoading) return; | |
| const display = options?.displayContent ?? trimmed; | |
| const userMsg: Message = { | |
| id: uuidv4(), | |
| role: "user", | |
| content: display, | |
| timestamp: new Date(), | |
| }; | |
| setMessages((prev) => [...prev, userMsg]); | |
| setIsLoading(true); | |
| setError(null); | |
| try { | |
| const res = await fetch("/api/chat", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| session_id: sessionId.current, | |
| message: trimmed || display, | |
| model: preferredModel, | |
| email_action: options?.emailAction, | |
| email_payload: options?.emailPayload, | |
| }), | |
| }); | |
| if (!res.ok) { | |
| const data = await res.json().catch(() => ({})); | |
| throw new Error(data.detail ?? `Server error ${res.status}`); | |
| } | |
| const data: ChatResponse = await res.json(); | |
| const assistantMsg: Message = { | |
| id: uuidv4(), | |
| role: "assistant", | |
| content: data.answer, | |
| sources: data.sources, | |
| timestamp: new Date(), | |
| responseType: data.response_type, | |
| structuredData: data.structured_data, | |
| tokenUsage: data.token_usage, | |
| modelUsed: data.model_used, | |
| rateLimit: data.rate_limit, | |
| }; | |
| setMessages((prev) => [...prev, assistantMsg]); | |
| } catch (err) { | |
| const message = err instanceof Error ? err.message : "An unexpected error occurred"; | |
| setError(message); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }, [isLoading, preferredModel]); | |
| const clearConversation = useCallback(async () => { | |
| const sid = sessionId.current; | |
| sessionId.current = uuidv4(); | |
| setMessages([]); | |
| setError(null); | |
| await fetch(`/api/chat/${sid}`, { method: "DELETE" }).catch(() => null); | |
| }, []); | |
| return { | |
| messages, | |
| isLoading, | |
| error, | |
| sendMessage, | |
| clearConversation, | |
| hasMessages: messages.length > 0, | |
| preferredModel, | |
| setPreferredModel: selectModel, | |
| }; | |
| } | |