Deploy: 2026-05-22 16:30:19

This commit is contained in:
Hamza-Ayed
2026-05-22 16:30:19 +03:00
parent 9a2666a1c8
commit 88c8a0987e
3 changed files with 101 additions and 10 deletions

View File

@@ -13,6 +13,8 @@ DB_PASSWORD=
# AI Model Configuration
GEMINI_API_KEY=
ELEVENLABS_API_KEY=
ELEVENLABS_VOICE_ID=
# Messaging Gateway Settings
WHATSAPP_GATEWAY_URL=http://localhost:3722

View File

@@ -29,6 +29,9 @@ class ChatbotRule extends BaseModel
if (!empty($rule['gemini_api_key'])) {
$rule['gemini_api_key'] = Security::decrypt($rule['gemini_api_key']);
}
if (!empty($rule['elevenlabs_api_key'])) {
$rule['elevenlabs_api_key'] = Security::decrypt($rule['elevenlabs_api_key']);
}
}
return $rules;
@@ -53,8 +56,13 @@ class ChatbotRule extends BaseModel
);
}
if ($rule && !empty($rule['gemini_api_key'])) {
$rule['gemini_api_key'] = Security::decrypt($rule['gemini_api_key']);
if ($rule) {
if (!empty($rule['gemini_api_key'])) {
$rule['gemini_api_key'] = Security::decrypt($rule['gemini_api_key']);
}
if (!empty($rule['elevenlabs_api_key'])) {
$rule['elevenlabs_api_key'] = Security::decrypt($rule['elevenlabs_api_key']);
}
}
return $rule;
@@ -71,6 +79,10 @@ class ChatbotRule extends BaseModel
$data['gemini_api_key'] = Security::encrypt($data['gemini_api_key']);
}
if (!empty($data['elevenlabs_api_key'])) {
$data['elevenlabs_api_key'] = Security::encrypt($data['elevenlabs_api_key']);
}
if (isset($data['id'])) {
$id = $data['id'];
unset($data['id']);
@@ -89,14 +101,26 @@ class ChatbotRule extends BaseModel
static $checked = false;
if ($checked) return;
try {
// Check if column exists
// Check if gemini_api_key exists
$columns = Database::select("SHOW COLUMNS FROM " . static::$table . " LIKE 'gemini_api_key'");
if (empty($columns)) {
Database::execute("ALTER TABLE " . static::$table . " ADD COLUMN gemini_api_key VARCHAR(512) DEFAULT NULL AFTER ai_prompt");
}
// Check if elevenlabs_api_key exists
$elColumns = Database::select("SHOW COLUMNS FROM " . static::$table . " LIKE 'elevenlabs_api_key'");
if (empty($elColumns)) {
Database::execute("ALTER TABLE " . static::$table . " ADD COLUMN elevenlabs_api_key VARCHAR(512) DEFAULT NULL AFTER gemini_api_key");
}
// Check if elevenlabs_voice_id exists
$voiceColumns = Database::select("SHOW COLUMNS FROM " . static::$table . " LIKE 'elevenlabs_voice_id'");
if (empty($voiceColumns)) {
Database::execute("ALTER TABLE " . static::$table . " ADD COLUMN elevenlabs_voice_id VARCHAR(100) DEFAULT NULL AFTER elevenlabs_api_key");
}
$checked = true;
} catch (\Exception $e) {
error_log("Failed to ensure chatbot_rules column: " . $e->getMessage());
error_log("Failed to ensure chatbot_rules columns: " . $e->getMessage());
}
}
}

View File

@@ -214,10 +214,68 @@ class GeminiService
}
/**
* Call Gemini API to generate a native audio (speech) response from text
* Call ElevenLabs API to generate a native audio response from text
*/
public static function generateAudioResponse(string $apiKey, string $systemPrompt, string $userMessage, string $voiceName = 'Puck'): ?array
public static function generateAudioResponseWithElevenLabs(string $elApiKey, string $text, string $voiceId): ?array
{
$url = 'https://api.elevenlabs.io/v1/text-to-speech/' . $voiceId;
$payload = json_encode([
'text' => $text,
'model_id' => 'eleven_multilingual_v2',
'voice_settings' => [
'stability' => 0.5,
'similarity_boost' => 0.8
]
]);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'xi-api-key: ' . $elApiKey
]);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
return [
'audio' => base64_encode($response),
'mimeType' => 'audio/mpeg'
];
} else {
error_log("[ElevenLabs API Error] HTTP " . $httpCode . " | Response: " . $response);
return null;
}
}
/**
* Call Gemini API or ElevenLabs to generate a native audio (speech) response from text
*/
public static function generateAudioResponse(
string $apiKey,
string $systemPrompt,
string $userMessage,
string $voiceName = 'Puck',
?string $elApiKey = null,
?string $elVoiceId = null
): ?array {
// Use ElevenLabs if the API Key is provided
if (!empty($elApiKey)) {
$voiceId = !empty($elVoiceId) ? $elVoiceId : 'pNInz6obpgDQGcFmaJgB'; // Default to Adam
$audioData = self::generateAudioResponseWithElevenLabs($elApiKey, $userMessage, $voiceId);
if ($audioData) {
return $audioData;
}
error_log("[TTS Service] ElevenLabs failed, falling back to Gemini TTS.");
}
// Gemini Fallback Logic:
$models = [
'gemini-3.1-flash-tts-preview',
'gemini-2.5-flash-preview-tts'
@@ -284,8 +342,15 @@ class GeminiService
/**
* Call Gemini API with audio inline data to generate a native audio response
*/
public static function generateAudioResponseFromAudio(string $apiKey, string $systemPrompt, string $audioBase64, string $mimeType, string $voiceName = 'Puck'): ?array
{
public static function generateAudioResponseFromAudio(
string $apiKey,
string $systemPrompt,
string $audioBase64,
string $mimeType,
string $voiceName = 'Puck',
?string $elApiKey = null,
?string $elVoiceId = null
): ?array {
// Step 1: Use gemini-flash-lite-latest (which supports audio input) to understand the audio message and generate a text reply
$replyText = self::generateResponseFromAudio($apiKey, $systemPrompt, $audioBase64, $mimeType);
if (empty($replyText)) {
@@ -293,7 +358,7 @@ class GeminiService
return null;
}
// Step 2: Use gemini-3.1-flash-tts-preview to convert the text response into a native audio voice note
return self::generateAudioResponse($apiKey, $systemPrompt, $replyText, $voiceName);
// Step 2: Use ElevenLabs or Gemini TTS to convert the text response into a native audio voice note
return self::generateAudioResponse($apiKey, $systemPrompt, $replyText, $voiceName, $elApiKey, $elVoiceId);
}
}