import { useState, useCallback, useRef } from 'react'; export interface StructuredQuestion { number: number; text: string; options: string[]; } export interface KeypointCard { color: string; title: string; points: string[]; } export interface StructuredInterviewState { isActive: boolean; topic: string; currentQuestion: StructuredQuestion | null; totalQuestions: number; answers: string[]; reasoning: string; keypointCards: KeypointCard[]; followUp: string; isComplete: boolean; isGenerating: boolean; error: string | null; } export function useStructuredInterview() { const [state, setState] = useState({ isActive: false, topic: '', currentQuestion: null, totalQuestions: 5, answers: [], reasoning: '', keypointCards: [], followUp: '', isComplete: false, isGenerating: false, error: null, }); const abortRef = useRef(null); const sessionIdRef = useRef(''); const startInterview = useCallback((topic: string) => { if (!topic.trim()) return; setState({ isActive: true, topic, currentQuestion: null, totalQuestions: 5, answers: [], reasoning: '', keypointCards: [], followUp: '', isComplete: false, isGenerating: true, error: null, }); const sid = 'si_' + Math.random().toString(36).substring(2, 10); sessionIdRef.current = sid; const abort = new AbortController(); abortRef.current = abort; fetch('/structured-interview', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: sid, message: topic }), signal: abort.signal, }).then(async res => { if (!res.body) return; const reader = res.body.getReader(); const decoder = new TextDecoder(); let buf = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buf += decoder.decode(value, { stream: true }); const lines = buf.split('\n'); buf = lines.pop() || ''; for (const line of lines) { if (!line.startsWith('data: ')) continue; const raw = line.slice(6).trim(); if (!raw) continue; try { const ev = JSON.parse(raw); switch (ev.type) { case 'reasoning': setState(prev => ({ ...prev, reasoning: prev.reasoning + (ev.content || '') })); break; case 'question': setState(prev => ({ ...prev, currentQuestion: { number: ev.number, text: ev.text, options: ev.options || [] }, totalQuestions: ev.total || 5, isGenerating: false, })); break; case 'done': setState(prev => ({ ...prev, keypointCards: ev.keypoints || [], followUp: ev.follow_up || '', isComplete: true, isGenerating: false, isActive: false, })); break; } } catch { /* */ } } } }).catch(() => { setState(prev => ({ ...prev, error: 'Connection failed', isGenerating: false })); }); }, []); const sendAnswer = useCallback((answer: string) => { if (!answer.trim() || !sessionIdRef.current) return; setState(prev => ({ ...prev, answers: [...prev.answers, answer], currentQuestion: null, isGenerating: true, })); fetch('/structured-interview', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: sessionIdRef.current, message: answer }), }).then(async res => { if (!res.body) return; const reader = res.body.getReader(); const decoder = new TextDecoder(); let buf = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buf += decoder.decode(value, { stream: true }); const lines = buf.split('\n'); buf = lines.pop() || ''; for (const line of lines) { if (!line.startsWith('data: ')) continue; const raw = line.slice(6).trim(); if (!raw) continue; try { const ev = JSON.parse(raw); switch (ev.type) { case 'reasoning': setState(prev => ({ ...prev, reasoning: prev.reasoning + (ev.content || '') })); break; case 'question': setState(prev => ({ ...prev, currentQuestion: { number: ev.number, text: ev.text, options: ev.options || [] }, isGenerating: false, })); break; case 'done': setState(prev => ({ ...prev, keypointCards: ev.keypoints || [], followUp: ev.follow_up || '', isComplete: true, isGenerating: false, isActive: false, })); break; } } catch { /* */ } } } }).catch(() => { setState(prev => ({ ...prev, error: 'Connection failed', isGenerating: false })); }); }, []); const reset = useCallback(() => { if (abortRef.current) abortRef.current.abort(); setState({ isActive: false, topic: '', currentQuestion: null, totalQuestions: 5, answers: [], reasoning: '', keypointCards: [], followUp: '', isComplete: false, isGenerating: false, error: null, }); }, []); return { ...state, startInterview, sendAnswer, reset }; }