| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="chat-container"> |
| | | <!-- 左侧åå²å¯¹è¯å表 --> |
| | | <div class="sidebar"> |
| | | <div class="sidebar-header"> |
| | | <el-button type="primary" icon="el-icon-plus" @click="createNewChat"> |
| | | æ°å»ºå¯¹è¯ |
| | | </el-button> |
| | | </div> |
| | | |
| | | <div class="chat-list"> |
| | | <div |
| | | v-for="(chat, index) in chatList" |
| | | :key="index" |
| | | :class="['chat-item', currentChatIndex === index ? 'active' : '']" |
| | | @click="switchChat(index)" |
| | | > |
| | | <div class="chat-item-icon"> |
| | | <i class="el-icon-chat-dot-round" /> |
| | | </div> |
| | | <div class="chat-item-content"> |
| | | <div class="chat-item-title">{{ chat.title }}</div> |
| | | <div class="chat-item-time">{{ chat.time }}</div> |
| | | </div> |
| | | <div class="chat-item-delete" @click.stop="deleteChat(index)"> |
| | | <i class="el-icon-close" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è天主åºå --> |
| | | <div class="chat-main"> |
| | | <!-- æ¶æ¯å表 --> |
| | | <div ref="messageList" class="message-list"> |
| | | <div |
| | | v-for="(message, index) in messages" |
| | | :key="index" |
| | | :class="['message-item', message.role === 'user' ? 'message-user' : 'message-ai']" |
| | | > |
| | | <!-- AI 头å --> |
| | | <div v-if="message.role === 'assistant'" class="avatar avatar-ai"> |
| | | <span>AI</span> |
| | | </div> |
| | | |
| | | <!-- æ¶æ¯å
容 --> |
| | | <div class="message-content"> |
| | | <div class="message-info"> |
| | | <span class="sender-name">{{ message.role === 'user' ? 'æ' : 'AI 婿' }}</span> |
| | | <span class="message-time">{{ message.time }}</span> |
| | | </div> |
| | | <div class="message-text" v-html="formattedContent(message.content)" /> |
| | | |
| | | <!-- æµå¼å è½½ä¸çå
æ --> |
| | | <span v-if="message.streaming" class="cursor-blink">|</span> |
| | | </div> |
| | | |
| | | <!-- ç¨æ·å¤´å --> |
| | | <div v-if="message.role === 'user'" class="avatar avatar-user"> |
| | | <span>æ</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å è½½ä¸æç¤º --> |
| | | <div v-if="isLoading" class="loading-indicator"> |
| | | <span class="loading-dot" /> |
| | | <span class="loading-dot" /> |
| | | <span class="loading-dot" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¾å
¥åºå --> |
| | | <div class="input-area"> |
| | | <div class="input-wrapper"> |
| | | <el-input |
| | | v-model="userInput" |
| | | type="textarea" |
| | | :rows="3" |
| | | autofocus |
| | | placeholder="请è¾å
¥æ¨çé®é¢... (å车åé)" |
| | | resize="none" |
| | | class="chat-input" |
| | | @keyup.enter.native="sendMessage" |
| | | @keydown.enter.native="e => e.preventDefault()" |
| | | /> |
| | | <!-- @keydown.enter.exact.prevent="sendMessage"--> |
| | | <el-button |
| | | type="primary" |
| | | :disabled="!userInput.trim() || isLoading" |
| | | class="send-button" |
| | | @click="sendMessage" |
| | | > |
| | | {{ isLoading ? 'æèä¸...' : 'åé' }} |
| | | </el-button> |
| | | </div> |
| | | <div class="input-tips"> |
| | | <span>AI çæå
容ä»
ä¾åèï¼è¯·è°¨æ
çå«</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import { marked } from 'marked' |
| | | |
| | | // é
ç½® marked é项 |
| | | marked.setOptions({ |
| | | breaks: true, // æ¯æ GFM æ¢è¡ |
| | | gfm: true, // GitHub Flavored Markdown |
| | | sanitize: false // ä¸è¿æ»¤ HTMLï¼å¦æéè¦ XSS ä¿æ¤ï¼å¯ä»¥ä½¿ç¨ DOMPurifyï¼ |
| | | }) |
| | | |
| | | export default { |
| | | name: 'ProcessSetting', |
| | | data() { |
| | | return { |
| | | userInput: '', |
| | | messages: [], |
| | | isLoading: false, |
| | | currentStreamingIndex: null, |
| | | // é¿éäºåé®é
ç½® |
| | | API_KEY: 'sk-825b87021a0a4dfb9d3fdf20acb4fcc2', // è¯·æ¿æ¢ä¸ºä½ ç API Key |
| | | API_URL: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', |
| | | // åå²å¯¹è¯ç¸å
³ |
| | | chatList: [], |
| | | currentChatIndex: 0 |
| | | } |
| | | }, |
| | | watch: { |
| | | // ç嬿¶æ¯ååï¼èªå¨æ´æ°æ é¢ |
| | | messages: { |
| | | handler() { |
| | | this.updateChatTitle() |
| | | this.saveChatHistory() |
| | | }, |
| | | deep: true |
| | | } |
| | | }, |
| | | mounted() { |
| | | // åå§åæ¬¢è¿æ¶æ¯ |
| | | this.createNewChat() |
| | | // 仿¬å°åå¨å è½½åå²å¯¹è¯ |
| | | this.loadChatHistory() |
| | | }, |
| | | methods: { |
| | | // ä½¿ç¨ marked åºæ¸²æ Markdown |
| | | formattedContent(content) { |
| | | if (!content) return '' |
| | | |
| | | try { |
| | | return marked.parse(content) |
| | | } catch (e) { |
| | | console.error('Markdown è§£æå¤±è´¥:', e) |
| | | return content.replace(/\n/g, '<br>') |
| | | } |
| | | }, |
| | | |
| | | // å¤ç Enter é® - ç´æ¥åé |
| | | handleEnterKey(event) { |
| | | event.preventDefault() |
| | | this.sendMessage() |
| | | }, |
| | | |
| | | // åéæ¶æ¯ |
| | | async sendMessage() { |
| | | const text = this.userInput.trim() |
| | | if (!text || this.isLoading) return |
| | | |
| | | // æ·»å ç¨æ·æ¶æ¯ |
| | | this.addMessage('user', text) |
| | | this.userInput = '' |
| | | |
| | | // å建 AI å夿¶æ¯ï¼ç©ºå
å®¹ï¼ |
| | | const aiMessageIndex = this.addMessage('assistant', '', true) |
| | | |
| | | // è°ç¨é¿éäºå鮿µå¼æ¥å£ |
| | | await this.fetchQwenStreamingResponse(aiMessageIndex) |
| | | }, |
| | | |
| | | // æ·»å æ¶æ¯å°å表 |
| | | addMessage(role, content, streaming = false) { |
| | | const now = new Date() |
| | | const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}` |
| | | |
| | | const message = { |
| | | role, |
| | | content, |
| | | time, |
| | | streaming |
| | | } |
| | | |
| | | this.messages.push(message) |
| | | this.$nextTick(() => { |
| | | this.scrollToBottom() |
| | | }) |
| | | |
| | | return this.messages.length - 1 |
| | | }, |
| | | |
| | | // é¿éäºå鮿µå¼ååº |
| | | async fetchQwenStreamingResponse(messageIndex) { |
| | | this.isLoading = true |
| | | this.currentStreamingIndex = messageIndex |
| | | |
| | | try { |
| | | // æå»ºåå²å¯¹è¯ï¼ç¨äºä¸ä¸æï¼ |
| | | const conversationHistory = this.messages |
| | | .filter((msg, idx) => idx < this.messages.length - 1 || msg.role === 'user') |
| | | .map(msg => ({ |
| | | role: msg.role === 'assistant' ? 'assistant' : 'user', |
| | | content: msg.content |
| | | })) |
| | | |
| | | // è·åæå䏿¡ç¨æ·æ¶æ¯ä½ä¸ºå½åé®é¢ |
| | | const lastUserMessage = this.messages[this.messages.length - 2] |
| | | if (lastUserMessage) { |
| | | conversationHistory.push({ |
| | | role: 'user', |
| | | content: lastUserMessage.content |
| | | }) |
| | | } |
| | | |
| | | const response = await fetch(this.API_URL, { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': `Bearer ${this.API_KEY}` |
| | | }, |
| | | body: JSON.stringify({ |
| | | model: 'qwen-plus', // æ qwen-turbo, qwen-max ç |
| | | messages: conversationHistory, |
| | | stream: true, // å¯ç¨æµå¼è¾åº |
| | | temperature: 0.7 |
| | | }) |
| | | }) |
| | | |
| | | if (!response.ok) { |
| | | throw new Error(`HTTP error! status: ${response.status}`) |
| | | } |
| | | |
| | | // å¤çæµå¼ååº |
| | | const reader = response.body.getReader() |
| | | const decoder = new TextDecoder('utf-8') |
| | | let accumulatedContent = '' |
| | | |
| | | // eslint-disable-next-line no-constant-condition |
| | | while (true) { |
| | | const { done, value } = await reader.read() |
| | | |
| | | if (done) { |
| | | break |
| | | } |
| | | |
| | | // è§£ç æ°æ® |
| | | const chunk = decoder.decode(value, { stream: true }) |
| | | const lines = chunk.split('\n') |
| | | |
| | | for (const line of lines) { |
| | | if (line.startsWith('data: ')) { |
| | | const data = line.slice(6) |
| | | |
| | | // [DONE] æ 记表示æµå¼ç»æ |
| | | if (data.trim() === '[DONE]') { |
| | | continue |
| | | } |
| | | |
| | | try { |
| | | const parsed = JSON.parse(data) |
| | | const delta = parsed.choices?.[0]?.delta?.content |
| | | |
| | | if (delta) { |
| | | accumulatedContent += delta |
| | | |
| | | // ä½¿ç¨ $set ç¡®ä¿ Vue2 çååºå¼æ´æ° |
| | | this.$set(this.messages[messageIndex], 'content', accumulatedContent) |
| | | |
| | | // æ»å¨å°åºé¨ |
| | | this.$nextTick(() => { |
| | | this.scrollToBottom() |
| | | }) |
| | | } |
| | | } catch (e) { |
| | | console.error('è§£æ SSE æ°æ®å¤±è´¥:', e) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // ç»ææµå¼ç¶æ |
| | | this.$set(this.messages[messageIndex], 'streaming', false) |
| | | } catch (error) { |
| | | console.error('è°ç¨å鮿¥å£å¤±è´¥:', error) |
| | | this.$message.error('AI ååºå¤±è´¥ï¼' + error.message) |
| | | |
| | | // 设置é误信æ¯å°æ¶æ¯ä¸ |
| | | this.$set(this.messages[messageIndex], 'content', 'æ±æï¼ååºå¤±è´¥ï¼' + error.message) |
| | | this.$set(this.messages[messageIndex], 'streaming', false) |
| | | } finally { |
| | | this.isLoading = false |
| | | this.currentStreamingIndex = null |
| | | } |
| | | }, |
| | | |
| | | // æ»å¨å°åºé¨ |
| | | scrollToBottom() { |
| | | const container = this.$refs.messageList |
| | | if (container) { |
| | | this.$nextTick(() => { |
| | | container.scrollTop = container.scrollHeight |
| | | }) |
| | | } |
| | | }, |
| | | |
| | | // å建æ°å¯¹è¯ |
| | | createNewChat() { |
| | | const now = new Date() |
| | | const time = `${now.getMonth() + 1}/${now.getDate()} ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}` |
| | | |
| | | const newChat = { |
| | | title: 'æ°å¯¹è¯', |
| | | time: time, |
| | | messages: [{ |
| | | role: 'assistant', |
| | | content: 'æ¨å¥½ï¼ææ¯ AI 婿ï¼è¯·é®æä»ä¹å¯ä»¥å¸®æ¨ï¼', |
| | | time: time, |
| | | streaming: false |
| | | }] |
| | | } |
| | | |
| | | this.chatList.unshift(newChat) |
| | | this.currentChatIndex = 0 |
| | | this.messages = newChat.messages |
| | | this.saveChatHistory() |
| | | }, |
| | | |
| | | // åæ¢å¯¹è¯ |
| | | switchChat(index) { |
| | | this.currentChatIndex = index |
| | | this.messages = this.chatList[index].messages |
| | | this.$nextTick(() => { |
| | | this.scrollToBottom() |
| | | }) |
| | | }, |
| | | |
| | | // å é¤å¯¹è¯ |
| | | deleteChat(index) { |
| | | this.$confirm('ç¡®å®è¦å é¤è¿ä¸ªå¯¹è¯åï¼', 'æç¤º', { |
| | | confirmButtonText: 'ç¡®å®', |
| | | cancelButtonText: 'åæ¶', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | this.chatList.splice(index, 1) |
| | | |
| | | if (this.chatList.length === 0) { |
| | | this.createNewChat() |
| | | } else if (index === this.currentChatIndex) { |
| | | this.currentChatIndex = Math.max(0, index - 1) |
| | | this.switchChat(this.currentChatIndex) |
| | | } else if (index < this.currentChatIndex) { |
| | | this.currentChatIndex-- |
| | | } |
| | | |
| | | this.saveChatHistory() |
| | | this.$message.success('å 餿å') |
| | | }).catch(() => {}) |
| | | }, |
| | | |
| | | // æ´æ°å½åå¯¹è¯æ é¢ |
| | | updateChatTitle() { |
| | | if (this.messages.length > 0 && this.chatList[this.currentChatIndex]) { |
| | | const firstUserMessage = this.messages.find(msg => msg.role === 'user') |
| | | if (firstUserMessage) { |
| | | const title = firstUserMessage.content.substring(0, 20) + (firstUserMessage.content.length > 20 ? '...' : '') |
| | | this.$set(this.chatList[this.currentChatIndex], 'title', title) |
| | | this.saveChatHistory() |
| | | } |
| | | } |
| | | }, |
| | | |
| | | // ä¿å对è¯åå²å°æ¬å°åå¨ |
| | | saveChatHistory() { |
| | | try { |
| | | localStorage.setItem('chatHistory', JSON.stringify({ |
| | | chatList: this.chatList, |
| | | currentChatIndex: this.currentChatIndex |
| | | })) |
| | | } catch (e) { |
| | | console.error('ä¿åè天记å½å¤±è´¥:', e) |
| | | } |
| | | }, |
| | | |
| | | // 仿¬å°åå¨å 载对è¯åå² |
| | | loadChatHistory() { |
| | | try { |
| | | const saved = localStorage.getItem('chatHistory') |
| | | if (saved) { |
| | | const data = JSON.parse(saved) |
| | | this.chatList = data.chatList || [] |
| | | this.currentChatIndex = data.currentChatIndex || 0 |
| | | |
| | | if (this.chatList.length > 0) { |
| | | this.messages = this.chatList[this.currentChatIndex].messages |
| | | } else { |
| | | this.createNewChat() |
| | | } |
| | | } |
| | | } catch (e) { |
| | | console.error('å è½½è天记å½å¤±è´¥:', e) |
| | | this.createNewChat() |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .chat-container { |
| | | display: flex; |
| | | height: 100vh; |
| | | background: #f5f7fa; |
| | | } |
| | | |
| | | .sidebar { |
| | | width: 260px; |
| | | background: #1a1a2e; |
| | | display: flex; |
| | | flex-direction: column; |
| | | //border-radius: 8px 0 0 8px; |
| | | overflow: hidden; |
| | | |
| | | .sidebar-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid rgba(255, 255, 255, 0.1); |
| | | |
| | | button { |
| | | width: 100%; |
| | | background: rgba(255, 255, 255, 0.1); |
| | | border-color: transparent; |
| | | color: white; |
| | | |
| | | &:hover { |
| | | background: rgba(255, 255, 255, 0.2); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .chat-list { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | padding: 8px; |
| | | |
| | | &::-webkit-scrollbar { |
| | | width: 4px; |
| | | } |
| | | |
| | | &::-webkit-scrollbar-thumb { |
| | | background: rgba(255, 255, 255, 0.2); |
| | | border-radius: 2px; |
| | | } |
| | | } |
| | | |
| | | .chat-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 12px; |
| | | margin-bottom: 4px; |
| | | border-radius: 8px; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | position: relative; |
| | | |
| | | &:hover { |
| | | background: rgba(255, 255, 255, 0.1); |
| | | |
| | | .chat-item-delete { |
| | | opacity: 1; |
| | | } |
| | | } |
| | | |
| | | &.active { |
| | | background: rgba(64, 158, 255, 0.2); |
| | | border-left: 3px solid #409eff; |
| | | } |
| | | |
| | | .chat-item-icon { |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 8px; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: white; |
| | | font-size: 16px; |
| | | margin-right: 12px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .chat-item-content { |
| | | flex: 1; |
| | | overflow: hidden; |
| | | |
| | | .chat-item-title { |
| | | color: white; |
| | | font-size: 14px; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .chat-item-time { |
| | | color: rgba(255, 255, 255, 0.5); |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | |
| | | .chat-item-delete { |
| | | opacity: 0; |
| | | color: rgba(255, 255, 255, 0.6); |
| | | font-size: 14px; |
| | | padding: 4px; |
| | | transition: opacity 0.3s; |
| | | |
| | | &:hover { |
| | | color: #ff4d4f; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .chat-main { |
| | | flex: 1; |
| | | overflow: hidden; |
| | | padding: 20px; |
| | | background: white; |
| | | border-radius: 0 8px 8px 0; |
| | | } |
| | | |
| | | .message-list { |
| | | height: 85%; |
| | | overflow-y: auto; |
| | | padding: 0 10px; |
| | | |
| | | &::-webkit-scrollbar { |
| | | width: 6px; |
| | | } |
| | | |
| | | &::-webkit-scrollbar-thumb { |
| | | background: #c0c4cc; |
| | | border-radius: 3px; |
| | | } |
| | | } |
| | | |
| | | .message-item { |
| | | display: flex; |
| | | margin-bottom: 24px; |
| | | align-items: flex-start; |
| | | |
| | | &.message-user { |
| | | flex-direction: row-reverse; |
| | | |
| | | .message-content { |
| | | margin-right: 12px; |
| | | |
| | | .message-info { |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .message-text { |
| | | background: #409eff; |
| | | color: white; |
| | | border-radius: 12px 12px 0 12px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | &.message-ai { |
| | | .message-content { |
| | | margin-left: 12px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .avatar { |
| | | width: 40px; |
| | | height: 40px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 14px; |
| | | font-weight: bold; |
| | | flex-shrink: 0; |
| | | |
| | | &.avatar-ai { |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | color: white; |
| | | } |
| | | |
| | | &.avatar-user { |
| | | background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); |
| | | color: white; |
| | | } |
| | | } |
| | | |
| | | .message-content { |
| | | max-width: 70%; |
| | | |
| | | .message-info { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin-bottom: 6px; |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .message-text { |
| | | padding: 12px 16px; |
| | | line-height: 1.6; |
| | | word-break: break-word; |
| | | box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| | | |
| | | ::v-deep { |
| | | pre { |
| | | background: #282c34; |
| | | color: #abb2bf; |
| | | padding: 12px; |
| | | border-radius: 6px; |
| | | overflow-x: auto; |
| | | margin: 8px 0; |
| | | } |
| | | |
| | | code { |
| | | background: rgba(27,31,35,0.05); |
| | | padding: 2px 4px; |
| | | border-radius: 3px; |
| | | font-family: Consolas, monospace; |
| | | } |
| | | |
| | | pre code { |
| | | background: transparent; |
| | | padding: 0; |
| | | } |
| | | |
| | | strong { |
| | | color: #303133; |
| | | } |
| | | |
| | | table { |
| | | border-collapse: collapse; |
| | | width: 100%; |
| | | margin: 12px 0; |
| | | font-size: 14px; |
| | | |
| | | th, td { |
| | | border: 1px solid #dcdfe6; |
| | | padding: 8px 12px; |
| | | text-align: left; |
| | | } |
| | | |
| | | th { |
| | | background: #f5f7fa; |
| | | font-weight: 600; |
| | | color: #606266; |
| | | } |
| | | |
| | | tr:nth-child(even) { |
| | | background: #fafafa; |
| | | } |
| | | |
| | | tr:hover { |
| | | background: #f5f7fa; |
| | | } |
| | | } |
| | | |
| | | blockquote { |
| | | border-left: 4px solid #409eff; |
| | | margin: 12px 0; |
| | | padding: 8px 16px; |
| | | background: #ecf5ff; |
| | | color: #606266; |
| | | } |
| | | |
| | | ul, ol { |
| | | padding-left: 24px; |
| | | margin: 8px 0; |
| | | |
| | | li { |
| | | margin: 4px 0; |
| | | } |
| | | } |
| | | |
| | | h1, h2, h3, h4, h5, h6 { |
| | | margin: 16px 0 8px; |
| | | color: #303133; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | h1 { font-size: 24px; } |
| | | h2 { font-size: 20px; } |
| | | h3 { font-size: 18px; } |
| | | h4 { font-size: 16px; } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .cursor-blink { |
| | | display: inline-block; |
| | | animation: blink 1s infinite; |
| | | color: #409eff; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | @keyframes blink { |
| | | 0%, 50% { opacity: 1; } |
| | | 51%, 100% { opacity: 0; } |
| | | } |
| | | |
| | | .loading-indicator { |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 4px; |
| | | padding: 16px; |
| | | |
| | | .loading-dot { |
| | | width: 8px; |
| | | height: 8px; |
| | | background: #409eff; |
| | | border-radius: 50%; |
| | | animation: bounce 1.4s infinite ease-in-out both; |
| | | |
| | | &:nth-child(1) { |
| | | animation-delay: -0.32s; |
| | | } |
| | | |
| | | &:nth-child(2) { |
| | | animation-delay: -0.16s; |
| | | } |
| | | } |
| | | } |
| | | |
| | | @keyframes bounce { |
| | | 0%, 80%, 100% { |
| | | transform: scale(0); |
| | | } |
| | | 40% { |
| | | transform: scale(1); |
| | | } |
| | | } |
| | | |
| | | .input-area { |
| | | background: white; |
| | | padding: 16px 20px ; |
| | | border-top: 1px solid #e4e7ed; |
| | | |
| | | .input-wrapper { |
| | | display: flex; |
| | | gap: 12px; |
| | | align-items: flex-end; |
| | | max-width: 1200px; |
| | | margin: 0 auto; |
| | | } |
| | | |
| | | .chat-input { |
| | | flex: 1; |
| | | |
| | | ::v-deep textarea { |
| | | resize: none; |
| | | padding: 12px 16px; |
| | | font-size: 14px; |
| | | line-height: 1.6; |
| | | |
| | | &:focus { |
| | | border-color: #409eff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .send-button { |
| | | min-width: 80px; |
| | | height: auto; |
| | | padding: 12px 24px; |
| | | } |
| | | |
| | | .input-tips { |
| | | text-align: center; |
| | | margin-top: 8px; |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | } |
| | | </style> |