| <template> |
| <div v-if="isVisible" class="modal-overlay" @click="closeModal"> |
| <div class="modal-container" @click.stop> |
| |
| <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"> |
| |
| <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, |
| 'processing': specialist.id === currentlyProcessingAgent |
| }]" |
| > |
| <div class="specialist-icon"> |
| {{ getSpecialistIcon(specialist.specialization) }} |
| <div v-if="specialist.id === currentlyProcessingAgent" class="processing-indicator"> |
| <div class="spinner-small"></div> |
| </div> |
| </div> |
| <div class="specialist-info"> |
| <div class="specialist-name">{{ specialist.name }}</div> |
| <div class="specialist-role">{{ specialist.specialization }}</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="chat-section"> |
| |
| <div v-if="delegationStatus" class="delegation-status"> |
| <div class="delegation-icon">โก</div> |
| <div class="delegation-text">{{ delegationStatus }}</div> |
| <div class="delegation-progress"> |
| <div class="progress-bar" :style="{ width: delegationProgress + '%' }"></div> |
| </div> |
| </div> |
| |
| <div class="chat-messages-wrapper"> |
| <div class="chat-messages" ref="messagesContainer"> |
| <div |
| v-for="message in chatMessages" |
| :key="message.id" |
| :class="['message', message.type, { 'highlighted': message.isHighlighted }]" |
| > |
| <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> |
| <span v-if="message.provider" class="provider-badge" :class="message.provider"> |
| {{ message.provider === 'colossus' ? '๐ Local' : 'โก Cloud' }} |
| </span> |
| </div> |
| <div class="message-time">{{ formatTime(message.timestamp) }}</div> |
| </div> |
| <div class="message-content"> |
| {{ message.content }} |
| </div> |
| <div v-if="message.processingTime || message.cost" class="message-meta"> |
| <span v-if="message.processingTime" class="processing-time"> |
| โฑ๏ธ {{ message.processingTime }}s |
| </span> |
| <span v-if="message.cost" class="cost-info"> |
| ๐ฐ ${{ message.cost.toFixed(6) }} |
| </span> |
| <span v-if="message.provider" class="provider-info"> |
| via {{ message.provider }} |
| </span> |
| </div> |
| </div> |
| |
| |
| <div v-if="currentCoordinationChain.length > 0" class="coordination-chain enhanced"> |
| <h4 class="chain-title">๐ Active Coordination Chain</h4> |
| <div class="chain-flow"> |
| <div |
| v-for="(agent, index) in currentCoordinationChain" |
| :key="index" |
| :class="['chain-agent', { |
| 'active': index === currentChainStep, |
| 'completed': index < currentChainStep, |
| 'pending': index > currentChainStep |
| }]" |
| > |
| <span class="chain-icon">{{ getAgentIcon(agent) }}</span> |
| <span class="chain-name">{{ getAgentName(agent) }}</span> |
| <div v-if="index === currentChainStep" class="chain-progress"> |
| <div class="progress-dots"> |
| <span></span><span></span><span></span> |
| </div> |
| </div> |
| <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 class="chain-status"> |
| {{ getChainStatusText() }} |
| </div> |
| </div> |
| |
| |
| <div v-if="isProcessing" class="message processing enhanced"> |
| <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 class="processing-timer">{{ processingTime }}s</div> |
| </div> |
| <div class="message-content"> |
| <div class="enhanced-typing-indicator"> |
| <div class="typing-text">{{ getProcessingMessage() }}</div> |
| <div class="typing-dots"> |
| <span></span><span></span><span></span> |
| </div> |
| </div> |
| |
| <div v-if="providerSwitchMessage" class="provider-switch-notification"> |
| <div class="switch-icon">๐</div> |
| <div class="switch-text">{{ providerSwitchMessage }}</div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div ref="scrollTrigger"></div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="chat-input-section enhanced"> |
| <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="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 class="provider-selection"> |
| <label>Provider:</label> |
| <div class="provider-buttons"> |
| <button |
| @click="preferredProvider = 'auto'" |
| :class="['provider-btn', { active: preferredProvider === 'auto' }]" |
| > |
| ๐ค Auto |
| </button> |
| <button |
| @click="preferredProvider = 'colossus'" |
| :class="['provider-btn', { active: preferredProvider === 'colossus' }]" |
| > |
| ๐ Local (Free) |
| </button> |
| <button |
| @click="preferredProvider = 'openrouter'" |
| :class="['provider-btn', { active: preferredProvider === 'openrouter' }]" |
| > |
| โก Cloud (Fast) |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="modal-footer enhanced"> |
| <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> |
| <span class="stat-item"> |
| <span class="stat-icon">๐ฐ</span> |
| <span class="stat-value">${{ totalCost.toFixed(4) }}</span> |
| <span class="stat-label">Total Cost</span> |
| </span> |
| </div> |
| <div class="footer-actions"> |
| <button @click="clearChat" class="clear-button"> |
| ๐๏ธ Clear Chat |
| </button> |
| <button @click="closeModal" class="close-modal-button"> |
| Schlieรen |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </template> |
| |
| <script> |
| import { ref, computed, nextTick, onMounted, onUnmounted, watch } from 'vue' |
| import { saapApi } from '../../services/saapApi' |
| |
| export default { |
| name: 'MultiAgentChatModalEnhanced', |
| props: { |
| isVisible: { |
| type: Boolean, |
| default: false |
| } |
| }, |
| emits: ['close'], |
| setup(props, { emit }) { |
| |
| const chatMessages = ref([]) |
| const currentMessage = ref('') |
| const isProcessing = ref(false) |
| const currentProcessor = ref('Jane Alesi') |
| const processingStatus = ref('Ready') |
| const processingTime = ref(0) |
| const selectedSpecialist = ref(null) |
| const currentlyProcessingAgent = ref(null) |
| const currentCoordinationChain = ref([]) |
| const currentChainStep = ref(0) |
| const taskPriority = ref('normal') |
| const preferredAgent = ref('') |
| const preferredProvider = ref('auto') |
| const messagesContainer = ref(null) |
| const scrollTrigger = ref(null) |
| |
| |
| const delegationStatus = ref('') |
| const delegationProgress = ref(0) |
| const providerSwitchMessage = ref('') |
| |
| |
| let processingTimer = null |
| |
| |
| 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' |
| } |
| ]) |
| |
| |
| 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) |
| }) |
| |
| const totalCost = computed(() => { |
| return chatMessages.value |
| .filter(msg => msg.cost) |
| .reduce((sum, msg) => sum + msg.cost, 0) |
| }) |
| |
| |
| 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 getProcessingMessage = () => { |
| const messages = [ |
| 'Jane analysiert deine Anfrage...', |
| 'Wรคhlt den besten Spezialisten aus...', |
| 'Delegiert an Fachexperten...', |
| 'Verarbeitet Antworten...', |
| 'Koordiniert finale Response...' |
| ] |
| return messages[Math.min(Math.floor(processingTime.value / 5), messages.length - 1)] |
| } |
| |
| const getChainStatusText = () => { |
| if (currentChainStep.value === 0) return 'Jane analysiert Intent...' |
| if (currentChainStep.value === 1) return 'Delegiert an Specialist...' |
| if (currentChainStep.value === 2) return 'Koordiniert finale Antwort...' |
| return 'Workflow abgeschlossen' |
| } |
| |
| |
| const scrollToBottom = (force = false) => { |
| nextTick(() => { |
| if (messagesContainer.value) { |
| const container = messagesContainer.value |
| const isNearBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 50 |
| |
| if (force || isNearBottom) { |
| if (scrollTrigger.value) { |
| scrollTrigger.value.scrollIntoView({ behavior: 'smooth' }) |
| } |
| } |
| } |
| }) |
| } |
| |
| |
| 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, |
| isHighlighted: meta.highlight || false |
| } |
| |
| chatMessages.value.push(message) |
| scrollToBottom(true) |
| return message |
| } |
| |
| |
| const updateDelegationStatus = (status, progress = 0) => { |
| delegationStatus.value = status |
| delegationProgress.value = progress |
| |
| if (progress >= 100) { |
| setTimeout(() => { |
| delegationStatus.value = '' |
| delegationProgress.value = 0 |
| }, 2000) |
| } |
| } |
| |
| const startProcessingTimer = () => { |
| processingTime.value = 0 |
| processingTimer = setInterval(() => { |
| processingTime.value += 1 |
| |
| |
| if (processingTime.value === 15 && preferredProvider.value === 'auto') { |
| providerSwitchMessage.value = 'Colossus timeout โ switching to OpenRouter...' |
| setTimeout(() => providerSwitchMessage.value = '', 3000) |
| } |
| }, 1000) |
| } |
| |
| const stopProcessingTimer = () => { |
| if (processingTimer) { |
| clearInterval(processingTimer) |
| processingTimer = null |
| } |
| processingTime.value = 0 |
| providerSwitchMessage.value = '' |
| } |
| |
| const sendMessage = async () => { |
| if (!canSendMessage.value) return |
| |
| const message = currentMessage.value.trim() |
| currentMessage.value = '' |
| |
| |
| addMessage(message, 'user', 'Du', 'user') |
| |
| |
| isProcessing.value = true |
| currentProcessor.value = 'Jane Alesi' |
| processingStatus.value = 'Analysiert Intent...' |
| currentCoordinationChain.value = ['jane_alesi'] |
| currentChainStep.value = 0 |
| |
| updateDelegationStatus('Jane Alesi analysiert Intent...', 20) |
| startProcessingTimer() |
| |
| try { |
| |
| setTimeout(() => { |
| updateDelegationStatus('Wรคhlt besten Spezialisten...', 40) |
| currentChainStep.value = 1 |
| }, 2000) |
| |
| setTimeout(() => { |
| updateDelegationStatus('Delegiert Aufgabe...', 60) |
| currentChainStep.value = 2 |
| }, 4000) |
| |
| |
| const response = await saapApi.multiAgentChat({ |
| user_message: message, |
| user_context: { |
| timestamp: new Date().toISOString(), |
| priority: taskPriority.value, |
| preferred_provider: preferredProvider.value |
| }, |
| preferred_agent: preferredAgent.value || null, |
| task_priority: taskPriority.value |
| }) |
| |
| updateDelegationStatus('Koordiniert finale Antwort...', 80) |
| |
| |
| currentCoordinationChain.value = response.coordination_chain || ['jane_alesi'] |
| |
| |
| addMessage( |
| response.coordinator_response, |
| 'jane_alesi', |
| 'Jane Alesi', |
| 'coordinator', |
| { |
| role: 'Master Coordinator', |
| processingTime: response.processing_time, |
| cost: response.cost_info?.total_cost, |
| provider: response.provider || 'auto', |
| highlight: true |
| } |
| ) |
| |
| |
| if (response.delegated_agent && response.specialist_response) { |
| const specialistName = getAgentName(response.delegated_agent) |
| const specialist = availableSpecialists.value.find(s => s.id === response.delegated_agent) |
| |
| selectedSpecialist.value = response.delegated_agent |
| currentlyProcessingAgent.value = response.delegated_agent |
| |
| setTimeout(() => { |
| addMessage( |
| response.specialist_response, |
| response.delegated_agent, |
| specialistName, |
| 'specialist', |
| { |
| role: specialist?.specialization || 'Specialist', |
| processingTime: response.processing_time, |
| provider: response.provider || 'auto', |
| highlight: true |
| } |
| ) |
| selectedSpecialist.value = null |
| currentlyProcessingAgent.value = null |
| updateDelegationStatus('Workflow abgeschlossen!', 100) |
| }, 1000) |
| } else { |
| updateDelegationStatus('Workflow abgeschlossen!', 100) |
| } |
| |
| } 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', highlight: true } |
| ) |
| updateDelegationStatus('Fehler aufgetreten', 0) |
| } finally { |
| isProcessing.value = false |
| currentProcessor.value = 'Jane Alesi' |
| processingStatus.value = 'Ready' |
| currentChainStep.value = 0 |
| stopProcessingTimer() |
| } |
| } |
| |
| const clearChat = () => { |
| chatMessages.value = [] |
| currentCoordinationChain.value = [] |
| selectedSpecialist.value = null |
| currentlyProcessingAgent.value = null |
| delegationStatus.value = '' |
| delegationProgress.value = 0 |
| |
| |
| 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') |
| } |
| |
| |
| watch(() => props.isVisible, (newVal) => { |
| if (newVal && chatMessages.value.length === 0) { |
| clearChat() |
| } |
| }) |
| |
| |
| onMounted(() => { |
| if (chatMessages.value.length === 0) { |
| clearChat() |
| } |
| }) |
| |
| onUnmounted(() => { |
| stopProcessingTimer() |
| }) |
| |
| return { |
| |
| chatMessages, |
| currentMessage, |
| isProcessing, |
| currentProcessor, |
| processingStatus, |
| processingTime, |
| selectedSpecialist, |
| currentlyProcessingAgent, |
| currentCoordinationChain, |
| currentChainStep, |
| taskPriority, |
| preferredAgent, |
| preferredProvider, |
| messagesContainer, |
| scrollTrigger, |
| availableSpecialists, |
| delegationStatus, |
| delegationProgress, |
| providerSwitchMessage, |
| |
| |
| canSendMessage, |
| uniqueAgentsCount, |
| averageResponseTime, |
| totalCost, |
| |
| |
| getSpecialistIcon, |
| getAgentIcon, |
| getAgentName, |
| formatTime, |
| getProcessingMessage, |
| getChainStatusText, |
| sendMessage, |
| clearChat, |
| closeModal |
| } |
| } |
| } |
| </script> |
| |
| <style scoped> |
| |
| .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); |
| } |
| |
| |
| .modal-container { |
| background: white; |
| border-radius: 16px; |
| width: 96vw; |
| max-width: 1400px; |
| height: 96vh; |
| max-height: 98vh; |
| display: flex; |
| flex-direction: column; |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); |
| overflow: hidden; |
| } |
| |
| |
| .modal-header { |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| color: white; |
| padding: 16px 20px; |
| position: relative; |
| flex-shrink: 0; |
| } |
| |
| .modal-title { |
| margin: 0; |
| font-size: 22px; |
| font-weight: 700; |
| } |
| |
| .modal-subtitle { |
| margin: 6px 0 0 0; |
| font-size: 13px; |
| opacity: 0.9; |
| } |
| |
| .close-button { |
| position: absolute; |
| top: 16px; |
| 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); |
| } |
| |
| |
| .modal-body { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| min-height: 0; |
| } |
| |
| |
| .capabilities-section { |
| padding: 6px 16px; |
| border-bottom: 1px solid #e5e7eb; |
| background: #f8fafc; |
| flex-shrink: 0; |
| } |
| |
| .section-title { |
| margin: 0 0 6px 0; |
| font-size: 13px; |
| font-weight: 600; |
| color: #374151; |
| } |
| |
| .specialists-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); |
| gap: 3px; |
| } |
| |
| .specialist-card { |
| display: flex; |
| align-items: center; |
| padding: 4px 6px; |
| border: 2px solid #e5e7eb; |
| border-radius: 6px; |
| background: white; |
| transition: all 0.2s; |
| cursor: pointer; |
| position: relative; |
| } |
| |
| .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-card.processing { |
| border-color: #f59e0b; |
| background: #fffbeb; |
| animation: processing-pulse 2s infinite; |
| } |
| |
| @keyframes processing-pulse { |
| 0%, 100% { transform: scale(1); } |
| 50% { transform: scale(1.02); } |
| } |
| |
| .specialist-icon { |
| font-size: 16px; |
| margin-right: 5px; |
| position: relative; |
| } |
| |
| .processing-indicator { |
| position: absolute; |
| top: -2px; |
| right: -2px; |
| width: 8px; |
| height: 8px; |
| } |
| |
| .spinner-small { |
| width: 8px; |
| height: 8px; |
| border: 1px solid #f59e0b; |
| border-radius: 50%; |
| border-top-color: transparent; |
| animation: spin 0.8s linear infinite; |
| } |
| |
| .specialist-info { |
| flex: 1; |
| } |
| |
| .specialist-name { |
| font-size: 10px; |
| font-weight: 600; |
| color: #374151; |
| } |
| |
| .specialist-role { |
| font-size: 9px; |
| color: #6b7280; |
| } |
| |
| |
| .chat-section { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| min-height: 0; |
| } |
| |
| |
| .delegation-status { |
| background: linear-gradient(90deg, #3b82f6, #1d4ed8); |
| color: white; |
| padding: 8px 16px; |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| font-size: 13px; |
| flex-shrink: 0; |
| } |
| |
| .delegation-icon { |
| font-size: 16px; |
| animation: bounce 1s infinite; |
| } |
| |
| @keyframes bounce { |
| 0%, 100% { transform: translateY(0); } |
| 50% { transform: translateY(-2px); } |
| } |
| |
| .delegation-text { |
| flex: 1; |
| font-weight: 500; |
| } |
| |
| .delegation-progress { |
| width: 100px; |
| height: 4px; |
| background: rgba(255, 255, 255, 0.3); |
| border-radius: 2px; |
| overflow: hidden; |
| } |
| |
| .progress-bar { |
| height: 100%; |
| background: rgba(255, 255, 255, 0.9); |
| transition: width 0.5s ease; |
| border-radius: 2px; |
| } |
| |
| |
| .chat-messages-wrapper { |
| flex: 1; |
| overflow: hidden; |
| display: flex; |
| flex-direction: column; |
| min-height: 400px; |
| } |
| |
| .chat-messages { |
| flex: 1; |
| overflow-y: auto; |
| padding: 16px 20px; |
| background: #fafafa; |
| scroll-behavior: smooth; |
| |
| height: 100%; |
| max-height: none !important; |
| } |
| |
| |
| .message { |
| margin-bottom: 16px; |
| padding: 12px 16px; |
| border-radius: 12px; |
| background: white; |
| border: 1px solid #e5e7eb; |
| transition: all 0.3s ease; |
| } |
| |
| .message.highlighted { |
| transform: scale(1.01); |
| box-shadow: 0 8px 16px -4px rgba(0, 0, 0, 0.1); |
| border-color: #3b82f6; |
| } |
| |
| .message.user { |
| background: #eff6ff; |
| border-color: #3b82f6; |
| margin-left: 60px; |
| } |
| |
| .message.coordinator { |
| background: #f3e8ff; |
| border-color: #8b5cf6; |
| } |
| |
| .message.specialist { |
| background: #ecfdf5; |
| border-color: #10b981; |
| } |
| |
| .message.error { |
| background: #fef2f2; |
| border-color: #ef4444; |
| } |
| |
| .message.processing.enhanced { |
| background: linear-gradient(45deg, #fffbeb, #fef3c7); |
| border-color: #f59e0b; |
| animation: processing-glow 2s infinite; |
| } |
| |
| @keyframes processing-glow { |
| 0%, 100% { box-shadow: 0 0 8px rgba(245, 158, 11, 0.3); } |
| 50% { box-shadow: 0 0 16px rgba(245, 158, 11, 0.5); } |
| } |
| |
| .message-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 8px; |
| } |
| |
| .message-agent { |
| display: flex; |
| align-items: center; |
| font-size: 14px; |
| font-weight: 600; |
| gap: 4px; |
| } |
| |
| .agent-icon { |
| font-size: 16px; |
| } |
| |
| .agent-icon.loading { |
| animation: pulse 1.5s infinite; |
| } |
| |
| .agent-name { |
| color: #374151; |
| } |
| |
| .agent-role { |
| color: #6b7280; |
| font-weight: 400; |
| } |
| |
| .provider-badge { |
| padding: 2px 6px; |
| border-radius: 4px; |
| font-size: 10px; |
| font-weight: 500; |
| } |
| |
| .provider-badge.colossus { |
| background: #dcfce7; |
| color: #166534; |
| } |
| |
| .provider-badge.openrouter { |
| background: #dbeafe; |
| color: #1e40af; |
| } |
| |
| .message-time { |
| font-size: 12px; |
| color: #9ca3af; |
| } |
| |
| .processing-timer { |
| font-size: 12px; |
| color: #f59e0b; |
| font-weight: 600; |
| } |
| |
| .message-content { |
| color: #374151; |
| line-height: 1.5; |
| white-space: pre-wrap; |
| font-size: 14px; |
| } |
| |
| .message-meta { |
| display: flex; |
| gap: 12px; |
| margin-top: 8px; |
| font-size: 12px; |
| color: #6b7280; |
| flex-wrap: wrap; |
| } |
| |
| .processing-time, .cost-info, .provider-info { |
| display: flex; |
| align-items: center; |
| gap: 4px; |
| } |
| |
| |
| .coordination-chain.enhanced { |
| background: linear-gradient(135deg, #f0f9ff, #e0f2fe); |
| border: 2px solid #0ea5e9; |
| border-radius: 12px; |
| padding: 16px; |
| margin: 16px 0; |
| animation: chain-appear 0.5s ease; |
| } |
| |
| @keyframes chain-appear { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| .chain-title { |
| margin: 0 0 12px 0; |
| font-size: 14px; |
| font-weight: 700; |
| color: #0c4a6e; |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| } |
| |
| .chain-flow { |
| display: flex; |
| align-items: center; |
| flex-wrap: wrap; |
| gap: 8px; |
| margin-bottom: 8px; |
| } |
| |
| .chain-agent { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| padding: 6px 12px; |
| background: white; |
| border-radius: 8px; |
| font-size: 12px; |
| font-weight: 500; |
| border: 2px solid #e5e7eb; |
| transition: all 0.3s ease; |
| position: relative; |
| } |
| |
| .chain-agent.active { |
| border-color: #3b82f6; |
| background: #eff6ff; |
| color: #1e40af; |
| transform: scale(1.05); |
| } |
| |
| .chain-agent.completed { |
| border-color: #10b981; |
| background: #ecfdf5; |
| color: #065f46; |
| } |
| |
| .chain-agent.pending { |
| opacity: 0.6; |
| } |
| |
| .chain-progress { |
| position: absolute; |
| bottom: -2px; |
| left: 0; |
| right: 0; |
| height: 2px; |
| background: #3b82f6; |
| animation: progress-flow 1.5s infinite; |
| } |
| |
| @keyframes progress-flow { |
| 0% { transform: translateX(-100%); } |
| 100% { transform: translateX(100%); } |
| } |
| |
| .progress-dots { |
| display: flex; |
| gap: 2px; |
| margin-left: 4px; |
| } |
| |
| .progress-dots span { |
| width: 3px; |
| height: 3px; |
| background: #3b82f6; |
| border-radius: 50%; |
| animation: dot-bounce 1s infinite; |
| } |
| |
| .progress-dots span:nth-child(2) { animation-delay: 0.1s; } |
| .progress-dots span:nth-child(3) { animation-delay: 0.2s; } |
| |
| @keyframes dot-bounce { |
| 0%, 80%, 100% { transform: scale(0); } |
| 40% { transform: scale(1); } |
| } |
| |
| .chain-status { |
| font-size: 12px; |
| color: #0c4a6e; |
| font-weight: 500; |
| text-align: center; |
| margin-top: 8px; |
| font-style: italic; |
| } |
| |
| .chain-icon { |
| font-size: 14px; |
| } |
| |
| .chain-arrow { |
| width: 16px; |
| height: 16px; |
| color: #0ea5e9; |
| } |
| |
| |
| .enhanced-typing-indicator { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| } |
| |
| .typing-text { |
| font-style: italic; |
| color: #6b7280; |
| } |
| |
| .typing-dots { |
| display: flex; |
| gap: 4px; |
| } |
| |
| .typing-dots span { |
| width: 8px; |
| height: 8px; |
| border-radius: 50%; |
| background: #f59e0b; |
| animation: typing 1.4s infinite ease-in-out; |
| } |
| |
| .typing-dots span:nth-child(1) { animation-delay: -0.32s; } |
| .typing-dots span:nth-child(2) { animation-delay: -0.16s; } |
| |
| @keyframes typing { |
| 0%, 80%, 100% { |
| transform: scale(0); |
| opacity: 0.5; |
| } |
| 40% { |
| transform: scale(1); |
| opacity: 1; |
| } |
| } |
| |
| .provider-switch-notification { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| margin-top: 8px; |
| padding: 6px 10px; |
| background: #fef3c7; |
| border-radius: 6px; |
| font-size: 12px; |
| color: #92400e; |
| border: 1px solid #f59e0b; |
| } |
| |
| .switch-icon { |
| font-size: 14px; |
| animation: spin 1s linear infinite; |
| } |
| |
| @keyframes spin { |
| to { transform: rotate(360deg); } |
| } |
| |
| @keyframes pulse { |
| 0%, 100% { transform: scale(1); } |
| 50% { transform: scale(1.1); } |
| } |
| |
| |
| .chat-input-section.enhanced { |
| background: white; |
| border-top: 2px solid #e5e7eb; |
| padding: 16px 20px; |
| flex-shrink: 0 !important; |
| position: relative; |
| z-index: 50; |
| min-height: 120px !important; |
| box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.1); |
| } |
| |
| .input-group { |
| display: flex; |
| gap: 12px; |
| align-items: flex-end; |
| margin-bottom: 12px; |
| } |
| |
| .message-input { |
| flex: 1; |
| padding: 12px 16px; |
| border: 2px solid #e5e7eb; |
| border-radius: 12px; |
| font-size: 14px; |
| line-height: 1.4; |
| resize: vertical; |
| min-height: 48px; |
| max-height: 120px; |
| transition: border-color 0.2s; |
| background: white; |
| } |
| |
| .message-input:focus { |
| outline: none; |
| border-color: #3b82f6; |
| box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); |
| } |
| |
| .message-input:disabled { |
| background: #f9fafb; |
| color: #9ca3af; |
| } |
| |
| .send-button { |
| padding: 12px; |
| background: #3b82f6; |
| color: white; |
| border: none; |
| border-radius: 12px; |
| cursor: pointer; |
| transition: all 0.2s; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| width: 48px; |
| height: 48px; |
| flex-shrink: 0; |
| } |
| |
| .send-button:hover:not(:disabled) { |
| background: #2563eb; |
| transform: translateY(-1px); |
| } |
| |
| .send-button:disabled { |
| background: #9ca3af; |
| cursor: not-allowed; |
| } |
| |
| .spinner { |
| width: 20px; |
| height: 20px; |
| border: 2px solid rgba(255,255,255,0.3); |
| border-radius: 50%; |
| border-top-color: white; |
| animation: spin 1s linear infinite; |
| } |
| |
| |
| .input-options { |
| display: flex; |
| gap: 16px; |
| align-items: center; |
| font-size: 12px; |
| flex-wrap: wrap; |
| } |
| |
| .task-priority, .preferred-agent, .provider-selection { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| } |
| |
| .priority-select, .agent-select { |
| padding: 4px 8px; |
| border: 1px solid #d1d5db; |
| border-radius: 6px; |
| font-size: 12px; |
| background: white; |
| min-width: 120px; |
| } |
| |
| .provider-buttons { |
| display: flex; |
| gap: 4px; |
| } |
| |
| .provider-btn { |
| padding: 4px 8px; |
| border: 1px solid #d1d5db; |
| border-radius: 6px; |
| background: white; |
| cursor: pointer; |
| transition: all 0.2s; |
| font-size: 11px; |
| } |
| |
| .provider-btn.active { |
| background: #3b82f6; |
| color: white; |
| border-color: #3b82f6; |
| } |
| |
| .provider-btn:hover:not(.active) { |
| background: #f3f4f6; |
| } |
| |
| |
| .modal-footer.enhanced { |
| padding: 12px 20px; |
| border-top: 2px solid #e5e7eb; |
| background: #f8fafc; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| flex-shrink: 0; |
| } |
| |
| .footer-stats { |
| display: flex; |
| gap: 20px; |
| flex-wrap: wrap; |
| } |
| |
| .stat-item { |
| display: flex; |
| align-items: center; |
| gap: 4px; |
| font-size: 12px; |
| color: #6b7280; |
| } |
| |
| .stat-icon { |
| font-size: 14px; |
| } |
| |
| .stat-value { |
| font-weight: 600; |
| color: #374151; |
| } |
| |
| .footer-actions { |
| display: flex; |
| gap: 12px; |
| } |
| |
| .clear-button, .close-modal-button { |
| padding: 6px 12px; |
| border: 1px solid #d1d5db; |
| border-radius: 8px; |
| background: white; |
| cursor: pointer; |
| transition: all 0.2s; |
| font-size: 13px; |
| } |
| |
| .clear-button:hover { |
| background: #f3f4f6; |
| } |
| |
| .close-modal-button { |
| background: #3b82f6; |
| color: white; |
| border-color: #3b82f6; |
| } |
| |
| .close-modal-button:hover { |
| background: #2563eb; |
| } |
| |
| |
| @media (max-width: 768px) { |
| .modal-container { |
| width: 98vw; |
| height: 98vh; |
| } |
| |
| .chat-messages-wrapper { |
| min-height: 300px; |
| } |
| |
| .chat-messages { |
| padding: 12px 16px; |
| } |
| |
| .specialists-grid { |
| grid-template-columns: repeat(2, 1fr); |
| } |
| |
| .input-options { |
| flex-direction: column; |
| align-items: flex-start; |
| gap: 8px; |
| } |
| |
| .footer-stats { |
| flex-direction: column; |
| gap: 8px; |
| } |
| |
| .chat-input-section.enhanced { |
| padding: 12px 16px; |
| min-height: 100px !important; |
| } |
| |
| .chain-flow { |
| flex-direction: column; |
| align-items: stretch; |
| } |
| |
| .chain-arrow { |
| transform: rotate(90deg); |
| margin: 4px 0; |
| } |
| } |
| |
| |
| @media (min-width: 1400px) { |
| .modal-container { |
| max-width: 1600px; |
| } |
| |
| .chat-messages-wrapper { |
| min-height: 600px; |
| } |
| |
| .specialists-grid { |
| grid-template-columns: repeat(6, 1fr); |
| } |
| } |
| |
| |
| * { |
| box-sizing: border-box; |
| } |
| |
| .chat-messages::-webkit-scrollbar { |
| width: 8px; |
| } |
| |
| .chat-messages::-webkit-scrollbar-track { |
| background: #f1f5f9; |
| border-radius: 4px; |
| } |
| |
| .chat-messages::-webkit-scrollbar-thumb { |
| background: #cbd5e1; |
| border-radius: 4px; |
| } |
| |
| .chat-messages::-webkit-scrollbar-thumb:hover { |
| background: #94a3b8; |
| } |
| </style> |