| | |
| | | </div> |
| | | <div class="header-actions"> |
| | | <el-tooltip content="会话历史" placement="bottom"> |
| | | <el-button link @click="toggleHistory"> |
| | | <el-button link class="header-action-btn" @click="handleToggleHistory"> |
| | | <el-icon :size="18"><Timer /></el-icon> |
| | | </el-button> |
| | | </el-tooltip> |
| | | <el-tooltip content="开启新会话" placement="bottom"> |
| | | <el-button link @click="newChat"> |
| | | <el-button link class="header-action-btn" @click="handleNewChat"> |
| | | <el-icon :size="18"><Plus /></el-icon> |
| | | </el-button> |
| | | </el-tooltip> |
| | | <div class="action-divider"></div> |
| | | <el-tooltip content="关闭" placement="bottom"> |
| | | <el-button link class="close-btn" @click="visible = false"> |
| | | <el-button link class="header-action-btn close-btn" @click="handleManualClose"> |
| | | <el-icon :size="18"><Close /></el-icon> |
| | | </el-button> |
| | | </el-tooltip> |
| | |
| | | </div> |
| | | |
| | | <div v-else class="chat-main"> |
| | | <div :class="['chat-hero', { compact: hasMessages }]"> |
| | | <div :class="['assistant-stand', { thinking: isSending, compact: hasMessages }]"> |
| | | <div class="assistant-halo"></div> |
| | | <div class="assistant-scan-ring"></div> |
| | | <div class="assistant-orbit assistant-orbit-a"></div> |
| | | <div class="assistant-orbit assistant-orbit-b"></div> |
| | | <div class="assistant-bot"> |
| | | <div class="assistant-bot-antenna assistant-bot-antenna-left"></div> |
| | | <div class="assistant-bot-antenna assistant-bot-antenna-right"></div> |
| | | <div class="assistant-bot-head"> |
| | | <div class="assistant-bot-head-glow"></div> |
| | | <div class="assistant-bot-eye assistant-bot-eye-left"></div> |
| | | <div class="assistant-bot-eye assistant-bot-eye-right"></div> |
| | | <div class="assistant-bot-mouth"></div> |
| | | </div> |
| | | <div class="assistant-bot-neck"></div> |
| | | <div class="assistant-bot-body"> |
| | | <div class="assistant-bot-core"> |
| | | <div class="assistant-bot-core-ring"></div> |
| | | <el-icon :size="22"><component :is="currentAssistant.icon" /></el-icon> |
| | | </div> |
| | | <div class="assistant-bot-arm assistant-bot-arm-left"></div> |
| | | <div class="assistant-bot-arm assistant-bot-arm-right"></div> |
| | | </div> |
| | | </div> |
| | | <div class="assistant-status"> |
| | | <span class="assistant-status-dot"></span> |
| | | {{ isSending ? '思考中...' : currentAssistant.label }} |
| | | </div> |
| | | <div class="assistant-base assistant-base-lg"></div> |
| | | <div class="assistant-base assistant-base-md"></div> |
| | | <div class="assistant-base assistant-base-sm"></div> |
| | | </div> |
| | | |
| | | <div :class="['welcome-card', { compact: hasMessages }]"> |
| | | <div class="welcome-eyebrow">智能助手</div> |
| | | <h3 class="welcome-title"> |
| | | 您好 |
| | | <br /> |
| | | 我是{{ currentAssistant.label }}分析解读助手 |
| | | </h3> |
| | | <p class="welcome-desc"> |
| | | {{ currentAssistant.description || '我可以围绕业务问题提供解读、查询建议和分析支持,帮助你更快完成判断与处理。' }} |
| | | </p> |
| | | |
| | | <div class="quick-prompt-list"> |
| | | <button |
| | | v-for="prompt in displayedQuickPrompts" |
| | | :key="prompt" |
| | | type="button" |
| | | class="quick-prompt-btn" |
| | | :disabled="isSending" |
| | | @click="sendQuickPrompt(prompt)" |
| | | > |
| | | {{ prompt }} |
| | | </button> |
| | | </div> |
| | | |
| | | <button |
| | | v-if="quickPrompts.length > quickPromptLimit" |
| | | type="button" |
| | | class="more-prompts-btn" |
| | | @click="refreshQuickPrompts" |
| | | > |
| | | <el-icon><RefreshRight /></el-icon> |
| | | <span>换一换</span> |
| | | </button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-show="!hasMessages" class="hero-dot-grid" aria-hidden="true"> |
| | | <span v-for="dot in 28" :key="dot"></span> |
| | | </div> |
| | | |
| | | <div class="message-list" ref="messageListRef"> |
| | | <div |
| | | v-for="(message, index) in messages" |
| | |
| | | |
| | | <div class="input-area"> |
| | | <div class="input-actions"> |
| | | <el-button link type="primary" size="small" @click="newChat"> |
| | | <el-button link class="utility-action-btn" type="primary" size="small" @click="handleNewChat"> |
| | | <el-icon><Plus /></el-icon>新会话 |
| | | </el-button> |
| | | <el-button v-if="isSending" link type="danger" size="small" @click="stopGeneration"> |
| | | <el-button v-if="isSending" link class="utility-action-btn stop-action-btn" type="danger" size="small" @click="stopGeneration"> |
| | | <el-icon><VideoPause /></el-icon>停止生成 |
| | | </el-button> |
| | | <el-upload |
| | |
| | | :on-change="handleFileChange" |
| | | :disabled="isSending" |
| | | > |
| | | <el-button link type="primary" size="small" :disabled="isSending"> |
| | | <el-button link class="utility-action-btn upload-action-btn" type="primary" size="small" :disabled="isSending"> |
| | | <el-icon><Upload /></el-icon>分析文件 |
| | | </el-button> |
| | | </el-upload> |
| | |
| | | class="send-btn" |
| | | :disabled="isSending || (!inputMessage.trim() && !selectedFile)" |
| | | @click="sendMessage" |
| | | aria-label="发送" |
| | | > |
| | | 发送 |
| | | <el-icon><Promotion /></el-icon> |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | |
| | | import { ref, onMounted, onUnmounted, nextTick, watch, computed } from 'vue' |
| | | import request from '@/utils/request' |
| | | import * as echarts from 'echarts' |
| | | import { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, ShoppingCart } from '@element-plus/icons-vue' |
| | | import { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, ShoppingCart, Promotion, RefreshRight } from '@element-plus/icons-vue' |
| | | import { ElMessage } from 'element-plus' |
| | | |
| | | const props = defineProps({ |
| | |
| | | storageKey: 'ai_chat_uuid', |
| | | placeholder: '请输入您的问题... (Enter 发送, Shift+Enter 换行)', |
| | | welcomeMessage: '你好', |
| | | description: '我可以回答你的问题,为你提供业务数据解读信息、处理建议和辅助决策支持。', |
| | | allowFileUpload: true, |
| | | emptySessionText: '暂无历史会话' |
| | | }, |
| | |
| | | storageKey: 'purchase_ai_chat_uuid', |
| | | placeholder: '请输入采购问题... (Enter 发送, Shift+Enter 换行)', |
| | | welcomeMessage: '你好', |
| | | description: '我可以协助你分析采购订单、到货进度、供应商表现和付款情况,帮助你快速定位采购异常。', |
| | | allowFileUpload: false, |
| | | emptySessionText: '暂无采购会话' |
| | | } |
| | |
| | | const selectedAssistantKey = ref(props.defaultAssistant || assistants.value[0]?.key || 'general') |
| | | const currentAssistant = computed(() => assistants.value.find(item => item.key === selectedAssistantKey.value) || assistants.value[0] || builtInAssistants[0]) |
| | | const showAssistantSwitch = computed(() => assistants.value.length > 1) |
| | | const assistantQuickPromptMap = { |
| | | general: [ |
| | | '我当前有哪些审批待办需要处理?', |
| | | '帮我列出今天新增的审批待办。', |
| | | '当前待我审批的单据,按时间倒序列出来。', |
| | | '我发起的审批里,哪些还在处理中?', |
| | | '查询流程编号 XXX 的审批详情。', |
| | | '流程编号 XXX 现在卡在哪个审批节点?当前审批人是谁?', |
| | | '帮我查看流程编号 XXX 的审批流转记录。', |
| | | '近7天我的审批待办统计情况怎么样?', |
| | | '本月我的审批中,通过、驳回、处理中各有多少?', |
| | | '近30天各类型审批数量分布是什么?', |
| | | '帮我审批通过流程编号 XXX,备注“同意”。', |
| | | '帮我驳回流程编号 XXX,备注“请补充说明”。', |
| | | '撤销我刚刚对流程编号 XXX 的审批操作。', |
| | | '帮我修改流程编号 XXX 的备注为“已补充附件”。', |
| | | '删除我发起的流程编号 XXX。' |
| | | ], |
| | | purchase: [ |
| | | '本月采购金额排名前十的物料有哪些?', |
| | | '哪些采购订单还未入库?', |
| | | '最近7天供应商到货异常有哪些?', |
| | | '帮我统计待付款采购单', |
| | | '列出本月采购退货情况' |
| | | ] |
| | | } |
| | | const quickPromptLimit = 3 |
| | | const quickPromptStart = ref(0) |
| | | const quickPrompts = computed(() => { |
| | | const assistant = currentAssistant.value || {} |
| | | if (Array.isArray(assistant.quickPrompts) && assistant.quickPrompts.length) { |
| | | return assistant.quickPrompts |
| | | } |
| | | return assistantQuickPromptMap[assistant.key] || assistantQuickPromptMap.general |
| | | }) |
| | | const displayedQuickPrompts = computed(() => { |
| | | const prompts = quickPrompts.value || [] |
| | | if (prompts.length <= quickPromptLimit) return prompts |
| | | |
| | | const result = [] |
| | | for (let i = 0; i < quickPromptLimit; i++) { |
| | | result.push(prompts[(quickPromptStart.value + i) % prompts.length]) |
| | | } |
| | | return result |
| | | }) |
| | | const hasMessages = computed(() => messages.value.length > 0) |
| | | |
| | | const visible = ref(false) |
| | | const windowWidth = ref(window.innerWidth) |
| | |
| | | const sessions = ref([]) |
| | | const loadingSessions = ref(false) |
| | | |
| | | const abortCurrentRequest = () => { |
| | | if (!currentAbortController.value) return |
| | | |
| | | currentAbortController.value.abort() |
| | | currentAbortController.value = null |
| | | isSending.value = false |
| | | |
| | | const lastMsg = messages.value[messages.value.length - 1] |
| | | if (lastMsg && !lastMsg.isUser) { |
| | | lastMsg.isTyping = false |
| | | } |
| | | } |
| | | |
| | | const toggleHistory = () => { |
| | | showHistory.value = !showHistory.value |
| | | if (showHistory.value) { |
| | | loadSessions() |
| | | } |
| | | } |
| | | |
| | | const handleToggleHistory = () => { |
| | | if (isSending.value) { |
| | | abortCurrentRequest() |
| | | } |
| | | toggleHistory() |
| | | } |
| | | |
| | | const loadSessions = async () => { |
| | |
| | | watch(selectedAssistantKey, (nextKey, prevKey) => { |
| | | if (!prevKey || nextKey === prevKey) return |
| | | |
| | | if (currentAbortController.value) { |
| | | currentAbortController.value.abort() |
| | | currentAbortController.value = null |
| | | } |
| | | |
| | | isSending.value = false |
| | | abortCurrentRequest() |
| | | disposeCharts() |
| | | messages.value = [] |
| | | outputState.value = {} |
| | |
| | | showHistory.value = false |
| | | selectedFile.value = null |
| | | inputMessage.value = '' |
| | | quickPromptStart.value = 0 |
| | | initUUID() |
| | | hello() |
| | | }) |
| | |
| | | |
| | | const handleClose = () => { |
| | | visible.value = false |
| | | } |
| | | |
| | | const handleManualClose = () => { |
| | | if (isSending.value) { |
| | | abortCurrentRequest() |
| | | } |
| | | handleClose() |
| | | } |
| | | |
| | | const initUUID = () => { |
| | |
| | | sessions.value = [] |
| | | showHistory.value = false |
| | | selectedFile.value = null |
| | | quickPromptStart.value = 0 |
| | | localStorage.removeItem(currentAssistant.value.storageKey) |
| | | initUUID() |
| | | hello() |
| | | } |
| | | |
| | | const handleNewChat = () => { |
| | | if (isSending.value) { |
| | | abortCurrentRequest() |
| | | } |
| | | newChat() |
| | | } |
| | | |
| | | const sendQuickPrompt = (prompt) => { |
| | | if (!prompt || isSending.value) return |
| | | inputMessage.value = prompt |
| | | sendMessage() |
| | | } |
| | | |
| | | const refreshQuickPrompts = () => { |
| | | const prompts = quickPrompts.value || [] |
| | | if (prompts.length <= quickPromptLimit) return |
| | | quickPromptStart.value = (quickPromptStart.value + quickPromptLimit) % prompts.length |
| | | } |
| | | |
| | | const disposeCharts = () => { |
| | |
| | | } |
| | | |
| | | const stopGeneration = () => { |
| | | if (currentAbortController.value) { |
| | | currentAbortController.value.abort() |
| | | currentAbortController.value = null |
| | | isSending.value = false |
| | | |
| | | // 将最后一条消息标记为非打字状态 |
| | | const lastMsg = messages.value[messages.value.length - 1] |
| | | if (lastMsg && !lastMsg.isUser) { |
| | | lastMsg.isTyping = false |
| | | } |
| | | } |
| | | abortCurrentRequest() |
| | | } |
| | | |
| | | const sendRequest = (message) => { |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | :deep(.header-action-btn) { |
| | | position: relative; |
| | | overflow: hidden; |
| | | background: linear-gradient(180deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.08)); |
| | | border: 1px solid rgba(255, 255, 255, 0.16); |
| | | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 10px 18px rgba(0, 0, 0, 0.12); |
| | | |
| | | &::before { |
| | | content: ''; |
| | | position: absolute; |
| | | inset: 0; |
| | | background: linear-gradient(135deg, rgba(255, 255, 255, 0.22), transparent 55%); |
| | | pointer-events: none; |
| | | } |
| | | |
| | | &::after { |
| | | content: ''; |
| | | position: absolute; |
| | | top: -120%; |
| | | left: -40%; |
| | | width: 60%; |
| | | height: 260%; |
| | | background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.28), transparent); |
| | | transform: rotate(24deg); |
| | | opacity: 0; |
| | | transition: all 0.35s ease; |
| | | } |
| | | |
| | | &:hover::after { |
| | | left: 100%; |
| | | opacity: 1; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .assistant-switcher { |
| | |
| | | gap: 6px; |
| | | flex-wrap: wrap; |
| | | justify-content: center; |
| | | padding: 4px; |
| | | border-radius: 999px; |
| | | background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.08)); |
| | | border: 1px solid rgba(255, 255, 255, 0.12); |
| | | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 10px 18px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | :deep(.el-radio-button__inner) { |
| | |
| | | display: inline-flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | :deep(.utility-action-btn) { |
| | | position: relative; |
| | | height: 34px; |
| | | padding: 0 14px; |
| | | border-radius: 999px; |
| | | border: 1px solid rgba(92, 119, 255, 0.18); |
| | | background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(236, 243, 255, 0.98)); |
| | | color: $primary-blue; |
| | | font-weight: 600; |
| | | box-shadow: 0 10px 20px rgba(0, 85, 212, 0.08); |
| | | transition: all 0.25s ease; |
| | | |
| | | .el-icon { |
| | | margin-right: 5px; |
| | | } |
| | | |
| | | &:hover:not(.is-disabled) { |
| | | color: #fff; |
| | | border-color: transparent; |
| | | background: linear-gradient(135deg, #1f6dff 0%, #6b38ef 100%); |
| | | box-shadow: 0 14px 24px rgba(64, 90, 255, 0.2); |
| | | transform: translateY(-1px); |
| | | } |
| | | } |
| | | |
| | | :deep(.stop-action-btn) { |
| | | border-color: rgba(255, 99, 123, 0.18); |
| | | color: #d33e5e; |
| | | |
| | | &:hover:not(.is-disabled) { |
| | | background: linear-gradient(135deg, #f5536e 0%, #a33cff 100%); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .input-box { |
| | |
| | | box-shadow: 0 6px 20px rgba(0, 85, 212, 0.4); |
| | | transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275); |
| | | overflow: hidden; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | letter-spacing: 0.3px; |
| | | |
| | | &::before { |
| | | content: ''; |
| | |
| | | background: linear-gradient(145deg, #b0b0b0, #c5c5c5); |
| | | box-shadow: none; |
| | | cursor: not-allowed; |
| | | } |
| | | |
| | | .el-icon { |
| | | font-size: 15px; |
| | | transform: translateY(-1px); |
| | | } |
| | | } |
| | | } |
| | |
| | | color: #5ac8fa; |
| | | } |
| | | } |
| | | |
| | | .chat-main { |
| | | background: |
| | | radial-gradient(circle at top left, rgba(46, 140, 224, 0.12) 0%, transparent 34%), |
| | | linear-gradient(180deg, #fff 0%, #f7fbff 46%, #fff 100%); |
| | | } |
| | | |
| | | .chat-hero { |
| | | display: grid; |
| | | grid-template-columns: 164px minmax(0, 1fr); |
| | | gap: 18px; |
| | | align-items: start; |
| | | padding: 14px 18px 6px; |
| | | |
| | | &.compact { |
| | | grid-template-columns: 122px minmax(0, 1fr); |
| | | gap: 12px; |
| | | padding: 8px 18px 2px; |
| | | } |
| | | } |
| | | |
| | | .assistant-stand { |
| | | position: relative; |
| | | min-height: 252px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-direction: column; |
| | | padding-top: 18px; |
| | | overflow: hidden; |
| | | |
| | | &.compact { |
| | | min-height: 176px; |
| | | padding-top: 8px; |
| | | } |
| | | |
| | | &.thinking { |
| | | .assistant-halo { |
| | | opacity: 1; |
| | | transform: scale(1.08); |
| | | filter: blur(8px); |
| | | } |
| | | |
| | | .assistant-scan-ring { |
| | | opacity: 1; |
| | | animation-duration: 1.6s; |
| | | } |
| | | |
| | | .assistant-orbit { |
| | | opacity: 1; |
| | | } |
| | | |
| | | .assistant-bot { |
| | | transform: translateY(-4px) scale(1.02); |
| | | } |
| | | |
| | | .assistant-bot-head { |
| | | box-shadow: 0 0 30px rgba(80, 157, 255, 0.36); |
| | | } |
| | | |
| | | .assistant-bot-eye { |
| | | animation: robotBlinkFast 1.1s infinite; |
| | | box-shadow: 0 0 16px rgba(72, 186, 255, 0.95); |
| | | } |
| | | |
| | | .assistant-bot-mouth { |
| | | width: 28px; |
| | | opacity: 1; |
| | | animation: robotTalk 1.2s ease-in-out infinite; |
| | | } |
| | | |
| | | .assistant-bot-core { |
| | | animation: corePulse 1.4s ease-in-out infinite; |
| | | box-shadow: 0 0 24px rgba(78, 120, 255, 0.26); |
| | | } |
| | | |
| | | .assistant-bot-core-ring { |
| | | animation: coreRotate 3s linear infinite; |
| | | } |
| | | |
| | | .assistant-status { |
| | | color: #6a3bee; |
| | | box-shadow: 0 10px 22px rgba(106, 59, 238, 0.14); |
| | | } |
| | | |
| | | .assistant-status-dot { |
| | | background: #6a3bee; |
| | | box-shadow: 0 0 12px rgba(106, 59, 238, 0.9); |
| | | animation: thinkingDot 1s ease-in-out infinite; |
| | | } |
| | | |
| | | .assistant-base-sm { |
| | | box-shadow: 0 0 24px rgba(255, 93, 122, 0.48); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .assistant-halo { |
| | | position: absolute; |
| | | top: 22px; |
| | | width: 130px; |
| | | height: 130px; |
| | | border-radius: 50%; |
| | | background: radial-gradient(circle, rgba(46, 140, 224, 0.3) 0%, rgba(0, 85, 212, 0.18) 42%, rgba(113, 54, 244, 0.12) 60%, transparent 78%); |
| | | filter: blur(6px); |
| | | opacity: 0.82; |
| | | transition: all 0.35s ease; |
| | | } |
| | | |
| | | .assistant-scan-ring { |
| | | position: absolute; |
| | | top: 40px; |
| | | width: 132px; |
| | | height: 132px; |
| | | border-radius: 50%; |
| | | border: 1px solid rgba(90, 159, 224, 0.22); |
| | | box-shadow: inset 0 0 16px rgba(255, 255, 255, 0.25); |
| | | opacity: 0.55; |
| | | animation: scanRing 4s linear infinite; |
| | | } |
| | | |
| | | .assistant-orbit { |
| | | position: absolute; |
| | | top: 52px; |
| | | width: 150px; |
| | | height: 150px; |
| | | border-radius: 50%; |
| | | border: 1px dashed rgba(92, 135, 255, 0.22); |
| | | opacity: 0.45; |
| | | } |
| | | |
| | | .assistant-orbit-a { |
| | | animation: orbitRotate 8s linear infinite; |
| | | } |
| | | |
| | | .assistant-orbit-b { |
| | | width: 118px; |
| | | height: 118px; |
| | | top: 68px; |
| | | border-color: rgba(255, 108, 150, 0.22); |
| | | animation: orbitRotateReverse 5.6s linear infinite; |
| | | } |
| | | |
| | | .assistant-bot { |
| | | position: relative; |
| | | z-index: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-top: 12px; |
| | | transition: transform 0.35s ease; |
| | | } |
| | | |
| | | .assistant-bot-antenna { |
| | | position: absolute; |
| | | top: -4px; |
| | | width: 4px; |
| | | height: 20px; |
| | | border-radius: 999px; |
| | | background: linear-gradient(180deg, #fefefe, #aac9ff); |
| | | |
| | | &::before { |
| | | content: ''; |
| | | position: absolute; |
| | | top: -6px; |
| | | left: 50%; |
| | | width: 10px; |
| | | height: 10px; |
| | | border-radius: 50%; |
| | | transform: translateX(-50%); |
| | | background: linear-gradient(135deg, #54bfff, #7a41ff); |
| | | box-shadow: 0 0 14px rgba(84, 191, 255, 0.65); |
| | | } |
| | | } |
| | | |
| | | .assistant-bot-antenna-left { |
| | | left: 36px; |
| | | transform: rotate(-14deg); |
| | | } |
| | | |
| | | .assistant-bot-antenna-right { |
| | | right: 36px; |
| | | transform: rotate(14deg); |
| | | } |
| | | |
| | | .assistant-bot-head { |
| | | position: relative; |
| | | width: 92px; |
| | | height: 78px; |
| | | border-radius: 28px; |
| | | background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, #e8f1ff 100%); |
| | | border: 1px solid rgba(0, 85, 212, 0.14); |
| | | box-shadow: 0 16px 32px rgba(0, 85, 212, 0.14); |
| | | } |
| | | |
| | | .assistant-bot-head-glow { |
| | | position: absolute; |
| | | inset: 10px 16px auto; |
| | | height: 20px; |
| | | border-radius: 999px; |
| | | background: linear-gradient(180deg, rgba(0, 85, 212, 0.16), transparent); |
| | | } |
| | | |
| | | .assistant-bot-eye { |
| | | position: absolute; |
| | | top: 30px; |
| | | width: 16px; |
| | | height: 16px; |
| | | border-radius: 50%; |
| | | background: radial-gradient(circle, #8ef0ff 0%, #56c0ff 42%, #2869ff 100%); |
| | | box-shadow: 0 0 12px rgba(72, 186, 255, 0.72); |
| | | animation: robotBlink 3.2s infinite; |
| | | } |
| | | |
| | | .assistant-bot-eye-left { |
| | | left: 22px; |
| | | } |
| | | |
| | | .assistant-bot-eye-right { |
| | | right: 22px; |
| | | } |
| | | |
| | | .assistant-bot-mouth { |
| | | position: absolute; |
| | | left: 50%; |
| | | bottom: 16px; |
| | | width: 22px; |
| | | height: 4px; |
| | | transform: translateX(-50%); |
| | | border-radius: 999px; |
| | | background: linear-gradient(90deg, rgba(72, 186, 255, 0.2), rgba(72, 186, 255, 0.9), rgba(72, 186, 255, 0.2)); |
| | | } |
| | | |
| | | .assistant-bot-neck { |
| | | width: 16px; |
| | | height: 10px; |
| | | border-radius: 0 0 10px 10px; |
| | | background: linear-gradient(180deg, #dceaff, #bdd5ff); |
| | | margin-top: -2px; |
| | | } |
| | | |
| | | .assistant-bot-body { |
| | | position: relative; |
| | | width: 104px; |
| | | height: 92px; |
| | | margin-top: 2px; |
| | | border-radius: 28px 28px 34px 34px; |
| | | background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, #e3eeff 100%); |
| | | border: 1px solid rgba(0, 85, 212, 0.14); |
| | | box-shadow: 0 18px 36px rgba(0, 85, 212, 0.16); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .assistant-bot-arm { |
| | | position: absolute; |
| | | top: 18px; |
| | | width: 16px; |
| | | height: 44px; |
| | | border-radius: 999px; |
| | | background: linear-gradient(180deg, #eff5ff, #c7dbff); |
| | | border: 1px solid rgba(0, 85, 212, 0.12); |
| | | } |
| | | |
| | | .assistant-bot-arm-left { |
| | | left: -10px; |
| | | transform: rotate(16deg); |
| | | } |
| | | |
| | | .assistant-bot-arm-right { |
| | | right: -10px; |
| | | transform: rotate(-16deg); |
| | | } |
| | | |
| | | .assistant-bot-core { |
| | | position: relative; |
| | | width: 46px; |
| | | height: 46px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: $primary-blue; |
| | | background: radial-gradient(circle, rgba(255, 255, 255, 1) 0%, #dae8ff 55%, #adc7ff 100%); |
| | | } |
| | | |
| | | .assistant-bot-core-ring { |
| | | position: absolute; |
| | | inset: -6px; |
| | | border-radius: 50%; |
| | | border: 1px solid rgba(88, 135, 255, 0.3); |
| | | border-top-color: rgba(255, 96, 139, 0.85); |
| | | border-right-color: rgba(79, 145, 255, 0.9); |
| | | } |
| | | |
| | | .assistant-status { |
| | | position: relative; |
| | | z-index: 1; |
| | | margin-top: 14px; |
| | | padding: 6px 12px; |
| | | border-radius: 999px; |
| | | font-size: 12px; |
| | | font-weight: 600; |
| | | color: $deep-blue; |
| | | background: rgba(255, 255, 255, 0.95); |
| | | border: 1px solid rgba(0, 85, 212, 0.12); |
| | | box-shadow: 0 8px 20px rgba(0, 85, 212, 0.08); |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .assistant-status-dot { |
| | | width: 8px; |
| | | height: 8px; |
| | | border-radius: 50%; |
| | | background: #2e8ce0; |
| | | box-shadow: 0 0 10px rgba(46, 140, 224, 0.72); |
| | | } |
| | | |
| | | .assistant-base { |
| | | position: absolute; |
| | | bottom: 0; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | border-radius: 50%; |
| | | border: 2px solid rgba(255, 93, 122, 0.22); |
| | | background: radial-gradient(circle, rgba(255, 255, 255, 0.9) 0%, rgba(255, 111, 145, 0.1) 70%, transparent 100%); |
| | | } |
| | | |
| | | .assistant-base-lg { |
| | | width: 118px; |
| | | height: 30px; |
| | | } |
| | | |
| | | .assistant-base-md { |
| | | bottom: 6px; |
| | | width: 88px; |
| | | height: 20px; |
| | | border-color: rgba(255, 93, 122, 0.34); |
| | | } |
| | | |
| | | .assistant-base-sm { |
| | | bottom: 11px; |
| | | width: 54px; |
| | | height: 10px; |
| | | background: linear-gradient(90deg, rgba(255, 93, 122, 0.95), rgba(255, 173, 188, 0.9)); |
| | | border: none; |
| | | box-shadow: 0 0 18px rgba(255, 93, 122, 0.38); |
| | | } |
| | | |
| | | @keyframes robotBlink { |
| | | 0%, 44%, 48%, 100% { |
| | | transform: scaleY(1); |
| | | } |
| | | 46% { |
| | | transform: scaleY(0.14); |
| | | } |
| | | } |
| | | |
| | | @keyframes robotBlinkFast { |
| | | 0%, 100% { |
| | | transform: scaleY(1); |
| | | } |
| | | 50% { |
| | | transform: scaleY(0.3); |
| | | } |
| | | } |
| | | |
| | | @keyframes robotTalk { |
| | | 0%, 100% { |
| | | transform: translateX(-50%) scaleX(1); |
| | | } |
| | | 50% { |
| | | transform: translateX(-50%) scaleX(1.35); |
| | | } |
| | | } |
| | | |
| | | @keyframes corePulse { |
| | | 0%, 100% { |
| | | transform: scale(1); |
| | | filter: brightness(1); |
| | | } |
| | | 50% { |
| | | transform: scale(1.08); |
| | | filter: brightness(1.08); |
| | | } |
| | | } |
| | | |
| | | @keyframes coreRotate { |
| | | from { |
| | | transform: rotate(0deg); |
| | | } |
| | | to { |
| | | transform: rotate(360deg); |
| | | } |
| | | } |
| | | |
| | | @keyframes orbitRotate { |
| | | from { |
| | | transform: rotate(0deg); |
| | | } |
| | | to { |
| | | transform: rotate(360deg); |
| | | } |
| | | } |
| | | |
| | | @keyframes orbitRotateReverse { |
| | | from { |
| | | transform: rotate(360deg); |
| | | } |
| | | to { |
| | | transform: rotate(0deg); |
| | | } |
| | | } |
| | | |
| | | @keyframes scanRing { |
| | | 0%, 100% { |
| | | transform: scale(0.96); |
| | | opacity: 0.42; |
| | | } |
| | | 50% { |
| | | transform: scale(1.04); |
| | | opacity: 0.86; |
| | | } |
| | | } |
| | | |
| | | @keyframes thinkingDot { |
| | | 0%, 100% { |
| | | transform: scale(1); |
| | | } |
| | | 50% { |
| | | transform: scale(1.35); |
| | | } |
| | | } |
| | | |
| | | .welcome-card { |
| | | position: relative; |
| | | padding: 14px 14px 12px; |
| | | border-radius: 16px; |
| | | background: |
| | | linear-gradient(#fff, #fff) padding-box, |
| | | linear-gradient(135deg, rgba(255, 64, 96, 0.85), rgba(117, 65, 255, 0.9)) border-box; |
| | | border: 1px solid transparent; |
| | | box-shadow: 0 16px 36px rgba(0, 85, 212, 0.12); |
| | | |
| | | &.compact { |
| | | padding: 10px 12px; |
| | | border-radius: 12px; |
| | | box-shadow: 0 8px 16px rgba(0, 85, 212, 0.07); |
| | | |
| | | .welcome-eyebrow { |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .welcome-title { |
| | | font-size: 17px; |
| | | line-height: 1.3; |
| | | |
| | | br { |
| | | display: none; |
| | | } |
| | | } |
| | | |
| | | .welcome-desc { |
| | | margin-top: 6px; |
| | | font-size: 12px; |
| | | line-height: 1.55; |
| | | } |
| | | |
| | | .quick-prompt-list { |
| | | margin-top: 10px; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .quick-prompt-btn { |
| | | padding: 8px 10px; |
| | | font-size: 12px; |
| | | border-radius: 7px; |
| | | } |
| | | |
| | | .more-prompts-btn { |
| | | margin-top: 8px; |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .welcome-eyebrow { |
| | | font-size: 11px; |
| | | font-weight: 700; |
| | | letter-spacing: 2px; |
| | | color: rgba(0, 85, 212, 0.58); |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .welcome-title { |
| | | margin: 0; |
| | | font-size: 26px; |
| | | line-height: 1.2; |
| | | font-weight: 800; |
| | | color: #172033; |
| | | } |
| | | |
| | | .welcome-desc { |
| | | margin: 10px 0 0; |
| | | font-size: 13px; |
| | | line-height: 1.7; |
| | | color: #5f6980; |
| | | } |
| | | |
| | | .quick-prompt-list { |
| | | display: grid; |
| | | gap: 8px; |
| | | margin-top: 14px; |
| | | } |
| | | |
| | | .quick-prompt-btn { |
| | | width: 100%; |
| | | border: none; |
| | | border-radius: 10px; |
| | | padding: 11px 14px; |
| | | text-align: left; |
| | | font-size: 13px; |
| | | font-weight: 600; |
| | | color: #fff; |
| | | cursor: pointer; |
| | | background: linear-gradient(90deg, #ff4c55 0%, #7c38ef 100%); |
| | | box-shadow: 0 12px 22px rgba(124, 56, 239, 0.18); |
| | | transition: transform 0.25s ease, box-shadow 0.25s ease, opacity 0.2s ease; |
| | | position: relative; |
| | | overflow: hidden; |
| | | |
| | | &::before { |
| | | content: ''; |
| | | position: absolute; |
| | | inset: 0; |
| | | background: linear-gradient(135deg, rgba(255, 255, 255, 0.22), transparent 56%); |
| | | pointer-events: none; |
| | | } |
| | | |
| | | &::after { |
| | | content: ''; |
| | | position: absolute; |
| | | top: -120%; |
| | | left: -30%; |
| | | width: 45%; |
| | | height: 260%; |
| | | background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.3), transparent); |
| | | transform: rotate(22deg); |
| | | opacity: 0; |
| | | transition: all 0.35s ease; |
| | | } |
| | | |
| | | &:hover:not(:disabled) { |
| | | transform: translateY(-2px) scale(1.01); |
| | | box-shadow: 0 16px 28px rgba(124, 56, 239, 0.24); |
| | | |
| | | &::after { |
| | | left: 100%; |
| | | opacity: 1; |
| | | } |
| | | } |
| | | |
| | | &:disabled { |
| | | cursor: not-allowed; |
| | | opacity: 0.65; |
| | | } |
| | | } |
| | | |
| | | .more-prompts-btn { |
| | | margin-top: 10px; |
| | | padding: 0 12px; |
| | | height: 32px; |
| | | border: 1px solid rgba(208, 65, 81, 0.12); |
| | | border-radius: 999px; |
| | | background: linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(255, 241, 245, 0.96)); |
| | | color: #d04151; |
| | | font-size: 13px; |
| | | font-weight: 600; |
| | | cursor: pointer; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | box-shadow: 0 10px 18px rgba(208, 65, 81, 0.08); |
| | | transition: all 0.25s ease; |
| | | |
| | | &:hover { |
| | | transform: translateY(-1px); |
| | | background: linear-gradient(135deg, #ff5570 0%, #8a3df6 100%); |
| | | border-color: transparent; |
| | | color: #fff; |
| | | box-shadow: 0 14px 24px rgba(138, 61, 246, 0.18); |
| | | } |
| | | } |
| | | |
| | | .hero-dot-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(14, 1fr); |
| | | gap: 7px; |
| | | padding: 0 18px 14px; |
| | | |
| | | span { |
| | | display: block; |
| | | width: 100%; |
| | | aspect-ratio: 1; |
| | | border-radius: 2px; |
| | | background: linear-gradient(135deg, rgba(255, 110, 138, 0.95), rgba(255, 190, 201, 0.55)); |
| | | } |
| | | } |
| | | |
| | | .message-list { |
| | | padding: 8px 18px 18px; |
| | | gap: 16px; |
| | | background: transparent; |
| | | } |
| | | |
| | | .input-area { |
| | | padding: 12px 18px 16px; |
| | | background: #fff; |
| | | border-top: none; |
| | | |
| | | &::before { |
| | | display: none; |
| | | } |
| | | |
| | | .input-box { |
| | | padding: 14px 16px 16px; |
| | | border: 1px solid rgba(123, 56, 239, 0.9); |
| | | border-radius: 22px; |
| | | margin: 0; |
| | | transition: all 0.25s ease; |
| | | box-shadow: 0 14px 34px rgba(0, 85, 212, 0.08); |
| | | |
| | | &:focus-within { |
| | | border-color: #7c38ef; |
| | | box-shadow: 0 0 0 3px rgba(124, 56, 239, 0.1), 0 18px 40px rgba(0, 85, 212, 0.12); |
| | | transform: none; |
| | | } |
| | | |
| | | :deep(.el-textarea__inner) { |
| | | padding-right: 58px; |
| | | padding-bottom: 0; |
| | | min-height: 104px; |
| | | |
| | | &::placeholder { |
| | | color: #a0a9bc; |
| | | } |
| | | } |
| | | |
| | | .send-btn { |
| | | right: 25px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 36px; |
| | | min-width: 36px; |
| | | height: 36px; |
| | | padding: 0; |
| | | background: linear-gradient(135deg, #ff5570 0%, #7a36f2 58%, #2d79ff 100%); |
| | | border-radius: 50%; |
| | | box-shadow: 0 12px 24px rgba(109, 50, 236, 0.24); |
| | | transition: all 0.25s ease; |
| | | gap: 0; |
| | | |
| | | &:hover:not(:disabled) { |
| | | transform: translateY(calc(-50% - 1px)) scale(1.04); |
| | | box-shadow: 0 16px 28px rgba(109, 50, 236, 0.3); |
| | | } |
| | | |
| | | &:active:not(:disabled) { |
| | | transform: translateY(-50%) scale(0.96); |
| | | } |
| | | |
| | | .el-icon { |
| | | margin: 0; |
| | | font-size: 16px; |
| | | transform: translate(0, -1px); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 767px) { |
| | | .chat-hero { |
| | | grid-template-columns: 1fr; |
| | | gap: 10px; |
| | | padding: 14px 14px 6px; |
| | | |
| | | &.compact { |
| | | padding: 8px 14px 4px; |
| | | } |
| | | } |
| | | |
| | | .assistant-stand { |
| | | min-height: 184px; |
| | | } |
| | | |
| | | .welcome-card { |
| | | padding: 12px 12px 10px; |
| | | } |
| | | |
| | | .welcome-title { |
| | | font-size: 21px; |
| | | } |
| | | |
| | | .hero-dot-grid { |
| | | grid-template-columns: repeat(12, 1fr); |
| | | gap: 6px; |
| | | padding: 0 14px 12px; |
| | | } |
| | | |
| | | .message-list { |
| | | padding: 8px 14px 14px; |
| | | } |
| | | |
| | | .input-area { |
| | | padding: 10px 14px 14px; |
| | | } |
| | | |
| | | .input-area .input-actions { |
| | | gap: 10px; |
| | | flex-wrap: wrap; |
| | | } |
| | | } |
| | | </style> |