قم بتطوير التطبيق واضف التحسينات واجعل اسم التطبيق "المساعد الذكي" واضف اسفل الشاشة النص التالي "تصميم المهندس احمد السقاف"
c3f9420
verified
| <html lang="ar" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>المساعد الذكي - الدردشة الذكية</title> | |
| <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
| <script> | |
| tailwind.config = { | |
| theme: { | |
| extend: { | |
| colors: { | |
| primary: '#6366f1', | |
| secondary: '#8b5cf6', | |
| accent: '#06b6d4' | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;800&display=swap'); | |
| * { | |
| font-family: 'Tajawal', sans-serif; | |
| } | |
| .gradient-bg { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| } | |
| .chat-bubble-user { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border-radius: 20px 20px 0 20px; | |
| } | |
| .chat-bubble-ai { | |
| background: #f8fafc; | |
| border: 1px solid #e2e8f0; | |
| border-radius: 20px 20px 20px 0; | |
| } | |
| .sidebar-item { | |
| transition: all 0.3s ease; | |
| } | |
| .sidebar-item:hover { | |
| background: #f1f5f9; | |
| transform: translateX(-5px); | |
| } | |
| .character-card { | |
| transition: all 0.3s ease; | |
| border: 2px solid transparent; | |
| } | |
| .character-card:hover { | |
| border-color: #6366f1; | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 25px rgba(99, 102, 241, 0.15); | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.3s ease-in-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .markdown-content h1, .markdown-content h2, .markdown-content h3 { | |
| margin-top: 1rem; | |
| margin-bottom: 0.5rem; | |
| font-weight: bold; | |
| } | |
| .markdown-content h1 { font-size: 1.5rem; } | |
| .markdown-content h2 { font-size: 1.25rem; } | |
| .markdown-content h3 { font-size: 1.125rem; } | |
| .markdown-content code { | |
| background: #f1f5f9; | |
| padding: 0.25rem 0.5rem; | |
| border-radius: 0.375rem; | |
| font-family: 'Courier New', monospace; | |
| } | |
| .markdown-content pre { | |
| background: #1e293b; | |
| color: #e2e8f0; | |
| padding: 1rem; | |
| border-radius: 0.5rem; | |
| overflow-x: auto; | |
| margin: 1rem 0; | |
| } | |
| .loading-spinner { | |
| border: 3px solid #f3f3f3; | |
| border-top: 3px solid #6366f1; | |
| border-radius: 50%; | |
| width: 20px; | |
| height: 20px; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .page-container { | |
| display: none; | |
| opacity: 0; | |
| transition: opacity 0.3s ease-in-out; | |
| } | |
| .active-page { | |
| display: block; | |
| opacity: 1; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen flex flex-col"> | |
| <!-- Navigation --> | |
| <nav class="bg-white shadow-lg border-b"> | |
| <div class="max-w-7xl mx-auto px-4"> | |
| <div class="flex justify-between items-center h-16"> | |
| <div class="flex items-center space-x-4 space-x-reverse"> | |
| <i data-feather="message-circle" class="text-primary w-8 h-8"></i> | |
| <h1 class="text-xl font-bold text-gray-800">المساعد الذكي</h1> | |
| </div> | |
| <div class="flex items-center space-x-4 space-x-reverse"> | |
| <button onclick="showPage('settings')" class="text-gray-600 hover:text-primary transition-colors"> | |
| <i data-feather="settings"></i> | |
| </button> | |
| <button onclick="showPage('characters')" class="text-gray-600 hover:text-primary transition-colors"> | |
| <i data-feather="users"></i> | |
| </button> | |
| <button onclick="showPage('conversations')" class="text-gray-600 hover:text-primary transition-colors"> | |
| <i data-feather="message-square"></i> | |
| </button> | |
| <button onclick="showPage('voice-chat')" class="text-gray-600 hover:text-primary transition-colors"> | |
| <i data-feather="mic"></i> | |
| </button> | |
| <button onclick="showPage('chat')" class="text-gray-600 hover:text-primary transition-colors"> | |
| <i data-feather="message-circle"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </nav> | |
| <!-- Home Page --> | |
| <div id="home" class="page-container active-page"> | |
| <div class="max-w-4xl mx-auto py-8 px-4"> | |
| <!-- Welcome Section --> | |
| <div class="text-center mb-12"> | |
| <h2 class="text-3xl font-bold text-gray-800 mb-4">مرحباً بك في المساعد الذكي</h2> | |
| <p class="text-gray-600 text-lg">تطبيق الدردشة الذكي الذي يتقمص الشخصيات باستخدام نماذج الذكاء الاصطناعي المتقدمة</p> | |
| </div> | |
| <!-- Quick Actions --> | |
| <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-12"> | |
| <button onclick="showPage('chat')" class="bg-white p-6 rounded-xl shadow-md border border-gray-200 hover:shadow-lg transition-shadow group"> | |
| <div class="text-center"> | |
| <i data-feather="message-square" class="w-12 h-12 text-primary mx-auto mb-4 group-hover:scale-110 transition-transform"></i> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-2">بدء محادثة جديدة</h3> | |
| <p class="text-gray-600">ابدأ محادثة مع الذكاء الاصطناعي</p> | |
| </div> | |
| </button> | |
| <button onclick="showPage('voice-chat')" class="bg-white p-6 rounded-xl shadow-md border border-gray-200 hover:shadow-lg transition-shadow group"> | |
| <div class="text-center"> | |
| <i data-feather="mic" class="w-12 h-12 text-green-500 mx-auto mb-4 group-hover:scale-110 transition-transform"></i> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-2">المحادثة الصوتية</h3> | |
| <p class="text-gray-600">تحدث مع الذكاء الاصطناعي صوتياً</p> | |
| </div> | |
| </button> | |
| <button onclick="showPage('characters')" class="bg-white p-6 rounded-xl shadow-md border border-gray-200 hover:shadow-lg transition-shadow group"> | |
| <div class="text-center"> | |
| <i data-feather="users" class="w-12 h-12 text-secondary mx-auto mb-4 group-hover:scale-110 transition-transform"></i> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-2">إدارة الشخصيات</h3> | |
| <p class="text-gray-600">أنشئ وعدّل الشخصيات الافتراضية</p> | |
| </div> | |
| </button> | |
| <button onclick="showPage('conversations')" class="bg-white p-6 rounded-xl shadow-md border border-gray-200 hover:shadow-lg transition-shadow group"> | |
| <div class="text-center"> | |
| <i data-feather="archive" class="w-12 h-12 text-accent mx-auto mb-4 group-hover:scale-110 transition-transform"></i> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-2">المحادثات السابقة</h3> | |
| <p class="text-gray-600">راجع محادثاتك المحفوظة</p> | |
| </div> | |
| </button> | |
| </div> | |
| <!-- Features Section --> | |
| <div class="bg-white rounded-xl shadow-md p-8 mb-8"> | |
| <h3 class="text-2xl font-bold text-gray-800 mb-6 text-center">المميزات الرئيسية</h3> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| <div class="flex items-start space-x-4 space-x-reverse"> | |
| <i data-feather="zap" class="w-6 h-6 text-green-500 mt-1"></i> | |
| <div> | |
| <h4 class="font-semibold text-gray-800 mb-2">نماذج الذكاء الاصطناعي المتقدمة</h4> | |
| <p class="text-gray-600">استخدم أحدث نماذج الذكاء الاصطناعي المتقدمة</p> | |
| </div> | |
| </div> | |
| <div class="flex items-start space-x-4 space-x-reverse"> | |
| <i data-feather="user" class="w-6 h-6 text-blue-500 mt-1"></i> | |
| <div> | |
| <h4 class="font-semibold text-gray-800 mb-2">تقمص الشخصيات</h4> | |
| <p class="text-gray-600">تحدث مع شخصيات افتراضية مختلفة</p> | |
| </div> | |
| </div> | |
| <div class="flex items-start space-x-4 space-x-reverse"> | |
| <i data-feather="save" class="w-6 h-6 text-purple-500 mt-1"></i> | |
| <div> | |
| <h4 class="font-semibold text-gray-800 mb-2">حفظ تلقائي</h4> | |
| <p class="text-gray-600">المحادثات تحفظ تلقائياً في قاعدة البيانات المحلية</p> | |
| </div> | |
| </div> | |
| <div class="flex items-start space-x-4 space-x-reverse"> | |
| <i data-feather="code" class="w-6 h-6 text-orange-500 mt-1"></i> | |
| <div> | |
| <h4 class="font-semibold text-gray-800 mb-2">دعم Markdown</h4> | |
| <p class="text-gray-600">عرض المحادثات بتنسيق Markdown متقدم</p> | |
| </div> | |
| </div> | |
| <div class="flex items-start space-x-4 space-x-reverse"> | |
| <i data-feather="mic" class="w-6 h-6 text-red-500 mt-1"></i> | |
| <div> | |
| <h4 class="font-semibold text-gray-800 mb-2">المحادثة الصوتية</h4> | |
| <p class="text-gray-600">تحدث مع الذكاء الاصطناعي صوتياً باستخدام Web Speech API</p> | |
| </div> | |
| </div> | |
| <div class="flex items-start space-x-4 space-x-reverse"> | |
| <i data-feather="download" class="w-6 h-6 text-indigo-500 mt-1"></i> | |
| <div> | |
| <h4 class="font-semibold text-gray-800 mb-2">تصدير المحادثات</h4> | |
| <p class="text-gray-600">حفظ المحادثات بصيغة PDF أو نصية</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Statistics Section --> | |
| <div class="bg-gradient-to-r from-primary to-secondary rounded-xl shadow-md p-8 text-white mb-8"> | |
| <h3 class="text-2xl font-bold mb-6 text-center">إحصائيات التطبيق</h3> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6 text-center"> | |
| <div> | |
| <div class="text-3xl font-bold mb-2" id="totalCharacters">0</div> | |
| <p class="text-blue-100">شخصية مضافة</p> | |
| </div> | |
| <div> | |
| <div class="text-3xl font-bold mb-2" id="totalConversations">0</div> | |
| <p class="text-blue-100">محادثة محفوظة</p> | |
| </div> | |
| <div> | |
| <div class="text-3xl font-bold mb-2" id="totalMessages">0</div> | |
| <p class="text-blue-100">رسالة مرسلة</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Characters Page --> | |
| <div id="characters" class="page-container"> | |
| <div class="max-w-6xl mx-auto py-8 px-4"> | |
| <!-- Header with Add Button --> | |
| <div class="flex justify-between items-center mb-8"> | |
| <div> | |
| <h2 class="text-2xl font-bold text-gray-800">شخصياتك الافتراضية</h2> | |
| <p class="text-gray-600 mt-1">إدارة وتعديل الشخصيات الافتراضية التي يمكنك الدردشة معها</p> | |
| </div> | |
| <button onclick="openAddCharacterModal()" class="bg-primary text-white px-6 py-3 rounded-lg hover:bg-primary/90 transition-colors flex items-center space-x-2 space-x-reverse"> | |
| <i data-feather="plus" class="w-4 h-4"></i> | |
| <span>إضافة شخصية جديدة</span> | |
| </button> | |
| </div> | |
| <!-- Search and Filter --> | |
| <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6"> | |
| <div class="flex flex-col md:flex-row gap-4"> | |
| <div class="flex-1"> | |
| <input type="text" id="searchInput" placeholder="ابحث عن شخصية..." | |
| class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"> | |
| </div> | |
| <div class="flex gap-2"> | |
| <select id="sortSelect" class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"> | |
| <option value="name">الاسم (أ-ي)</option> | |
| <option value="nameDesc">الاسم (ي-أ)</option> | |
| <option value="recent">الأحدث</option> | |
| <option value="oldest">الأقدم</option> | |
| </select> | |
| <button onclick="exportCharacters()" class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors flex items-center space-x-2 space-x-reverse"> | |
| <i data-feather="download" class="w-4 h-4"></i> | |
| <span>تصدير</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Characters Grid --> | |
| <div id="charactersGrid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| <!-- Characters will be loaded here --> | |
| </div> | |
| <!-- Load More Button --> | |
| <div id="loadMoreContainer" class="text-center mt-8 hidden"> | |
| <button onclick="loadMoreCharacters()" class="bg-gray-200 text-gray-700 px-6 py-3 rounded-lg hover:bg-gray-300 transition-colors"> | |
| تحميل المزيد | |
| </button> | |
| </div> | |
| <!-- Empty State --> | |
| <div id="emptyState" class="text-center py-12 hidden"> | |
| <i data-feather="users" class="w-16 h-16 text-gray-300 mx-auto mb-4"></i> | |
| <h3 class="text-xl font-semibold text-gray-600 mb-2">لا توجد شخصيات</h3> | |
| <p class="text-gray-500 mb-6">ابدأ بإضافة أول شخصية افتراضية لك</p> | |
| <button onclick="openAddCharacterModal()" class="bg-primary text-white px-6 py-3 rounded-lg hover:bg-primary/90 transition-colors"> | |
| إضافة شخصية جديدة | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Chat Page --> | |
| <div id="chat" class="page-container"> | |
| <div class="max-w-4xl mx-auto h-[calc(100vh-4rem)] flex flex-col"> | |
| <!-- Chat Header --> | |
| <div class="bg-white border-b p-4"> | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <h2 class="text-xl font-bold text-gray-800" id="chatTitle">المحادثة الجديدة</h2> | |
| <p class="text-gray-600 text-sm" id="characterInfo">اختر شخصية للبدء</p> | |
| </div> | |
| <div class="flex space-x-2 space-x-reverse"> | |
| <button onclick="saveConversation()" class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors flex items-center space-x-2 space-x-reverse"> | |
| <i data-feather="save" class="w-4 h-4"></i> | |
| <span>حفظ</span> | |
| </button> | |
| <button onclick="clearChat()" class="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 transition-colors flex items-center space-x-2 space-x-reverse"> | |
| <i data-feather="trash-2" class="w-4 h-4"></i> | |
| <span>مسح</span> | |
| </button> | |
| <button onclick="exportChat()" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors flex items-center space-x-2 space-x-reverse"> | |
| <i data-feather="download" class="w-4 h-4"></i> | |
| <span>تصدير</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Character Selection --> | |
| <div id="characterSelection" class="bg-white border-b p-4"> | |
| <select id="characterSelect" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"> | |
| <option value="">اختر شخصية...</option> | |
| </select> | |
| </div> | |
| <!-- Messages Container --> | |
| <div id="messagesContainer" class="flex-1 overflow-y-auto p-4 space-y-4"> | |
| <div class="text-center text-gray-500 py-8"> | |
| <i data-feather="message-circle" class="w-12 h-12 mx-auto mb-4"></i> | |
| <p>ابدأ محادثة جديدة...</p> | |
| </div> | |
| </div> | |
| <!-- Input Area --> | |
| <div class="bg-white border-t p-4"> | |
| <div class="flex space-x-2 space-x-reverse"> | |
| <input type="text" id="messageInput" placeholder="اكتب رسالتك هنا..." | |
| class="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" | |
| disabled> | |
| <button id="sendButton" onclick="sendMessage()" class="bg-primary text-white px-6 py-3 rounded-lg hover:bg-primary/90 transition-colors" disabled> | |
| <i data-feather="send" class="w-4 h-4"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Voice Chat Page --> | |
| <div id="voice-chat" class="page-container"> | |
| <div class="max-w-4xl mx-auto h-[calc(100vh-4rem)] flex flex-col"> | |
| <!-- Voice Chat Header --> | |
| <div class="bg-white border-b p-4"> | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <h2 class="text-xl font-bold text-gray-800">المحادثة الصوتية</h2> | |
| <p class="text-gray-600 text-sm">تحدث مع الذكاء الاصطناعي صوتياً</p> | |
| </div> | |
| <div class="flex space-x-2 space-x-reverse"> | |
| <button onclick="toggleListening()" id="listenButton" class="bg-red-600 text-white px-6 py-3 rounded-lg hover:bg-red-700 transition-colors flex items-center space-x-2 space-x-reverse"> | |
| <i data-feather="mic" class="w-4 h-4"></i> | |
| <span>بدء الاستماع</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Character Selection --> | |
| <div class="bg-white border-b p-4"> | |
| <select id="voiceCharacterSelect" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"> | |
| <option value="">اختر شخصية...</option> | |
| </select> | |
| </div> | |
| <!-- Messages Container --> | |
| <div id="voiceMessagesContainer" class="flex-1 overflow-y-auto p-4 space-y-4"> | |
| <div class="text-center text-gray-500 py-8"> | |
| <i data-feather="mic" class="w-12 h-12 mx-auto mb-4"></i> | |
| <p>انقر على "بدء الاستماع" للبدء...</p> | |
| </div> | |
| </div> | |
| <!-- Status --> | |
| <div class="bg-white border-t p-4"> | |
| <div id="voiceStatus" class="text-center text-gray-600"> | |
| جاهز للاستماع... | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Conversations Page --> | |
| <div id="conversations" class="page-container"> | |
| <div class="max-w-6xl mx-auto py-8 px-4"> | |
| <!-- Header --> | |
| <div class="flex justify-between items-center mb-8"> | |
| <div> | |
| <h2 class="text-2xl font-bold text-gray-800">المحادثات السابقة</h2> | |
| <p class="text-gray-600 mt-1">راجع وأعد فتح المحادثات المحفوظة</p> | |
| </div> | |
| </div> | |
| <!-- Search and Filter --> | |
| <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6"> | |
| <div class="flex flex-col md:flex-row gap-4"> | |
| <div class="flex-1"> | |
| <input type="text" id="conversationSearch" placeholder="ابحث في المحادثات..." | |
| class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"> | |
| </div> | |
| <div class="flex gap-2"> | |
| <select id="conversationSort" class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"> | |
| <option value="recent">الأحدث</option> | |
| <option value="oldest">الأقدم</option> | |
| <option value="title">العنوان (أ-ي)</option> | |
| </select> | |
| <button onclick="exportAllConversations()" class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors flex items-center space-x-2 space-x-reverse"> | |
| <i data-feather="download" class="w-4 h-4"></i> | |
| <span>تصدير الكل</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Conversations List --> | |
| <div id="conversationsList" class="space-y-4"> | |
| <!-- Conversations will be loaded here --> | |
| </div> | |
| <!-- Empty State --> | |
| <div id="conversationsEmptyState" class="text-center py-12 hidden"> | |
| <i data-feather="message-square" class="w-16 h-16 text-gray-300 mx-auto mb-4"></i> | |
| <h3 class="text-xl font-semibold text-gray-600 mb-2">لا توجد محادثات</h3> | |
| <p class="text-gray-500">ابدأ محادثة جديدة لحفظها هنا</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Settings Page --> | |
| <div id="settings" class="page-container"> | |
| <div class="max-w-4xl mx-auto py-8 px-4"> | |
| <!-- Header --> | |
| <div class="mb-8"> | |
| <h2 class="text-2xl font-bold text-gray-800">الإعدادات</h2> | |
| <p class="text-gray-600 mt-1">إعدادات التطبيق والمظهر</p> | |
| </div> | |
| <!-- Settings Sections --> | |
| <div class="space-y-6"> | |
| <!-- API Settings --> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-4">إعدادات API</h3> | |
| <div class="space-y-4"> | |
| <div> | |
| <label for="apiKey" class="block text-sm font-medium text-gray-700 mb-2">مفتاح Gemini API</label> | |
| <input type="password" id="apiKey" | |
| class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" | |
| placeholder="أدخل مفتاح API الخاص بك"> | |
| <p class="text-sm text-gray-500 mt-1">يمكنك الحصول على مفتاح API من <a href="https://aistudio.google.com/" class="text-primary hover:underline" target="_blank">Google AI Studio</a></p> | |
| </div> | |
| <button onclick="saveApiKey()" class="bg-primary text-white px-6 py-3 rounded-lg hover:bg-primary/90 transition-colors"> | |
| حفظ الإعدادات | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Appearance Settings --> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-4">الإعدادات العامة</h3> | |
| <div class="space-y-4"> | |
| <div> | |
| <label for="language" class="block text-sm font-medium text-gray-700 mb-2">اللغة</label> | |
| <select id="language" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"> | |
| <option value="ar">العربية</option> | |
| <option value="en">English</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="theme" class="block text-sm font-medium text-gray-700 mb-2">المظهر</label> | |
| <select id="theme" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"> | |
| <option value="light">فاتح</option> | |
| <option value="dark">داكن</option> | |
| <option value="auto">تلقائي</option> | |
| </select> | |
| </div> | |
| <button onclick="saveGeneralSettings()" class="bg-primary text-white px-6 py-3 rounded-lg hover:bg-primary/90 transition-colors"> | |
| حفظ الإعدادات | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Data Management --> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-4">إدارة البيانات</h3> | |
| <div class="space-y-4"> | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <h4 class="font-medium text-gray-800">مسح جميع البيانات</h4> | |
| <p class="text-sm text-gray-600">سيتم حذف جميع المحادثات والشخصيات والإعدادات</p> | |
| </div> | |
| <button onclick="clearAllData()" class="bg-red-600 text-white px-6 py-3 rounded-lg hover:bg-red-700 transition-colors"> | |
| مسح الكل | |
| </button> | |
| </div> | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <h4 class="font-medium text-gray-800">تصدير البيانات</h4> | |
| <p class="text-sm text-gray-600">تحميل نسخة احتياطية من جميع البيانات</p> | |
| </div> | |
| <button onclick="exportAllData()" class="bg-green-600 text-white px-6 py-3 rounded-lg hover:bg-green-700 transition-colors"> | |
| تصدير الكل | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Add/Edit Character Modal --> | |
| <div id="characterModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 hidden"> | |
| <div class="bg-white rounded-xl shadow-xl max-w-md w-full max-h-[90vh] overflow-y-auto"> | |
| <div class="p-6"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h3 id="modalTitle" class="text-xl font-bold text-gray-800">إضافة شخصية جديدة</h3> | |
| <button onclick="closeCharacterModal()" class="text-gray-400 hover:text-gray-600"> | |
| <i data-feather="x" class="w-6 h-6"></i> | |
| </button> | |
| </div> | |
| <form id="characterForm" class="space-y-4"> | |
| <input type="hidden" id="characterId"> | |
| <div> | |
| <label for="characterName" class="block text-sm font-medium text-gray-700 mb-2">اسم الشخصية</label> | |
| <input type="text" id="characterName" required | |
| class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"> | |
| </div> | |
| <div> | |
| <label for="characterRole" class="block text-sm font-medium text-gray-700 mb-2">الدور/المهنة</label> | |
| <input type="text" id="characterRole" | |
| class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"> | |
| </div> | |
| <div> | |
| <label for="characterDescription" class="block text-sm font-medium text-gray-700 mb-2">الوصف</label> | |
| <textarea id="characterDescription" rows="4" | |
| class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" | |
| placeholder="صف شخصية الذكاء الاصطناعي ودوره وطريقة تحدثه..."></textarea> | |
| </div> | |
| <div> | |
| <label for="characterInstructions" class="block text-sm font-medium text-gray-700 mb-2">التعليمات الخاصة</label> | |
| <textarea id="characterInstructions" rows="4" | |
| class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" | |
| placeholder="أدخل التعليمات الخاصة بالشخصية..."></textarea> | |
| </div> | |
| <div class="flex justify-end space-x-3 space-x-reverse pt-4"> | |
| <button type="button" onclick="closeCharacterModal()" class="px-6 py-3 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"> | |
| إلغاء | |
| </button> | |
| <button type="submit" class="px-6 py-3 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors"> | |
| حفظ الشخصية | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Global variables | |
| let characters = []; | |
| let filteredCharacters = []; | |
| let currentPage = 1; | |
| const charactersPerPage = 9; | |
| let currentSort = 'recent'; | |
| let currentConversationId = null; | |
| let isListening = false; | |
| let recognition = null; | |
| // Initialize database | |
| function initDB() { | |
| if (!window.indexedDB) { | |
| console.log("IndexedDB غير مدعوم في هذا المتصفح"); | |
| return; | |
| } | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onerror = function(event) { | |
| console.log("خطأ في فتح قاعدة البيانات"); | |
| }; | |
| request.onupgradeneeded = function(event) { | |
| const db = event.target.result; | |
| // Create conversations store | |
| if (!db.objectStoreNames.contains('conversations')) { | |
| const conversationsStore = db.createObjectStore('conversations', { keyPath: 'id', autoIncrement: true }); | |
| conversationsStore.createIndex('title', 'title', { unique: false }); | |
| conversationsStore.createIndex('createdAt', 'createdAt', { unique: false }); | |
| } | |
| // Create characters store | |
| if (!db.objectStoreNames.contains('characters')) { | |
| const charactersStore = db.createObjectStore('characters', { keyPath: 'id', autoIncrement: true }); | |
| charactersStore.createIndex('name', 'name', { unique: false }); | |
| } | |
| // Create settings store | |
| if (!db.objectStoreNames.contains('settings')) { | |
| const settingsStore = db.createObjectStore('settings', { keyPath: 'id' }); | |
| } | |
| }; | |
| } | |
| // Page navigation | |
| function showPage(pageName) { | |
| // Hide all pages with fade out | |
| document.querySelectorAll('.page-container').forEach(page => { | |
| page.classList.remove('active-page'); | |
| page.style.opacity = '0'; | |
| }); | |
| // Show the selected page with fade in | |
| setTimeout(() => { | |
| document.getElementById(pageName).classList.add('active-page'); | |
| document.getElementById(pageName).style.opacity = '1'; | |
| }, 100); | |
| // Update page-specific content | |
| if (pageName === 'characters') { | |
| loadCharacters(); | |
| } else if (pageName === 'conversations') { | |
| loadConversations(); | |
| } else if (pageName === 'chat') { | |
| loadCharacterSelect(); | |
| } else if (pageName === 'voice-chat') { | |
| loadVoiceCharacterSelect(); | |
| initSpeechRecognition(); | |
| } else if (pageName === 'settings') { | |
| loadSettings(); | |
| } else if (pageName === 'home') { | |
| loadStatistics(); | |
| } | |
| feather.replace(); | |
| } | |
| // Initialize app | |
| document.addEventListener('DOMContentLoaded', function() { | |
| initDB(); | |
| feather.replace(); | |
| loadStatistics(); | |
| showPage('home'); | |
| // Add smooth scroll behavior | |
| document.querySelectorAll('a[href^="#"]').forEach(anchor => { | |
| anchor.addEventListener('click', function (e) { | |
| e.preventDefault(); | |
| const target = document.querySelector(this.getAttribute('href')); | |
| if (target) { | |
| target.scrollIntoView({ behavior: 'smooth' }); | |
| } | |
| }); | |
| }); | |
| // Add loading animation | |
| document.querySelectorAll('.page-container').forEach(page => { | |
| page.style.opacity = '0'; | |
| page.style.transition = 'opacity 0.3s ease-in-out'; | |
| }); | |
| }); | |
| // Load statistics | |
| function loadStatistics() { | |
| const dbRequest = indexedDB.open("GeminiPersonaDB", 1); | |
| dbRequest.onsuccess = function(event) { | |
| const db = event.target.result; | |
| // Count characters | |
| const characterTransaction = db.transaction(['characters'], 'readonly'); | |
| const characterStore = characterTransaction.objectStore('characters'); | |
| characterStore.count().onsuccess = function(e) { | |
| document.getElementById('totalCharacters').textContent = e.target.result; | |
| }; | |
| // Count conversations and messages | |
| const conversationTransaction = db.transaction(['conversations'], 'readonly'); | |
| const conversationStore = conversationTransaction.objectStore('conversations'); | |
| conversationStore.count().onsuccess = function(e) { | |
| document.getElementById('totalConversations').textContent = e.target.result; | |
| }; | |
| // Count total messages | |
| conversationStore.getAll().onsuccess = function(e) { | |
| const conversations = e.target.result; | |
| const totalMessages = conversations.reduce((sum, conv) => sum + (conv.messages ? conv.messages.length : 0), 0); | |
| document.getElementById('totalMessages').textContent = totalMessages; | |
| }; | |
| }; | |
| } | |
| // Characters functionality | |
| // Load characters from IndexedDB | |
| function loadCharacters() { | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['characters'], 'readonly'); | |
| const store = transaction.objectStore('characters'); | |
| const getAllRequest = store.getAll(); | |
| getAllRequest.onsuccess = function() { | |
| characters = getAllRequest.result; | |
| sortCharacters(currentSort); | |
| filterCharacters(); | |
| renderCharacters(); | |
| }; | |
| }; | |
| } | |
| // Sort characters | |
| function sortCharacters(sortType) { | |
| currentSort = sortType; | |
| switch(sortType) { | |
| case 'name': | |
| characters.sort((a, b) => a.name.localeCompare(b.name)); | |
| break; | |
| case 'nameDesc': | |
| characters.sort((a, b) => b.name.localeCompare(a.name)); | |
| break; | |
| case 'recent': | |
| characters.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); | |
| break; | |
| case 'oldest': | |
| characters.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)); | |
| break; | |
| } | |
| } | |
| // Filter characters | |
| function filterCharacters() { | |
| const searchTerm = document.getElementById('searchInput').value.toLowerCase(); | |
| if (searchTerm) { | |
| filteredCharacters = characters.filter(character => | |
| character.name.toLowerCase().includes(searchTerm) || | |
| (character.role && character.role.toLowerCase().includes(searchTerm)) || | |
| (character.description && character.description.toLowerCase().includes(searchTerm)) | |
| ); | |
| } else { | |
| filteredCharacters = [...characters]; | |
| } | |
| } | |
| // Load more characters | |
| function loadMoreCharacters() { | |
| currentPage++; | |
| renderCharacters(); | |
| } | |
| // Render characters grid | |
| function renderCharacters() { | |
| const grid = document.getElementById('charactersGrid'); | |
| const emptyState = document.getElementById('emptyState'); | |
| const loadMoreContainer = document.getElementById('loadMoreContainer'); | |
| filterCharacters(); | |
| if (filteredCharacters.length === 0) { | |
| grid.classList.add('hidden'); | |
| emptyState.classList.remove('hidden'); | |
| loadMoreContainer.classList.add('hidden'); | |
| return; | |
| } | |
| grid.classList.remove('hidden'); | |
| emptyState.classList.add('hidden'); | |
| const startIndex = 0; | |
| const endIndex = currentPage * charactersPerPage; | |
| const charactersToShow = filteredCharacters.slice(0, endIndex); | |
| grid.innerHTML = charactersToShow.map(character => ` | |
| <div class="character-card bg-white rounded-xl shadow-md p-6"> | |
| <div class="flex justify-between items-start mb-4"> | |
| <div> | |
| <h3 class="text-lg font-semibold text-gray-800">${character.name}</h3> | |
| ${character.role ? `<p class="text-sm text-gray-600">${character.role}</p>` : ''} | |
| </div> | |
| <div class="flex space-x-2 space-x-reverse"> | |
| <button onclick="editCharacter(${character.id})" class="text-blue-600 hover:text-blue-800 transition-colors"> | |
| <i data-feather="edit-2" class="w-4 h-4"></i> | |
| </button> | |
| <button onclick="duplicateCharacter(${character.id})" class="text-green-600 hover:text-green-800 transition-colors"> | |
| <i data-feather="copy" class="w-4 h-4"></i> | |
| </button> | |
| <button onclick="deleteCharacter(${character.id})" class="text-red-600 hover:text-red-800 transition-colors"> | |
| <i data-feather="trash-2" class="w-4 h-4"></i> | |
| </button> | |
| </div> | |
| </div> | |
| ${character.description ? `<p class="text-gray-700 text-sm mb-4 line-clamp-3">${character.description}</p>` : ''} | |
| <div class="flex space-x-2 space-x-reverse"> | |
| <button onclick="startChatWithCharacter(${character.id})" class="flex-1 bg-primary text-white py-2 px-4 rounded-lg hover:bg-primary/90 transition-colors text-sm"> | |
| بدء محادثة | |
| </button> | |
| <button onclick="startVoiceChatWithCharacter(${character.id})" class="bg-green-600 text-white p-2 rounded-lg hover:bg-green-700 transition-colors"> | |
| <i data-feather="mic" class="w-4 h-4"></i> | |
| </button> | |
| </div> | |
| </div> | |
| `).join(''); | |
| // Show/hide load more button | |
| if (endIndex < filteredCharacters.length) { | |
| loadMoreContainer.classList.remove('hidden'); | |
| } else { | |
| loadMoreContainer.classList.add('hidden'); | |
| } | |
| feather.replace(); | |
| } | |
| // Open add character modal | |
| function openAddCharacterModal() { | |
| document.getElementById('characterModal').classList.remove('hidden'); | |
| document.getElementById('modalTitle').textContent = 'إضافة شخصية جديدة'; | |
| document.getElementById('characterForm').reset(); | |
| document.getElementById('characterId').value = ''; | |
| } | |
| // Close character modal | |
| function closeCharacterModal() { | |
| document.getElementById('characterModal').classList.add('hidden'); | |
| } | |
| // Edit character | |
| function editCharacter(id) { | |
| const character = characters.find(c => c.id === id); | |
| if (!character) return; | |
| document.getElementById('characterModal').classList.remove('hidden'); | |
| document.getElementById('modalTitle').textContent = 'تعديل الشخصية'; | |
| document.getElementById('characterId').value = character.id; | |
| document.getElementById('characterName').value = character.name; | |
| document.getElementById('characterRole').value = character.role || ''; | |
| document.getElementById('characterDescription').value = character.description || ''; | |
| document.getElementById('characterInstructions').value = character.instructions || ''; | |
| } | |
| // Delete character | |
| function deleteCharacter(id) { | |
| if (!confirm('هل أنت متأكد من حذف هذه الشخصية؟')) return; | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['characters'], 'readwrite'); | |
| const store = transaction.objectStore('characters'); | |
| store.delete(id); | |
| transaction.oncomplete = function() { | |
| characters = characters.filter(c => c.id !== id); | |
| renderCharacters(); | |
| }; | |
| }; | |
| } | |
| // Start chat with character | |
| function startChatWithCharacter(characterId) { | |
| const character = characters.find(c => c.id === characterId); | |
| if (character) { | |
| localStorage.setItem('selectedCharacter', JSON.stringify(character)); | |
| showPage('chat'); | |
| loadCharacterSelect(); | |
| } | |
| } | |
| // Start voice chat with character | |
| function startVoiceChatWithCharacter(characterId) { | |
| const character = characters.find(c => c.id === characterId); | |
| if (character) { | |
| localStorage.setItem('selectedCharacter', JSON.stringify(character)); | |
| showPage('voice-chat'); | |
| loadVoiceCharacterSelect(); | |
| } | |
| } | |
| // Duplicate character | |
| function duplicateCharacter(id) { | |
| const character = characters.find(c => c.id === id); | |
| if (!character) return; | |
| const duplicatedCharacter = { | |
| ...character, | |
| name: `${character.name} (نسخة)`, | |
| createdAt: new Date().toISOString() | |
| }; | |
| delete duplicatedCharacter.id; | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['characters'], 'readwrite'); | |
| const store = transaction.objectStore('characters'); | |
| store.add(duplicatedCharacter); | |
| transaction.oncomplete = function() { | |
| loadCharacters(); | |
| }; | |
| }; | |
| } | |
| // Export characters | |
| function exportCharacters() { | |
| const dataStr = JSON.stringify(characters, null, 2); | |
| const dataBlob = new Blob([dataStr], {type: 'application/json'}); | |
| const url = URL.createObjectURL(dataBlob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.download = `smart-assistant-characters-${new Date().toISOString().split('T')[0]}.json`; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| URL.revokeObjectURL(url); | |
| } | |
| // Handle form submission | |
| document.getElementById('characterForm').addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| const characterData = { | |
| name: document.getElementById('characterName').value, | |
| role: document.getElementById('characterRole').value, | |
| description: document.getElementById('characterDescription').value, | |
| instructions: document.getElementById('characterInstructions').value, | |
| createdAt: new Date().toISOString() | |
| }; | |
| const characterId = document.getElementById('characterId').value; | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['characters'], 'readwrite'); | |
| const store = transaction.objectStore('characters'); | |
| if (characterId) { | |
| // Update existing character | |
| characterData.id = parseInt(characterId); | |
| store.put(characterData); | |
| } else { | |
| // Add new character | |
| store.add(characterData); | |
| } | |
| transaction.oncomplete = function() { | |
| closeCharacterModal(); | |
| loadCharacters(); | |
| }; | |
| }; | |
| }); | |
| // Chat functionality | |
| function loadCharacterSelect() { | |
| const select = document.getElementById('characterSelect'); | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['characters'], 'readonly'); | |
| const store = transaction.objectStore('characters'); | |
| const getAllRequest = store.getAll(); | |
| getAllRequest.onsuccess = function() { | |
| const characters = getAllRequest.result; | |
| select.innerHTML = '<option value="">اختر شخصية...</option>'; | |
| characters.forEach(character => { | |
| const option = document.createElement('option'); | |
| option.value = character.id; | |
| option.textContent = character.name; | |
| select.appendChild(option); | |
| }); | |
| // Check if there's a selected character in localStorage | |
| const selectedCharacter = localStorage.getItem('selectedCharacter'); | |
| if (selectedCharacter) { | |
| const character = JSON.parse(selectedCharacter); | |
| select.value = character.id; | |
| updateChatUI(character); | |
| } | |
| }; | |
| }; | |
| } | |
| function updateChatUI(character) { | |
| document.getElementById('characterInfo').textContent = `تتحدث مع: ${character.name}`; | |
| document.getElementById('messageInput').disabled = false; | |
| document.getElementById('sendButton').disabled = false; | |
| } | |
| async function sendMessage() { | |
| const messageInput = document.getElementById('messageInput'); | |
| const message = messageInput.value.trim(); | |
| if (!message) return; | |
| const characterSelect = document.getElementById('characterSelect'); | |
| const characterId = characterSelect.value; | |
| if (!characterId) { | |
| alert('يرجى اختيار شخصية أولاً'); | |
| return; | |
| } | |
| // Add user message to chat | |
| addMessageToChat('user', message); | |
| messageInput.value = ''; | |
| // Show loading indicator | |
| const loadingDiv = document.createElement('div'); | |
| loadingDiv.className = 'flex justify-start'; | |
| loadingDiv.innerHTML = '<div class="chat-bubble-ai px-4 py-2"><div class="loading-spinner"></div></div>'; | |
| document.getElementById('messagesContainer').appendChild(loadingDiv); | |
| document.getElementById('messagesContainer').scrollTop = document.getElementById('messagesContainer').scrollHeight; | |
| // Get character info | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['characters'], 'readonly'); | |
| const store = transaction.objectStore('characters'); | |
| const getRequest = store.get(parseInt(characterId)); | |
| getRequest.onsuccess = function() { | |
| const character = getRequest.result; | |
| // Remove loading indicator | |
| loadingDiv.remove(); | |
| // Simulate AI response with better formatting | |
| setTimeout(() => { | |
| const response = generateSmartResponse(message, character); | |
| addMessageToChat('ai', response, character); | |
| }, 1500); | |
| }; | |
| }; | |
| } | |
| function generateSmartResponse(message, character) { | |
| const responses = { | |
| doctor: [ | |
| "بناءً على ما ذكرت، أنصحك باستشارة الطبيب المختص للحصول على تشخيص دقيق.", | |
| "هذه الأعراض قد تتطلب فحصاً طبياً شاملاً. هل يمكنك توضيح المزيد؟", | |
| "من المهم مراقبة هذه الأعراض وتسجيلها لمساعدة الطبيب في التشخيص." | |
| ], | |
| teacher: [ | |
| "هذا سؤال ممتاز! دعنا نحلل المفهوم خطوة بخطوة.", | |
| "يمكنني شرح هذا الموضوع بطريقة أبسط. هل تريد التركيز على جانب معين؟", | |
| "هذه نقطة مهمة جداً في الفهم. سأوضحها لك بمثال عملي." | |
| ], | |
| friend: [ | |
| "أفهم ما تمر به تماماً. هل تريد أن تتحدث عن الأمر أكثر؟", | |
| "أنا هنا للاستماع. شاركني ما يجول في خاطرك.", | |
| "هذا موقف صعب حقاً. كيف تشعر حيال ذلك؟" | |
| ], | |
| default: [ | |
| "هذا مثير للاهتمام! أخبرني المزيد عن هذا الموضوع.", | |
| "أفهم وجهة نظرك. هل هناك جانب آخر ترغب في مناقشته؟", | |
| "سؤال جيد! دعنا نفكر في هذا معاً." | |
| ] | |
| }; | |
| const roleResponses = responses[character.role?.toLowerCase()] || responses.default; | |
| return roleResponses[Math.floor(Math.random() * roleResponses.length)]; | |
| } | |
| function addMessageToChat(sender, content, character = null) { | |
| const messagesContainer = document.getElementById('messagesContainer'); | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = `flex ${sender === 'user' ? 'justify-end' : 'justify-start'}`; | |
| const bubbleClass = sender === 'user' ? 'chat-bubble-user' : 'chat-bubble-ai'; | |
| const bubble = document.createElement('div'); | |
| bubble.className = `${bubbleClass} max-w-xs md:max-w-md lg:max-w-lg px-4 py-2`; | |
| if (sender === 'ai' && character) { | |
| const nameDiv = document.createElement('div'); | |
| nameDiv.className = 'font-semibold text-sm mb-1'; | |
| nameDiv.textContent = character.name; | |
| bubble.appendChild(nameDiv); | |
| } | |
| const contentDiv = document.createElement('div'); | |
| contentDiv.className = 'markdown-content'; | |
| contentDiv.innerHTML = marked.parse(content); | |
| bubble.appendChild(contentDiv); | |
| messageDiv.appendChild(bubble); | |
| messagesContainer.appendChild(messageDiv); | |
| messagesContainer.scrollTop = messagesContainer.scrollHeight; | |
| } | |
| function saveConversation() { | |
| const messages = document.querySelectorAll('#messagesContainer .flex'); | |
| if (messages.length === 0) { | |
| alert('لا توجد رسائل لحفظها'); | |
| return; | |
| } | |
| const characterSelect = document.getElementById('characterSelect'); | |
| const characterId = characterSelect.value; | |
| const characterName = characterSelect.options[characterSelect.selectedIndex].text; | |
| const conversationData = { | |
| title: `محادثة مع ${characterName}`, | |
| characterId: parseInt(characterId), | |
| characterName: characterName, | |
| messages: Array.from(messages).map(msg => { | |
| const isUser = msg.classList.contains('justify-end'); | |
| const content = msg.querySelector('.markdown-content')?.innerHTML || msg.querySelector('div:not(.font-semibold)')?.textContent || ''; | |
| return { | |
| sender: isUser ? 'user' : 'ai', | |
| content: content, | |
| timestamp: new Date().toISOString() | |
| }; | |
| }), | |
| createdAt: new Date().toISOString() | |
| }; | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['conversations'], 'readwrite'); | |
| const store = transaction.objectStore('conversations'); | |
| store.add(conversationData); | |
| transaction.oncomplete = function() { | |
| alert('تم حفظ المحادثة بنجاح'); | |
| }; | |
| }; | |
| } | |
| function clearChat() { | |
| document.getElementById('messagesContainer').innerHTML = ` | |
| <div class="text-center text-gray-500 py-8"> | |
| <i data-feather="message-circle" class="w-12 h-12 mx-auto mb-4"></i> | |
| <p>ابدأ محادثة جديدة...</p> | |
| </div> | |
| `; | |
| feather.replace(); | |
| } | |
| function exportChat() { | |
| const messages = document.querySelectorAll('#messagesContainer .flex'); | |
| if (messages.length === 0) { | |
| alert('لا توجد رسائل لتصديرها'); | |
| return; | |
| } | |
| const characterSelect = document.getElementById('characterSelect'); | |
| const characterName = characterSelect.options[characterSelect.selectedIndex].text; | |
| let chatContent = `محادثة مع ${characterName}\n`; | |
| chatContent += `التاريخ: ${new Date().toLocaleString('ar-SA')}\n`; | |
| chatContent += '=' .repeat(50) + '\n\n'; | |
| messages.forEach(msg => { | |
| const isUser = msg.classList.contains('justify-end'); | |
| const sender = isUser ? 'أنت' : characterName; | |
| const content = msg.querySelector('.markdown-content')?.textContent || msg.querySelector('div:not(.font-semibold)')?.textContent || ''; | |
| chatContent += `${sender}: ${content}\n\n`; | |
| }); | |
| const dataBlob = new Blob([chatContent], {type: 'text/plain'}); | |
| const url = URL.createObjectURL(dataBlob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.download = `chat-${characterName}-${new Date().toISOString().split('T')[0]}.txt`; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| URL.revokeObjectURL(url); | |
| } | |
| // Voice chat functionality | |
| function loadVoiceCharacterSelect() { | |
| const select = document.getElementById('voiceCharacterSelect'); | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['characters'], 'readonly'); | |
| const store = transaction.objectStore('characters'); | |
| const getAllRequest = store.getAll(); | |
| getAllRequest.onsuccess = function() { | |
| const characters = getAllRequest.result; | |
| select.innerHTML = '<option value="">اختر شخصية...</option>'; | |
| characters.forEach(character => { | |
| const option = document.createElement('option'); | |
| option.value = character.id; | |
| option.textContent = character.name; | |
| select.appendChild(option); | |
| }); | |
| // Check if there's a selected character in localStorage | |
| const selectedCharacter = localStorage.getItem('selectedCharacter'); | |
| if (selectedCharacter) { | |
| const character = JSON.parse(selectedCharacter); | |
| select.value = character.id; | |
| } | |
| }; | |
| }; | |
| } | |
| function initSpeechRecognition() { | |
| if ('webkitSpeechRecognition' in window) { | |
| recognition = new webkitSpeechRecognition(); | |
| recognition.continuous = false; | |
| recognition.interimResults = false; | |
| recognition.lang = 'ar-SA'; | |
| recognition.onstart = function() { | |
| isListening = true; | |
| document.getElementById('listenButton').innerHTML = '<i data-feather="square" class="w-4 h-4"></i><span>إيقاف الاستماع</span>'; | |
| document.getElementById('voiceStatus').textContent = 'جاري الاستماع...'; | |
| feather.replace(); | |
| }; | |
| recognition.onresult = function(event) { | |
| const transcript = event.results[0][0].transcript; | |
| addVoiceMessage('user', transcript); | |
| // Simulate AI response | |
| setTimeout(() => { | |
| addVoiceMessage('ai', `تم استقبال رسالتك: "${transcript}"`); | |
| }, 1000); | |
| }; | |
| recognition.onerror = function(event) { | |
| console.error('Speech recognition error', event.error); | |
| document.getElementById('voiceStatus').textContent = 'خطأ في التعرف على الصوت'; | |
| }; | |
| recognition.onend = function() { | |
| isListening = false; | |
| document.getElementById('listenButton').innerHTML = '<i data-feather="mic" class="w-4 h-4"></i><span>بدء الاستماع</span>'; | |
| document.getElementById('voiceStatus').textContent = 'جاهز للاستماع...'; | |
| feather.replace(); | |
| }; | |
| } else { | |
| alert('متصفحك لا يدعم التعرف على الصوت'); | |
| } | |
| } | |
| function toggleListening() { | |
| if (!recognition) return; | |
| if (isListening) { | |
| recognition.stop(); | |
| } else { | |
| const characterSelect = document.getElementById('voiceCharacterSelect'); | |
| if (!characterSelect.value) { | |
| alert('يرجى اختيار شخصية أولاً'); | |
| return; | |
| } | |
| recognition.start(); | |
| } | |
| } | |
| function addVoiceMessage(sender, content) { | |
| const messagesContainer = document.getElementById('voiceMessagesContainer'); | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = `flex ${sender === 'user' ? 'justify-end' : 'justify-start'}`; | |
| const bubbleClass = sender === 'user' ? 'chat-bubble-user' : 'chat-bubble-ai'; | |
| const bubble = document.createElement('div'); | |
| bubble.className = `${bubbleClass} max-w-xs md:max-w-md lg:max-w-lg px-4 py-2`; | |
| bubble.textContent = content; | |
| messageDiv.appendChild(bubble); | |
| messagesContainer.appendChild(messageDiv); | |
| messagesContainer.scrollTop = messagesContainer.scrollHeight; | |
| } | |
| // Conversations functionality | |
| function loadConversations() { | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['conversations'], 'readonly'); | |
| const store = transaction.objectStore('conversations'); | |
| const getAllRequest = store.getAll(); | |
| getAllRequest.onsuccess = function() { | |
| const conversations = getAllRequest.result; | |
| renderConversations(conversations); | |
| }; | |
| }; | |
| } | |
| function renderConversations(conversations) { | |
| const container = document.getElementById('conversationsList'); | |
| const emptyState = document.getElementById('conversationsEmptyState'); | |
| if (conversations.length === 0) { | |
| container.classList.add('hidden'); | |
| emptyState.classList.remove('hidden'); | |
| return; | |
| } | |
| container.classList.remove('hidden'); | |
| emptyState.classList.add('hidden'); | |
| container.innerHTML = conversations.map(conv => ` | |
| <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4 hover:shadow-md transition-shadow"> | |
| <div class="flex justify-between items-start"> | |
| <div class="flex-1"> | |
| <h3 class="text-lg font-semibold text-gray-800">${conv.title || 'محادثة بدون عنوان'}</h3> | |
| <p class="text-gray-600 text-sm mt-1">${new Date(conv.createdAt).toLocaleString('ar-SA')}</p> | |
| <p class="text-gray-500 text-sm mt-2">${conv.messages ? conv.messages.length + ' رسالة' : 'لا توجد رسائل'}</p> | |
| </div> | |
| <div class="flex space-x-2 space-x-reverse"> | |
| <button onclick="loadConversation(${conv.id})" class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-primary/90 transition-colors text-sm"> | |
| فتح | |
| </button> | |
| <button onclick="deleteConversation(${conv.id})" class="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 transition-colors text-sm"> | |
| حذف | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| function deleteConversation(id) { | |
| if (!confirm('هل أنت متأكد من حذف هذه المحادثة؟')) return; | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['conversations'], 'readwrite'); | |
| const store = transaction.objectStore('conversations'); | |
| store.delete(id); | |
| transaction.oncomplete = function() { | |
| loadConversations(); | |
| }; | |
| }; | |
| } | |
| function exportAllConversations() { | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['conversations'], 'readonly'); | |
| const store = transaction.objectStore('conversations'); | |
| const getAllRequest = store.getAll(); | |
| getAllRequest.onsuccess = function() { | |
| const conversations = getAllRequest.result; | |
| const dataStr = JSON.stringify(conversations, null, 2); | |
| const dataBlob = new Blob([dataStr], {type: 'application/json'}); | |
| const url = URL.createObjectURL(dataBlob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.download = `smart-assistant-conversations-${new Date().toISOString().split('T')[0]}.json`; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| URL.revokeObjectURL(url); | |
| }; | |
| }; | |
| } | |
| // Settings functionality | |
| function loadSettings() { | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['settings'], 'readonly'); | |
| const store = transaction.objectStore('settings'); | |
| const getRequest = store.get('apiKey'); | |
| getRequest.onsuccess = function() { | |
| if (getRequest.result) { | |
| document.getElementById('apiKey').value = getRequest.result.value || ''; | |
| } | |
| }; | |
| const getSettingsRequest = store.get('general'); | |
| getSettingsRequest.onsuccess = function() { | |
| if (getSettingsRequest.result) { | |
| const settings = getSettingsRequest.result; | |
| document.getElementById('language').value = settings.language || 'ar'; | |
| document.getElementById('theme').value = settings.theme || 'light'; | |
| } | |
| }; | |
| }; | |
| } | |
| function saveApiKey() { | |
| const apiKey = document.getElementById('apiKey').value; | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['settings'], 'readwrite'); | |
| const store = transaction.objectStore('settings'); | |
| store.put({ id: 'apiKey', value: apiKey }); | |
| transaction.oncomplete = function() { | |
| alert('تم حفظ مفتاح API'); | |
| }; | |
| }; | |
| } | |
| function saveGeneralSettings() { | |
| const language = document.getElementById('language').value; | |
| const theme = document.getElementById('theme').value; | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['settings'], 'readwrite'); | |
| const store = transaction.objectStore('settings'); | |
| store.put({ id: 'general', language, theme }); | |
| transaction.oncomplete = function() { | |
| alert('تم حفظ الإعدادات'); | |
| }; | |
| }; | |
| } | |
| function clearAllData() { | |
| if (!confirm('هل أنت متأكد من مسح جميع البيانات؟ لا يمكن التراجع عن هذا الإجراء.')) return; | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['conversations', 'characters', 'settings'], 'readwrite'); | |
| transaction.objectStore('conversations').clear(); | |
| transaction.objectStore('characters').clear(); | |
| transaction.objectStore('settings').clear(); | |
| transaction.oncomplete = function() { | |
| alert('تم مسح جميع البيانات'); | |
| location.reload(); | |
| }; | |
| }; | |
| } | |
| function exportAllData() { | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['conversations', 'characters', 'settings'], 'readonly'); | |
| const conversationsStore = transaction.objectStore('conversations'); | |
| const charactersStore = transaction.objectStore('characters'); | |
| const settingsStore = transaction.objectStore('settings'); | |
| Promise.all([ | |
| new Promise(resolve => conversationsStore.getAll().onsuccess = e => resolve(e.target.result)), | |
| new Promise(resolve => charactersStore.getAll().onsuccess = e => resolve(e.target.result)), | |
| new Promise(resolve => settingsStore.getAll().onsuccess = e => resolve(e.target.result)) | |
| ]).then(([conversations, characters, settings]) => { | |
| const data = { | |
| conversations, | |
| characters, | |
| settings, | |
| exportDate: new Date().toISOString() | |
| }; | |
| const dataStr = JSON.stringify(data, null, 2); | |
| const dataBlob = new Blob([dataStr], {type: 'application/json'}); | |
| const url = URL.createObjectURL(dataBlob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.download = `smart-assistant-backup-${new Date().toISOString().split('T')[0]}.json`; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| URL.revokeObjectURL(url); | |
| }); | |
| }; | |
| } | |
| function importData(event) { | |
| const file = event.target.files[0]; | |
| if (!file) return; | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| try { | |
| const data = JSON.parse(e.target.result); | |
| const request = indexedDB.open("GeminiPersonaDB", 1); | |
| request.onsuccess = function(event) { | |
| const db = event.target.result; | |
| const transaction = db.transaction(['conversations', 'characters', 'settings'], 'readwrite'); | |
| // Clear existing data | |
| transaction.objectStore('conversations').clear(); | |
| transaction.objectStore('characters').clear(); | |
| transaction.objectStore('settings').clear(); | |
| // Import new data | |
| if (data.conversations && data.conversations.length > 0) { | |
| const conversationsStore = transaction.objectStore('conversations'); | |
| data.conversations.forEach(conv => { | |
| delete conv.id; // Let IndexedDB generate new ID | |
| conversationsStore.add(conv); | |
| }); | |
| } | |
| if (data.characters && data.characters.length > 0) { | |
| const charactersStore = transaction.objectStore('characters'); | |
| data.characters.forEach(char => { | |
| delete char.id; // Let IndexedDB generate new ID | |
| charactersStore.add(char); | |
| }); | |
| } | |
| if (data.settings && data.settings.length > 0) { | |
| const settingsStore = transaction.objectStore('settings'); | |
| data.settings.forEach(setting => { | |
| settingsStore.add(setting); | |
| }); | |
| } | |
| transaction.oncomplete = function() { | |
| alert('تم استيراد البيانات بنجاح'); | |
| location.reload(); | |
| }; | |
| }; | |
| } catch (error) { | |
| alert('خطأ في قراءة الملف. يرجى التأكد من أنه ملف نسخة احتياطية صحيح.'); | |
| } | |
| }; | |
| reader.readAsText(file); | |
| } | |
| // Add event listeners for search and sort in characters page | |
| document.addEventListener('DOMContentLoaded', function() { | |
| document.getElementById('searchInput')?.addEventListener('input', function() { | |
| currentPage = 1; | |
| renderCharacters(); | |
| }); | |
| document.getElementById('sortSelect')?.addEventListener('change', function(e) { | |
| sortCharacters(e.target.value); | |
| currentPage = 1; | |
| renderCharacters(); | |
| }); | |
| document.getElementById('messageInput')?.addEventListener('keypress', function(e) { | |
| if (e.key === 'Enter') { | |
| sendMessage(); | |
| } | |
| }); | |
| }); | |
| </script> | |
| <!-- Footer --> | |
| <footer class="bg-gray-800 text-white py-4 mt-auto"> | |
| <div class="text-center"> | |
| <p class="text-sm">تصميم المهندس احمد السقاف</p> | |
| </div> | |
| </footer> | |
| </body> | |
| </html> | |