قم بتطوير التطبيق واضف التحسينات التالية : اضف صفحة لاختيار النموذج وادخال مفتاح api ، اضف صفحة لادارة المحادثات السابقة ، قم بتطوير ادارة الشخصيات الافتراضية مع امكانية اضافة شخصية او تعديل شخصية او حذف شخصية ، اجعل التطبيق يقوم بحفظ المحادثات تلقائيا في قاعدة بيانات محلية ، اضف دعم markdown لعرض المحادثات بتنسيق ، اجعل التطبيق قادر على حفظ السياق في المحادثة ويجب ان يتقمص النموذج الشخصية ، النماذج الذي يستخدمها التطبيق هي نماذج Gemini-2.0-flash و glm-4.5-flash ، قم باستخدام واجهة انيقة ومتناسبة مع شاشاة الهواتف المحمولة، اضف التحسينات لجعل التطبيق مثالي
b9d2156
verified
| <html lang="ar" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>المحادثة - GeminiPersona</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> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;800&display=swap'); | |
| * { | |
| font-family: 'Tajawal', sans-serif; | |
| } | |
| .chat-container { | |
| height: calc(100vh - 8rem); | |
| } | |
| .messages-container { | |
| height: calc(100% - 80px); | |
| } | |
| .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; | |
| } | |
| .typing-indicator { | |
| display: inline-flex; | |
| align-items: center; | |
| } | |
| .typing-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| background-color: #9ca3af; | |
| margin: 0 2px; | |
| animation: typing 1.4s infinite ease-in-out; | |
| } | |
| .typing-dot:nth-child(1) { animation-delay: -0.32s; } | |
| .typing-dot:nth-child(2) { animation-delay: -0.16s; } | |
| @keyframes typing { | |
| 0%, 80%, 100% { transform: scale(0.8); opacity: 0.5; } | |
| 40% { transform: scale(1); opacity: 1; } | |
| } | |
| .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; | |
| } | |
| .markdown-content ul, .markdown-content ol { | |
| margin: 1rem 0; | |
| padding-right: 1.5rem; | |
| } | |
| .markdown-content li { | |
| margin: 0.5rem 0; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <!-- 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"> | |
| <a href="index.html" class="text-gray-600 hover:text-primary transition-colors"> | |
| <i data-feather="arrow-right"></i> | |
| </a> | |
| <i data-feather="message-circle" class="text-primary w-6 h-6"></i> | |
| <h1 class="text-xl font-bold text-gray-800" id="chatTitle">المحادثة</h1> | |
| </div> | |
| <div class="flex items-center space-x-4 space-x-reverse"> | |
| <button id="saveChatBtn" class="text-gray-600 hover:text-primary transition-colors"> | |
| <i data-feather="save"></i> | |
| </button> | |
| <a href="conversations.html" class="text-gray-600 hover:text-primary transition-colors"> | |
| <i data-feather="archive"></i> | |
| </a> | |
| <a href="characters.html" class="text-gray-600 hover:text-primary transition-colors"> | |
| <i data-feather="users"></i> | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| </nav> | |
| <!-- Character Info Bar --> | |
| <div id="characterInfo" class="bg-gradient-to-r from-primary to-secondary text-white py-2 px-4 hidden"> | |
| <div class="max-w-4xl mx-auto flex items-center justify-between"> | |
| <div class="flex items-center space-x-3 space-x-reverse"> | |
| <i data-feather="user" class="w-4 h-4"></i> | |
| <span id="currentCharacterName">الشخصية المحددة</span> | |
| </div> | |
| <button onclick="changeCharacter()" class="text-white/80 hover:text-white text-sm"> | |
| تغيير الشخصية | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Main Chat Area --> | |
| <div class="max-w-4xl mx-auto"> | |
| <div class="chat-container flex flex-col"> | |
| <!-- Messages Container --> | |
| <div id="messagesContainer" class="messages-container overflow-y-auto p-4 space-y-6"> | |
| <!-- Welcome Message --> | |
| <div class="text-center py-8"> | |
| <i data-feather="message-circle" class="w-16 h-16 text-gray-300 mx-auto mb-4"></i> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-2">مرحباً بك في GeminiPersona</h2> | |
| <p class="text-gray-600">ابدأ محادثة مع الذكاء الاصطناعي. اختر شخصية من قسم الشخصيات أو ابدأ مباشرة!</p> | |
| </div> | |
| </div> | |
| <!-- Input Area --> | |
| <div class="border-t bg-white p-4"> | |
| <form id="messageForm" class="flex space-x-3 space-x-reverse"> | |
| <div class="flex-1"> | |
| <input type="text" id="messageInput" | |
| placeholder="اكتب رسالتك هنا..." | |
| class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" | |
| autocomplete="off"> | |
| </div> | |
| <button type="submit" | |
| 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 disabled:opacity-50" | |
| id="sendButton"> | |
| <i data-feather="send" class="w-4 h-4"></i> | |
| <span>إرسال</span> | |
| </button> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Character Selection 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-[80vh] overflow-y-auto"> | |
| <div class="p-6"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h3 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> | |
| <div id="charactersList" class="space-y-3"> | |
| <!-- Characters will be loaded here --> | |
| </div> | |
| <div class="mt-6 pt-4 border-t"> | |
| <button onclick="useDefaultAI()" class="w-full bg-gray-100 text-gray-700 py-3 px-4 rounded-lg hover:bg-gray-200 transition-colors text-center"> | |
| استخدام الذكاء الاصطناعي الافتراضي | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| let messages = []; | |
| let currentCharacter = null; | |
| let isTyping = false; | |
| let currentConversationId = null; | |
| // 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() { | |
| const characters = getAllRequest.result; | |
| renderCharacterSelection(characters); | |
| }; | |
| }; | |
| } | |
| // Render character selection | |
| function renderCharacterSelection(characters) { | |
| const list = document.getElementById('charactersList'); | |
| list.innerHTML = characters.map(character => ` | |
| <div class="border border-gray-200 rounded-lg p-4 cursor-pointer hover:border-primary transition-colors" onclick="selectCharacter(${character.id})"> | |
| <h4 class="font-semibold text-gray-800">${character.name}</h4> | |
| ${character.role ? `<p class="text-sm text-gray-600">${character.role}</p>` : ''} | |
| ${character.description ? `<p class="text-sm text-gray-500 mt-2">${character.description}</p>` : ''} | |
| </div> | |
| `).join(''); | |
| } | |
| // Select character | |
| function selectCharacter(characterId) { | |
| 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(characterId); | |
| getRequest.onsuccess = function() { | |
| currentCharacter = getRequest.result; | |
| document.getElementById('characterInfo').classList.remove('hidden'); | |
| document.getElementById('currentCharacterName').textContent = currentCharacter.name; | |
| closeCharacterModal(); | |
| // Add system message if this is the first message | |
| if (messages.length === 0) { | |
| addSystemMessage(`أنت الآن تتحدث مع ${currentCharacter.name}${currentCharacter.role ? ` - ${currentCharacter.role}` : ''}. ${currentCharacter.instructions || ''}`); | |
| } | |
| }; | |
| }; | |
| } | |
| // Use default AI | |
| function useDefaultAI() { | |
| currentCharacter = null; | |
| document.getElementById('characterInfo').classList.add('hidden'); | |
| closeCharacterModal(); | |
| if (messages.length === 0) { | |
| addSystemMessage('أنت الآن تتحدث مع الذكاء الاصطناعي الافتراضي. كيف يمكنني مساعدتك؟'); | |
| } | |
| } | |
| // Change character | |
| function changeCharacter() { | |
| loadCharacters(); | |
| document.getElementById('characterModal').classList.remove('hidden'); | |
| } | |
| // Close character modal | |
| function closeCharacterModal() { | |
| document.getElementById('characterModal').classList.add('hidden'); | |
| } | |
| // Add system message | |
| function addSystemMessage(content) { | |
| const message = { | |
| role: 'system', | |
| content: content, | |
| timestamp: new Date().toISOString() | |
| }; | |
| messages.push(message); | |
| renderMessages(); | |
| } | |
| // Add message to chat | |
| function addMessage(role, content) { | |
| const message = { | |
| role: role, | |
| content: content, | |
| timestamp: new Date().toISOString() | |
| }; | |
| messages.push(message); | |
| renderMessages(); | |
| // Auto-save if enabled | |
| const settings = JSON.parse(localStorage.getItem('geminiPersonaSettings') || '{}'); | |
| if (settings.autoSave !== false) { | |
| autoSaveConversation(); | |
| } | |
| } | |
| // Render messages | |
| function renderMessages() { | |
| const container = document.getElementById('messagesContainer'); | |
| const markdownEnabled = JSON.parse(localStorage.getItem('geminiPersonaSettings') || '{}').markdownEnabled !== false; | |
| container.innerHTML = messages.map(message => { | |
| if (message.role === 'system') { | |
| return ` | |
| <div class="text-center"> | |
| <div class="inline-flex items-center space-x-2 space-x-reverse bg-gray-100 text-gray-600 px-4 py-2 rounded-full text-sm"> | |
| <i data-feather="info" class="w-3 h-3"></i> | |
| <span>${message.content}</span> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| return ` | |
| <div class="flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}"> | |
| <div class="max-w-3/4 ${message.role === 'user' ? 'chat-bubble-user' : 'chat-bubble-ai'} p-4"> | |
| <div class="${markdownEnabled && message.role === 'assistant' ? 'markdown-content' : ''}"> | |
| ${markdownEnabled && message.role === 'assistant' ? marked.parse(message.content) : message.content} | |
| </div> | |
| <div class="text-xs opacity-70 mt-2 ${message.role === 'user' ? 'text-blue-100' : 'text-gray-500'}"> | |
| ${new Date(message.timestamp).toLocaleTimeString('ar-EG', { hour: '2-digit', minute: '2-digit' })} | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| }).join(''); | |
| container.scrollTop = container.scrollHeight; | |
| feather.replace(); | |
| } | |
| // Show typing indicator | |
| function showTypingIndicator() { | |
| if (isTyping) return; | |
| isTyping = true; | |
| const container = document.getElementById('messagesContainer'); | |
| const indicator = document.createElement('div'); | |
| indicator.className = 'flex justify-start'; | |
| indicator.innerHTML = ` | |
| <div class="chat-bubble-ai p-4"> | |
| <div class="typing-indicator"> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| </div> | |
| </div> | |
| `; | |
| container.appendChild(indicator); | |
| container.scrollTop = container.scrollHeight; | |
| } | |
| // Hide typing indicator | |
| function hideTypingIndicator() { | |
| isTyping = false; | |
| const container = document.getElementById('messagesContainer'); | |
| const indicator = container.querySelector('.typing-indicator'); | |
| if (indicator) { | |
| indicator.closest('.flex').remove(); | |
| } | |
| } | |
| // Send message to AI | |
| async function sendMessageToAI(message) { | |
| const settings = JSON.parse(localStorage.getItem('geminiPersonaSettings') || '{}'); | |
| if (!settings.apiKey) { | |
| addMessage('assistant', '⚠️ يرجى إضافة مفتاح API من صفحة الإعدادات أولاً.'); | |
| return; | |
| } | |
| showTypingIndicator(); | |
| try { | |
| // Prepare conversation context | |
| const conversationContext = messages | |
| .filter(msg => msg.role !== 'system') | |
| .slice(-10) // Keep last 10 messages for context | |
| .map(msg => ({ | |
| role: msg.role === 'user' ? 'user' : 'model', | |
| parts: [{ text: msg.content }] | |
| })); | |
| // Add system prompt if character is selected | |
| let systemInstruction = ''; | |
| if (currentCharacter) { | |
| systemInstruction = `أنت ${currentCharacter.name}${currentCharacter.role ? ` - ${currentCharacter.role}` : ''}. ${currentCharacter.description || ''}. ${currentCharacter.instructions || ''}. يجب أن تتصرف وتتحدث كما تم وصف شخصيتك.`; | |
| } | |
| const requestBody = { | |
| contents: conversationContext, | |
| generationConfig: { | |
| temperature: 0.7, | |
| topK: 40, | |
| topP: 0.95, | |
| maxOutputTokens: 1024, | |
| } | |
| }; | |
| if (systemInstruction) { | |
| requestBody.systemInstruction = { | |
| parts: [{ text: systemInstruction }] | |
| }; | |
| } | |
| const model = settings.model === 'glm-4.5-flash' ? 'glm-4-5-flash' : 'gemini-2.0-flash-exp'; | |
| const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${settings.apiKey}`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(requestBody) | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| if (data.candidates && data.candidates[0] && data.candidates[0].content) { | |
| const aiResponse = data.candidates[0].content.parts[0].text; | |
| hideTypingIndicator(); | |
| addMessage('assistant', aiResponse); | |
| } else { | |
| throw new Error('No response from AI'); | |
| } | |
| } catch (error) { | |
| hideTypingIndicator(); | |
| console.error('Error:', error); | |
| addMessage('assistant', '❌ حدث خطأ في الاتصال بالذكاء الاصطناعي. يرجى التحقق من مفتاح API وإعدادات الاتصال.'); | |
| } | |
| } | |
| // Auto-save conversation | |
| function autoSaveConversation() { | |
| if (messages.length < 2) return; // Don't save empty conversations | |
| const conversation = { | |
| title: messages.find(m => m.role === 'user')?.content.substring(0, 50) + '...' || 'محادثة جديدة', | |
| messages: messages, | |
| character: currentCharacter, | |
| createdAt: new Date().toISOString(), | |
| updatedAt: 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'); | |
| if (currentConversationId) { | |
| conversation.id = currentConversationId; | |
| store.put(conversation); | |
| } else { | |
| const addRequest = store.add(conversation); | |
| addRequest.onsuccess = function() { | |
| currentConversationId = addRequest.result; | |
| }; | |
| } | |
| }; | |
| } | |
| // Manual save | |
| document.getElementById('saveChatBtn').addEventListener('click', function() { | |
| autoSaveConversation(); | |
| // Show success message | |
| const btn = this; | |
| const originalHTML = btn.innerHTML; | |
| btn.innerHTML = '<i data-feather="check" class="w-4 h-4 text-green-500"></i>'; | |
| feather.replace(); | |
| setTimeout(() => { | |
| btn.innerHTML = originalHTML; | |
| feather.replace(); | |
| }, 2000); | |
| }); | |
| // Handle form submission | |
| document.getElementById('messageForm').addEventListener('submit', async function(e) { | |
| e.preventDefault(); | |
| const messageInput = document.getElementById('messageInput'); | |
| const message = messageInput.value.trim(); | |
| if (!message) return; | |
| // Add user message | |
| addMessage('user', message); | |
| messageInput.value = ''; | |
| // Send to AI | |
| await sendMessageToAI(message); | |
| }); | |
| // Load restored conversation | |
| function loadRestoredConversation() { | |
| const restored = localStorage.getItem('restoredConversation'); | |
| if (restored) { | |
| const conversation = JSON.parse(restored); | |
| messages = conversation.messages; | |
| currentCharacter = conversation.character; | |
| currentConversationId = conversation.id; | |
| if (currentCharacter) { | |
| document.getElementById('characterInfo').classList.remove('hidden'); | |
| document.getElementById('currentCharacterName').textContent = currentCharacter.name; | |
| } | |
| renderMessages(); | |
| localStorage.removeItem('restoredConversation'); | |
| } | |
| } | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', function() { | |
| feather.replace(); | |
| loadCharacters(); | |
| loadRestoredConversation(); | |
| // Focus on input | |
| document.getElementById('messageInput').focus(); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |