103 lines
2.5 KiB
TypeScript
103 lines
2.5 KiB
TypeScript
'use client';
|
|
|
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
import { initializeVapi, vapiConfig } from '@/lib/vapi';
|
|
|
|
export type VapiCallStatus = 'idle' | 'connecting' | 'active' | 'ended' | 'error';
|
|
|
|
interface CallStats {
|
|
durationSeconds: number;
|
|
creditsUsed: number;
|
|
}
|
|
|
|
const formatError = (error: unknown) => {
|
|
if (error instanceof Error) return error.message;
|
|
if (typeof error === 'string') return error;
|
|
return 'Something went wrong starting the call.';
|
|
};
|
|
|
|
export const useVapi = () => {
|
|
const clientRef = useRef<any>(null);
|
|
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
|
const [status, setStatus] = useState<VapiCallStatus>('idle');
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [durationSeconds, setDurationSeconds] = useState(0);
|
|
const [callStats, setCallStats] = useState<CallStats | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (status === 'active') {
|
|
timerRef.current = setInterval(() => {
|
|
setDurationSeconds((prev) => prev + 1);
|
|
}, 1000);
|
|
} else {
|
|
if (timerRef.current) {
|
|
clearInterval(timerRef.current);
|
|
}
|
|
}
|
|
|
|
return () => {
|
|
if (timerRef.current) {
|
|
clearInterval(timerRef.current);
|
|
}
|
|
};
|
|
}, [status]);
|
|
|
|
const startCall = useCallback(async () => {
|
|
setError(null);
|
|
setCallStats(null);
|
|
setDurationSeconds(0);
|
|
setStatus('connecting');
|
|
|
|
try {
|
|
if (!clientRef.current) {
|
|
clientRef.current = initializeVapi();
|
|
}
|
|
|
|
const assistantId = vapiConfig.assistantId;
|
|
if (!assistantId) {
|
|
throw new Error('Missing NEXT_PUBLIC_VAPI_ASSISTANT_ID');
|
|
}
|
|
|
|
const client = clientRef.current;
|
|
const response =
|
|
(await client?.start?.({ assistantId })) ??
|
|
(await client?.startCall?.({ assistantId })) ??
|
|
(await client?.call?.({ assistantId }));
|
|
|
|
if (response?.id) {
|
|
// Future: store call id for status lookup
|
|
}
|
|
|
|
setStatus('active');
|
|
} catch (err) {
|
|
setError(formatError(err));
|
|
setStatus('error');
|
|
}
|
|
}, []);
|
|
|
|
const endCall = useCallback(async () => {
|
|
try {
|
|
const client = clientRef.current;
|
|
await client?.stop?.();
|
|
await client?.end?.();
|
|
} catch (err) {
|
|
setError(formatError(err));
|
|
} finally {
|
|
setStatus('ended');
|
|
setCallStats({
|
|
durationSeconds,
|
|
creditsUsed: Math.max(1, Math.round(durationSeconds / 60)),
|
|
});
|
|
}
|
|
}, [durationSeconds]);
|
|
|
|
return {
|
|
status,
|
|
error,
|
|
durationSeconds,
|
|
callStats,
|
|
startCall,
|
|
endCall,
|
|
};
|
|
};
|