saap-plattform / frontend /src /components /modals /MultiAgentChatModal.vue
Hwandji's picture
feat: initial HuggingFace Space deployment
4343907
raw
history blame
41.5 kB
<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 !important;
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>