civicsetu / frontend /src /hooks /useChat.ts
adeshboudh16
feat: decouple frontend + multi-turn conversation + graph explorer + CAG layer
d91cbff
'use client';
import { useCallback, useRef, useState } from 'react';
import { queryRera, querySectionContext } from '@/lib/api';
import type { ApiResponse, ChatMessage, Jurisdiction } from '@/lib/types';
const SESSION_KEY = 'civicsetu_session_id';
export interface UseChatReturn {
messages: ChatMessage[];
isLoading: boolean;
sendMessage: (text: string, jurisdiction: Jurisdiction | '') => Promise<void>;
sendSectionMessage: (text: string, sectionId: string, jurisdiction: string) => Promise<void>;
newConversation: () => void;
}
export function useChat(): UseChatReturn {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [isLoading, setIsLoading] = useState(false);
const sessionIdRef = useRef<string | null>(
typeof window !== 'undefined' ? window.localStorage.getItem(SESSION_KEY) : null,
);
const _handleResponse = useCallback((data: ApiResponse) => {
if (data.session_id) {
sessionIdRef.current = data.session_id;
if (typeof window !== 'undefined') {
window.localStorage.setItem(SESSION_KEY, data.session_id);
}
}
setMessages(prev => [
...prev,
{ id: crypto.randomUUID(), role: 'assistant', text: data.answer, data },
]);
}, []);
const _handleError = useCallback((error: unknown) => {
setMessages(prev => [
...prev,
{
id: crypto.randomUUID(),
role: 'error',
text: error instanceof Error ? error.message : 'Request failed',
},
]);
}, []);
const sendMessage = useCallback(
async (text: string, jurisdiction: Jurisdiction | '') => {
setMessages(prev => [...prev, { id: crypto.randomUUID(), role: 'user', text }]);
setIsLoading(true);
try {
const data = await queryRera({
query: text,
top_k: 5,
...(jurisdiction ? { jurisdiction_filter: jurisdiction } : {}),
...(sessionIdRef.current ? { session_id: sessionIdRef.current } : {}),
});
_handleResponse(data);
} catch (error) {
_handleError(error);
} finally {
setIsLoading(false);
}
},
[_handleResponse, _handleError],
);
const sendSectionMessage = useCallback(
async (text: string, sectionId: string, jurisdiction: string) => {
setMessages(prev => [...prev, { id: crypto.randomUUID(), role: 'user', text }]);
setIsLoading(true);
try {
const data = await querySectionContext({
query: text,
section_id: sectionId,
jurisdiction,
...(sessionIdRef.current ? { session_id: sessionIdRef.current } : {}),
});
_handleResponse(data);
} catch (error) {
_handleError(error);
} finally {
setIsLoading(false);
}
},
[_handleResponse, _handleError],
);
const newConversation = useCallback(() => {
sessionIdRef.current = null;
if (typeof window !== 'undefined') {
window.localStorage.removeItem(SESSION_KEY);
}
setMessages([]);
}, []);
return { messages, isLoading, sendMessage, sendSectionMessage, newConversation };
}