Spaces:
Sleeping
Sleeping
| <template> | |
| <div class="modal-overlay" @click="$emit('close')"> | |
| <div class="chat-modal" @click.stop> | |
| <!-- Modal Header --> | |
| <div class="chat-header"> | |
| <div class="agent-info"> | |
| <div | |
| class="agent-avatar" | |
| :class="`bg-agent-${agent.id.replace('_alesi', '')}`" | |
| > | |
| {{ getAgentInitials(agent.name) }} | |
| </div> | |
| <div> | |
| <h3 class="saap-heading-3">{{ agent.name }}</h3> | |
| <p class="saap-body-small text-saap-gray-600"> | |
| {{ agent.type }} Agent • {{ agent.status }} | |
| </p> | |
| </div> | |
| </div> | |
| <button | |
| @click="$emit('close')" | |
| class="close-button" | |
| title="Close Chat" | |
| > | |
| <XMarkIcon class="w-5 h-5" /> | |
| </button> | |
| </div> | |
| <!-- Chat Messages --> | |
| <div class="chat-messages" ref="messagesContainer"> | |
| <div v-if="chatHistory.length === 0" class="empty-chat"> | |
| <ChatBubbleLeftEllipsisIcon class="w-12 h-12 text-saap-gray-400 mx-auto mb-3" /> | |
| <p class="saap-body text-saap-gray-600"> | |
| Start a conversation with {{ agent.name }} | |
| </p> | |
| </div> | |
| <div | |
| v-for="message in chatHistory" | |
| :key="message.id" | |
| class="message-group" | |
| > | |
| <!-- User Message --> | |
| <div class="message user-message"> | |
| <div class="message-content"> | |
| <p>{{ message.user_message }}</p> | |
| </div> | |
| <div class="message-time"> | |
| {{ formatTime(message.timestamp) }} | |
| </div> | |
| </div> | |
| <!-- Agent Response --> | |
| <div class="message agent-message"> | |
| <div | |
| class="agent-message-avatar" | |
| :class="`bg-agent-${agent.id.replace('_alesi', '')}`" | |
| > | |
| {{ getAgentInitials(agent.name) }} | |
| </div> | |
| <div class="message-content"> | |
| <div class="agent-name">{{ agent.name }}</div> | |
| <p>{{ message.agent_response }}</p> | |
| </div> | |
| <div class="message-time"> | |
| {{ formatTime(message.timestamp) }} | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Loading indicator --> | |
| <div v-if="isLoading" class="message agent-message loading"> | |
| <div | |
| class="agent-message-avatar" | |
| :class="`bg-agent-${agent.id.replace('_alesi', '')}`" | |
| > | |
| {{ getAgentInitials(agent.name) }} | |
| </div> | |
| <div class="message-content"> | |
| <div class="agent-name">{{ agent.name }}</div> | |
| <div class="typing-indicator"> | |
| <span></span> | |
| <span></span> | |
| <span></span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Chat Input --> | |
| <div class="chat-input-section"> | |
| <form @submit.prevent="sendMessage" class="chat-form"> | |
| <div class="input-container"> | |
| <textarea | |
| v-model="currentMessage" | |
| @keydown.enter.exact.prevent="sendMessage" | |
| @keydown.enter.shift.exact="addNewLine" | |
| placeholder="Type your message... (Enter to send, Shift+Enter for new line)" | |
| class="message-input" | |
| rows="1" | |
| ref="messageInput" | |
| :disabled="isLoading" | |
| ></textarea> | |
| <button | |
| type="submit" | |
| :disabled="!canSend" | |
| class="send-button" | |
| title="Send Message" | |
| > | |
| <PaperAirplaneIcon class="w-5 h-5" /> | |
| </button> | |
| </div> | |
| <!-- Quick Actions --> | |
| <div class="quick-actions"> | |
| <button | |
| v-for="quickAction in quickActions" | |
| :key="quickAction.text" | |
| @click="setQuickMessage(quickAction.text)" | |
| class="quick-action" | |
| type="button" | |
| > | |
| {{ quickAction.label }} | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| </template> | |
| <script setup> | |
| import { ref, computed, watch, nextTick, onMounted } from 'vue' | |
| import { useAgentStore } from '@/stores/agents' | |
| import { | |
| XMarkIcon, | |
| PaperAirplaneIcon, | |
| ChatBubbleLeftEllipsisIcon | |
| } from '@heroicons/vue/24/outline' | |
| // Props | |
| const props = defineProps({ | |
| agent: { | |
| type: Object, | |
| required: true | |
| } | |
| }) | |
| // Emits | |
| const emit = defineEmits(['close', 'send-message']) | |
| // Store | |
| const agentStore = useAgentStore() | |
| // Reactive state | |
| const currentMessage = ref('') | |
| const isLoading = ref(false) | |
| const messagesContainer = ref(null) | |
| const messageInput = ref(null) | |
| // Computed | |
| const chatHistory = computed(() => { | |
| return agentStore.getChatMessages(props.agent.id) | |
| }) | |
| const canSend = computed(() => { | |
| return currentMessage.value.trim() && !isLoading.value && props.agent.status === 'active' | |
| }) | |
| // Quick actions based on agent type | |
| const quickActions = computed(() => { | |
| const baseActions = [ | |
| { label: 'Status', text: 'What is your current status?' }, | |
| { label: 'Help', text: 'What can you help me with?' } | |
| ] | |
| const agentSpecificActions = { | |
| jane_alesi: [ | |
| { label: 'Coordinate', text: 'Help me coordinate a multi-agent project' }, | |
| { label: 'Strategy', text: 'What is your recommended strategy for this task?' } | |
| ], | |
| john_alesi: [ | |
| { label: 'Code Review', text: 'Can you review my code?' }, | |
| { label: 'Architecture', text: 'Help me design the system architecture' } | |
| ], | |
| lara_alesi: [ | |
| { label: 'Medical Analysis', text: 'Provide medical analysis for this case' }, | |
| { label: 'Compliance', text: 'Check medical compliance requirements' } | |
| ] | |
| } | |
| return [...baseActions, ...(agentSpecificActions[props.agent.id] || [])] | |
| }) | |
| // Methods | |
| const getAgentInitials = (name) => { | |
| return name.split(' ').map(word => word[0]).join('').toUpperCase().slice(0, 2) | |
| } | |
| const formatTime = (timestamp) => { | |
| return new Date(timestamp).toLocaleTimeString('de-DE', { | |
| hour: '2-digit', | |
| minute: '2-digit' | |
| }) | |
| } | |
| const sendMessage = async () => { | |
| const message = currentMessage.value.trim() | |
| if (!canSend.value) return | |
| isLoading.value = true | |
| const originalMessage = currentMessage.value | |
| currentMessage.value = '' | |
| try { | |
| await agentStore.chatWithAgent(props.agent.id, message) | |
| await scrollToBottom() | |
| } catch (error) { | |
| console.error('Failed to send message:', error) | |
| // Restore message on error | |
| currentMessage.value = originalMessage | |
| } finally { | |
| isLoading.value = false | |
| focusInput() | |
| } | |
| } | |
| const addNewLine = () => { | |
| currentMessage.value += '\n' | |
| } | |
| const setQuickMessage = (message) => { | |
| currentMessage.value = message | |
| focusInput() | |
| } | |
| const scrollToBottom = async () => { | |
| await nextTick() | |
| if (messagesContainer.value) { | |
| messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight | |
| } | |
| } | |
| const focusInput = async () => { | |
| await nextTick() | |
| if (messageInput.value) { | |
| messageInput.value.focus() | |
| } | |
| } | |
| // Watchers | |
| watch(chatHistory, () => { | |
| scrollToBottom() | |
| }, { deep: true }) | |
| // Lifecycle | |
| onMounted(() => { | |
| focusInput() | |
| scrollToBottom() | |
| }) | |
| </script> | |
| <style scoped> | |
| .modal-overlay { | |
| @apply fixed inset-0 bg-black bg-opacity-50 z-50; | |
| @apply flex items-center justify-center p-4; | |
| } | |
| .chat-modal { | |
| @apply bg-white rounded-xl shadow-2xl; | |
| @apply w-full max-w-2xl h-[600px]; | |
| @apply flex flex-col; | |
| } | |
| /* Header */ | |
| .chat-header { | |
| @apply flex items-center justify-between; | |
| @apply p-6 border-b border-saap-gray-200; | |
| } | |
| .agent-info { | |
| @apply flex items-center space-x-3; | |
| } | |
| .agent-avatar { | |
| @apply w-10 h-10 rounded-full; | |
| @apply flex items-center justify-center; | |
| @apply text-white font-semibold text-sm; | |
| } | |
| .close-button { | |
| @apply p-2 hover:bg-saap-gray-100 rounded-lg; | |
| @apply transition-colors duration-200; | |
| @apply text-saap-gray-500 hover:text-saap-gray-700; | |
| } | |
| /* Chat Messages */ | |
| .chat-messages { | |
| @apply flex-1 overflow-y-auto p-6 space-y-4; | |
| @apply bg-saap-gray-50; | |
| } | |
| .empty-chat { | |
| @apply text-center py-12; | |
| } | |
| .message-group { | |
| @apply space-y-3; | |
| } | |
| .message { | |
| @apply flex space-x-3; | |
| } | |
| /* User Message */ | |
| .user-message { | |
| @apply justify-end; | |
| } | |
| .user-message .message-content { | |
| @apply bg-saap-primary-600 text-white; | |
| @apply px-4 py-2 rounded-2xl rounded-tr-md; | |
| @apply max-w-xs sm:max-w-md; | |
| } | |
| .user-message .message-time { | |
| @apply text-xs text-saap-gray-500 mt-1 text-right; | |
| } | |
| /* Agent Message */ | |
| .agent-message { | |
| @apply justify-start; | |
| } | |
| .agent-message-avatar { | |
| @apply w-8 h-8 rounded-full flex-shrink-0; | |
| @apply flex items-center justify-center; | |
| @apply text-white font-semibold text-xs; | |
| } | |
| .agent-message .message-content { | |
| @apply bg-white border border-saap-gray-200; | |
| @apply px-4 py-2 rounded-2xl rounded-tl-md; | |
| @apply max-w-xs sm:max-w-md; | |
| } | |
| .agent-name { | |
| @apply text-xs font-medium text-saap-gray-600 mb-1; | |
| } | |
| .agent-message .message-time { | |
| @apply text-xs text-saap-gray-500 mt-1; | |
| } | |
| /* Loading Message */ | |
| .message.loading .message-content { | |
| @apply bg-white border border-saap-gray-200; | |
| } | |
| .typing-indicator { | |
| @apply flex space-x-1 py-2; | |
| } | |
| .typing-indicator span { | |
| @apply w-2 h-2 bg-saap-gray-400 rounded-full; | |
| @apply animate-pulse; | |
| } | |
| .typing-indicator span:nth-child(1) { | |
| animation-delay: 0s; | |
| } | |
| .typing-indicator span:nth-child(2) { | |
| animation-delay: 0.2s; | |
| } | |
| .typing-indicator span:nth-child(3) { | |
| animation-delay: 0.4s; | |
| } | |
| /* Chat Input */ | |
| .chat-input-section { | |
| @apply p-6 border-t border-saap-gray-200 bg-white; | |
| } | |
| .chat-form { | |
| @apply space-y-3; | |
| } | |
| .input-container { | |
| @apply flex items-end space-x-3; | |
| } | |
| .message-input { | |
| @apply flex-1 resize-none; | |
| @apply border border-saap-gray-300 rounded-lg px-4 py-3; | |
| @apply focus:outline-none focus:ring-2 focus:ring-saap-primary-500 focus:border-transparent; | |
| @apply disabled:bg-saap-gray-100 disabled:cursor-not-allowed; | |
| min-height: 44px; | |
| max-height: 120px; | |
| } | |
| .send-button { | |
| @apply bg-saap-primary-600 hover:bg-saap-primary-700; | |
| @apply text-white p-3 rounded-lg; | |
| @apply disabled:bg-saap-gray-400 disabled:cursor-not-allowed; | |
| @apply transition-colors duration-200; | |
| @apply flex-shrink-0; | |
| } | |
| .quick-actions { | |
| @apply flex flex-wrap gap-2; | |
| } | |
| .quick-action { | |
| @apply px-3 py-1 text-xs; | |
| @apply bg-saap-gray-100 hover:bg-saap-gray-200; | |
| @apply text-saap-gray-700 rounded-full; | |
| @apply transition-colors duration-200; | |
| } | |
| /* Agent-specific colors */ | |
| .bg-agent-jane { | |
| @apply bg-saap-jane; | |
| } | |
| .bg-agent-john { | |
| @apply bg-saap-john; | |
| } | |
| .bg-agent-lara { | |
| @apply bg-saap-lara; | |
| } | |
| .bg-agent-justus { | |
| @apply bg-saap-justus; | |
| } | |
| /* Responsive */ | |
| @media (max-width: 640px) { | |
| .chat-modal { | |
| @apply h-[90vh] m-2; | |
| } | |
| .user-message .message-content, | |
| .agent-message .message-content { | |
| @apply max-w-xs; | |
| } | |
| .quick-actions { | |
| @apply hidden; | |
| } | |
| } | |
| /* Scrollbar */ | |
| .chat-messages::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .chat-messages::-webkit-scrollbar-track { | |
| @apply bg-saap-gray-200; | |
| } | |
| .chat-messages::-webkit-scrollbar-thumb { | |
| @apply bg-saap-gray-400 rounded-full; | |
| } | |
| .chat-messages::-webkit-scrollbar-thumb:hover { | |
| @apply bg-saap-gray-500; | |
| } | |
| </style> |