WIP - realtie advice from openAI
This commit is contained in:
@@ -436,12 +436,30 @@ export class VoiceService {
|
|||||||
const { callSid, tenantId, userId } = params;
|
const { callSid, tenantId, userId } = params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get OpenAI config
|
// Get OpenAI config - tenantId might be a domain, so look it up
|
||||||
const centralPrisma = getCentralPrisma();
|
const centralPrisma = getCentralPrisma();
|
||||||
const tenant = await centralPrisma.tenant.findUnique({
|
|
||||||
where: { id: tenantId },
|
// Try to find tenant by domain first (if tenantId is like "tenant1")
|
||||||
select: { integrationsConfig: true },
|
let tenant;
|
||||||
});
|
if (!tenantId.match(/^[0-9a-f]{8}-[0-9a-f]{4}-/i)) {
|
||||||
|
// Looks like a domain, not a UUID
|
||||||
|
const domainRecord = await centralPrisma.domain.findUnique({
|
||||||
|
where: { domain: tenantId },
|
||||||
|
include: { tenant: { select: { id: true, integrationsConfig: true } } },
|
||||||
|
});
|
||||||
|
tenant = domainRecord?.tenant;
|
||||||
|
} else {
|
||||||
|
// It's a UUID
|
||||||
|
tenant = await centralPrisma.tenant.findUnique({
|
||||||
|
where: { id: tenantId },
|
||||||
|
select: { id: true, integrationsConfig: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tenant) {
|
||||||
|
this.logger.warn(`Tenant not found for identifier: ${tenantId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const config = this.getIntegrationConfig(tenant?.integrationsConfig as any);
|
const config = this.getIntegrationConfig(tenant?.integrationsConfig as any);
|
||||||
|
|
||||||
@@ -451,7 +469,8 @@ export class VoiceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Connect to OpenAI Realtime API
|
// Connect to OpenAI Realtime API
|
||||||
const ws = new WebSocket('wss://api.openai.com/v1/realtime', {
|
const model = config.openai.model || 'gpt-4o-realtime-preview-2024-10-01';
|
||||||
|
const ws = new WebSocket(`wss://api.openai.com/v1/realtime?model=${model}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${config.openai.apiKey}`,
|
'Authorization': `Bearer ${config.openai.apiKey}`,
|
||||||
'OpenAI-Beta': 'realtime=v1',
|
'OpenAI-Beta': 'realtime=v1',
|
||||||
@@ -461,6 +480,9 @@ export class VoiceService {
|
|||||||
ws.on('open', () => {
|
ws.on('open', () => {
|
||||||
this.logger.log(`OpenAI Realtime connected for call ${callSid}`);
|
this.logger.log(`OpenAI Realtime connected for call ${callSid}`);
|
||||||
|
|
||||||
|
// Add to connections map only after it's open
|
||||||
|
this.openaiConnections.set(callSid, ws);
|
||||||
|
|
||||||
// Initialize session
|
// Initialize session
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
type: 'session.update',
|
type: 'session.update',
|
||||||
@@ -477,19 +499,21 @@ export class VoiceService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ws.on('message', (data: Buffer) => {
|
ws.on('message', (data: Buffer) => {
|
||||||
this.handleOpenAIMessage(callSid, tenantId, userId, JSON.parse(data.toString()));
|
// Pass the tenant UUID (tenant.id) instead of the domain string
|
||||||
|
this.handleOpenAIMessage(callSid, tenant.id, userId, JSON.parse(data.toString()));
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on('error', (error) => {
|
ws.on('error', (error) => {
|
||||||
this.logger.error(`OpenAI WebSocket error for call ${callSid}`, error);
|
this.logger.error(`OpenAI WebSocket error for call ${callSid}:`, error);
|
||||||
});
|
|
||||||
|
|
||||||
ws.on('close', () => {
|
|
||||||
this.logger.log(`OpenAI Realtime disconnected for call ${callSid}`);
|
|
||||||
this.openaiConnections.delete(callSid);
|
this.openaiConnections.delete(callSid);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.openaiConnections.set(callSid, ws);
|
ws.on('close', (code, reason) => {
|
||||||
|
this.logger.log(`OpenAI Realtime disconnected for call ${callSid} - Code: ${code}, Reason: ${reason.toString()}`);
|
||||||
|
this.openaiConnections.delete(callSid);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Don't add to connections here - wait for 'open' event
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Failed to initialize OpenAI Realtime', error);
|
this.logger.error('Failed to initialize OpenAI Realtime', error);
|
||||||
}
|
}
|
||||||
@@ -563,11 +587,14 @@ export class VoiceService {
|
|||||||
message: any,
|
message: any,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
// Log all message types for debugging
|
||||||
|
this.logger.debug(`OpenAI message type: ${message.type} for call ${callSid}`);
|
||||||
|
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case 'conversation.item.created':
|
case 'conversation.item.created':
|
||||||
if (message.item.type === 'message' && message.item.role === 'assistant') {
|
if (message.item.type === 'message' && message.item.role === 'assistant') {
|
||||||
// AI response generated
|
// AI response generated
|
||||||
this.logger.log(`AI response for call ${callSid}`);
|
this.logger.log(`AI response for call ${callSid}: ${JSON.stringify(message.item.content)}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -595,6 +622,7 @@ export class VoiceService {
|
|||||||
// Real-time transcript chunk
|
// Real-time transcript chunk
|
||||||
const deltaState = this.callStates.get(callSid);
|
const deltaState = this.callStates.get(callSid);
|
||||||
if (deltaState?.userId && message.delta) {
|
if (deltaState?.userId && message.delta) {
|
||||||
|
this.logger.log(`📝 Transcript chunk: "${message.delta}"`);
|
||||||
// Emit to frontend via gateway
|
// Emit to frontend via gateway
|
||||||
if (this.voiceGateway) {
|
if (this.voiceGateway) {
|
||||||
await this.voiceGateway.notifyAiTranscript(deltaState.userId, {
|
await this.voiceGateway.notifyAiTranscript(deltaState.userId, {
|
||||||
@@ -603,13 +631,13 @@ export class VoiceService {
|
|||||||
isFinal: false,
|
isFinal: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.logger.debug(`Transcript delta for call ${callSid}: ${message.delta}`);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'response.audio_transcript.done':
|
case 'response.audio_transcript.done':
|
||||||
// Final transcript
|
// Final transcript
|
||||||
const transcript = message.transcript;
|
const transcript = message.transcript;
|
||||||
|
this.logger.log(`✅ Final transcript for call ${callSid}: "${transcript}"`);
|
||||||
await this.updateCallTranscript(callSid, tenantId, transcript);
|
await this.updateCallTranscript(callSid, tenantId, transcript);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -618,8 +646,21 @@ export class VoiceService {
|
|||||||
await this.handleToolCall(callSid, tenantId, userId, message);
|
await this.handleToolCall(callSid, tenantId, userId, message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'session.created':
|
||||||
|
this.logger.log(`OpenAI session created for call ${callSid}`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'session.updated':
|
||||||
|
this.logger.log(`OpenAI session updated for call ${callSid}`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'error':
|
||||||
|
this.logger.error(`OpenAI error for call ${callSid}: ${JSON.stringify(message.error)}`);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Handle other message types
|
// Log other message types for debugging
|
||||||
|
this.logger.debug(`Unhandled OpenAI message type: ${message.type}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user