qwen-hackthon / frontend /src /hooks /useStructuredInterview.ts
Yousef
Add all project files
f97e4ec
Raw
History Blame Contribute Delete
5.78 kB
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<StructuredInterviewState>({
isActive: false,
topic: '',
currentQuestion: null,
totalQuestions: 5,
answers: [],
reasoning: '',
keypointCards: [],
followUp: '',
isComplete: false,
isGenerating: false,
error: null,
});
const abortRef = useRef<AbortController | null>(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 };
}