Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/breakoutthough_chengzhilian.json
T

1 line
55 KiB
JSON

{"repo": "breakoutthough/chengzhilian", "files": {"frontend/src/views/admin/AiAssistant.vue": {"0195e1f0c527bb64ebf6e23b50c7ab2abe85f6a7": {"t": 1785746532.2601175, "n": 24688, "h": "a6aac6224c045c2b", "c": "<template>\n <div class=\"ai-assistant-container\">\n <!-- 聊天头部 -->\n <div class=\"chat-header\">\n <div class=\"header-left\">\n <img src=\"/assets/images/deepseek-color.png\" alt=\"DeepSeek Logo\" class=\"logo\" />\n <h2>Hi, 这里是基于DeepSeek打造的\"城治链\"智能AI助手</h2>\n </div>\n <div class=\"header-right\">\n <el-switch\n v-model=\"isDeepThinkingMode\"\n active-text=\"深度思考模式\"\n inactive-text=\"快速回答模式\"\n class=\"mode-toggle\"\n />\n </div>\n </div>\n \n <div class=\"welcome-text\">\n <p>你好~我是\"城治链\"智能AI助手,你可以向我提问任何问题,我都会尽力回答。</p>\n </div>\n \n <!-- 消息列表区域 -->\n <div class=\"messages\" ref=\"messagesRef\">\n <!-- 消息内容 -->\n <div v-for=\"(message, index) in messages\" :key=\"index\" \n :class=\"['message', message.role === 'user' ? 'user' : 'assistant']\">\n <div class=\"message-avatar\" v-if=\"message.role === 'assistant'\">\n <el-avatar :size=\"40\" :src=\"botAvatar\">AI</el-avatar>\n </div>\n <div class=\"message-bubble\">\n <div class=\"message-content\" v-html=\"formatMessage(message.content) || '&nbsp;'\"></div>\n \n <!-- 在第一条AI消息中添加快捷提示 -->\n <div v-if=\"message.role === 'assistant' && index === 0\" class=\"quick-suggestions\">\n <div v-for=\"(prompt, pIndex) in quickPrompts\" :key=\"pIndex\" class=\"suggestion-btn\" @click=\"insertPrompt(prompt.text)\">\n <span class=\"suggestion-icon\">{{ prompt.icon }}</span> {{ prompt.text }}\n </div>\n </div>\n \n <div v-if=\"message.role === 'assistant'\" class=\"message-actions\">\n <button class=\"copy-btn\" @click=\"copyMessage(message.content)\" title=\"复制内容\" :disabled=\"!message.content\">\n <el-icon><CopyDocument /></el-icon> 复制\n </button>\n </div>\n </div>\n <div class=\"message-avatar\" v-if=\"message.role === 'user'\">\n <el-avatar :size=\"40\" :src=\"userAvatar\">U</el-avatar>\n </div>\n \n <!-- 思维过程展示(仅深度思考模式下的AI回复) -->\n <div v-if=\"message.role === 'assistant' && message.reasoning_content && isDeepThinkingMode\" class=\"reasoning-content\">\n <div class=\"reasoning-header\">\n <span>思考过程</span>\n </div>\n <div class=\"reasoning-text\" v-html=\"formatMessage(message.reasoning_content)\"></div>\n </div>\n </div>\n \n <!-- 打字指示器 -->\n <div v-if=\"isTyping\" class=\"message assistant typing\">\n <div class=\"message-avatar\">\n <el-avatar :size=\"40\" :src=\"botAvatar\">AI</el-avatar>\n </div>\n <div class=\"message-bubble\">\n <div class=\"typing-indicator\">\n <span></span>\n <span></span>\n <span></span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- 输入区域 -->\n <div class=\"input-area\">\n <div class=\"input-wrapper\">\n <div class=\"input-box\">\n <div class=\"feature-notice\">\n <el-alert\n type=\"info\"\n :closable=\"false\"\n show-icon\n >\n <template #title>\n <span>暂时无法上传文件,但您可以输入网址或详细描述进行询问</span>\n </template>\n </el-alert>\n </div>\n \n <el-input\n v-model=\"userInput\"\n type=\"textarea\"\n :rows=\"2\"\n autosize\n :maxlength=\"2000\"\n show-word-limit\n placeholder=\"有问题,尽管问\"\n :disabled=\"isTyping\"\n @keyup.enter.ctrl=\"sendMessage\"\n ></el-input>\n \n <div class=\"input-hint\">按 Ctrl + Enter 发送</div>\n \n <div class=\"button-group\">\n <el-tooltip content=\"清空对话\" placement=\"top\">\n <button class=\"clear-btn\" @click=\"clearMessages\" :disabled=\"isTyping || messages.length === 0\">\n <el-icon><DeleteIcon /></el-icon>\n </button>\n </el-tooltip>\n \n <el-tooltip :content=\"isDeepThinkingMode ? '深度思考模式' : '快速回答模式'\" placement=\"top\">\n <button class=\"thinking-btn\" @click=\"toggleThinkingMode\" :class=\"{'active': isDeepThinkingMode}\">\n <el-icon><Help /></el-icon>\n </button>\n </el-tooltip>\n </div>\n </div>\n \n <button \n class=\"send-btn\" \n @click=\"sendMessage\" \n :disabled=\"!userInput.trim() || isTyping\"\n >\n <span v-if=\"!isTyping\">\n <el-icon class=\"send-icon\"><Promotion /></el-icon>\n </span>\n <span v-else>\n <el-icon class=\"send-icon\"><Loading /></el-icon>\n </span>\n </button>\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport { ref, nextTick, onMounted } from 'vue';\nimport { ElMessage } from 'element-plus';\nimport axios from 'axios';\nimport DOMPurify from 'dompurify';\nimport { marked } from 'marked';\nimport {\n Delete as DeleteIcon,\n Promotion,\n Help,\n Loading,\n Link as LinkIcon,\n CopyDocument,\n Document\n} from '@element-plus/icons-vue';\n\nexport default {\n name: 'AiAssistant',\n components: {\n DeleteIcon,\n Promotion,\n Help,\n Loading,\n LinkIcon,\n CopyDocument,\n Document\n },\n setup() {\n // 状态变量\n const userInput = ref('');\n const messages = ref([\n {\n role: \"assistant\",\n content: \"您好!我是基于DeepSeek打造的城治链智能AI助手,很高兴为您服务。您可以询问我任何问题,我会尽力提供简洁明了的回答。您也可以尝试使用上方的快捷提示,或者切换到深度思考模式获取更详细的分析。\",\n timestamp: new Date(),\n showReasoning: false\n }\n ]);\n const isTyping = ref(false);\n const isConnected = ref(true);\n const isDeepThinkingMode = ref(false);\n const messagesRef = ref(null);\n \n // 提示库 - 包含多种可能的提示\n const promptsLibrary = [\n { text: '自我介绍一下~', icon: '📋' },\n { text: '办理身份证需要准备什么?', icon: '🔄' },\n { text: '办理营业执照需要准备什么?', icon: '📑' },\n { text: '如何申请公积金贷款?', icon: '🏠' },\n { text: '社保缴费标准是多少?', icon: '💰' },\n { text: '如何预约政务大厅服务?', icon: '📅' },\n { text: '驾驶证到期如何续期?', icon: '🚗' },\n { text: '如何查询不动产信息?', icon: '🏢' },\n { text: '公积金提取条件是什么?', icon: '💼' },\n { text: '居住证办理流程是什么?', icon: '🏘️' },\n { text: '如何申请创业补贴?', icon: '💡' },\n { text: '个人所得税如何计算?', icon: '💲' },\n { text: '如何办理出生证明?', icon: '👶' },\n { text: '退休手续如何办理?', icon: '👴' },\n { text: '企业注册需要哪些材料?', icon: '🏭' }\n ];\n \n // 随机选择的快捷提示\n const quickPrompts = ref([]);\n \n // 随机选择提示\n const getRandomPrompts = () => {\n // 打乱提示库并选择前三个\n const shuffled = [...promptsLibrary].sort(() => 0.5 - Math.random());\n quickPrompts.value = shuffled.slice(0, 3);\n };\n \n // DeepSeek API配置\n const API_KEY = 'sk-482ddc70f6324e73ab3c7faad5e1b20c';\n const API_URL = 'https://api.deepseek.com/v1/chat/completions';\n \n // 头像\n const userAvatar = ref('');\n const botAvatar = ref('/assets/images/deepseek-color.png');\n \n // 获取用户头像\n const getUserAvatar = async () => {\n try {\n const token = localStorage.getItem('token');\n if (!token) return;\n \n const response = await axios.get('/api/auth/me', {\n headers: {\n Authorization: `Bearer ${token}`\n }\n });\n \n if (response.data.success) {\n userAvatar.value = response.data.data.avatarUrl;\n }\n } catch (error) {\n console.error('获取用户头像失败:', error);\n }\n };\n \n // 切换思考模式\n const toggleThinkingMode = () => {\n isDeepThinkingMode.value = !isDeepThinkingMode.value;\n ElMessage.success(`已切换为${isDeepThinkingMode.value ? '深度思考' : '快速回答'}模式`);\n };\n \n // 插入预设提示语\n const insertPrompt = (prompt) => {\n userInput.value = prompt;\n // 自动发送\n setTimeout(() => {\n sendMessage();\n }, 100);\n };\n \n // 格式化消息内容(支持markdown)\n const formatMessage = (content) => {\n if (!content || content.trim() === '') return '<p class=\"empty-message\">AI正在思考,请稍作等待~</p>';\n // 转换markdown为HTML并净化以防止XSS攻击\n return DOMPurify.sanitize(marked.parse(content));\n };\n \n // 格式化时间\n const formatTime = (timestamp) => {\n if (!timestamp) return '';\n const date = new Date(timestamp);\n return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;\n };\n \n // 滚动到底部\n const scrollToBottom = async () => {\n await nextTick();\n if (messagesRef.value) {\n messagesRef.value.scrollTop = messagesRef.value.scrollHeight;\n }\n };\n \n // 选择合适的模型\n const getApiModel = () => {\n // 根据思考模式选择模型\n return isDeepThinkingMode.value ? \"deepseek-reasoner\" : \"deepseek-chat\";\n };\n \n // 发送消息\n const sendMessage = async () => {\n if (!userInput.value.trim() || isTyping.value) return;\n \n // 设置正在输入状态\n isTyping.value = true;\n \n // 准备用户消息内容\n let userContent = userInput.value.trim();\n \n // 添加用户消息\n const userMessage = {\n role: \"user\",\n content: userContent,\n timestamp: new Date()\n };\n \n messages.value.push(userMessage);\n \n // 清空输入框\n userInput.value = '';\n \n // 提示音效果(可选)\n try {\n const audio = new Audio('/assets/sounds/message-sent.mp3');\n audio.volume = 0.5;\n audio.play().catch(e => console.log('无法播放提示音', e));\n } catch(e) {\n console.log('音频播放不受支持', e);\n }\n \n // 滚动到底部\n await scrollToBottom();\n \n // 准备AI响应消息\n const assistantMessage = {\n role: \"assistant\",\n content: \"\",\n reasoning_content: \"\",\n timestamp: new Date(),\n showReasoning: false\n };\n messages.value.push(assistantMessage);\n \n try {\n // 调用DeepSeek API\n const headers = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${API_KEY}`\n };\n \n // 准备消息内容\n let apiMessages = [\n { role: \"system\", content: \"你是基于DeepSeek打造的城治链智能AI助手,一个专业的智能助手,可以用简洁明了但信息丰富的方式回答用户的问题。你可以回答用户提供的网址内容相关问题,或基于用户详细描述提供帮助。\" },\n ...messages.value\n .filter(m => m.role === 'user' || (m.role === 'assistant' && m.content))\n .map(m => ({ role: m.role, content: m.content }))\n ];\n \n // 准备API请求内容\n const payload = {\n messages: apiMessages,\n model: getApiModel(),\n stream: true,\n temperature: 0.7,\n max_tokens: isDeepThinkingMode.value ? 2000 : 1000\n };\n \n console.log('发送请求到API:', JSON.stringify(payload, null, 2));\n \n const response = await fetch(API_URL, {\n method: 'POST',\n headers: headers,\n body: JSON.stringify(payload)\n });\n \n if (!response.ok) {\n const errorText = await response.text();\n console.error('API响应错误:', response.status, errorText);\n throw new Error(`API调用失败: ${response.status} - ${errorText}`);\n }\n \n // 处理流式响应\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let partialLine = '';\n let fullContent = '';\n let reasoningContent = '';\n \n let streamDone = false;\n while (!streamDone) {\n const { done, value } = await reader.read();\n if (done) {\n streamDone = true;\n break;\n }\n \n // 解码并处理流数据\n const text = decoder.decode(value);\n const lines = (partialLine + text).split('\\n');\n partialLine = lines.pop() || '';\n \n for (const line of lines) {\n if (line.trim() === '') continue;\n if (line.trim() === 'data: [DONE]') continue;\n \n try {\n const jsonData = JSON.parse(line.replace(/^data: /, ''));\n \n if (jsonData.choices && jsonData.choices.length > 0) {\n const choice = jsonData.choices[0];\n \n if (choice.delta && choice.delta.content) {\n // 更新内容\n fullContent += choice.delta.content;\n assistantMessage.content = fullContent;\n assistantMessage.timestamp = new Date();\n \n // 自动滚动到底部\n await scrollToBottom();\n }\n \n // 如果是深度思考模式且有思考过程\n if (isDeepThinkingMode.value && choice.delta && choice.delta.reasoning) {\n reasoningContent += choice.delta.reasoning;\n assistantMessage.reasoning_content = reasoningContent;\n }\n }\n } catch (e) {\n console.error('解析流数据出错:', e);\n }\n }\n }\n \n // 更新消息列表\n const lastIndex = messages.value.length - 1;\n if (lastIndex >= 0) {\n messages.value[lastIndex] = {\n ...assistantMessage,\n content: fullContent || \"抱歉,我的回答生成过程中出现了问题。\",\n reasoning_content: reasoningContent,\n timestamp: new Date()\n };\n }\n } catch (error) {\n console.error('API调用错误:', error);\n ElMessage.error('AI助手暂时无法回应,请稍后再试');\n \n // 出错时,更新消息为错误提示\n const lastIndex = messages.value.length - 1;\n if (lastIndex >= 0 && messages.value[lastIndex].role === 'assistant') {\n messages.value[lastIndex].content = '抱歉,我遇到了一些问题,无法回应您的请求。请稍后再试。';\n \n // 可选:允许用户重试\n ElMessage({\n message: '连接失败,请检查网络后重试',\n type: 'error',\n showClose: true,\n duration: 5000\n });\n }\n } finally {\n isTyping.value = false;\n await scrollToBottom();\n }\n };\n \n // 清空消息\n const clearMessages = () => {\n messages.value = [];\n ElMessage.success('对话已清空');\n };\n \n // 复制消息内容\n const copyMessage = (content) => {\n if (!content || content.trim() === '') {\n ElMessage.warning('没有可复制的内容');\n return;\n }\n \n // 创建一个临时textarea元素来复制纯文本内容\n const textArea = document.createElement('textarea');\n // 移除HTML标签获取纯文本\n const plainText = content.replace(/<[^>]*>/g, '');\n \n textArea.value = plainText;\n document.body.appendChild(textArea);\n textArea.select();\n \n try {\n document.execCommand('copy');\n ElMessage({\n message: '已复制到剪贴板',\n type: 'success',\n duration: 2000\n });\n } catch (err) {\n ElMessage.error('复制失败,请手动复制');\n }\n \n document.body.removeChild(textArea);\n };\n \n onMounted(() => {\n getUserAvatar();\n getRandomPrompts(); // 生成随机提示\n });\n \n return {\n userInput,\n messages,\n isTyping,\n isConnected,\n isDeepThinkingMode,\n messagesRef,\n userAvatar,\n botAvatar,\n quickPrompts,\n formatMessage,\n formatTime,\n sendMessage,\n clearMessages,\n insertPrompt,\n toggleThinkingMode,\n copyMessage,\n getApiModel\n };\n }\n}\n</script>\n\n<style scoped>\n.ai-assistant-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n background-color: #f9f9f9;\n overflow: hidden;\n font-family: \"PingFang SC\", \"Microsoft YaHei\", sans-serif;\n position: relative;\n}\n\n.chat-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 20px;\n background-color: #fff;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.header-left {\n display: flex;\n align-items: center;\n}\n\n.logo {\n width: 32px;\n height: 32px;\n margin-right: 10px;\n}\n\n.header-left h2 {\n margin: 0;\n font-size: 1.2rem;\n color: #1e80ff;\n font-weight: 600;\n}\n\n.mode-toggle {\n font-size: 0.8rem;\n}\n\n.welcome-text {\n text-align: center;\n padding: 15px 20px;\n color: #666;\n font-size: 14px;\n background-color: #fff;\n margin-bottom: 10px;\n border-bottom: 1px solid #eee;\n}\n\n.messages {\n flex: 1;\n padding: 10px 80px 20px;\n overflow-y: auto;\n scroll-behavior: smooth;\n background-color: #f9f9f9;\n}\n\n.message {\n display: flex;\n margin-bottom: 25px;\n align-items: flex-start;\n}\n\n.message.user {\n flex-direction: row-reverse;\n}\n\n.message-avatar {\n margin: 0 10px;\n flex-shrink: 0;\n}\n\n.message-bubble {\n max-width: 80%;\n padding: 12px 16px;\n border-radius: 10px;\n position: relative;\n min-height: 24px;\n min-width: 60px;\n}\n\n.user .message-bubble {\n background-color: #1e80ff;\n color: white;\n border-top-right-radius: 4px;\n margin-right: 10px;\n transform-origin: right top;\n animation: message-pop 0.3s ease;\n}\n\n.assistant .message-bubble {\n background-color: white;\n color: #333;\n border-top-left-radius: 4px;\n margin-left: 10px;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);\n transform-origin: left top;\n animation: message-pop 0.3s ease;\n border-left: 3px solid #1e80ff;\n padding: 12px 24px 12px 24px;\n}\n\n.message-content {\n word-break: break-word;\n line-height: 1.6;\n font-size: 14px;\n padding: 0 4px;\n min-height: 16px;\n}\n\n.empty-message {\n color: #999;\n font-style: italic;\n margin: 0;\n}\n\n.message-content p {\n margin: 0 0 10px;\n}\n\n.message-content p:last-child {\n margin-bottom: 0;\n}\n\n.message-content pre {\n background-color: #f8f8f8;\n padding: 10px;\n border-radius: 5px;\n overflow-x: auto;\n margin: 10px 0;\n}\n\n.message-content code {\n background-color: rgba(0, 0, 0, 0.05);\n padding: 2px 4px;\n border-radius: 3px;\n font-family: monospace;\n}\n\n.user .message-content code {\n background-color: rgba(255, 255, 255, 0.2);\n}\n\n.reasoning-content {\n margin-top: 10px;\n border-top: 1px dashed #dcdfe6;\n padding-top: 10px;\n background-color: #f9fafc;\n border-radius: 8px;\n padding: 10px 15px;\n margin-left: 60px;\n margin-right: 10px;\n position: relative;\n}\n\n.reasoning-header {\n font-size: 0.9rem;\n color: #1e80ff;\n font-weight: 500;\n display: flex;\n align-items: center;\n margin-bottom: 8px;\n}\n\n.reasoning-header::before {\n content: '';\n display: inline-block;\n width: 4px;\n height: 16px;\n background-color: #1e80ff;\n margin-right: 8px;\n border-radius: 2px;\n}\n\n.reasoning-text {\n padding: 8px;\n background-color: #fff;\n border-radius: 6px;\n font-size: 0.9rem;\n color: #606266;\n border-left: 2px solid #e6e6e6;\n}\n\n.typing-indicator {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n height: 24px;\n}\n\n.typing-indicator span {\n width: 6px;\n height: 6px;\n margin: 0 2px;\n background-color: #aaa;\n border-radius: 50%;\n display: inline-block;\n animation: bounce 1.4s infinite ease-in-out both;\n}\n\n.typing-indicator span:nth-child(1) {\n animation-delay: -0.32s;\n}\n\n.typing-indicator span:nth-child(2) {\n animation-delay: -0.16s;\n}\n\n.input-area {\n background-color: #fff;\n padding: 15px 20px;\n border-top: 1px solid #eee;\n}\n\n.input-wrapper {\n display: flex;\n align-items: flex-end;\n}\n\n.input-box {\n flex: 1;\n border: 1px solid #dcdfe6;\n border-radius: 8px;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n background-color: #fff;\n padding: 8px 12px;\n margin-right: 10px;\n}\n\n.input-box .el-textarea {\n border: none;\n padding: 0;\n}\n\n.input-box .el-textarea__inner {\n border: none;\n box-shadow: none !important;\n padding: 0;\n min-height: 42px !important;\n max-height: 120px;\n line-height: 1.5;\n font-size: 14px;\n}\n\n.input-hint {\n font-size: 12px;\n color: #999;\n text-align: right;\n margin-top: 4px;\n margin-bottom: 4px;\n}\n\n.button-group {\n display: flex;\n margin-top: 8px;\n align-items: center;\n}\n\n.clear-btn, .thinking-btn {\n background: none;\n border: none;\n color: #999;\n font-size: 16px;\n cursor: pointer;\n padding: 5px 8px;\n border-radius: 4px;\n margin-right: 10px;\n transition: all 0.2s;\n}\n\n.clear-btn:hover, .thinking-btn:hover {\n color: #1e80ff;\n background-color: #f2f6fc;\n}\n\n.clear-btn:disabled, .thinking-btn:disabled {\n color: #ccc;\n cursor: not-allowed;\n}\n\n.thinking-btn.active {\n color: #1e80ff;\n}\n\n.send-btn {\n background-color: #1e80ff;\n color: white;\n border: none;\n border-radius: 8px;\n width: 48px;\n height: 48px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s;\n box-shadow: 0 2px 6px rgba(30, 128, 255, 0.3);\n}\n\n.send-btn:hover {\n background-color: #1873e8;\n transform: translateY(-2px);\n box-shadow: 0 4px 8px rgba(30, 128, 255, 0.4);\n}\n\n.send-btn:disabled {\n background-color: #b3d4ff;\n cursor: not-allowed;\n transform: none;\n box-shadow: none;\n}\n\n.send-icon {\n font-size: 20px;\n}\n\n.footer-hint {\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n color: #999;\n margin-top: 10px;\n}\n\n.page-info {\n color: #666;\n}\n\n@keyframes bounce {\n 0%, 80%, 100% { transform: scale(0); }\n 40% { transform: scale(1); }\n}\n\n@keyframes message-pop {\n 0% { opacity: 0; transform: scale(0.8); }\n 100% { opacity: 1; transform: scale(1); }\n}\n\n/* 媒体查询 */\n@media (max-width: 768px) {\n .chat-header {\n padding: 12px 15px;\n }\n \n .welcome-text, .quick-actions {\n padding: 10px 15px;\n }\n \n .messages {\n padding: 10px 15px;\n }\n \n .message-bubble {\n max-width: 90%;\n padding: 10px 12px;\n }\n \n .input-area {\n padding: 12px 15px;\n }\n \n .action-btn {\n padding: 8px 12px;\n font-size: 13px;\n }\n}\n\n/* 在小屏幕上隐藏一些元素 */\n@media (max-width: 576px) {\n .welcome-text {\n padding: 10px 15px;\n font-size: 13px;\n }\n \n .input-hint {\n display: none;\n }\n \n .footer-hint {\n font-size: 10px;\n }\n \n .header-right {\n display: none;\n }\n \n .header-left h2 {\n font-size: 1rem;\n }\n}\n\n.message-actions {\n position: absolute;\n top: 8px;\n right: 8px;\n opacity: 1;\n transition: opacity 0.2s;\n}\n\n.message-bubble:hover .message-actions {\n opacity: 1;\n}\n\n.copy-btn {\n background: rgba(30, 128, 255, 0.1);\n border: none;\n color: #1e80ff;\n cursor: pointer;\n padding: 5px 10px;\n border-radius: 4px;\n transition: all 0.2s;\n font-size: 13px;\n display: flex;\n align-items: center;\n gap: 4px;\n font-weight: 500;\n}\n\n.copy-btn:hover {\n color: #fff;\n background-color: #1e80ff;\n}\n\n.copy-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n background: rgba(0, 0, 0, 0.05);\n color: #999;\n}\n\n.uploaded-file-preview {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 8px;\n margin-bottom: 8px;\n padding: 8px 12px;\n background-color: #f0f8ff;\n border-radius: 6px;\n border: 1px dashed #1e80ff;\n}\n\n.file-info {\n display: flex;\n align-items: center;\n gap: 6px;\n overflow: hidden;\n}\n\n.file-name {\n font-size: 12px;\n font-weight: 500;\n color: #333;\n max-width: 200px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.file-size {\n font-size: 11px;\n color: #666;\n}\n\n.remove-file-btn {\n background: none;\n border: none;\n color: #999;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 4px;\n border-radius: 4px;\n transition: all 0.2s;\n}\n\n.remove-file-btn:hover {\n color: #ff4d4f;\n background-color: rgba(255, 77, 79, 0.1);\n}\n\n.file-attachment {\n margin-top: 8px;\n padding: 8px 10px;\n background-color: rgba(0, 0, 0, 0.03);\n border-radius: 6px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.user .file-attachment {\n background-color: rgba(255, 255, 255, 0.15);\n}\n\n.file-icon {\n color: #1e80ff;\n font-size: 18px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.file-attachment-name {\n font-size: 13px;\n color: inherit;\n word-break: break-all;\n max-width: calc(100% - 30px);\n}\n\n.image-preview {\n width: 100%;\n max-width: 300px;\n margin-top: 5px;\n margin-bottom: 5px;\n border-radius: 8px;\n overflow: hidden;\n}\n\n.image-preview img {\n width: 100%;\n height: auto;\n object-fit: contain;\n border-radius: 8px;\n}\n\n.feature-notice {\n margin-bottom: 10px;\n}\n\n.feature-notice :deep(.el-alert) {\n border-radius: 6px;\n padding: 10px 12px;\n background-color: rgba(30, 128, 255, 0.1);\n border: 1px dashed #1e80ff;\n}\n\n.feature-notice :deep(.el-alert__title) {\n font-size: 13px;\n color: #1e80ff;\n}\n\n.feature-notice :deep(.el-alert__icon) {\n color: #1e80ff;\n}\n\n/* 添加新的快捷提示样式 */\n.quick-suggestions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 15px;\n padding-top: 12px;\n border-top: 1px dashed #e6e6e6;\n}\n\n.suggestion-btn {\n background-color: #f2f3f5;\n padding: 8px 12px;\n border-radius: 16px;\n font-size: 13px;\n color: #1e80ff;\n cursor: pointer;\n display: flex;\n align-items: center;\n transition: all 0.2s;\n box-shadow: 0 1px 2px rgba(0,0,0,0.05);\n}\n\n.suggestion-btn:hover {\n background-color: #e6f4ff;\n transform: translateY(-2px);\n box-shadow: 0 2px 6px rgba(30, 128, 255, 0.2);\n}\n\n.suggestion-icon {\n margin-right: 6px;\n font-size: 14px;\n}\n</style> "}, "03632cf8cebae9e7bc46e50f346e7230298834ab": {"t": 1785765390.102924, "n": 24688, "h": "a6aac6224c045c2b", "c": "<template>\n <div class=\"ai-assistant-container\">\n <!-- 聊天头部 -->\n <div class=\"chat-header\">\n <div class=\"header-left\">\n <img src=\"/assets/images/deepseek-color.png\" alt=\"DeepSeek Logo\" class=\"logo\" />\n <h2>Hi, 这里是基于DeepSeek打造的\"城治链\"智能AI助手</h2>\n </div>\n <div class=\"header-right\">\n <el-switch\n v-model=\"isDeepThinkingMode\"\n active-text=\"深度思考模式\"\n inactive-text=\"快速回答模式\"\n class=\"mode-toggle\"\n />\n </div>\n </div>\n \n <div class=\"welcome-text\">\n <p>你好~我是\"城治链\"智能AI助手,你可以向我提问任何问题,我都会尽力回答。</p>\n </div>\n \n <!-- 消息列表区域 -->\n <div class=\"messages\" ref=\"messagesRef\">\n <!-- 消息内容 -->\n <div v-for=\"(message, index) in messages\" :key=\"index\" \n :class=\"['message', message.role === 'user' ? 'user' : 'assistant']\">\n <div class=\"message-avatar\" v-if=\"message.role === 'assistant'\">\n <el-avatar :size=\"40\" :src=\"botAvatar\">AI</el-avatar>\n </div>\n <div class=\"message-bubble\">\n <div class=\"message-content\" v-html=\"formatMessage(message.content) || '&nbsp;'\"></div>\n \n <!-- 在第一条AI消息中添加快捷提示 -->\n <div v-if=\"message.role === 'assistant' && index === 0\" class=\"quick-suggestions\">\n <div v-for=\"(prompt, pIndex) in quickPrompts\" :key=\"pIndex\" class=\"suggestion-btn\" @click=\"insertPrompt(prompt.text)\">\n <span class=\"suggestion-icon\">{{ prompt.icon }}</span> {{ prompt.text }}\n </div>\n </div>\n \n <div v-if=\"message.role === 'assistant'\" class=\"message-actions\">\n <button class=\"copy-btn\" @click=\"copyMessage(message.content)\" title=\"复制内容\" :disabled=\"!message.content\">\n <el-icon><CopyDocument /></el-icon> 复制\n </button>\n </div>\n </div>\n <div class=\"message-avatar\" v-if=\"message.role === 'user'\">\n <el-avatar :size=\"40\" :src=\"userAvatar\">U</el-avatar>\n </div>\n \n <!-- 思维过程展示(仅深度思考模式下的AI回复) -->\n <div v-if=\"message.role === 'assistant' && message.reasoning_content && isDeepThinkingMode\" class=\"reasoning-content\">\n <div class=\"reasoning-header\">\n <span>思考过程</span>\n </div>\n <div class=\"reasoning-text\" v-html=\"formatMessage(message.reasoning_content)\"></div>\n </div>\n </div>\n \n <!-- 打字指示器 -->\n <div v-if=\"isTyping\" class=\"message assistant typing\">\n <div class=\"message-avatar\">\n <el-avatar :size=\"40\" :src=\"botAvatar\">AI</el-avatar>\n </div>\n <div class=\"message-bubble\">\n <div class=\"typing-indicator\">\n <span></span>\n <span></span>\n <span></span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- 输入区域 -->\n <div class=\"input-area\">\n <div class=\"input-wrapper\">\n <div class=\"input-box\">\n <div class=\"feature-notice\">\n <el-alert\n type=\"info\"\n :closable=\"false\"\n show-icon\n >\n <template #title>\n <span>暂时无法上传文件,但您可以输入网址或详细描述进行询问</span>\n </template>\n </el-alert>\n </div>\n \n <el-input\n v-model=\"userInput\"\n type=\"textarea\"\n :rows=\"2\"\n autosize\n :maxlength=\"2000\"\n show-word-limit\n placeholder=\"有问题,尽管问\"\n :disabled=\"isTyping\"\n @keyup.enter.ctrl=\"sendMessage\"\n ></el-input>\n \n <div class=\"input-hint\">按 Ctrl + Enter 发送</div>\n \n <div class=\"button-group\">\n <el-tooltip content=\"清空对话\" placement=\"top\">\n <button class=\"clear-btn\" @click=\"clearMessages\" :disabled=\"isTyping || messages.length === 0\">\n <el-icon><DeleteIcon /></el-icon>\n </button>\n </el-tooltip>\n \n <el-tooltip :content=\"isDeepThinkingMode ? '深度思考模式' : '快速回答模式'\" placement=\"top\">\n <button class=\"thinking-btn\" @click=\"toggleThinkingMode\" :class=\"{'active': isDeepThinkingMode}\">\n <el-icon><Help /></el-icon>\n </button>\n </el-tooltip>\n </div>\n </div>\n \n <button \n class=\"send-btn\" \n @click=\"sendMessage\" \n :disabled=\"!userInput.trim() || isTyping\"\n >\n <span v-if=\"!isTyping\">\n <el-icon class=\"send-icon\"><Promotion /></el-icon>\n </span>\n <span v-else>\n <el-icon class=\"send-icon\"><Loading /></el-icon>\n </span>\n </button>\n </div>\n </div>\n </div>\n</template>\n\n<script>\nimport { ref, nextTick, onMounted } from 'vue';\nimport { ElMessage } from 'element-plus';\nimport axios from 'axios';\nimport DOMPurify from 'dompurify';\nimport { marked } from 'marked';\nimport {\n Delete as DeleteIcon,\n Promotion,\n Help,\n Loading,\n Link as LinkIcon,\n CopyDocument,\n Document\n} from '@element-plus/icons-vue';\n\nexport default {\n name: 'AiAssistant',\n components: {\n DeleteIcon,\n Promotion,\n Help,\n Loading,\n LinkIcon,\n CopyDocument,\n Document\n },\n setup() {\n // 状态变量\n const userInput = ref('');\n const messages = ref([\n {\n role: \"assistant\",\n content: \"您好!我是基于DeepSeek打造的城治链智能AI助手,很高兴为您服务。您可以询问我任何问题,我会尽力提供简洁明了的回答。您也可以尝试使用上方的快捷提示,或者切换到深度思考模式获取更详细的分析。\",\n timestamp: new Date(),\n showReasoning: false\n }\n ]);\n const isTyping = ref(false);\n const isConnected = ref(true);\n const isDeepThinkingMode = ref(false);\n const messagesRef = ref(null);\n \n // 提示库 - 包含多种可能的提示\n const promptsLibrary = [\n { text: '自我介绍一下~', icon: '📋' },\n { text: '办理身份证需要准备什么?', icon: '🔄' },\n { text: '办理营业执照需要准备什么?', icon: '📑' },\n { text: '如何申请公积金贷款?', icon: '🏠' },\n { text: '社保缴费标准是多少?', icon: '💰' },\n { text: '如何预约政务大厅服务?', icon: '📅' },\n { text: '驾驶证到期如何续期?', icon: '🚗' },\n { text: '如何查询不动产信息?', icon: '🏢' },\n { text: '公积金提取条件是什么?', icon: '💼' },\n { text: '居住证办理流程是什么?', icon: '🏘️' },\n { text: '如何申请创业补贴?', icon: '💡' },\n { text: '个人所得税如何计算?', icon: '💲' },\n { text: '如何办理出生证明?', icon: '👶' },\n { text: '退休手续如何办理?', icon: '👴' },\n { text: '企业注册需要哪些材料?', icon: '🏭' }\n ];\n \n // 随机选择的快捷提示\n const quickPrompts = ref([]);\n \n // 随机选择提示\n const getRandomPrompts = () => {\n // 打乱提示库并选择前三个\n const shuffled = [...promptsLibrary].sort(() => 0.5 - Math.random());\n quickPrompts.value = shuffled.slice(0, 3);\n };\n \n // DeepSeek API配置\n const API_KEY = 'sk-482ddc70f6324e73ab3c7faad5e1b20c';\n const API_URL = 'https://api.deepseek.com/v1/chat/completions';\n \n // 头像\n const userAvatar = ref('');\n const botAvatar = ref('/assets/images/deepseek-color.png');\n \n // 获取用户头像\n const getUserAvatar = async () => {\n try {\n const token = localStorage.getItem('token');\n if (!token) return;\n \n const response = await axios.get('/api/auth/me', {\n headers: {\n Authorization: `Bearer ${token}`\n }\n });\n \n if (response.data.success) {\n userAvatar.value = response.data.data.avatarUrl;\n }\n } catch (error) {\n console.error('获取用户头像失败:', error);\n }\n };\n \n // 切换思考模式\n const toggleThinkingMode = () => {\n isDeepThinkingMode.value = !isDeepThinkingMode.value;\n ElMessage.success(`已切换为${isDeepThinkingMode.value ? '深度思考' : '快速回答'}模式`);\n };\n \n // 插入预设提示语\n const insertPrompt = (prompt) => {\n userInput.value = prompt;\n // 自动发送\n setTimeout(() => {\n sendMessage();\n }, 100);\n };\n \n // 格式化消息内容(支持markdown)\n const formatMessage = (content) => {\n if (!content || content.trim() === '') return '<p class=\"empty-message\">AI正在思考,请稍作等待~</p>';\n // 转换markdown为HTML并净化以防止XSS攻击\n return DOMPurify.sanitize(marked.parse(content));\n };\n \n // 格式化时间\n const formatTime = (timestamp) => {\n if (!timestamp) return '';\n const date = new Date(timestamp);\n return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;\n };\n \n // 滚动到底部\n const scrollToBottom = async () => {\n await nextTick();\n if (messagesRef.value) {\n messagesRef.value.scrollTop = messagesRef.value.scrollHeight;\n }\n };\n \n // 选择合适的模型\n const getApiModel = () => {\n // 根据思考模式选择模型\n return isDeepThinkingMode.value ? \"deepseek-reasoner\" : \"deepseek-chat\";\n };\n \n // 发送消息\n const sendMessage = async () => {\n if (!userInput.value.trim() || isTyping.value) return;\n \n // 设置正在输入状态\n isTyping.value = true;\n \n // 准备用户消息内容\n let userContent = userInput.value.trim();\n \n // 添加用户消息\n const userMessage = {\n role: \"user\",\n content: userContent,\n timestamp: new Date()\n };\n \n messages.value.push(userMessage);\n \n // 清空输入框\n userInput.value = '';\n \n // 提示音效果(可选)\n try {\n const audio = new Audio('/assets/sounds/message-sent.mp3');\n audio.volume = 0.5;\n audio.play().catch(e => console.log('无法播放提示音', e));\n } catch(e) {\n console.log('音频播放不受支持', e);\n }\n \n // 滚动到底部\n await scrollToBottom();\n \n // 准备AI响应消息\n const assistantMessage = {\n role: \"assistant\",\n content: \"\",\n reasoning_content: \"\",\n timestamp: new Date(),\n showReasoning: false\n };\n messages.value.push(assistantMessage);\n \n try {\n // 调用DeepSeek API\n const headers = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${API_KEY}`\n };\n \n // 准备消息内容\n let apiMessages = [\n { role: \"system\", content: \"你是基于DeepSeek打造的城治链智能AI助手,一个专业的智能助手,可以用简洁明了但信息丰富的方式回答用户的问题。你可以回答用户提供的网址内容相关问题,或基于用户详细描述提供帮助。\" },\n ...messages.value\n .filter(m => m.role === 'user' || (m.role === 'assistant' && m.content))\n .map(m => ({ role: m.role, content: m.content }))\n ];\n \n // 准备API请求内容\n const payload = {\n messages: apiMessages,\n model: getApiModel(),\n stream: true,\n temperature: 0.7,\n max_tokens: isDeepThinkingMode.value ? 2000 : 1000\n };\n \n console.log('发送请求到API:', JSON.stringify(payload, null, 2));\n \n const response = await fetch(API_URL, {\n method: 'POST',\n headers: headers,\n body: JSON.stringify(payload)\n });\n \n if (!response.ok) {\n const errorText = await response.text();\n console.error('API响应错误:', response.status, errorText);\n throw new Error(`API调用失败: ${response.status} - ${errorText}`);\n }\n \n // 处理流式响应\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let partialLine = '';\n let fullContent = '';\n let reasoningContent = '';\n \n let streamDone = false;\n while (!streamDone) {\n const { done, value } = await reader.read();\n if (done) {\n streamDone = true;\n break;\n }\n \n // 解码并处理流数据\n const text = decoder.decode(value);\n const lines = (partialLine + text).split('\\n');\n partialLine = lines.pop() || '';\n \n for (const line of lines) {\n if (line.trim() === '') continue;\n if (line.trim() === 'data: [DONE]') continue;\n \n try {\n const jsonData = JSON.parse(line.replace(/^data: /, ''));\n \n if (jsonData.choices && jsonData.choices.length > 0) {\n const choice = jsonData.choices[0];\n \n if (choice.delta && choice.delta.content) {\n // 更新内容\n fullContent += choice.delta.content;\n assistantMessage.content = fullContent;\n assistantMessage.timestamp = new Date();\n \n // 自动滚动到底部\n await scrollToBottom();\n }\n \n // 如果是深度思考模式且有思考过程\n if (isDeepThinkingMode.value && choice.delta && choice.delta.reasoning) {\n reasoningContent += choice.delta.reasoning;\n assistantMessage.reasoning_content = reasoningContent;\n }\n }\n } catch (e) {\n console.error('解析流数据出错:', e);\n }\n }\n }\n \n // 更新消息列表\n const lastIndex = messages.value.length - 1;\n if (lastIndex >= 0) {\n messages.value[lastIndex] = {\n ...assistantMessage,\n content: fullContent || \"抱歉,我的回答生成过程中出现了问题。\",\n reasoning_content: reasoningContent,\n timestamp: new Date()\n };\n }\n } catch (error) {\n console.error('API调用错误:', error);\n ElMessage.error('AI助手暂时无法回应,请稍后再试');\n \n // 出错时,更新消息为错误提示\n const lastIndex = messages.value.length - 1;\n if (lastIndex >= 0 && messages.value[lastIndex].role === 'assistant') {\n messages.value[lastIndex].content = '抱歉,我遇到了一些问题,无法回应您的请求。请稍后再试。';\n \n // 可选:允许用户重试\n ElMessage({\n message: '连接失败,请检查网络后重试',\n type: 'error',\n showClose: true,\n duration: 5000\n });\n }\n } finally {\n isTyping.value = false;\n await scrollToBottom();\n }\n };\n \n // 清空消息\n const clearMessages = () => {\n messages.value = [];\n ElMessage.success('对话已清空');\n };\n \n // 复制消息内容\n const copyMessage = (content) => {\n if (!content || content.trim() === '') {\n ElMessage.warning('没有可复制的内容');\n return;\n }\n \n // 创建一个临时textarea元素来复制纯文本内容\n const textArea = document.createElement('textarea');\n // 移除HTML标签获取纯文本\n const plainText = content.replace(/<[^>]*>/g, '');\n \n textArea.value = plainText;\n document.body.appendChild(textArea);\n textArea.select();\n \n try {\n document.execCommand('copy');\n ElMessage({\n message: '已复制到剪贴板',\n type: 'success',\n duration: 2000\n });\n } catch (err) {\n ElMessage.error('复制失败,请手动复制');\n }\n \n document.body.removeChild(textArea);\n };\n \n onMounted(() => {\n getUserAvatar();\n getRandomPrompts(); // 生成随机提示\n });\n \n return {\n userInput,\n messages,\n isTyping,\n isConnected,\n isDeepThinkingMode,\n messagesRef,\n userAvatar,\n botAvatar,\n quickPrompts,\n formatMessage,\n formatTime,\n sendMessage,\n clearMessages,\n insertPrompt,\n toggleThinkingMode,\n copyMessage,\n getApiModel\n };\n }\n}\n</script>\n\n<style scoped>\n.ai-assistant-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n background-color: #f9f9f9;\n overflow: hidden;\n font-family: \"PingFang SC\", \"Microsoft YaHei\", sans-serif;\n position: relative;\n}\n\n.chat-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 20px;\n background-color: #fff;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n\n.header-left {\n display: flex;\n align-items: center;\n}\n\n.logo {\n width: 32px;\n height: 32px;\n margin-right: 10px;\n}\n\n.header-left h2 {\n margin: 0;\n font-size: 1.2rem;\n color: #1e80ff;\n font-weight: 600;\n}\n\n.mode-toggle {\n font-size: 0.8rem;\n}\n\n.welcome-text {\n text-align: center;\n padding: 15px 20px;\n color: #666;\n font-size: 14px;\n background-color: #fff;\n margin-bottom: 10px;\n border-bottom: 1px solid #eee;\n}\n\n.messages {\n flex: 1;\n padding: 10px 80px 20px;\n overflow-y: auto;\n scroll-behavior: smooth;\n background-color: #f9f9f9;\n}\n\n.message {\n display: flex;\n margin-bottom: 25px;\n align-items: flex-start;\n}\n\n.message.user {\n flex-direction: row-reverse;\n}\n\n.message-avatar {\n margin: 0 10px;\n flex-shrink: 0;\n}\n\n.message-bubble {\n max-width: 80%;\n padding: 12px 16px;\n border-radius: 10px;\n position: relative;\n min-height: 24px;\n min-width: 60px;\n}\n\n.user .message-bubble {\n background-color: #1e80ff;\n color: white;\n border-top-right-radius: 4px;\n margin-right: 10px;\n transform-origin: right top;\n animation: message-pop 0.3s ease;\n}\n\n.assistant .message-bubble {\n background-color: white;\n color: #333;\n border-top-left-radius: 4px;\n margin-left: 10px;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);\n transform-origin: left top;\n animation: message-pop 0.3s ease;\n border-left: 3px solid #1e80ff;\n padding: 12px 24px 12px 24px;\n}\n\n.message-content {\n word-break: break-word;\n line-height: 1.6;\n font-size: 14px;\n padding: 0 4px;\n min-height: 16px;\n}\n\n.empty-message {\n color: #999;\n font-style: italic;\n margin: 0;\n}\n\n.message-content p {\n margin: 0 0 10px;\n}\n\n.message-content p:last-child {\n margin-bottom: 0;\n}\n\n.message-content pre {\n background-color: #f8f8f8;\n padding: 10px;\n border-radius: 5px;\n overflow-x: auto;\n margin: 10px 0;\n}\n\n.message-content code {\n background-color: rgba(0, 0, 0, 0.05);\n padding: 2px 4px;\n border-radius: 3px;\n font-family: monospace;\n}\n\n.user .message-content code {\n background-color: rgba(255, 255, 255, 0.2);\n}\n\n.reasoning-content {\n margin-top: 10px;\n border-top: 1px dashed #dcdfe6;\n padding-top: 10px;\n background-color: #f9fafc;\n border-radius: 8px;\n padding: 10px 15px;\n margin-left: 60px;\n margin-right: 10px;\n position: relative;\n}\n\n.reasoning-header {\n font-size: 0.9rem;\n color: #1e80ff;\n font-weight: 500;\n display: flex;\n align-items: center;\n margin-bottom: 8px;\n}\n\n.reasoning-header::before {\n content: '';\n display: inline-block;\n width: 4px;\n height: 16px;\n background-color: #1e80ff;\n margin-right: 8px;\n border-radius: 2px;\n}\n\n.reasoning-text {\n padding: 8px;\n background-color: #fff;\n border-radius: 6px;\n font-size: 0.9rem;\n color: #606266;\n border-left: 2px solid #e6e6e6;\n}\n\n.typing-indicator {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n height: 24px;\n}\n\n.typing-indicator span {\n width: 6px;\n height: 6px;\n margin: 0 2px;\n background-color: #aaa;\n border-radius: 50%;\n display: inline-block;\n animation: bounce 1.4s infinite ease-in-out both;\n}\n\n.typing-indicator span:nth-child(1) {\n animation-delay: -0.32s;\n}\n\n.typing-indicator span:nth-child(2) {\n animation-delay: -0.16s;\n}\n\n.input-area {\n background-color: #fff;\n padding: 15px 20px;\n border-top: 1px solid #eee;\n}\n\n.input-wrapper {\n display: flex;\n align-items: flex-end;\n}\n\n.input-box {\n flex: 1;\n border: 1px solid #dcdfe6;\n border-radius: 8px;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n background-color: #fff;\n padding: 8px 12px;\n margin-right: 10px;\n}\n\n.input-box .el-textarea {\n border: none;\n padding: 0;\n}\n\n.input-box .el-textarea__inner {\n border: none;\n box-shadow: none !important;\n padding: 0;\n min-height: 42px !important;\n max-height: 120px;\n line-height: 1.5;\n font-size: 14px;\n}\n\n.input-hint {\n font-size: 12px;\n color: #999;\n text-align: right;\n margin-top: 4px;\n margin-bottom: 4px;\n}\n\n.button-group {\n display: flex;\n margin-top: 8px;\n align-items: center;\n}\n\n.clear-btn, .thinking-btn {\n background: none;\n border: none;\n color: #999;\n font-size: 16px;\n cursor: pointer;\n padding: 5px 8px;\n border-radius: 4px;\n margin-right: 10px;\n transition: all 0.2s;\n}\n\n.clear-btn:hover, .thinking-btn:hover {\n color: #1e80ff;\n background-color: #f2f6fc;\n}\n\n.clear-btn:disabled, .thinking-btn:disabled {\n color: #ccc;\n cursor: not-allowed;\n}\n\n.thinking-btn.active {\n color: #1e80ff;\n}\n\n.send-btn {\n background-color: #1e80ff;\n color: white;\n border: none;\n border-radius: 8px;\n width: 48px;\n height: 48px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s;\n box-shadow: 0 2px 6px rgba(30, 128, 255, 0.3);\n}\n\n.send-btn:hover {\n background-color: #1873e8;\n transform: translateY(-2px);\n box-shadow: 0 4px 8px rgba(30, 128, 255, 0.4);\n}\n\n.send-btn:disabled {\n background-color: #b3d4ff;\n cursor: not-allowed;\n transform: none;\n box-shadow: none;\n}\n\n.send-icon {\n font-size: 20px;\n}\n\n.footer-hint {\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n color: #999;\n margin-top: 10px;\n}\n\n.page-info {\n color: #666;\n}\n\n@keyframes bounce {\n 0%, 80%, 100% { transform: scale(0); }\n 40% { transform: scale(1); }\n}\n\n@keyframes message-pop {\n 0% { opacity: 0; transform: scale(0.8); }\n 100% { opacity: 1; transform: scale(1); }\n}\n\n/* 媒体查询 */\n@media (max-width: 768px) {\n .chat-header {\n padding: 12px 15px;\n }\n \n .welcome-text, .quick-actions {\n padding: 10px 15px;\n }\n \n .messages {\n padding: 10px 15px;\n }\n \n .message-bubble {\n max-width: 90%;\n padding: 10px 12px;\n }\n \n .input-area {\n padding: 12px 15px;\n }\n \n .action-btn {\n padding: 8px 12px;\n font-size: 13px;\n }\n}\n\n/* 在小屏幕上隐藏一些元素 */\n@media (max-width: 576px) {\n .welcome-text {\n padding: 10px 15px;\n font-size: 13px;\n }\n \n .input-hint {\n display: none;\n }\n \n .footer-hint {\n font-size: 10px;\n }\n \n .header-right {\n display: none;\n }\n \n .header-left h2 {\n font-size: 1rem;\n }\n}\n\n.message-actions {\n position: absolute;\n top: 8px;\n right: 8px;\n opacity: 1;\n transition: opacity 0.2s;\n}\n\n.message-bubble:hover .message-actions {\n opacity: 1;\n}\n\n.copy-btn {\n background: rgba(30, 128, 255, 0.1);\n border: none;\n color: #1e80ff;\n cursor: pointer;\n padding: 5px 10px;\n border-radius: 4px;\n transition: all 0.2s;\n font-size: 13px;\n display: flex;\n align-items: center;\n gap: 4px;\n font-weight: 500;\n}\n\n.copy-btn:hover {\n color: #fff;\n background-color: #1e80ff;\n}\n\n.copy-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n background: rgba(0, 0, 0, 0.05);\n color: #999;\n}\n\n.uploaded-file-preview {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 8px;\n margin-bottom: 8px;\n padding: 8px 12px;\n background-color: #f0f8ff;\n border-radius: 6px;\n border: 1px dashed #1e80ff;\n}\n\n.file-info {\n display: flex;\n align-items: center;\n gap: 6px;\n overflow: hidden;\n}\n\n.file-name {\n font-size: 12px;\n font-weight: 500;\n color: #333;\n max-width: 200px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.file-size {\n font-size: 11px;\n color: #666;\n}\n\n.remove-file-btn {\n background: none;\n border: none;\n color: #999;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 4px;\n border-radius: 4px;\n transition: all 0.2s;\n}\n\n.remove-file-btn:hover {\n color: #ff4d4f;\n background-color: rgba(255, 77, 79, 0.1);\n}\n\n.file-attachment {\n margin-top: 8px;\n padding: 8px 10px;\n background-color: rgba(0, 0, 0, 0.03);\n border-radius: 6px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.user .file-attachment {\n background-color: rgba(255, 255, 255, 0.15);\n}\n\n.file-icon {\n color: #1e80ff;\n font-size: 18px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.file-attachment-name {\n font-size: 13px;\n color: inherit;\n word-break: break-all;\n max-width: calc(100% - 30px);\n}\n\n.image-preview {\n width: 100%;\n max-width: 300px;\n margin-top: 5px;\n margin-bottom: 5px;\n border-radius: 8px;\n overflow: hidden;\n}\n\n.image-preview img {\n width: 100%;\n height: auto;\n object-fit: contain;\n border-radius: 8px;\n}\n\n.feature-notice {\n margin-bottom: 10px;\n}\n\n.feature-notice :deep(.el-alert) {\n border-radius: 6px;\n padding: 10px 12px;\n background-color: rgba(30, 128, 255, 0.1);\n border: 1px dashed #1e80ff;\n}\n\n.feature-notice :deep(.el-alert__title) {\n font-size: 13px;\n color: #1e80ff;\n}\n\n.feature-notice :deep(.el-alert__icon) {\n color: #1e80ff;\n}\n\n/* 添加新的快捷提示样式 */\n.quick-suggestions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 15px;\n padding-top: 12px;\n border-top: 1px dashed #e6e6e6;\n}\n\n.suggestion-btn {\n background-color: #f2f3f5;\n padding: 8px 12px;\n border-radius: 16px;\n font-size: 13px;\n color: #1e80ff;\n cursor: pointer;\n display: flex;\n align-items: center;\n transition: all 0.2s;\n box-shadow: 0 1px 2px rgba(0,0,0,0.05);\n}\n\n.suggestion-btn:hover {\n background-color: #e6f4ff;\n transform: translateY(-2px);\n box-shadow: 0 2px 6px rgba(30, 128, 255, 0.2);\n}\n\n.suggestion-icon {\n margin-right: 6px;\n font-size: 14px;\n}\n</style> "}}}}