Initial commit
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
'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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user