saap-plattform / frontend /src /components /modals /MultiAgentChatModal_Enhanced.vue
Hwandji's picture
feat: initial HuggingFace Space deployment
4343907
raw
history blame
38.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,
'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>
<!-- 🚀 ENHANCED: Chat Interface with guaranteed scroll -->
<div class="chat-section">
<!-- Live Delegation Status -->
<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>
<!-- Enhanced Coordination Chain Visualization -->
<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>
<!-- Enhanced Processing State -->
<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>
<!-- Provider switch notification -->
<div v-if="providerSwitchMessage" class="provider-switch-notification">
<div class="switch-icon">🔄</div>
<div class="switch-text">{{ providerSwitchMessage }}</div>
</div>
</div>
</div>
<!-- Auto-scroll trigger -->
<div ref="scrollTrigger"></div>
</div>
</div>
</div>
</div>
<!-- 🚀 ENHANCED INPUT SECTION: Guaranteed visibility -->
<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>
<!-- Provider selection -->
<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>
<!-- 🔧 ENHANCED: Modal Footer with better stats -->
<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 }) {
// Reactive state
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)
// Enhanced state for delegation tracking
const delegationStatus = ref('')
const delegationProgress = ref(0)
const providerSwitchMessage = ref('')
// Processing timer
let processingTimer = null
// 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)
})
const totalCost = computed(() => {
return chatMessages.value
.filter(msg => msg.cost)
.reduce((sum, msg) => sum + msg.cost, 0)
})
// 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 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'
}
// Enhanced scrolling with intersection observer
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' })
}
}
}
})
}
// 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,
isHighlighted: meta.highlight || false
}
chatMessages.value.push(message)
scrollToBottom(true)
return message
}
// Enhanced WebSocket delegation tracking
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
// Provider switch logic
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 = ''
// Add user message
addMessage(message, 'user', 'Du', 'user')
// Start enhanced processing
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 {
// Simulated delegation steps
setTimeout(() => {
updateDelegationStatus('Wählt besten Spezialisten...', 40)
currentChainStep.value = 1
}, 2000)
setTimeout(() => {
updateDelegationStatus('Delegiert Aufgabe...', 60)
currentChainStep.value = 2
}, 4000)
// Call multi-agent API
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)
// Update coordination chain
currentCoordinationChain.value = response.coordination_chain || ['jane_alesi']
// Add coordinator response
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
}
)
// Add specialist response if available
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
// 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')
}
// Watch for modal visibility changes
watch(() => props.isVisible, (newVal) => {
if (newVal && chatMessages.value.length === 0) {
clearChat()
}
})
// Lifecycle
onMounted(() => {
if (chatMessages.value.length === 0) {
clearChat()
}
})
onUnmounted(() => {
stopProcessingTimer()
})
return {
// State
chatMessages,
currentMessage,
isProcessing,
currentProcessor,
processingStatus,
processingTime,
selectedSpecialist,
currentlyProcessingAgent,
currentCoordinationChain,
currentChainStep,
taskPriority,
preferredAgent,
preferredProvider,
messagesContainer,
scrollTrigger,
availableSpecialists,
delegationStatus,
delegationProgress,
providerSwitchMessage,
// Computed
canSendMessage,
uniqueAgentsCount,
averageResponseTime,
totalCost,
// Methods
getSpecialistIcon,
getAgentIcon,
getAgentName,
formatTime,
getProcessingMessage,
getChainStatusText,
sendMessage,
clearChat,
closeModal
}
}
}
</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);
}
/* 🚀 ENHANCED: Modal Container - MAXIMUM VISIBILITY */
.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 */
.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 */
.modal-body {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
/* 🔧 OPTIMIZED: Capabilities Section */
.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;
}
/* 🚀 ENHANCED: Chat Section with GUARANTEED SCROLL */
.chat-section {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
/* Live Delegation Status */
.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;
}
/* 🚀 CRITICAL: Chat Messages Wrapper - GUARANTEED SCROLLABLE */
.chat-messages-wrapper {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
min-height: 400px; /* Minimum guaranteed height */
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px 20px;
background: #fafafa;
scroll-behavior: smooth;
/* Ensure scrolling always works */
height: 100%;
max-height: none !important;
}
/* Message Styles */
.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;
}
/* Enhanced Coordination Chain */
.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 */
.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); }
}
/* 🚀 ENHANCED: Input Section - ABSOLUTE VISIBILITY */
.chat-input-section.enhanced {
background: white;
border-top: 2px solid #e5e7eb;
padding: 16px 20px;
flex-shrink: 0 !important; /* CRITICAL: Never allow shrinking */
position: relative;
z-index: 50; /* Higher z-index to ensure visibility */
min-height: 120px !important; /* GUARANTEED minimum space */
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.1); /* Visual separation */
}
.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 */
.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;
}
/* Enhanced Modal Footer */
.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;
}
/* 🚀 ENHANCED: Responsive Design */
@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;
}
}
/* 🚀 ULTRA-WIDE: Maximum Screen Utilization */
@media (min-width: 1400px) {
.modal-container {
max-width: 1600px;
}
.chat-messages-wrapper {
min-height: 600px;
}
.specialists-grid {
grid-template-columns: repeat(6, 1fr);
}
}
/* 🚀 CRITICAL: Prevent any scroll issues */
* {
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>