Deploy: 2026-05-22 16:30:19
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user