mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-14 18:14:54 +00:00
- Simplify message builder content_attributes handling - Remove AI captain integration from incoming call service - Clean up FloatingCallWidget by removing non-essential features: - Remove Gravatar/MD5 dependency - Remove keypad/DTMF functionality - Remove fullscreen toggle - Simplify avatar handling - Apply consistent code formatting across voice components - Remove debug logging and unused code 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
187 lines
5.0 KiB
JavaScript
187 lines
5.0 KiB
JavaScript
import { computed } from 'vue';
|
|
|
|
export const useVoiceCallHelpers = (props, { t }) => {
|
|
// Check if the conversation is from a voice channel
|
|
const isVoiceChannelConversation = computed(() => {
|
|
// First check the meta.inbox.channel_type
|
|
if (props.conversation?.meta?.inbox?.channel_type === 'Channel::Voice') {
|
|
return true;
|
|
}
|
|
|
|
// Also check the inbox_id to find the channel type
|
|
// This is useful when the meta.inbox is not fully populated
|
|
if (
|
|
props.conversation?.inbox_id &&
|
|
props.conversation?.meta?.channel_type === 'Channel::Voice'
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
// Function to check if a conversation is a voice channel conversation (non-computed version)
|
|
const isConversationFromVoiceChannel = conversation => {
|
|
if (!conversation) return false;
|
|
|
|
// Check meta inbox channel type
|
|
if (conversation.meta?.inbox?.channel_type === 'Channel::Voice') {
|
|
return true;
|
|
}
|
|
|
|
// Check meta channel type
|
|
if (conversation.meta?.channel_type === 'Channel::Voice') {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
// Helper function to find call information from various sources
|
|
const getCallData = conversation => {
|
|
if (!conversation) return {};
|
|
|
|
// First check for data directly in conversation attributes
|
|
const conversationAttributes =
|
|
conversation.custom_attributes ||
|
|
conversation.additional_attributes ||
|
|
{};
|
|
if (conversationAttributes.call_data) {
|
|
return conversationAttributes.call_data;
|
|
}
|
|
|
|
return {};
|
|
};
|
|
|
|
// Check if a message has an arrow prefix
|
|
const hasArrow = message => {
|
|
if (!message?.content) return false;
|
|
|
|
return (
|
|
typeof message.content === 'string' &&
|
|
(message.content.startsWith('←') ||
|
|
message.content.startsWith('→') ||
|
|
message.content.startsWith('↔️'))
|
|
);
|
|
};
|
|
|
|
// Determine if it's an incoming call
|
|
const isIncomingCall = (callData, message) => {
|
|
if (!message) return null;
|
|
|
|
// Check for arrow in content
|
|
if (hasArrow(message)) {
|
|
return message.content.startsWith('←');
|
|
}
|
|
|
|
// Try to use the direction stored in the call data
|
|
if (callData?.call_direction) {
|
|
return callData.call_direction === 'inbound';
|
|
}
|
|
|
|
// Fall back to message_type
|
|
return message.message_type === 0;
|
|
};
|
|
|
|
// Get normalized call status from multiple sources
|
|
const normalizeCallStatus = (status, isIncoming) => {
|
|
// Map from Twilio status to our UI status
|
|
const statusMap = {
|
|
'in-progress': 'active',
|
|
completed: 'ended',
|
|
canceled: 'ended',
|
|
failed: 'ended',
|
|
busy: 'no-answer',
|
|
'no-answer': isIncoming ? 'missed' : 'no-answer',
|
|
active: 'active',
|
|
missed: 'missed',
|
|
ended: 'ended',
|
|
ringing: 'ringing',
|
|
};
|
|
|
|
return statusMap[status] || status;
|
|
};
|
|
|
|
// Get the appropriate icon for a call status
|
|
const getCallIconName = (status, isIncoming) => {
|
|
if (status === 'missed' || status === 'no-answer') {
|
|
return 'i-ph-phone-x-fill';
|
|
}
|
|
|
|
if (status === 'active') {
|
|
return 'i-ph-phone-call-fill';
|
|
}
|
|
|
|
if (status === 'ended' || status === 'completed') {
|
|
return isIncoming
|
|
? 'i-ph-phone-incoming-fill'
|
|
: 'i-ph-phone-outgoing-fill';
|
|
}
|
|
|
|
// Default phone icon for ringing state
|
|
return isIncoming ? 'i-ph-phone-incoming-fill' : 'i-ph-phone-outgoing-fill';
|
|
};
|
|
|
|
// Get the appropriate text for a call status
|
|
const getStatusText = (status, isIncoming) => {
|
|
if (status === 'active') {
|
|
return t('CONVERSATION.VOICE_CALL.CALL_IN_PROGRESS');
|
|
}
|
|
|
|
if (isIncoming) {
|
|
if (status === 'ringing') {
|
|
return t('CONVERSATION.VOICE_CALL.INCOMING_CALL');
|
|
}
|
|
|
|
if (status === 'missed') {
|
|
return t('CONVERSATION.VOICE_CALL.MISSED_CALL');
|
|
}
|
|
|
|
if (status === 'ended') {
|
|
return t('CONVERSATION.VOICE_CALL.CALL_ENDED');
|
|
}
|
|
} else {
|
|
if (status === 'ringing') {
|
|
return t('CONVERSATION.VOICE_CALL.OUTGOING_CALL');
|
|
}
|
|
|
|
if (status === 'no-answer') {
|
|
return t('CONVERSATION.VOICE_CALL.NO_ANSWER');
|
|
}
|
|
|
|
if (status === 'ended') {
|
|
return t('CONVERSATION.VOICE_CALL.CALL_ENDED');
|
|
}
|
|
}
|
|
|
|
return isIncoming
|
|
? t('CONVERSATION.VOICE_CALL.INCOMING_CALL')
|
|
: t('CONVERSATION.VOICE_CALL.OUTGOING_CALL');
|
|
};
|
|
|
|
// Process message content with arrow prefix
|
|
const processArrowContent = (content, isIncoming, normalizedStatus) => {
|
|
// Remove arrows and clean up the text
|
|
let text = content.replace(/^[←→↔️]/, '').trim();
|
|
|
|
// If it only says "Voice Call" or "jo", add more descriptive status info
|
|
if (text === 'Voice Call' || text === 'jo' || text === '') {
|
|
return getStatusText(normalizedStatus, isIncoming);
|
|
}
|
|
|
|
return text;
|
|
};
|
|
|
|
return {
|
|
isVoiceChannelConversation,
|
|
isConversationFromVoiceChannel,
|
|
getCallData,
|
|
hasArrow,
|
|
isIncomingCall,
|
|
normalizeCallStatus,
|
|
getCallIconName,
|
|
getStatusText,
|
|
processArrowContent,
|
|
};
|
|
};
|