Spaces:
Sleeping
Sleeping
| <template> | |
| <div v-if="isVisible" class="modal-overlay" @click="closeModal"> | |
| <div class="modal-container" @click.stop> | |
| <!-- Modal Header --> | |
| <div class="modal-header"> | |
| <h2 class="modal-title"> | |
| 🤖 Multi-Agent Communication | |
| </h2> | |
| <p class="modal-subtitle"> | |
| Jane Alesi Master Coordinator mit automatischer Spezialist-Delegation | |
| </p> | |
| <button @click="closeModal" class="close-button"> | |
| <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> | |
| </svg> | |
| </button> | |
| </div> | |
| <div class="modal-body"> | |
| <!-- 🔧 KOMPAKTER: Capabilities Section --> | |
| <div class="capabilities-section"> | |
| <h3 class="section-title">Verfügbare Spezialisten</h3> | |
| <div class="specialists-grid"> | |
| <div | |
| v-for="specialist in availableSpecialists" | |
| :key="specialist.id" | |
| :class="['specialist-card', { 'active': specialist.id === selectedSpecialist }]" | |
| > | |
| <div class="specialist-icon">{{ getSpecialistIcon(specialist.specialization) }}</div> | |
| <div class="specialist-info"> | |
| <div class="specialist-name">{{ specialist.name }}</div> | |
| <div class="specialist-role">{{ specialist.specialization }}</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 🚀 MASSIV VERGRÖSSERT: Chat Interface --> | |
| <div class="chat-section"> | |
| <div class="chat-messages" ref="messagesContainer"> | |
| <div | |
| v-for="message in chatMessages" | |
| :key="message.id" | |
| :class="['message', message.type]" | |
| > | |
| <div class="message-header"> | |
| <div class="message-agent"> | |
| <span class="agent-icon">{{ getAgentIcon(message.agent) }}</span> | |
| <span class="agent-name">{{ message.agentName }}</span> | |
| <span v-if="message.role" class="agent-role">({{ message.role }})</span> | |
| </div> | |
| <div class="message-time">{{ formatTime(message.timestamp) }}</div> | |
| </div> | |
| <div class="message-content"> | |
| {{ message.content }} | |
| </div> | |
| <div v-if="message.processingTime || message.provider" class="message-meta"> | |
| <span v-if="message.provider" class="provider-badge" :class="message.provider"> | |
| <span v-if="message.provider === 'colossus'">🔒 Colossus (Intern)</span> | |
| <span v-else-if="message.provider === 'openrouter'">🌐 OpenRouter (Extern)</span> | |
| <span v-else>{{ message.provider }}</span> | |
| </span> | |
| <span v-if="message.processingTime" class="processing-time">⏱️ {{ message.processingTime }}s</span> | |
| <span v-if="message.cost" class="cost-info">💰 {{ message.cost }}</span> | |
| </div> | |
| </div> | |
| <!-- 🚀 ENHANCED: Coordination Chain Visualization --> | |
| <div v-if="currentCoordinationChain.length > 0" class="coordination-chain"> | |
| <h4 class="chain-title">🔄 Agent Delegation Chain:</h4> | |
| <div class="chain-flow"> | |
| <div | |
| v-for="(agent, index) in currentCoordinationChain" | |
| :key="index" | |
| class="chain-agent" | |
| > | |
| <span class="chain-icon">{{ getAgentIcon(agent) }}</span> | |
| <span class="chain-name">{{ getAgentName(agent) }}</span> | |
| <svg v-if="index < currentCoordinationChain.length - 1" class="chain-arrow" fill="currentColor" viewBox="0 0 20 20"> | |
| <path fill-rule="evenodd" d="M10.293 15.707a1 1 0 010-1.414L14.586 10l-4.293-4.293a1 1 0 111.414-1.414l5 5a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0z" clip-rule="evenodd"></path> | |
| </svg> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Loading state with better delegation visibility --> | |
| <div v-if="isProcessing" class="message processing"> | |
| <div class="message-header"> | |
| <div class="message-agent"> | |
| <span class="agent-icon loading">🤖</span> | |
| <span class="agent-name">{{ currentProcessor }}</span> | |
| <span class="agent-role">({{ processingStatus }})</span> | |
| </div> | |
| </div> | |
| <div class="message-content"> | |
| <div class="typing-indicator"> | |
| <span></span> | |
| <span></span> | |
| <span></span> | |
| </div> | |
| <div v-if="processingStatus.includes('delegiert')" class="delegation-status"> | |
| 🔄 Agent-zu-Agent Kommunikation läuft... | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 🔒 AUTONOME Privacy-Info Toast (nur Information) --> | |
| <transition name="toast-fade"> | |
| <div v-if="showPrivacyInfo" class="privacy-toast"> | |
| <div class="toast-icon">🔒</div> | |
| <div class="toast-content"> | |
| <div class="toast-title">Sensible Daten erkannt</div> | |
| <div class="toast-message">Ihre Anfrage wird automatisch über den sicheren Colossus-Server (intern) verarbeitet</div> | |
| </div> | |
| <div class="toast-indicator"> | |
| <div class="progress-bar"></div> | |
| </div> | |
| </div> | |
| </transition> | |
| <!-- 🚀 INPUT BEREICH GARANTIERT SICHTBAR --> | |
| <div class="chat-input-section"> | |
| <div class="input-group"> | |
| <textarea | |
| v-model="currentMessage" | |
| @keydown.enter.exact.prevent="sendMessage" | |
| @keydown.enter.shift.exact="() => {}" | |
| placeholder="Stelle eine Frage oder gib eine Aufgabe ein..." | |
| class="message-input" | |
| rows="3" | |
| :disabled="isProcessing" | |
| ></textarea> | |
| <button | |
| @click="sendMessage" | |
| :disabled="!canSendMessage" | |
| class="send-button" | |
| > | |
| <svg v-if="!isProcessing" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path> | |
| </svg> | |
| <div v-else class="spinner"></div> | |
| </button> | |
| </div> | |
| <div class="input-options"> | |
| <div class="options-row"> | |
| <div class="task-priority"> | |
| <label for="priority">Priorität:</label> | |
| <select v-model="taskPriority" id="priority" class="priority-select"> | |
| <option value="normal">Normal</option> | |
| <option value="high">Hoch</option> | |
| <option value="urgent">Dringend</option> | |
| <option value="low">Niedrig</option> | |
| </select> | |
| </div> | |
| <div class="preferred-agent"> | |
| <label for="preferredAgent">Bevorzugter Agent:</label> | |
| <select v-model="preferredAgent" id="preferredAgent" class="agent-select"> | |
| <option value="">Automatische Auswahl</option> | |
| <option value="john_alesi">John Alesi (Development)</option> | |
| <option value="lara_alesi">Lara Alesi (Medical)</option> | |
| <option value="justus_alesi">Justus Alesi (Legal)</option> | |
| <option value="theo_alesi">Theo Alesi (Finance)</option> | |
| <option value="leon_alesi">Leon Alesi (System)</option> | |
| <option value="luna_alesi">Luna Alesi (Coaching)</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="provider-row"> | |
| <div class="provider-selection"> | |
| <label for="provider">🔒 Datenschutz-Modus:</label> | |
| <select | |
| v-model="selectedProvider" | |
| id="provider" | |
| class="provider-select" | |
| :class="{'privacy-mode': selectedProvider === 'colossus'}" | |
| > | |
| <option value="auto">🔄 Automatisch</option> | |
| <option value="colossus">🔒 Colossus (Intern)</option> | |
| <option value="openrouter">🌐 OpenRouter (Extern)</option> | |
| </select> | |
| <div v-if="selectedProvider === 'colossus'" class="privacy-badge"> | |
| ✅ Daten geschützt | |
| </div> | |
| <div v-else-if="selectedProvider === 'auto'" class="privacy-badge auto"> | |
| 🔄 Auto-Erkennung | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 🔧 KOMPAKTER: Modal Footer --> | |
| <div class="modal-footer"> | |
| <div class="footer-stats"> | |
| <span class="stat-item"> | |
| <span class="stat-icon">💬</span> | |
| <span class="stat-value">{{ chatMessages.length }}</span> | |
| <span class="stat-label">Messages</span> | |
| </span> | |
| <span class="stat-item"> | |
| <span class="stat-icon">🤖</span> | |
| <span class="stat-value">{{ uniqueAgentsCount }}</span> | |
| <span class="stat-label">Agents</span> | |
| </span> | |
| <span class="stat-item"> | |
| <span class="stat-icon">⏱️</span> | |
| <span class="stat-value">{{ averageResponseTime }}s</span> | |
| <span class="stat-label">Avg Response</span> | |
| </span> | |
| </div> | |
| <div class="footer-actions"> | |
| <button @click="showSystemStatus" class="action-button system-status"> | |
| 📊 System Status | |
| </button> | |
| <button @click="showPerformanceReport" class="action-button performance"> | |
| ⚡ Performance Report | |
| </button> | |
| <button @click="showListAgents" class="action-button list-agents"> | |
| 👥 List Agents | |
| </button> | |
| <button @click="showHelp" class="action-button help"> | |
| ❓ Help | |
| </button> | |
| <button @click="clearChat" class="clear-button"> | |
| 🗑️ Clear | |
| </button> | |
| <button @click="closeModal" class="close-modal-button"> | |
| Schließen | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </template> | |
| <script> | |
| import { ref, computed, nextTick, onMounted } from 'vue' | |
| import { saapApi } from '../../services/saapApi' | |
| export default { | |
| name: 'MultiAgentChatModal', | |
| props: { | |
| isVisible: { | |
| type: Boolean, | |
| default: false | |
| } | |
| }, | |
| emits: ['close'], | |
| setup(props, { emit }) { | |
| // Reactive state | |
| const chatMessages = ref([]) | |
| const currentMessage = ref('') | |
| const isProcessing = ref(false) | |
| const currentProcessor = ref('Jane Alesi') | |
| const processingStatus = ref('Ready') | |
| const selectedSpecialist = ref(null) | |
| const currentCoordinationChain = ref([]) | |
| const taskPriority = ref('normal') | |
| const preferredAgent = ref('') | |
| const messagesContainer = ref(null) | |
| const selectedProvider = ref('auto') | |
| const showPrivacyInfo = ref(false) | |
| // Available specialists data | |
| const availableSpecialists = ref([ | |
| { | |
| id: 'john_alesi', | |
| name: 'John Alesi', | |
| specialization: 'Development', | |
| description: 'Software-Entwicklung und AGI-Architekturen' | |
| }, | |
| { | |
| id: 'lara_alesi', | |
| name: 'Lara Alesi', | |
| specialization: 'Medical', | |
| description: 'Medizinische Expertise und Gesundheitswesen' | |
| }, | |
| { | |
| id: 'justus_alesi', | |
| name: 'Justus Alesi', | |
| specialization: 'Legal', | |
| description: 'Rechtsberatung für DE/CH/EU' | |
| }, | |
| { | |
| id: 'theo_alesi', | |
| name: 'Theo Alesi', | |
| specialization: 'Finance', | |
| description: 'Finanz- und Investitionsanalyse' | |
| }, | |
| { | |
| id: 'leon_alesi', | |
| name: 'Leon Alesi', | |
| specialization: 'System', | |
| description: 'IT-Systemintegration und Infrastructure' | |
| }, | |
| { | |
| id: 'luna_alesi', | |
| name: 'Luna Alesi', | |
| specialization: 'Coaching', | |
| description: 'Coaching und Organisationsentwicklung' | |
| } | |
| ]) | |
| // Computed properties | |
| const canSendMessage = computed(() => { | |
| return currentMessage.value.trim().length > 0 && !isProcessing.value | |
| }) | |
| const uniqueAgentsCount = computed(() => { | |
| const agents = new Set(chatMessages.value.map(msg => msg.agent)) | |
| return agents.size | |
| }) | |
| const averageResponseTime = computed(() => { | |
| const responseTimes = chatMessages.value | |
| .filter(msg => msg.processingTime) | |
| .map(msg => msg.processingTime) | |
| if (responseTimes.length === 0) return 0 | |
| return (responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length).toFixed(1) | |
| }) | |
| // Helper functions | |
| const getSpecialistIcon = (specialization) => { | |
| const icons = { | |
| 'Development': '💻', | |
| 'Medical': '⚕️', | |
| 'Legal': '⚖️', | |
| 'Finance': '💰', | |
| 'System': '🔧', | |
| 'Coaching': '🎯' | |
| } | |
| return icons[specialization] || '🤖' | |
| } | |
| const getAgentIcon = (agentId) => { | |
| const icons = { | |
| 'jane_alesi': '👩💼', | |
| 'john_alesi': '💻', | |
| 'lara_alesi': '⚕️', | |
| 'justus_alesi': '⚖️', | |
| 'theo_alesi': '💰', | |
| 'leon_alesi': '🔧', | |
| 'luna_alesi': '🎯', | |
| 'user': '👤' | |
| } | |
| return icons[agentId] || '🤖' | |
| } | |
| const getAgentName = (agentId) => { | |
| const names = { | |
| 'jane_alesi': 'Jane Alesi', | |
| 'john_alesi': 'John Alesi', | |
| 'lara_alesi': 'Lara Alesi', | |
| 'justus_alesi': 'Justus Alesi', | |
| 'theo_alesi': 'Theo Alesi', | |
| 'leon_alesi': 'Leon Alesi', | |
| 'luna_alesi': 'Luna Alesi' | |
| } | |
| return names[agentId] || agentId | |
| } | |
| const formatTime = (timestamp) => { | |
| return new Date(timestamp).toLocaleTimeString('de-DE', { | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit' | |
| }) | |
| } | |
| const scrollToBottom = () => { | |
| nextTick(() => { | |
| if (messagesContainer.value) { | |
| messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight | |
| } | |
| }) | |
| } | |
| // Message handling | |
| const addMessage = (content, agent, agentName, type = 'agent', meta = {}) => { | |
| const message = { | |
| id: Date.now() + Math.random(), | |
| content, | |
| agent, | |
| agentName, | |
| type, | |
| timestamp: new Date(), | |
| role: meta.role, | |
| processingTime: meta.processingTime, | |
| cost: meta.cost, | |
| provider: meta.provider | |
| } | |
| chatMessages.value.push(message) | |
| scrollToBottom() | |
| return message | |
| } | |
| const sendMessage = async () => { | |
| if (!canSendMessage.value) return | |
| const message = currentMessage.value.trim() | |
| currentMessage.value = '' | |
| // 🔒 AUTONOME Privacy-Detection - keine Benutzer-Interaktion! | |
| if (selectedProvider.value !== 'colossus') { | |
| const privacyCheck = await checkPrivacy(message) | |
| if (privacyCheck.requiresLocal) { | |
| // Zeige Info-Nachricht | |
| showPrivacyInfo.value = true | |
| // Automatisch nach 3 Sekunden ausblenden | |
| setTimeout(() => { | |
| showPrivacyInfo.value = false | |
| }, 3000) | |
| // Automatisch Colossus verwenden | |
| await sendMessageToBackend(message, 'colossus') | |
| return | |
| } | |
| } | |
| await sendMessageToBackend(message) | |
| } | |
| const sendMessageToBackend = async (message, forceProvider = null) => { | |
| // Add user message | |
| addMessage(message, 'user', 'Du', 'user') | |
| // Start processing | |
| isProcessing.value = true | |
| currentProcessor.value = 'Jane Alesi' | |
| processingStatus.value = 'Analysiert Intent...' | |
| try { | |
| // Determine provider | |
| let provider = forceProvider || selectedProvider.value | |
| if (provider === 'auto') { | |
| provider = null // Let backend decide | |
| } | |
| // Call multi-agent API with provider selection | |
| const response = await saapApi.multiAgentChat(message, { | |
| user_context: { | |
| timestamp: new Date().toISOString().split('.')[0] + 'Z', // 🔧 FIX: Remove milliseconds for Python compatibility | |
| priority: taskPriority.value | |
| }, | |
| preferred_agent: preferredAgent.value || null, | |
| task_priority: taskPriority.value, | |
| provider: provider, | |
| privacy_mode: forceProvider || selectedProvider.value | |
| }) | |
| // Update coordination chain based on backend response | |
| const coordinationChain = [] | |
| coordinationChain.push('jane_alesi') | |
| if (response.specialist_agent && response.specialist_agent !== 'jane_alesi') { | |
| coordinationChain.push(response.specialist_agent) | |
| } | |
| currentCoordinationChain.value = coordinationChain | |
| // Show delegation status if applicable | |
| if (response.delegation_used && response.specialist_agent && response.specialist_agent !== 'jane_alesi') { | |
| processingStatus.value = `Delegiert an ${getAgentName(response.specialist_agent)}...` | |
| } | |
| // Extract response content and provider info | |
| const responseContent = response.response?.content || response.content || 'Keine Antwort erhalten' | |
| const responseTime = response.response?.response_time || response.response_time || 0 | |
| const costUsd = response.response?.cost_usd || 0 | |
| const usedProvider = response.response?.provider || response.privacy_protection?.selected_provider || forceProvider || selectedProvider.value || 'unknown' | |
| // If delegation was used, show specialist info | |
| if (response.delegation_used && response.specialist_agent) { | |
| const specialistName = getAgentName(response.specialist_agent) | |
| const specialist = availableSpecialists.value.find(s => s.id === response.specialist_agent) | |
| selectedSpecialist.value = response.specialist_agent | |
| // Add delegation indicator | |
| addMessage( | |
| `🔄 Jane Alesi delegiert an ${specialistName} für spezialisierte Bearbeitung...`, | |
| 'system', | |
| 'System', | |
| 'delegation', | |
| { role: 'Delegation Info' } | |
| ) | |
| // Add specialist response with provider info | |
| setTimeout(() => { | |
| addMessage( | |
| responseContent, | |
| response.specialist_agent, | |
| specialistName, | |
| 'specialist', | |
| { | |
| role: specialist?.specialization || 'Specialist', | |
| processingTime: responseTime, | |
| provider: usedProvider | |
| } | |
| ) | |
| selectedSpecialist.value = null | |
| }, 500) | |
| } else { | |
| // Direct response from Jane (no delegation) with provider info | |
| addMessage( | |
| responseContent, | |
| 'jane_alesi', | |
| 'Jane Alesi', | |
| 'coordinator', | |
| { | |
| role: 'Master Coordinator', | |
| processingTime: responseTime, | |
| cost: costUsd > 0 ? `$${costUsd.toFixed(6)}` : null, | |
| provider: usedProvider | |
| } | |
| ) | |
| } | |
| } catch (error) { | |
| console.error('Multi-Agent Chat Error:', error) | |
| addMessage( | |
| 'Entschuldigung, es ist ein Fehler bei der Multi-Agent-Kommunikation aufgetreten. Bitte versuche es erneut.', | |
| 'jane_alesi', | |
| 'Jane Alesi', | |
| 'error', | |
| { role: 'Error Handler' } | |
| ) | |
| } finally { | |
| isProcessing.value = false | |
| currentProcessor.value = 'Jane Alesi' | |
| processingStatus.value = 'Ready' | |
| } | |
| } | |
| const checkPrivacy = async (message) => { | |
| // Simple client-side privacy detection | |
| const sensitiveKeywords = { | |
| medical: ['patient', 'diagnose', 'krankheit', 'symptom', 'arzt', 'medikament'], | |
| financial: ['konto', 'iban', 'kreditkarte', 'gehalt', 'steuer'], | |
| personal: ['geburtsdatum', 'adresse', 'telefon', 'ausweis', 'pass'] | |
| } | |
| const messageLower = message.toLowerCase() | |
| const detectedCategories = [] | |
| for (const [category, keywords] of Object.entries(sensitiveKeywords)) { | |
| if (keywords.some(keyword => messageLower.includes(keyword))) { | |
| detectedCategories.push(category) | |
| } | |
| } | |
| // Check patterns | |
| const patterns = { | |
| email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, | |
| phone: /\b(?:\+49|0)\s?\d{3,4}\s?\d{6,8}\b/, | |
| iban: /\b[A-Z]{2}\d{2}[A-Z0-9]{13,29}\b/, | |
| creditCard: /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/ | |
| } | |
| for (const [type, pattern] of Object.entries(patterns)) { | |
| if (pattern.test(message)) { | |
| detectedCategories.push(type) | |
| } | |
| } | |
| return { | |
| requiresLocal: detectedCategories.length > 0, | |
| categories: detectedCategories, | |
| level: detectedCategories.length > 0 ? 'private' : 'public' | |
| } | |
| } | |
| const clearChat = () => { | |
| chatMessages.value = [] | |
| currentCoordinationChain.value = [] | |
| selectedSpecialist.value = null | |
| // Add welcome message | |
| addMessage( | |
| 'Hallo! Ich bin Jane Alesi, deine Master Coordinatorin. Ich kann Aufgaben an spezialisierte Agenten delegieren oder sie selbst bearbeiten. Wie kann ich dir helfen?', | |
| 'jane_alesi', | |
| 'Jane Alesi', | |
| 'coordinator', | |
| { role: 'Master Coordinator' } | |
| ) | |
| } | |
| const closeModal = () => { | |
| emit('close') | |
| } | |
| // 📊 System Status Function | |
| const showSystemStatus = async () => { | |
| try { | |
| const response = await saapApi.getHealth() | |
| const status = response.status === 'healthy' ? '✅ Online' : '⚠️ Degraded' | |
| const agentsActive = availableSpecialists.value.length | |
| const statusMessage = `📊 SYSTEM STATUS REPORT | |
| 🟢 Backend: ${status} | |
| 🤖 Agents verfügbar: ${agentsActive}/6 | |
| 🔒 Colossus: ${response.colossus_available ? '✅ Verfügbar' : '❌ Nicht verfügbar'} | |
| 🌐 OpenRouter: ${response.openrouter_available ? '✅ Verfügbar' : '❌ Nicht verfügbar'} | |
| 💾 Datenbank: ${response.database ? '✅ Verbunden' : '❌ Getrennt'} | |
| ⏱️ Uptime: ${response.uptime || 'N/A'} | |
| 📅 Zeitstempel: ${new Date().toLocaleString('de-DE')}` | |
| addMessage(statusMessage, 'system', 'System', 'coordinator', { role: 'System Status' }) | |
| } catch (error) { | |
| addMessage( | |
| '❌ Fehler beim Abrufen des System-Status. Backend möglicherweise offline.', | |
| 'system', | |
| 'System', | |
| 'error', | |
| { role: 'Error' } | |
| ) | |
| } | |
| } | |
| // ⚡ Performance Report Function | |
| const showPerformanceReport = () => { | |
| const totalMessages = chatMessages.value.length | |
| const userMessages = chatMessages.value.filter(m => m.type === 'user').length | |
| const agentMessages = chatMessages.value.filter(m => m.type !== 'user').length | |
| const delegations = chatMessages.value.filter(m => m.type === 'delegation').length | |
| const responseTimes = chatMessages.value | |
| .filter(m => m.processingTime) | |
| .map(m => m.processingTime) | |
| const avgResponseTime = responseTimes.length > 0 | |
| ? (responseTimes.reduce((sum, t) => sum + t, 0) / responseTimes.length).toFixed(2) | |
| : '0' | |
| const minResponseTime = responseTimes.length > 0 ? Math.min(...responseTimes).toFixed(2) : '0' | |
| const maxResponseTime = responseTimes.length > 0 ? Math.max(...responseTimes).toFixed(2) : '0' | |
| const colossusMessages = chatMessages.value.filter(m => m.provider === 'colossus').length | |
| const openrouterMessages = chatMessages.value.filter(m => m.provider === 'openrouter').length | |
| const performanceMessage = `⚡ PERFORMANCE REPORT | |
| 📊 Nachrichten-Statistik: | |
| Gesamt: ${totalMessages} | |
| Nutzer: ${userMessages} | |
| Agents: ${agentMessages} | |
| Delegationen: ${delegations} | |
| ⏱️ Response Times: | |
| Durchschnitt: ${avgResponseTime}s | |
| Minimum: ${minResponseTime}s | |
| Maximum: ${maxResponseTime}s | |
| 🔒 Provider-Verteilung: | |
| Colossus (Intern): ${colossusMessages} | |
| OpenRouter (Extern): ${openrouterMessages} | |
| 🤖 Aktive Agents: ${uniqueAgentsCount.value} | |
| 📅 Session-Start: ${chatMessages.value[0]?.timestamp ? new Date(chatMessages.value[0].timestamp).toLocaleString('de-DE') : 'N/A'}` | |
| addMessage(performanceMessage, 'system', 'System', 'coordinator', { role: 'Performance Report' }) | |
| } | |
| // 👥 List Agents Function | |
| const showListAgents = () => { | |
| const agentsList = availableSpecialists.value | |
| .map(agent => { | |
| const messageCount = chatMessages.value.filter(m => m.agent === agent.id).length | |
| const lastUsed = chatMessages.value | |
| .filter(m => m.agent === agent.id) | |
| .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))[0] | |
| return `${getSpecialistIcon(agent.specialization)} ${agent.name} | |
| Expertise: ${agent.specialization} | |
| Beschreibung: ${agent.description} | |
| Nachrichten: ${messageCount} | |
| Zuletzt aktiv: ${lastUsed ? formatTime(lastUsed.timestamp) : 'Noch nicht verwendet'}` | |
| }) | |
| .join('\n\n') | |
| const listMessage = `👥 VERFÜGBARE SPEZIALISTEN (${availableSpecialists.value.length}) | |
| ${agentsList} | |
| 💡 Hinweis: Jane Alesi delegiert automatisch an den passenden Spezialisten basierend auf deiner Anfrage.` | |
| addMessage(listMessage, 'system', 'System', 'coordinator', { role: 'Agent List' }) | |
| } | |
| // ❓ Help Function | |
| const showHelp = () => { | |
| const helpMessage = `❓ MULTI-AGENT CHAT - HILFE | |
| 🤖 Was ist Multi-Agent Communication? | |
| Jane Alesi ist deine Master Coordinatorin. Sie analysiert deine Anfragen und delegiert automatisch an spezialisierte Agenten. | |
| 👥 Verfügbare Spezialisten: | |
| 💻 John Alesi - Software-Entwicklung & AGI | |
| ⚕️ Lara Alesi - Medizinische Expertise | |
| ⚖️ Justus Alesi - Rechtsberatung (DE/CH/EU) | |
| 💰 Theo Alesi - Finanz- & Investitionsanalyse | |
| 🔧 Leon Alesi - IT-Systemintegration | |
| 🎯 Luna Alesi - Coaching & Organisation | |
| 🔒 Datenschutz: | |
| 🔄 Automatisch: Erkennt sensible Daten automatisch | |
| 🔒 Colossus: Interner Server für sensible Daten | |
| 🌐 OpenRouter: Externer Provider für normale Anfragen | |
| ⚙️ Optionen: | |
| Priorität: Normal, Hoch, Dringend, Niedrig | |
| Agent-Auswahl: Bevorzugter Agent (optional) | |
| Datenschutz-Modus: Auto, Colossus, OpenRouter | |
| 💡 Tipps: | |
| ✓ Stelle klare, spezifische Fragen | |
| ✓ Jane delegiert automatisch zum besten Spezialisten | |
| ✓ Sensible Daten werden automatisch geschützt | |
| ✓ Nutze die Quick-Actions für System-Infos | |
| 📊 Quick Actions: | |
| 📊 System Status - Zeigt Backend-Status | |
| ⚡ Performance Report - Zeigt Session-Statistiken | |
| 👥 List Agents - Zeigt alle verfügbaren Spezialisten | |
| ❓ Help - Zeigt diese Hilfe | |
| 🚀 Beispiele: | |
| "Hilf mir beim Debuggen meines React-Codes" → John | |
| "Erkläre mir diesen medizinischen Befund" → Lara | |
| "Ist dieser Vertrag rechtlich korrekt?" → Justus | |
| "Analysiere diese Investitionsmöglichkeit" → Theo` | |
| addMessage(helpMessage, 'system', 'System', 'coordinator', { role: 'Help & Documentation' }) | |
| } | |
| // Lifecycle | |
| onMounted(() => { | |
| if (chatMessages.value.length === 0) { | |
| clearChat() | |
| } | |
| }) | |
| return { | |
| // State | |
| chatMessages, | |
| currentMessage, | |
| isProcessing, | |
| currentProcessor, | |
| processingStatus, | |
| selectedSpecialist, | |
| currentCoordinationChain, | |
| taskPriority, | |
| preferredAgent, | |
| messagesContainer, | |
| availableSpecialists, | |
| selectedProvider, | |
| showPrivacyInfo, | |
| // Computed | |
| canSendMessage, | |
| uniqueAgentsCount, | |
| averageResponseTime, | |
| // Methods | |
| getSpecialistIcon, | |
| getAgentIcon, | |
| getAgentName, | |
| formatTime, | |
| sendMessage, | |
| clearChat, | |
| closeModal, | |
| showSystemStatus, | |
| showPerformanceReport, | |
| showListAgents, | |
| showHelp | |
| } | |
| } | |
| } | |
| </script> | |
| <style scoped> | |
| /* Modal Overlay */ | |
| .modal-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background-color: rgba(0, 0, 0, 0.75); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 1000; | |
| backdrop-filter: blur(4px); | |
| } | |
| /* 🚀 DEUTLICH VERGRÖSSERT: Modal Container */ | |
| .modal-container { | |
| background: white; | |
| border-radius: 16px; | |
| width: 95vw; | |
| max-width: 1200px; | |
| height: 95vh; | |
| max-height: 98vh; | |
| display: flex; | |
| flex-direction: column; | |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); | |
| overflow: hidden; | |
| } | |
| /* Modal Header */ | |
| .modal-header { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 20px; | |
| position: relative; | |
| flex-shrink: 0; | |
| } | |
| .modal-title { | |
| margin: 0; | |
| font-size: 24px; | |
| font-weight: 700; | |
| } | |
| .modal-subtitle { | |
| margin: 8px 0 0 0; | |
| font-size: 14px; | |
| opacity: 0.9; | |
| } | |
| .close-button { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| background: rgba(255, 255, 255, 0.2); | |
| border: none; | |
| border-radius: 8px; | |
| padding: 8px; | |
| color: white; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| } | |
| .close-button:hover { | |
| background: rgba(255, 255, 255, 0.3); | |
| } | |
| .close-button svg { | |
| width: 24px; | |
| height: 24px; | |
| } | |
| /* Modal Body */ | |
| .modal-body { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| min-height: 0; | |
| } | |
| /* 🔧 KOMPAKTER: Capabilities Section */ | |
| .capabilities-section { | |
| padding: 4px 12px; | |
| border-bottom: 1px solid #e5e7eb; | |
| background: #f9fafb; | |
| flex-shrink: 0; | |
| max-height: 80px; | |
| overflow: hidden; | |
| } | |
| .section-title { | |
| margin: 0 0 4px 0; | |
| font-size: 12px; | |
| font-weight: 600; | |
| color: #374151; | |
| } | |
| .specialists-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); | |
| gap: 3px; | |
| } | |
| .specialist-card { | |
| display: flex; | |
| align-items: center; | |
| padding: 4px 6px; | |
| border: 1px solid #e5e7eb; | |
| border-radius: 6px; | |
| background: white; | |
| transition: all 0.2s; | |
| cursor: pointer; | |
| } | |
| .specialist-card:hover { | |
| border-color: #9ca3af; | |
| transform: translateY(-1px); | |
| } | |
| .specialist-card.active { | |
| border-color: #3b82f6; | |
| background: #eff6ff; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
| } | |
| .specialist-icon { | |
| font-size: 14px; | |
| margin-right: 4px; | |
| } | |
| .specialist-info { | |
| flex: 1; | |
| } | |
| .specialist-name { | |
| font-size: 10px; | |
| font-weight: 600; | |
| color: #374151; | |
| } | |
| .specialist-role { | |
| font-size: 9px; | |
| color: #6b7280; | |
| } | |
| /* Chat Section */ | |
| .chat-section { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| min-height: 0; | |
| } | |
| /* 🚀 MASSIV VERGRÖSSERT: Chat Messages Container */ | |
| .chat-messages { | |
| flex: 1; | |
| min-height: 0; | |
| height: 100%; | |
| overflow-y: auto ; | |
| overflow-x: hidden; | |
| padding: 16px 20px; | |
| background: #fafafa; | |
| scroll-behavior: smooth; | |
| } | |
| /* Message Styles */ | |
| .message { | |
| margin-bottom: 16px; | |
| padding: 12px 16px; | |
| border-radius: 12px; | |
| background: white; | |
| border: 1px solid #e5e7eb; | |
| } | |
| .message.user { | |
| background: #eff6ff; | |
| border-color: #3b82f6; | |
| } | |
| .message.coordinator { | |
| background: #f3e8ff; | |
| border-color: #a855f7; | |
| } | |
| .message.specialist { | |
| background: #fef3c7; | |
| border-color: #f59e0b; | |
| } | |
| .message.delegation { | |
| background: #dbeafe; | |
| border-color: #60a5fa; | |
| } | |
| .message.error { | |
| background: #fee2e2; | |
| border-color: #ef4444; | |
| } | |
| .message.processing { | |
| background: #f0f9ff; | |
| border-color: #38bdf8; | |
| } | |
| .message-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 8px; | |
| } | |
| .message-agent { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .agent-icon { | |
| font-size: 18px; | |
| } | |
| .agent-icon.loading { | |
| animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0.5; | |
| } | |
| } | |
| .agent-name { | |
| font-weight: 600; | |
| color: #1f2937; | |
| font-size: 14px; | |
| } | |
| .agent-role { | |
| font-size: 12px; | |
| color: #6b7280; | |
| font-style: italic; | |
| } | |
| .message-time { | |
| font-size: 11px; | |
| color: #9ca3af; | |
| } | |
| .message-content { | |
| color: #374151; | |
| line-height: 1.6; | |
| font-size: 14px; | |
| white-space: pre-wrap; | |
| word-wrap: break-word; | |
| } | |
| .message-meta { | |
| display: flex; | |
| gap: 12px; | |
| margin-top: 8px; | |
| font-size: 11px; | |
| flex-wrap: wrap; | |
| } | |
| .provider-badge { | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| font-weight: 600; | |
| font-size: 10px; | |
| } | |
| .provider-badge.colossus { | |
| background: #dcfce7; | |
| color: #16a34a; | |
| } | |
| .provider-badge.openrouter { | |
| background: #dbeafe; | |
| color: #2563eb; | |
| } | |
| .processing-time { | |
| color: #6b7280; | |
| } | |
| .cost-info { | |
| color: #f59e0b; | |
| font-weight: 600; | |
| } | |
| /* Typing Indicator */ | |
| .typing-indicator { | |
| display: flex; | |
| gap: 4px; | |
| padding: 8px 0; | |
| } | |
| .typing-indicator span { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| background: #3b82f6; | |
| animation: typing 1.4s infinite; | |
| } | |
| .typing-indicator span:nth-child(2) { | |
| animation-delay: 0.2s; | |
| } | |
| .typing-indicator span:nth-child(3) { | |
| animation-delay: 0.4s; | |
| } | |
| @keyframes typing { | |
| 0%, 60%, 100% { | |
| transform: translateY(0); | |
| } | |
| 30% { | |
| transform: translateY(-10px); | |
| } | |
| } | |
| .delegation-status { | |
| margin-top: 8px; | |
| color: #3b82f6; | |
| font-weight: 600; | |
| font-size: 12px; | |
| } | |
| /* Coordination Chain */ | |
| .coordination-chain { | |
| margin: 16px 0; | |
| padding: 12px; | |
| background: #f0f9ff; | |
| border: 1px solid #38bdf8; | |
| border-radius: 8px; | |
| } | |
| .chain-title { | |
| margin: 0 0 8px 0; | |
| font-size: 13px; | |
| font-weight: 600; | |
| color: #0c4a6e; | |
| } | |
| .chain-flow { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| flex-wrap: wrap; | |
| } | |
| .chain-agent { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 6px 12px; | |
| background: white; | |
| border: 1px solid #38bdf8; | |
| border-radius: 6px; | |
| font-size: 12px; | |
| } | |
| .chain-icon { | |
| font-size: 16px; | |
| } | |
| .chain-name { | |
| font-weight: 600; | |
| color: #0c4a6e; | |
| } | |
| .chain-arrow { | |
| width: 16px; | |
| height: 16px; | |
| color: #38bdf8; | |
| } | |
| /* 🔒 Privacy Toast - Modern & User-Friendly */ | |
| .privacy-toast { | |
| position: fixed; | |
| bottom: 100px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| z-index: 1100; | |
| min-width: 400px; | |
| max-width: 500px; | |
| } | |
| .privacy-toast { | |
| background: linear-gradient(135deg, #10b981 0%, #059669 100%); | |
| border-radius: 12px; | |
| padding: 16px 20px; | |
| box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
| display: flex; | |
| align-items: center; | |
| gap: 16px; | |
| color: white; | |
| } | |
| .toast-icon { | |
| font-size: 28px; | |
| flex-shrink: 0; | |
| animation: shield-pulse 2s ease-in-out infinite; | |
| } | |
| @keyframes shield-pulse { | |
| 0%, 100% { | |
| transform: scale(1); | |
| } | |
| 50% { | |
| transform: scale(1.1); | |
| } | |
| } | |
| .toast-content { | |
| flex: 1; | |
| } | |
| .toast-title { | |
| font-size: 15px; | |
| font-weight: 700; | |
| margin-bottom: 4px; | |
| letter-spacing: 0.3px; | |
| } | |
| .toast-message { | |
| font-size: 13px; | |
| opacity: 0.95; | |
| line-height: 1.4; | |
| } | |
| .toast-indicator { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| height: 3px; | |
| background: rgba(255, 255, 255, 0.2); | |
| border-radius: 0 0 12px 12px; | |
| overflow: hidden; | |
| } | |
| .progress-bar { | |
| height: 100%; | |
| background: rgba(255, 255, 255, 0.8); | |
| animation: progress 3s linear forwards; | |
| } | |
| @keyframes progress { | |
| from { | |
| width: 100%; | |
| } | |
| to { | |
| width: 0%; | |
| } | |
| } | |
| /* Toast Fade Transition */ | |
| .toast-fade-enter-active { | |
| animation: toast-slide-up 0.3s ease-out; | |
| } | |
| .toast-fade-leave-active { | |
| animation: toast-slide-down 0.3s ease-in; | |
| } | |
| @keyframes toast-slide-up { | |
| from { | |
| transform: translateX(-50%) translateY(50px); | |
| opacity: 0; | |
| } | |
| to { | |
| transform: translateX(-50%) translateY(0); | |
| opacity: 1; | |
| } | |
| } | |
| @keyframes toast-slide-down { | |
| from { | |
| transform: translateX(-50%) translateY(0); | |
| opacity: 1; | |
| } | |
| to { | |
| transform: translateX(-50%) translateY(50px); | |
| opacity: 0; | |
| } | |
| } | |
| /* Chat Input Section */ | |
| .chat-input-section { | |
| border-top: 2px solid #e5e7eb; | |
| background: white; | |
| padding: 16px 20px; | |
| flex-shrink: 0; | |
| } | |
| .input-group { | |
| display: flex; | |
| gap: 12px; | |
| margin-bottom: 12px; | |
| } | |
| .message-input { | |
| flex: 1; | |
| border: 2px solid #e5e7eb; | |
| border-radius: 8px; | |
| padding: 12px; | |
| font-size: 14px; | |
| font-family: inherit; | |
| resize: vertical; | |
| min-height: 60px; | |
| transition: border-color 0.2s; | |
| } | |
| .message-input:focus { | |
| outline: none; | |
| border-color: #3b82f6; | |
| } | |
| .message-input:disabled { | |
| background: #f3f4f6; | |
| cursor: not-allowed; | |
| } | |
| .send-button { | |
| align-self: flex-end; | |
| background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| padding: 12px 16px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-width: 48px; | |
| } | |
| .send-button:hover:not(:disabled) { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
| } | |
| .send-button:disabled { | |
| background: #9ca3af; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .send-button svg { | |
| width: 20px; | |
| height: 20px; | |
| } | |
| .spinner { | |
| width: 20px; | |
| height: 20px; | |
| border: 2px solid rgba(255, 255, 255, 0.3); | |
| border-top-color: white; | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| } | |
| @keyframes spin { | |
| to { | |
| transform: rotate(360deg); | |
| } | |
| } | |
| /* Input Options */ | |
| .input-options { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| .options-row { | |
| display: flex; | |
| gap: 12px; | |
| flex-wrap: wrap; | |
| } | |
| .task-priority, | |
| .preferred-agent { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .task-priority label, | |
| .preferred-agent label { | |
| font-size: 12px; | |
| color: #6b7280; | |
| font-weight: 500; | |
| } | |
| .priority-select, | |
| .agent-select { | |
| padding: 6px 10px; | |
| border: 1px solid #d1d5db; | |
| border-radius: 6px; | |
| font-size: 12px; | |
| background: white; | |
| cursor: pointer; | |
| transition: border-color 0.2s; | |
| } | |
| .priority-select:focus, | |
| .agent-select:focus { | |
| outline: none; | |
| border-color: #3b82f6; | |
| } | |
| .provider-row { | |
| display: flex; | |
| align-items: center; | |
| } | |
| .provider-selection { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .provider-selection label { | |
| font-size: 12px; | |
| color: #6b7280; | |
| font-weight: 500; | |
| } | |
| .provider-select { | |
| padding: 6px 10px; | |
| border: 1px solid #d1d5db; | |
| border-radius: 6px; | |
| font-size: 12px; | |
| background: white; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .provider-select:focus { | |
| outline: none; | |
| border-color: #3b82f6; | |
| } | |
| .provider-select.privacy-mode { | |
| border-color: #16a34a; | |
| background: #f0fdf4; | |
| } | |
| .privacy-badge { | |
| padding: 4px 8px; | |
| background: #dcfce7; | |
| color: #16a34a; | |
| border-radius: 4px; | |
| font-size: 10px; | |
| font-weight: 600; | |
| } | |
| .privacy-badge.auto { | |
| background: #dbeafe; | |
| color: #2563eb; | |
| } | |
| /* Modal Footer */ | |
| .modal-footer { | |
| border-top: 2px solid #e5e7eb; | |
| padding: 12px 20px; | |
| background: #f9fafb; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| flex-shrink: 0; | |
| } | |
| .footer-stats { | |
| display: flex; | |
| gap: 20px; | |
| } | |
| .stat-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-size: 12px; | |
| } | |
| .stat-icon { | |
| font-size: 16px; | |
| } | |
| .stat-value { | |
| font-weight: 700; | |
| color: #1f2937; | |
| } | |
| .stat-label { | |
| color: #6b7280; | |
| } | |
| .footer-actions { | |
| display: flex; | |
| gap: 8px; | |
| flex-wrap: wrap; | |
| } | |
| /* Action Buttons */ | |
| .action-button { | |
| padding: 6px 12px; | |
| border-radius: 6px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| border: 1px solid; | |
| white-space: nowrap; | |
| } | |
| .action-button:hover { | |
| transform: translateY(-1px); | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| } | |
| .action-button.system-status { | |
| background: #dbeafe; | |
| color: #1e40af; | |
| border-color: #93c5fd; | |
| } | |
| .action-button.system-status:hover { | |
| background: #bfdbfe; | |
| } | |
| .action-button.performance { | |
| background: #fef3c7; | |
| color: #92400e; | |
| border-color: #fcd34d; | |
| } | |
| .action-button.performance:hover { | |
| background: #fde68a; | |
| } | |
| .action-button.list-agents { | |
| background: #e9d5ff; | |
| color: #6b21a8; | |
| border-color: #d8b4fe; | |
| } | |
| .action-button.list-agents:hover { | |
| background: #ddd6fe; | |
| } | |
| .action-button.help { | |
| background: #d1fae5; | |
| color: #065f46; | |
| border-color: #6ee7b7; | |
| } | |
| .action-button.help:hover { | |
| background: #a7f3d0; | |
| } | |
| .clear-button, | |
| .close-modal-button { | |
| padding: 6px 12px; | |
| border-radius: 6px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .clear-button { | |
| background: #fee2e2; | |
| color: #dc2626; | |
| border: 1px solid #fca5a5; | |
| } | |
| .clear-button:hover { | |
| background: #fecaca; | |
| transform: translateY(-1px); | |
| } | |
| .close-modal-button { | |
| background: #e5e7eb; | |
| color: #374151; | |
| border: 1px solid #d1d5db; | |
| } | |
| .close-modal-button:hover { | |
| background: #d1d5db; | |
| transform: translateY(-1px); | |
| } | |
| /* Utility Classes */ | |
| .w-5 { | |
| width: 1.25rem; | |
| } | |
| .h-5 { | |
| height: 1.25rem; | |
| } | |
| .w-6 { | |
| width: 1.5rem; | |
| } | |
| .h-6 { | |
| height: 1.5rem; | |
| } | |
| /* Scrollbar Styling */ | |
| .chat-messages::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| .chat-messages::-webkit-scrollbar-track { | |
| background: #f3f4f6; | |
| border-radius: 4px; | |
| } | |
| .chat-messages::-webkit-scrollbar-thumb { | |
| background: #d1d5db; | |
| border-radius: 4px; | |
| } | |
| .chat-messages::-webkit-scrollbar-thumb:hover { | |
| background: #9ca3af; | |
| } | |
| </style> | |