|
| 1 | +<script setup lang="ts"> |
| 2 | +import { ref } from 'vue' |
| 3 | +import { ElMessage } from 'element-plus' |
| 4 | +import { ChatDotRound, Share, Refresh } from '@element-plus/icons-vue' |
| 5 | +import { useI18n } from 'vue-i18n' |
| 6 | +import { API } from './net' |
| 7 | +
|
| 8 | +const { t } = useI18n() |
| 9 | +
|
| 10 | +interface ChatMessage { |
| 11 | + id: string |
| 12 | + type: 'user' | 'assistant' |
| 13 | + content: string |
| 14 | + timestamp: Date |
| 15 | +} |
| 16 | +
|
| 17 | +const messages = ref<ChatMessage[]>([]) |
| 18 | +const inputMessage = ref('') |
| 19 | +const isLoading = ref(false) |
| 20 | +const chatContainer = ref<HTMLElement>() |
| 21 | +
|
| 22 | +// Add welcome message |
| 23 | +messages.value.push({ |
| 24 | + id: '1', |
| 25 | + type: 'assistant', |
| 26 | + content: 'Hello! I\'m your AI assistant. I can help you with:\n\n• Converting natural language to SQL queries\n• Generating test cases\n• Optimizing database queries\n\nHow can I assist you today?', |
| 27 | + timestamp: new Date() |
| 28 | +}) |
| 29 | +
|
| 30 | +const sendMessage = async () => { |
| 31 | + if (!inputMessage.value.trim() || isLoading.value) return |
| 32 | +
|
| 33 | + const userMessage: ChatMessage = { |
| 34 | + id: Date.now().toString(), |
| 35 | + type: 'user', |
| 36 | + content: inputMessage.value, |
| 37 | + timestamp: new Date() |
| 38 | + } |
| 39 | +
|
| 40 | + messages.value.push(userMessage) |
| 41 | + const userInput = inputMessage.value |
| 42 | + inputMessage.value = '' |
| 43 | + isLoading.value = true |
| 44 | +
|
| 45 | + try { |
| 46 | + // Call AI service through the backend API |
| 47 | + // For now, we'll simulate the AI response |
| 48 | + await simulateAIResponse(userInput) |
| 49 | + } catch (error) { |
| 50 | + ElMessage.error('Failed to get AI response: ' + error) |
| 51 | + } finally { |
| 52 | + isLoading.value = false |
| 53 | + scrollToBottom() |
| 54 | + } |
| 55 | +} |
| 56 | +
|
| 57 | +const simulateAIResponse = async (userInput: string) => { |
| 58 | + // Simulate API delay |
| 59 | + await new Promise(resolve => setTimeout(resolve, 1000)) |
| 60 | +
|
| 61 | + let response = '' |
| 62 | + const lowerInput = userInput.toLowerCase() |
| 63 | +
|
| 64 | + if (lowerInput.includes('sql') || lowerInput.includes('query') || lowerInput.includes('database')) { |
| 65 | + response = `Here's a SQL query based on your request:\n\n\`\`\`sql\nSELECT * FROM users WHERE status = 'active'\nORDER BY created_at DESC\nLIMIT 10;\n\`\`\`\n\nThis query will retrieve the 10 most recently created active users.` |
| 66 | + } else if (lowerInput.includes('test') || lowerInput.includes('case')) { |
| 67 | + response = `Here's a test case structure for your API:\n\n\`\`\`json\n{\n "name": "Test User Creation",\n "request": {\n "method": "POST",\n "url": "/api/users",\n "body": {\n "name": "John Doe",\n "email": "[email protected]"\n }\n },\n "response": {\n "statusCode": 201,\n "body": {\n "id": "{{generated_id}}",\n "name": "John Doe",\n "email": "[email protected]"\n }\n }\n}\n\`\`\`\n\nThis test case validates user creation functionality.` |
| 68 | + } else { |
| 69 | + response = `I understand you're asking about: "${userInput}"\n\nI'm currently in MVP mode and can help with:\n• SQL query generation\n• Test case creation\n• Query optimization\n\nCould you please specify what type of assistance you need?` |
| 70 | + } |
| 71 | +
|
| 72 | + const assistantMessage: ChatMessage = { |
| 73 | + id: Date.now().toString(), |
| 74 | + type: 'assistant', |
| 75 | + content: response, |
| 76 | + timestamp: new Date() |
| 77 | + } |
| 78 | +
|
| 79 | + messages.value.push(assistantMessage) |
| 80 | +} |
| 81 | +
|
| 82 | +const clearChat = () => { |
| 83 | + messages.value = [{ |
| 84 | + id: '1', |
| 85 | + type: 'assistant', |
| 86 | + content: 'Hello! I\'m your AI assistant. I can help you with:\n\n• Converting natural language to SQL queries\n• Generating test cases\n• Optimizing database queries\n\nHow can I assist you today?', |
| 87 | + timestamp: new Date() |
| 88 | + }] |
| 89 | +} |
| 90 | +
|
| 91 | +const scrollToBottom = () => { |
| 92 | + setTimeout(() => { |
| 93 | + if (chatContainer.value) { |
| 94 | + chatContainer.value.scrollTop = chatContainer.value.scrollHeight |
| 95 | + } |
| 96 | + }, 100) |
| 97 | +} |
| 98 | +
|
| 99 | +const handleKeyPress = (event: KeyboardEvent) => { |
| 100 | + if (event.key === 'Enter' && !event.shiftKey) { |
| 101 | + event.preventDefault() |
| 102 | + sendMessage() |
| 103 | + } |
| 104 | +} |
| 105 | +
|
| 106 | +const formatMessage = (content: string) => { |
| 107 | + // Simple formatting for code blocks and line breaks |
| 108 | + return content |
| 109 | + .replace(/\n/g, '<br>') |
| 110 | + .replace(/```(\w+)?\n([\s\S]*?)```/g, '<pre><code class="language-$1">$2</code></pre>') |
| 111 | + .replace(/`([^`]+)`/g, '<code>$1</code>') |
| 112 | +} |
| 113 | +</script> |
| 114 | + |
| 115 | +<template> |
| 116 | + <div class="ai-assistant-container"> |
| 117 | + <div class="page-header"> |
| 118 | + <span class="page-title"> |
| 119 | + <el-icon><ChatDotRound /></el-icon> |
| 120 | + AI Assistant |
| 121 | + </span> |
| 122 | + <el-button type="primary" @click="clearChat" :icon="Refresh">Clear Chat</el-button> |
| 123 | + </div> |
| 124 | + |
| 125 | + <div class="chat-container" ref="chatContainer"> |
| 126 | + <div |
| 127 | + v-for="message in messages" |
| 128 | + :key="message.id" |
| 129 | + :class="['message', message.type]" |
| 130 | + > |
| 131 | + <div class="message-content"> |
| 132 | + <div class="message-header"> |
| 133 | + <span class="message-sender"> |
| 134 | + {{ message.type === 'user' ? 'You' : 'AI Assistant' }} |
| 135 | + </span> |
| 136 | + <span class="message-time"> |
| 137 | + {{ message.timestamp.toLocaleTimeString() }} |
| 138 | + </span> |
| 139 | + </div> |
| 140 | + <div class="message-text" v-html="formatMessage(message.content)"></div> |
| 141 | + </div> |
| 142 | + </div> |
| 143 | + |
| 144 | + <div v-if="isLoading" class="message assistant"> |
| 145 | + <div class="message-content"> |
| 146 | + <div class="message-header"> |
| 147 | + <span class="message-sender">AI Assistant</span> |
| 148 | + </div> |
| 149 | + <div class="message-text"> |
| 150 | + <el-icon class="is-loading"><ChatDotRound /></el-icon> |
| 151 | + Thinking... |
| 152 | + </div> |
| 153 | + </div> |
| 154 | + </div> |
| 155 | + </div> |
| 156 | + |
| 157 | + <div class="input-container"> |
| 158 | + <el-input |
| 159 | + v-model="inputMessage" |
| 160 | + type="textarea" |
| 161 | + :rows="3" |
| 162 | + placeholder="Ask me anything about SQL queries, test cases, or database optimization..." |
| 163 | + :disabled="isLoading" |
| 164 | + @keypress="handleKeyPress" |
| 165 | + class="message-input" |
| 166 | + /> |
| 167 | + <el-button |
| 168 | + type="primary" |
| 169 | + @click="sendMessage" |
| 170 | + :disabled="!inputMessage.trim() || isLoading" |
| 171 | + :icon="Share" |
| 172 | + class="send-button" |
| 173 | + > |
| 174 | + Send |
| 175 | + </el-button> |
| 176 | + </div> |
| 177 | + </div> |
| 178 | +</template> |
| 179 | + |
| 180 | +<style scoped> |
| 181 | +.ai-assistant-container { |
| 182 | + display: flex; |
| 183 | + flex-direction: column; |
| 184 | + height: calc(100vh - 60px); |
| 185 | + padding: 20px; |
| 186 | +} |
| 187 | +
|
| 188 | +.page-header { |
| 189 | + display: flex; |
| 190 | + justify-content: space-between; |
| 191 | + align-items: center; |
| 192 | + margin-bottom: 20px; |
| 193 | + padding-bottom: 10px; |
| 194 | + border-bottom: 1px solid var(--el-border-color); |
| 195 | +} |
| 196 | +
|
| 197 | +.page-title { |
| 198 | + font-size: 24px; |
| 199 | + font-weight: 600; |
| 200 | + display: flex; |
| 201 | + align-items: center; |
| 202 | + gap: 8px; |
| 203 | +} |
| 204 | +
|
| 205 | +.chat-container { |
| 206 | + flex: 1; |
| 207 | + overflow-y: auto; |
| 208 | + padding: 20px; |
| 209 | + background: var(--el-bg-color-page); |
| 210 | + border-radius: 8px; |
| 211 | + margin-bottom: 20px; |
| 212 | +} |
| 213 | +
|
| 214 | +.message { |
| 215 | + margin-bottom: 20px; |
| 216 | + display: flex; |
| 217 | +} |
| 218 | +
|
| 219 | +.message.user { |
| 220 | + justify-content: flex-end; |
| 221 | +} |
| 222 | +
|
| 223 | +.message.assistant { |
| 224 | + justify-content: flex-start; |
| 225 | +} |
| 226 | +
|
| 227 | +.message-content { |
| 228 | + max-width: 70%; |
| 229 | + padding: 12px 16px; |
| 230 | + border-radius: 12px; |
| 231 | + background: var(--el-bg-color); |
| 232 | + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| 233 | +} |
| 234 | +
|
| 235 | +.message.user .message-content { |
| 236 | + background: var(--el-color-primary); |
| 237 | + color: white; |
| 238 | +} |
| 239 | +
|
| 240 | +.message-header { |
| 241 | + display: flex; |
| 242 | + justify-content: space-between; |
| 243 | + align-items: center; |
| 244 | + margin-bottom: 8px; |
| 245 | + font-size: 12px; |
| 246 | + opacity: 0.7; |
| 247 | +} |
| 248 | +
|
| 249 | +.message-sender { |
| 250 | + font-weight: 600; |
| 251 | +} |
| 252 | +
|
| 253 | +.message-time { |
| 254 | + font-size: 11px; |
| 255 | +} |
| 256 | +
|
| 257 | +.message-text { |
| 258 | + line-height: 1.5; |
| 259 | + word-wrap: break-word; |
| 260 | +} |
| 261 | +
|
| 262 | +.message-text :deep(pre) { |
| 263 | + background: var(--el-fill-color-light); |
| 264 | + padding: 12px; |
| 265 | + border-radius: 6px; |
| 266 | + margin: 8px 0; |
| 267 | + overflow-x: auto; |
| 268 | +} |
| 269 | +
|
| 270 | +.message-text :deep(code) { |
| 271 | + background: var(--el-fill-color-light); |
| 272 | + padding: 2px 6px; |
| 273 | + border-radius: 4px; |
| 274 | + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; |
| 275 | + font-size: 13px; |
| 276 | +} |
| 277 | +
|
| 278 | +.input-container { |
| 279 | + display: flex; |
| 280 | + gap: 12px; |
| 281 | + align-items: flex-end; |
| 282 | +} |
| 283 | +
|
| 284 | +.message-input { |
| 285 | + flex: 1; |
| 286 | +} |
| 287 | +
|
| 288 | +.send-button { |
| 289 | + height: 40px; |
| 290 | +} |
| 291 | +
|
| 292 | +.is-loading { |
| 293 | + animation: rotating 2s linear infinite; |
| 294 | +} |
| 295 | +
|
| 296 | +@keyframes rotating { |
| 297 | + from { |
| 298 | + transform: rotate(0deg); |
| 299 | + } |
| 300 | + to { |
| 301 | + transform: rotate(360deg); |
| 302 | + } |
| 303 | +} |
| 304 | +</style> |
0 commit comments