2026-04-29 f945f2fe9dae35c3b5fd4beea2b182904df0e16e
src/components/AIChatSidebar/index.vue
@@ -41,18 +41,18 @@
          </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>
@@ -99,6 +99,80 @@
        </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"
@@ -149,10 +223,10 @@
          <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
@@ -164,7 +238,7 @@
                  :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>
@@ -188,8 +262,9 @@
                  class="send-btn"
                  :disabled="isSending || (!inputMessage.trim() && !selectedFile)"
                  @click="sendMessage"
                  aria-label="发送"
              >
                发送
                <el-icon><Promotion /></el-icon>
              </el-button>
            </div>
          </div>
@@ -203,7 +278,7 @@
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({
@@ -228,6 +303,7 @@
    storageKey: 'ai_chat_uuid',
    placeholder: '请输入您的问题... (Enter 发送, Shift+Enter 换行)',
    welcomeMessage: '你好',
    description: '我可以回答你的问题,为你提供业务数据解读信息、处理建议和辅助决策支持。',
    allowFileUpload: true,
    emptySessionText: '暂无历史会话'
  },
@@ -241,6 +317,7 @@
    storageKey: 'purchase_ai_chat_uuid',
    placeholder: '请输入采购问题... (Enter 发送, Shift+Enter 换行)',
    welcomeMessage: '你好',
    description: '我可以协助你分析采购订单、到货进度、供应商表现和付款情况,帮助你快速定位采购异常。',
    allowFileUpload: false,
    emptySessionText: '暂无采购会话'
  }
@@ -250,6 +327,52 @@
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)
@@ -274,11 +397,31 @@
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 () => {
@@ -395,12 +538,7 @@
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 = {}
@@ -408,6 +546,7 @@
  showHistory.value = false
  selectedFile.value = null
  inputMessage.value = ''
  quickPromptStart.value = 0
  initUUID()
  hello()
})
@@ -425,6 +564,13 @@
const handleClose = () => {
  visible.value = false
}
const handleManualClose = () => {
  if (isSending.value) {
    abortCurrentRequest()
  }
  handleClose()
}
const initUUID = () => {
@@ -447,9 +593,29 @@
  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 = () => {
@@ -673,17 +839,7 @@
}
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) => {
@@ -1376,6 +1532,40 @@
        }
      }
    }
    :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 {
@@ -1392,6 +1582,11 @@
      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) {
@@ -1839,6 +2034,40 @@
      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 {
@@ -1944,6 +2173,10 @@
      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: '';
@@ -1973,6 +2206,11 @@
        background: linear-gradient(145deg, #b0b0b0, #c5c5c5);
        box-shadow: none;
        cursor: not-allowed;
      }
      .el-icon {
        font-size: 15px;
        transform: translateY(-1px);
      }
    }
  }
@@ -2034,4 +2272,730 @@
    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>