Deploy: 2026-05-22 00:54:36

This commit is contained in:
Hamza-Ayed
2026-05-22 00:54:36 +03:00
parent 31da995e88
commit 2ea36c98cd
5 changed files with 287 additions and 2 deletions

View File

@@ -618,6 +618,14 @@
}
.font-semibold { font-weight: 600; }
.text-muted { color: var(--text-secondary); }
@keyframes pulse-red {
0% { transform: scale(0.85); opacity: 0.5; }
50% { transform: scale(1.15); opacity: 1; }
100% { transform: scale(0.85); opacity: 0.5; }
}
.recording-pulse {
animation: pulse-red 1.2s infinite;
}
</style>
</head>
<body x-data="app()" x-init="checkAuth()">
@@ -1005,8 +1013,41 @@
</div>
<div class="form-group" id="chatbot-prompt-group">
<label class="form-label" x-text="chatbotSettings.trigger_type === 'gemini_ai' ? 'System Instruction Prompt' : 'Predefined Auto-Reply Message'"></label>
<textarea class="form-input" x-model="chatbotSettings.ai_prompt" rows="5" required :placeholder="chatbotSettings.trigger_type === 'gemini_ai' ? 'You are a helpful customer support assistant... Respond concisely and politely in Arabic.' : 'Thank you for reaching out!'" id="chatbot-prompt-input"></textarea>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; flex-wrap: wrap; gap: 0.5rem;">
<label class="form-label" x-text="chatbotSettings.trigger_type === 'gemini_ai' ? 'System Instruction Prompt' : 'Predefined Auto-Reply Message'" style="margin-bottom: 0;"></label>
<div x-show="chatbotSettings.trigger_type === 'gemini_ai'" style="display: flex; gap: 0.35rem; flex-wrap: wrap; align-items: center;">
<button type="button" class="btn btn-secondary" style="font-size: 0.75rem; padding: 0.25rem 0.5rem; width: auto;" @click="loadPromptTemplate('nabeh')">قالب تطبيق نبيه (سوري)</button>
<button type="button" class="btn btn-secondary" style="font-size: 0.75rem; padding: 0.25rem 0.5rem; width: auto;" @click="loadPromptTemplate('store')">قالب متجر إلكتروني</button>
<button type="button" class="btn btn-secondary" style="font-size: 0.75rem; padding: 0.25rem 0.5rem; width: auto;" @click="loadPromptTemplate('general')">قالب عام (إنجليزي)</button>
<!-- Voice Recording Button -->
<button type="button" class="btn" :class="isRecording ? 'btn-danger' : 'btn-secondary'" style="font-size: 0.75rem; padding: 0.25rem 0.5rem; width: auto; display: flex; align-items: center; gap: 0.25rem;" @click="toggleVoiceRecording()">
<svg x-show="!isRecording" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/></svg>
<span x-show="isRecording" class="recording-pulse" style="display: inline-block; width: 8px; height: 8px; background-color: white; border-radius: 50%;"></span>
<span x-text="isRecording ? 'إيقاف التسجيل (' + formatRecordTime(recordingTime) + ')' : '🎤 تسجيل الإرشادات'"></span>
</button>
</div>
</div>
<textarea class="form-input" x-model="chatbotSettings.ai_prompt" rows="7" required :placeholder="chatbotSettings.trigger_type === 'gemini_ai' ? 'أدخل تعليمات الذكاء الاصطناعي هنا...' : 'شكراً لتواصلك معنا!'" id="chatbot-prompt-input" :disabled="generatingFromVoice"></textarea>
<!-- Voice Processing Loading Status -->
<div x-show="generatingFromVoice" style="margin-top: 0.5rem; display: flex; align-items: center; gap: 0.5rem; font-size: 0.8rem; color: var(--primary-accent);" dir="rtl">
<span class="spinner" style="border-top-color: var(--primary-accent); display: inline-block;"></span>
<span>جاري صياغة التوجيهات من صوتك باستخدام الذكاء الاصطناعي... يرجى الانتظار</span>
</div>
<!-- Hints & Guidelines for Merchant -->
<div x-show="chatbotSettings.trigger_type === 'gemini_ai'" style="margin-top: 0.75rem; padding: 0.75rem; background: rgba(59, 130, 246, 0.05); border: 1px dashed rgba(59, 130, 246, 0.3); border-radius: 8px; font-size: 0.8rem; color: var(--text-secondary);" dir="rtl">
<strong style="color: var(--text-primary); display: block; margin-bottom: 0.35rem; display: flex; align-items: center; gap: 0.35rem;">
💡 نصائح وتوجيهات لكتابة تعليمات ممتازة:
</strong>
<ul style="list-style-type: disc; margin-right: 1.25rem; padding-left: 0; line-height: 1.5; margin-bottom: 0;">
<li><strong>الهوية والاسم:</strong> حدد اسم الروبوت بوضوح (مثال: "أنا سارة من فريق تطبيق نبيه").</li>
<li><strong>اللهجة والأسلوب:</strong> اطلب من الذكاء الاصطناعي الرد بلهجة معينة (مثال: اللهجة السورية أو الفصحى المبسطة).</li>
<li><strong>البيانات الأساسية:</strong> اكتب ساعات عمل المتجر، طرق الدفع والتوصيل، وسياسة الاستبدال لكي يجيب الروبوت بدقة.</li>
<li><strong>التعليمات اللغوية:</strong> قمنا بتضمين ميزة مطابقة اللغة تلقائياً (الرد بالإنجليزية على الرسائل الإنجليزية، وبالعربية على العربية).</li>
</ul>
</div>
</div>
<button type="submit" class="btn btn-primary" :disabled="actionLoading" id="chatbot-save-btn">
@@ -1209,6 +1250,14 @@
groups: [],
// Voice Recording States
isRecording: false,
recordingTime: 0,
recordingIntervalId: null,
mediaRecorder: null,
audioChunks: [],
generatingFromVoice: false,
chatbotSettings: {
is_active: '0',
trigger_type: 'keyword',
@@ -1570,6 +1619,37 @@
}
},
loadPromptTemplate(type) {
if (type === 'nabeh') {
this.chatbotSettings.ai_prompt = `أنتِ "سارة"، موظفة خدمة العملاء الافتراضية الذكية والودودة لتطبيق "نبيه" (Nabeh).
مهمتكِ هي مساعدة المستخدمين والإجابة على استفساراتهم بلطف وأدب بالمسائل التقنية والتجارية المتعلقة بالتطبيق.
اتبعي القواعد التالية بدقة عند الرد:
1. في أول رسالة تواصل مع العميل، عرّفي عن نفسكِ دائماً بالقول: "معك سارة من فريق تطبيق نبيه، كيف بقدر أساعدك اليوم؟" (باللهجة السورية الودية).
2. تحدثي دائماً باللهجة السورية اللطيفة والترحيبية والودية عند التحدث باللغة العربية.
3. إذا سأل العميل أو تحدث باللغة الإنجليزية، فتحدثي معه باللغة الإنجليزية بشكل احترافي وودي وحافظي على نفس الهوية والمساعدة.
4. كوني مختصرة ومباشرة في إجاباتكِ وتجنبي الإطالة غير الضرورية.
5. ساعدي المستخدمين في فهم ميزات تطبيق "نبيه" لإدارة وتسهيل حملات واتساب والردود الذكية.`;
} else if (type === 'store') {
this.chatbotSettings.ai_prompt = `أنت موظف خدمة عملاء ذكي ومرحب لمتجرنا الإلكتروني.
مهمتك هي مساعدة العملاء والإجابة على استفساراتهم حول المنتجات، الشحن، والطلبات بلطف وأدب.
اتبع القواعد التالية عند الرد:
1. رحب بالعميل دائماً بشكل ودّي وسريع في بداية المحادثة.
2. أجب عن الأسئلة بدقة واختصار.
3. إذا سأل العميل عن حالة طلب، اطلب منه تزويدك برقم الطلب للتحقق منه.
4. حافظ على نبرة إيجابية ومحترفة.`;
} else if (type === 'general') {
this.chatbotSettings.ai_prompt = `You are a professional and helpful customer support assistant.
Your goal is to assist users with their general inquiries, troubleshoot issues, and provide helpful guidance.
Guidelines:
1. Be polite, clear, and professional.
2. Provide concise and accurate information.
3. If you do not know the answer, politely ask the user to wait while you check with the team.`;
}
},
async saveChatbotSettings() {
this.actionLoading = true;
try {
@@ -1603,6 +1683,98 @@
}
},
formatRecordTime(seconds) {
const m = Math.floor(seconds / 60);
const s = seconds % 60;
return `${m}:${s < 10 ? '0' : ''}${s}`;
},
async toggleVoiceRecording() {
if (this.isRecording) {
await this.stopVoiceRecording();
} else {
await this.startVoiceRecording();
}
},
async startVoiceRecording() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.audioChunks = [];
this.mediaRecorder = new MediaRecorder(stream);
this.mediaRecorder.ondataavailable = (event) => {
if (event.data && event.data.size > 0) {
this.audioChunks.push(event.data);
}
};
this.mediaRecorder.onstop = async () => {
const mimeType = this.mediaRecorder.mimeType || 'audio/webm';
const audioBlob = new Blob(this.audioChunks, { type: mimeType });
// Stop all audio tracks to release microphone
stream.getTracks().forEach(track => track.stop());
await this.sendAudioToBackend(audioBlob);
};
this.mediaRecorder.start();
this.isRecording = true;
this.recordingTime = 0;
this.recordingIntervalId = setInterval(() => {
this.recordingTime++;
if (this.recordingTime >= 180) { // 3 minutes max limit
this.stopVoiceRecording();
}
}, 1000);
} catch (err) {
console.error('Error starting audio recording:', err);
alert('تعذر الوصول إلى الميكروفون. يرجى التحقق من صلاحيات المتصفح.');
}
},
async stopVoiceRecording() {
if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
this.mediaRecorder.stop();
}
if (this.recordingIntervalId) {
clearInterval(this.recordingIntervalId);
this.recordingIntervalId = null;
}
this.isRecording = false;
},
async sendAudioToBackend(audioBlob) {
this.generatingFromVoice = true;
try {
const formData = new FormData();
formData.append('audio', audioBlob, 'voice_instruction.webm');
const response = await fetch('/api/chatbot/generate-prompt-from-audio', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`
},
body: formData
});
const data = await response.json();
if (response.ok && data.status === 'success') {
this.chatbotSettings.ai_prompt = data.prompt;
// Automatically save the generated prompt in database
await this.saveChatbotSettings();
alert('تم توليد التوجيهات وحفظها بنجاح!');
} else {
alert(data.message || 'فشلت عملية صياغة التوجيهات من الصوت.');
}
} catch (err) {
console.error('Error sending audio to backend:', err);
alert('حدث خطأ أثناء التواصل مع السيرفر لصياغة التوجيهات.');
} finally {
this.generatingFromVoice = false;
}
},
openAddContactModal() {
this.contactName = '';
this.contactPhone = '';

View File

@@ -66,6 +66,7 @@ $router->post('/api/campaigns', [\App\Controllers\CampaignController::class,
$router->get('/api/chatbot/rules', [\App\Controllers\ChatbotController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/chatbot/rules',[\App\Controllers\ChatbotController::class, 'store'], [\App\Middlewares\AuthMiddleware::class]);
$router->post('/api/chatbot/generate-prompt-from-audio', [\App\Controllers\ChatbotController::class, 'generatePromptFromAudio'], [\App\Middlewares\AuthMiddleware::class]);
// 4. Dispatch the request
$router->dispatch($request, $response);