Spaces:
Running
Running
Download web/src/views/ChatView.vue from Benaah/MicProtocol: direct link, hf CLI and curl.
- Browser
- Download file 8.05 kB
-
https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/web/src/views/ChatView.vue
- Command line
-
hf download hf://spaces/Benaah/MicProtocol/web/src/views/ChatView.vue
-
curl -L -o ChatView.vue https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/web/src/views/ChatView.vue
8.05 kB
| <template> | |
| <div class="chat-view"> | |
| <!-- Chat Header --> | |
| <div class="chat-header"> | |
| <div class="chat-title"> | |
| <n-icon size="24" class="chat-icon"><chatbubbles-outline /></n-icon> | |
| <div> | |
| <h2>Ask the Auditor</h2> | |
| <p class="chat-subtitle">Ask questions about audit reports, findings, and public expenditure</p> | |
| </div> | |
| </div> | |
| <n-button quaternary size="small" @click="chatStore.clearChat"> | |
| <template #icon><n-icon><trash-outline /></n-icon></template> | |
| New Chat | |
| </n-button> | |
| </div> | |
| <!-- Messages Area --> | |
| <div class="messages-container" ref="messagesContainer"> | |
| <div class="messages-list"> | |
| <chat-message | |
| v-for="message in chatStore.allMessages" | |
| :key="message.id" | |
| :message="message" | |
| @feedback="handleFeedback" | |
| @citation-click="handleCitationClick" | |
| /> | |
| </div> | |
| </div> | |
| <!-- Input Area --> | |
| <div class="input-area"> | |
| <div class="input-container"> | |
| <!-- Suggested Questions --> | |
| <div v-if="showSuggestions" class="suggestions"> | |
| <p class="suggestions-label">Try asking:</p> | |
| <div class="suggestion-chips"> | |
| <n-button | |
| v-for="suggestion in suggestedQuestions" | |
| :key="suggestion" | |
| quaternary | |
| size="small" | |
| class="suggestion-chip" | |
| @click="sendSuggestion(suggestion)" | |
| > | |
| {{ suggestion }} | |
| </n-button> | |
| </div> | |
| </div> | |
| <div class="input-row"> | |
| <n-input | |
| v-model:value="inputMessage" | |
| type="textarea" | |
| placeholder="Ask about audit findings, ministries, counties, or specific reports..." | |
| :autosize="{ minRows: 1, maxRows: 4 }" | |
| class="chat-input" | |
| @keydown.enter.prevent="handleSend" | |
| /> | |
| <n-button | |
| type="primary" | |
| class="send-btn" | |
| :disabled="!inputMessage.trim() || chatStore.isLoading" | |
| :loading="chatStore.isLoading" | |
| @click="handleSend" | |
| > | |
| <template #icon> | |
| <n-icon><send-outline /></n-icon> | |
| </template> | |
| </n-button> | |
| </div> | |
| <div class="input-meta"> | |
| <span class="disclaimer"> | |
| Responses are generated from public OAG reports. Always verify with original documents. | |
| </span> | |
| <n-tag v-if="chatStore.isLoading" size="small" type="info" class="typing-tag"> | |
| <template #icon> | |
| <span class="typing-indicator"> | |
| <span class="typing-dot"></span> | |
| <span class="typing-dot"></span> | |
| <span class="typing-dot"></span> | |
| </span> | |
| </template> | |
| Searching reports... | |
| </n-tag> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Citation Modal --> | |
| <n-modal v-model:show="showCitationModal" preset="card" style="max-width: 700px"> | |
| <template #header> | |
| <div class="citation-header"> | |
| <n-icon size="20"><document-text-outline /></n-icon> | |
| <span>Source Document</span> | |
| </div> | |
| </template> | |
| <div v-if="selectedCitation" class="citation-content"> | |
| <h3>{{ selectedCitation.title }}</h3> | |
| <p class="citation-meta">Page {{ selectedCitation.page }}, Paragraph {{ selectedCitation.paragraph }}</p> | |
| <n-blockquote>{{ selectedCitation.excerpt }}</n-blockquote> | |
| <n-button type="primary" tag="a" :href="selectedCitation.url" target="_blank"> | |
| <template #icon><n-icon><open-outline /></n-icon></template> | |
| Open Full Report | |
| </n-button> | |
| </div> | |
| </n-modal> | |
| </div> | |
| </template> | |
| <script setup lang="ts"> | |
| import { ref, computed, nextTick, watch } from 'vue' | |
| import { useChatStore } from '@/stores/chat' | |
| import type { Citation } from '@/types' | |
| import { | |
| ChatbubblesOutline, | |
| TrashOutline, | |
| SendOutline, | |
| DocumentTextOutline, | |
| OpenOutline, | |
| } from '@vicons/ionicons5' | |
| import ChatMessage from '@/components/ChatMessage.vue' | |
| const chatStore = useChatStore() | |
| const inputMessage = ref('') | |
| const messagesContainer = ref<HTMLElement>() | |
| const showCitationModal = ref(false) | |
| const selectedCitation = ref<Citation | null>(null) | |
| const suggestedQuestions = [ | |
| 'How much was lost in the Ministry of Health audit?', | |
| 'Which counties received qualified opinions in 2023/24?', | |
| 'What were the findings in the NYS special audit?', | |
| 'Show me irregular expenditures in Nairobi County', | |
| 'What does the Constitution say about public audit?', | |
| ] | |
| const showSuggestions = computed(() => chatStore.messages.length <= 1 && !chatStore.isLoading) | |
| const scrollToBottom = () => { | |
| nextTick(() => { | |
| if (messagesContainer.value) { | |
| messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight | |
| } | |
| }) | |
| } | |
| watch(() => chatStore.allMessages.length, scrollToBottom) | |
| const handleSend = () => { | |
| if (!inputMessage.value.trim() || chatStore.isLoading) return | |
| chatStore.sendMessage(inputMessage.value) | |
| inputMessage.value = '' | |
| } | |
| const sendSuggestion = (suggestion: string) => { | |
| chatStore.sendMessage(suggestion) | |
| } | |
| const handleFeedback = (messageId: string, rating: number, correction?: string) => { | |
| chatStore.submitFeedback(messageId, rating, correction) | |
| } | |
| const handleCitationClick = (citation: Citation) => { | |
| selectedCitation.value = citation | |
| showCitationModal.value = true | |
| } | |
| </script> | |
| <style scoped lang="scss"> | |
| .chat-view { | |
| display: flex; | |
| flex-direction: column; | |
| height: calc(100vh - 64px - 48px); | |
| max-height: 800px; | |
| } | |
| .chat-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 16px 0; | |
| border-bottom: 1px solid var(--border); | |
| margin-bottom: 16px; | |
| } | |
| .chat-title { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| h2 { | |
| font-size: 20px; | |
| font-weight: 600; | |
| margin: 0; | |
| } | |
| } | |
| .chat-icon { | |
| color: var(--primary); | |
| } | |
| .chat-subtitle { | |
| font-size: 13px; | |
| color: var(--text-muted); | |
| margin: 4px 0 0; | |
| } | |
| .messages-container { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 16px 0; | |
| } | |
| .messages-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 16px; | |
| } | |
| .input-area { | |
| border-top: 1px solid var(--border); | |
| padding: 16px 0; | |
| } | |
| .input-container { | |
| max-width: 800px; | |
| margin: 0 auto; | |
| } | |
| .suggestions { | |
| margin-bottom: 12px; | |
| } | |
| .suggestions-label { | |
| font-size: 12px; | |
| color: var(--text-muted); | |
| margin-bottom: 8px; | |
| display: block; | |
| } | |
| .suggestion-chips { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 8px; | |
| } | |
| .suggestion-chip { | |
| font-size: 13px; | |
| } | |
| .input-row { | |
| display: flex; | |
| gap: 8px; | |
| align-items: flex-end; | |
| } | |
| .chat-input { | |
| flex: 1; | |
| :deep(.n-input__textarea-el) { | |
| font-size: 15px; | |
| line-height: 1.5; | |
| } | |
| } | |
| .send-btn { | |
| height: 42px; | |
| width: 42px; | |
| border-radius: 50%; | |
| } | |
| .input-meta { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-top: 8px; | |
| } | |
| .disclaimer { | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| } | |
| .typing-tag { | |
| :deep(.n-tag__icon) { | |
| margin-right: 4px; | |
| } | |
| } | |
| .typing-indicator { | |
| display: inline-flex; | |
| gap: 3px; | |
| align-items: center; | |
| } | |
| .typing-dot { | |
| width: 4px; | |
| height: 4px; | |
| background: currentColor; | |
| border-radius: 50%; | |
| animation: typing 1.4s infinite ease-in-out both; | |
| } | |
| .typing-dot:nth-child(1) { animation-delay: -0.32s; } | |
| .typing-dot:nth-child(2) { animation-delay: -0.16s; } | |
| @keyframes typing { | |
| 0%, 80%, 100% { transform: scale(0); } | |
| 40% { transform: scale(1); } | |
| } | |
| .citation-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .citation-content { | |
| h3 { | |
| font-size: 16px; | |
| font-weight: 600; | |
| margin-bottom: 8px; | |
| } | |
| } | |
| .citation-meta { | |
| font-size: 13px; | |
| color: var(--text-muted); | |
| margin-bottom: 12px; | |
| } | |
| @media (max-width: 768px) { | |
| .chat-view { | |
| height: calc(100vh - 64px); | |
| } | |
| .input-row { | |
| flex-direction: column; | |
| } | |
| .send-btn { | |
| width: 100%; | |
| border-radius: var(--radius-md); | |
| } | |
| } | |
| </style> | |