From d1d3ea967d916d798dbb1979130137bdd5b6152c Mon Sep 17 00:00:00 2001
From: yuan <123@>
Date: 星期一, 18 五月 2026 10:22:37 +0800
Subject: [PATCH] feat(search): 基础产品维护子节点模糊查询

---
 src/components/AIChatSidebar/index.vue | 1512 +++++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 1,132 insertions(+), 380 deletions(-)

diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
index a5dcf0b..e3a9e3c 100644
--- a/src/components/AIChatSidebar/index.vue
+++ b/src/components/AIChatSidebar/index.vue
@@ -1,7 +1,7 @@
 <template>
   <div class="ai-chat-sidebar-wrapper">
     <!-- 鎮诞鍥炬爣 -->
-    <div class="ai-chat-trigger" @click="toggleSidebar" v-show="!visible">
+    <div v-if="!hideTrigger" class="ai-chat-trigger" @click="toggleSidebar" v-show="!visible">
       <el-tooltip :content="currentAssistant.tooltip" placement="left">
         <div class="trigger-icon">
           <el-icon :size="30" color="#fff"><component :is="currentAssistant.icon" /></el-icon>
@@ -12,14 +12,16 @@
     <!-- 渚ц竟鏍忓璇濇 -->
     <el-drawer
         v-model="visible"
-        :size="drawerSize"
-        direction="rtl"
+        :size="computedDrawerSize"
+        :direction="drawerDirection"
         :with-header="true"
         class="ai-chat-drawer"
         :modal="false"
         modal-class="ai-chat-overlay"
         :show-close="false"
         :append-to-body="false"
+        :close-on-press-escape="!hideTrigger"
+        :close-on-click-modal="!hideTrigger"
         @close="handleClose"
     >
       <template #header>
@@ -50,8 +52,16 @@
                 <el-icon :size="18"><Plus /></el-icon>
               </el-button>
             </el-tooltip>
-            <div class="action-divider"></div>
-            <el-tooltip content="鍏抽棴" placement="bottom">
+            <el-button
+                v-if="headerExtraActionText"
+                link
+                class="header-action-btn header-action-btn--text"
+                @click="handleHeaderExtraAction"
+            >
+              {{ headerExtraActionText }}
+            </el-button>
+            <div v-if="!hideTrigger" class="action-divider"></div>
+            <el-tooltip v-if="!hideTrigger" content="鍏抽棴" placement="bottom">
               <el-button link class="header-action-btn close-btn" @click="handleManualClose">
                 <el-icon :size="18"><Close /></el-icon>
               </el-button>
@@ -105,23 +115,17 @@
               <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 class="assistant-model-shell">
+                <div class="assistant-model-cut">
+                  <img
+                      v-if="currentAssistantAvatar"
+                      class="assistant-model-img"
+                      :src="currentAssistantAvatar"
+                      :alt="currentAssistant.label"
+                  />
+                  <div v-else class="assistant-model-fallback">
+                    <el-icon :size="30"><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">
@@ -167,10 +171,6 @@
                 <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">
@@ -242,7 +242,123 @@
                   </el-table>
                 </div>
 
-                <!-- 鎵撳瓧涓姩鐢� -->
+                <div v-if="message.manufacturingData" class="manufacturing-card">
+                  <div class="manufacturing-card__title">{{ getManufacturingTypeLabel(message.type) }}</div>
+
+                  <div
+                      v-if="message.manufacturingData.summaryEntries?.length || message.manufacturingData.coreMetrics?.length"
+                      class="manufacturing-summary-grid"
+                  >
+                    <div
+                        v-for="(entry, entryIndex) in message.manufacturingData.summaryEntries"
+                        :key="`summary-${entry.key}-${entryIndex}`"
+                        class="manufacturing-summary-item"
+                    >
+                      <span class="manufacturing-summary-label">{{ entry.label }}</span>
+                      <strong class="manufacturing-summary-value">{{ entry.value }}</strong>
+                    </div>
+                    <div
+                        v-for="(metric, metricIndex) in message.manufacturingData.coreMetrics"
+                        :key="`core-${metric.key}-${metricIndex}`"
+                        class="manufacturing-summary-item manufacturing-summary-item--core"
+                    >
+                      <span class="manufacturing-summary-label">{{ metric.label }}</span>
+                      <strong class="manufacturing-summary-value">{{ metric.value }}</strong>
+                    </div>
+                  </div>
+
+                  <div v-if="message.manufacturingData.warningItems?.length" class="manufacturing-warning-list">
+                    <div
+                        v-for="(warning, warningIndex) in message.manufacturingData.warningItems"
+                        :key="`warning-${warning.title || warningIndex}`"
+                        class="manufacturing-warning-item"
+                    >
+                      <div class="manufacturing-warning-item__head">
+                        <el-tag size="small" :type="getManufacturingWarningLevelType(warning.level)">
+                          {{ getManufacturingWarningLevelLabel(warning.level) }}
+                        </el-tag>
+                        <strong>{{ warning.title || `棰勮 ${warningIndex + 1}` }}</strong>
+                        <span v-if="warning.count !== '' && warning.count !== null && warning.count !== undefined" class="manufacturing-warning-count">
+                          {{ warning.count }}
+                        </span>
+                      </div>
+                      <p v-if="warning.detail" class="manufacturing-warning-detail">{{ warning.detail }}</p>
+                    </div>
+                  </div>
+
+                  <div
+                      v-if="message.manufacturingData.listItems?.length && message.manufacturingData.columns?.length"
+                      class="table-wrapper manufacturing-table-wrapper"
+                  >
+                    <el-table :data="message.manufacturingData.listItems" border stripe size="small" style="width: 100%">
+                      <el-table-column
+                          v-for="col in message.manufacturingData.columns"
+                          :key="col"
+                          :label="getStructuredFieldLabel(col)"
+                          min-width="140"
+                          show-overflow-tooltip
+                      >
+                        <template #default="{ row }">
+                          {{ formatStructuredValue(row[col]) }}
+                        </template>
+                      </el-table-column>
+                    </el-table>
+                  </div>
+
+                  <div v-if="message.manufacturingData.actionCards?.length" class="manufacturing-action-list">
+                    <div
+                        v-for="(card, cardIndex) in message.manufacturingData.actionCards"
+                        :key="card.runtimeKey || `${card.code}-${card.targetApi}-${cardIndex}`"
+                        class="manufacturing-action-card"
+                    >
+                      <div class="manufacturing-action-card__head">
+                        <strong>{{ card.name || `鍔ㄤ綔 ${cardIndex + 1}` }}</strong>
+                        <el-tag size="small" type="info">{{ getNormalizedRequestMethod(card.method) }}</el-tag>
+                      </div>
+                      <div class="manufacturing-action-card__meta">
+                        <span>{{ card.code || '--' }}</span>
+                        <span>{{ card.targetApi || '--' }}</span>
+                      </div>
+                      <p v-if="card.description" class="manufacturing-action-card__desc">{{ card.description }}</p>
+                      <div v-if="card.requiredFields?.length" class="manufacturing-required-fields">
+                        <span>蹇呭~瀛楁</span>
+                        <el-tag
+                            v-for="field in card.requiredFields"
+                            :key="field"
+                            size="small"
+                            type="warning"
+                        >
+                          {{ getStructuredPathLabel(field) }}
+                        </el-tag>
+                      </div>
+                      <el-input
+                          v-model="card.payloadText"
+                          type="textarea"
+                          :rows="6"
+                          resize="vertical"
+                          :disabled="card.executing"
+                          placeholder="璇疯緭鍏� JSON 璇锋眰鍙傛暟"
+                      />
+                      <div class="manufacturing-action-footer">
+                        <span
+                            v-if="card.executeResult"
+                            :class="['manufacturing-action-result', card.executeError ? 'error' : 'success']"
+                        >
+                          {{ card.executeResult }}
+                        </span>
+                        <el-button
+                            type="primary"
+                            size="small"
+                            :loading="card.executing"
+                            @click="executeManufacturingAction(message, card, cardIndex)"
+                        >
+                          纭骞舵墽琛�
+                        </el-button>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+
                 <div v-if="message.purchaseAnalysisData" class="purchase-confirm-card">
                   <div class="purchase-confirm-header">
                     <span>{{ businessTypeLabelMap[message.purchaseAnalysisData.businessType] || message.purchaseAnalysisData.businessType || '閲囪喘涓氬姟' }}</span>
@@ -523,8 +639,16 @@
 import request from '@/utils/request'
 import * as echarts from 'echarts'
 import { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, Promotion, RefreshRight } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { builtInAssistants, generalAssistant } from './assistants'
+import todoAssistantAvatar from '@/assets/AI/寰呭姙鍔╂墜.png'
+import salesAssistantAvatar from '@/assets/AI/閿�鍞姪鎵�.png'
+import purchaseAssistantAvatar from '@/assets/AI/閲囪喘鍔╂墜.png'
+import productionAssistantAvatar from '@/assets/AI/鐢熶骇鍔╂墜.png'
+import financeAssistantAvatar from '@/assets/AI/璐㈠姟鍔╂墜.png'
+import bossAssistantAvatar from '@/assets/AI/寰呭姙鍔╂墜.png'
+
+const emit = defineEmits(['header-extra-action'])
 
 const props = defineProps({
   assistants: {
@@ -534,12 +658,50 @@
   defaultAssistant: {
     type: String,
     default: ''
+  },
+  hideTrigger: {
+    type: Boolean,
+    default: false
+  },
+  autoOpen: {
+    type: Boolean,
+    default: false
+  },
+  drawerSize: {
+    type: [String, Number],
+    default: ''
+  },
+  drawerDirection: {
+    type: String,
+    default: 'rtl'
+  },
+  headerExtraActionText: {
+    type: String,
+    default: ''
   }
 })
 
+const hideTrigger = computed(() => props.hideTrigger)
+const headerExtraActionText = computed(() => String(props.headerExtraActionText || '').trim())
+const drawerDirection = computed(() => (props.drawerDirection === 'ttb' || props.drawerDirection === 'btt' || props.drawerDirection === 'ltr' || props.drawerDirection === 'rtl')
+  ? props.drawerDirection
+  : 'rtl')
 const assistants = computed(() => props.assistants?.length ? props.assistants : builtInAssistants)
 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 assistantAvatarByKey = {
+  general: todoAssistantAvatar,
+  todo: todoAssistantAvatar,
+  purchase: purchaseAssistantAvatar,
+  sales: salesAssistantAvatar,
+  production: productionAssistantAvatar,
+  finance: financeAssistantAvatar,
+  boss: bossAssistantAvatar
+}
+const currentAssistantAvatar = computed(() => {
+  const assistant = currentAssistant.value || {}
+  return assistant.avatar || assistantAvatarByKey[assistant.key] || ''
+})
 const showAssistantSwitch = computed(() => assistants.value.length > 1)
 const quickPromptLimit = 3
 const quickPromptStart = ref(0)
@@ -564,11 +726,12 @@
 
 const visible = ref(false)
 const windowWidth = ref(window.innerWidth)
-const drawerSize = computed(() => {
+const responsiveDrawerSize = computed(() => {
   if (windowWidth.value < 768) return '100%'
   if (windowWidth.value < 1200) return '50%'
   return '50%'
 })
+const computedDrawerSize = computed(() => props.drawerSize || responsiveDrawerSize.value)
 const messageListRef = ref(null)
 const isSending = ref(false)
 const currentAbortController = ref(null)
@@ -586,6 +749,75 @@
   payment_registration: '浠樻鐧昏',
   purchase_return_order: '閲囪喘閫�璐у崟',
   unknown: '鏈煡閲囪喘涓氬姟'
+}
+const manufacturingStructuredTypeSet = new Set([
+  'manufacturing_site_snapshot',
+  'manufacturing_plan_list',
+  'manufacturing_workorder_list',
+  'manufacturing_device_list',
+  'manufacturing_device_repair_list',
+  'manufacturing_quality_list',
+  'manufacturing_material_list',
+  'manufacturing_exception_list',
+  'manufacturing_warning',
+  'manufacturing_analysis',
+  'manufacturing_action_plan'
+])
+const manufacturingListTypeSet = new Set([
+  'manufacturing_plan_list',
+  'manufacturing_workorder_list',
+  'manufacturing_device_list',
+  'manufacturing_device_repair_list',
+  'manufacturing_quality_list',
+  'manufacturing_material_list',
+  'manufacturing_exception_list'
+])
+const manufacturingTypeLabelMap = {
+  manufacturing_site_snapshot: '鐢熶骇鐜板満姒傝',
+  manufacturing_plan_list: '璁″垝鏌ヨ',
+  manufacturing_workorder_list: '宸ュ崟鏌ヨ',
+  manufacturing_device_list: '璁惧鏌ヨ',
+  manufacturing_device_repair_list: '璁惧缁翠慨璁板綍鏌ヨ',
+  manufacturing_quality_list: '璐ㄩ噺鏌ヨ',
+  manufacturing_material_list: '鐗╂枡鏌ヨ',
+  manufacturing_exception_list: '寮傚父鏌ヨ',
+  manufacturing_warning: '棰勮鐪嬫澘',
+  manufacturing_analysis: '缁忚惀鍒嗘瀽',
+  manufacturing_action_plan: '鍔炵悊寤鸿'
+}
+const structuredFieldLabelMap = {
+  workOrderNo: '宸ュ崟鍙�',
+  planEndTime: '璁″垝缁撴潫鏃堕棿',
+  planStartTime: '璁″垝寮�濮嬫椂闂�',
+  timeRange: '鏃堕棿鑼冨洿',
+  startDate: '寮�濮嬫棩鏈�',
+  endDate: '缁撴潫鏃ユ湡',
+  warningCount: '棰勮鏁伴噺',
+  overduePlanCount: '閫炬湡璁″垝鏁�',
+  overdueWorkOrderCount: '閫炬湡宸ュ崟鏁�',
+  actionCount: '寤鸿鍔ㄤ綔鏁�',
+  qualityOpenCount: '璐ㄩ噺寰呭鐞嗘暟',
+  lowStockCount: '浣庡簱瀛樻暟',
+  exceptionCount: '寮傚父鏁�',
+  userId: '鐢ㄦ埛ID',
+  tenantId: '绉熸埛ID',
+  status: '鐘舵��',
+  deviceName: '璁惧鍚嶇О',
+  deviceModel: '璁惧鍨嬪彿',
+  pendingRepairCount: '寰呯淮淇暟',
+  repairTime: '缁翠慨鏃堕棿',
+  repairName: '鎶ヤ慨浜�',
+  maintenanceName: '缁翠慨浜哄憳',
+  level: '棰勮绛夌骇',
+  title: '鏍囬',
+  count: '鏁伴噺',
+  detail: '璇︽儏',
+  remark: '澶囨敞',
+  createTime: '鍒涘缓鏃堕棿',
+  updateTime: '鏇存柊鏃堕棿',
+  exceptionType: '寮傚父绫诲瀷',
+  materialName: '鐗╂枡鍚嶇О',
+  stockQty: '搴撳瓨閲�'
 }
 const purchasePayloadFieldLabelMap = {
   purchaseLedgers: '閲囪喘鍙拌处',
@@ -738,6 +970,366 @@
   inventoryWarningQuantity: 'inventoryWarningQuantity',
   isInspected: 'isInspected',
   isChecked: 'isInspected'
+}
+const isPlainObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
+
+const stringifyStructuredPayload = (value, spaces = 2) => {
+  if (typeof value === 'string') return value
+  try {
+    return JSON.stringify(value ?? {}, null, spaces)
+  } catch (err) {
+    return '{}'
+  }
+}
+
+const structuredFieldTokenLabelMap = {
+  time: '鏃堕棿',
+  range: '鑼冨洿',
+  start: '寮�濮�',
+  end: '缁撴潫',
+  date: '鏃ユ湡',
+  warning: '棰勮',
+  overdue: '閫炬湡',
+  plan: '璁″垝',
+  work: '宸�',
+  order: '鍗�',
+  workorder: '宸ュ崟',
+  count: '鏁伴噺',
+  quality: '璐ㄩ噺',
+  low: '浣�',
+  stock: '搴撳瓨',
+  exception: '寮傚父',
+  action: '鍔ㄤ綔',
+  user: '鐢ㄦ埛',
+  tenant: '绉熸埛',
+  id: 'ID',
+  no: '缂栧彿',
+  number: '缂栧彿',
+  code: '缂栫爜',
+  name: '鍚嶇О',
+  status: '鐘舵��',
+  level: '绛夌骇',
+  title: '鏍囬',
+  detail: '璇︽儏',
+  total: '鎬绘暟',
+  rate: '姣旂巼',
+  type: '绫诲瀷',
+  pending: '寰�',
+  repair: '缁翠慨',
+  device: '璁惧',
+  material: '鐗╂枡'
+}
+
+const convertStructuredFieldKeyToChinese = (fieldKey = '') => {
+  const key = String(fieldKey || '').trim()
+  if (!key) return '-'
+  if (structuredFieldLabelMap[key]) return structuredFieldLabelMap[key]
+  if (/[\u4e00-\u9fa5]/.test(key)) return key
+
+  const rawTokens = key
+    .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
+    .replace(/[_-]+/g, ' ')
+    .trim()
+    .split(/\s+/)
+    .filter(Boolean)
+    .map(token => token.toLowerCase())
+
+  if (!rawTokens.length) return '瀛楁'
+
+  const mappedTokens = rawTokens
+    .map(token => structuredFieldTokenLabelMap[token] || '')
+    .filter(Boolean)
+
+  if (mappedTokens.length) {
+    return mappedTokens.join('')
+  }
+
+  return '瀛楁'
+}
+
+const getStructuredFieldLabel = (fieldKey = '') => {
+  return convertStructuredFieldKeyToChinese(fieldKey)
+}
+
+const getStructuredPathLabel = (fieldPath = '') => {
+  const path = String(fieldPath || '').trim()
+  if (!path) return '-'
+
+  const segments = path
+    .replace(/\[(\d+)]/g, '.$1')
+    .split('.')
+    .filter(Boolean)
+
+  if (!segments.length) return getStructuredFieldLabel(path)
+
+  return segments.map((segment) => {
+    if (/^\d+$/.test(segment)) {
+      return `绗�${Number(segment) + 1}椤筦
+    }
+    return getStructuredFieldLabel(segment)
+  }).join(' / ')
+}
+
+const formatStructuredValue = (value) => {
+  if (value === null || value === undefined || value === '') return '-'
+  if (Array.isArray(value)) {
+    const preview = value.slice(0, 3).map(item => formatStructuredValue(item)).join('銆�')
+    return value.length > 3 ? `${preview} 绛�${value.length}椤筦 : preview
+  }
+  if (isPlainObject(value)) return stringifyStructuredPayload(value, 0)
+  return String(value)
+}
+
+const normalizeManufacturingSummaryEntries = (summary) => {
+  if (!isPlainObject(summary)) return []
+  return Object.entries(summary)
+    .filter(([, value]) => value !== undefined && value !== null && `${value}`.trim() !== '')
+    .map(([key, value]) => ({
+      key,
+      label: getStructuredFieldLabel(key),
+      value: formatStructuredValue(value)
+    }))
+}
+
+const normalizeManufacturingCoreMetrics = (coreMetrics) => {
+  if (Array.isArray(coreMetrics)) {
+    return coreMetrics.map((item, index) => {
+      if (isPlainObject(item)) {
+        const label = item.label || item.name || item.key || `鎸囨爣${index + 1}`
+        const metricValue = item.value ?? item.metricValue ?? item.data ?? '-'
+        const unit = item.unit ? ` ${item.unit}` : ''
+        return {
+          key: String(item.key || item.name || index),
+          label,
+          value: `${formatStructuredValue(metricValue)}${unit}`.trim()
+        }
+      }
+      return {
+        key: String(index),
+        label: `鎸囨爣${index + 1}`,
+        value: formatStructuredValue(item)
+      }
+    })
+  }
+
+  if (isPlainObject(coreMetrics)) {
+    return Object.entries(coreMetrics).map(([key, value]) => ({
+      key,
+      label: getStructuredFieldLabel(key),
+      value: formatStructuredValue(value)
+    }))
+  }
+
+  return []
+}
+
+const normalizeManufacturingWarningItems = (items = []) => {
+  if (!Array.isArray(items)) return []
+  return items
+    .filter(item => isPlainObject(item))
+    .map(item => ({
+      level: String(item.level || '').toLowerCase(),
+      title: item.title || '',
+      count: item.count ?? '',
+      detail: item.detail ?? ''
+    }))
+}
+
+const inferManufacturingColumns = (items = []) => {
+  if (!Array.isArray(items) || !items.length) return []
+  const fieldSet = new Set()
+  items.forEach((item) => {
+    if (!isPlainObject(item)) return
+    Object.keys(item).forEach((key) => fieldSet.add(key))
+  })
+  return Array.from(fieldSet)
+}
+
+const getManufacturingActionCardRuntimeKey = (card = {}, index = 0) => {
+  const code = String(card?.code || '').trim()
+  const api = String(card?.targetApi || '').trim()
+  const name = String(card?.name || '').trim()
+  return `${code}::${api}::${name}::${index}`
+}
+
+const normalizeManufacturingActionCards = (actionCards = [], previousCards = []) => {
+  const previousMap = new Map()
+  if (Array.isArray(previousCards)) {
+    previousCards.forEach((card, index) => {
+      previousMap.set(getManufacturingActionCardRuntimeKey(card, index), card)
+    })
+  }
+
+  return (Array.isArray(actionCards) ? actionCards : [])
+    .filter(card => isPlainObject(card))
+    .map((card, index) => {
+      const runtimeKey = getManufacturingActionCardRuntimeKey(card, index)
+      const previousCard = previousMap.get(runtimeKey)
+      const fallbackPayloadText = stringifyStructuredPayload(card.examplePayload, 2)
+      return {
+        ...card,
+        runtimeKey,
+        payloadText: previousCard?.payloadText ?? fallbackPayloadText,
+        executing: Boolean(previousCard?.executing),
+        executed: Boolean(previousCard?.executed),
+        executeResult: previousCard?.executeResult || '',
+        executeError: Boolean(previousCard?.executeError)
+      }
+    })
+}
+
+const buildManufacturingStructuredData = (parsedData, previousData = null) => {
+  const type = String(parsedData?.type || '')
+  if (!manufacturingStructuredTypeSet.has(type)) return null
+
+  const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {}
+  const items = Array.isArray(rawData.items) ? rawData.items.filter(item => isPlainObject(item)) : []
+  const warningItems = type === 'manufacturing_warning' ? normalizeManufacturingWarningItems(items) : []
+  const listItems = manufacturingListTypeSet.has(type) ? items : []
+  const actionCards = type === 'manufacturing_action_plan'
+    ? normalizeManufacturingActionCards(rawData.actionCards, previousData?.actionCards)
+    : []
+
+  return {
+    type,
+    summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary),
+    coreMetrics: normalizeManufacturingCoreMetrics(rawData.coreMetrics),
+    listItems,
+    columns: inferManufacturingColumns(listItems),
+    warningItems,
+    actionCards
+  }
+}
+
+const getManufacturingTypeLabel = (type = '') => manufacturingTypeLabelMap[String(type || '')] || '鍒堕�犵粨鏋�'
+
+const getManufacturingWarningLevelType = (level = '') => {
+  const normalizedLevel = String(level || '').toLowerCase()
+  if (normalizedLevel === 'high') return 'danger'
+  if (normalizedLevel === 'medium') return 'warning'
+  return 'info'
+}
+
+const getManufacturingWarningLevelLabel = (level = '') => {
+  const normalizedLevel = String(level || '').toLowerCase()
+  if (normalizedLevel === 'high') return '楂�'
+  if (normalizedLevel === 'medium') return '涓�'
+  return normalizedLevel ? normalizedLevel.toUpperCase() : '涓�鑸�'
+}
+
+const normalizeRequestMethod = (method = 'POST') => {
+  const normalized = String(method || 'POST').trim().toUpperCase()
+  if (['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(normalized)) return normalized
+  return 'POST'
+}
+
+const getNormalizedRequestMethod = (method) => normalizeRequestMethod(method)
+
+const getPayloadValueByPath = (payload, fieldPath = '') => {
+  const normalizedPath = String(fieldPath || '').trim()
+  if (!normalizedPath || !isPlainObject(payload)) return undefined
+
+  const pathSegments = normalizedPath
+    .replace(/\[(\d+)]/g, '.$1')
+    .split('.')
+    .filter(Boolean)
+
+  return pathSegments.reduce((current, segment) => {
+    if (current === null || current === undefined) return undefined
+    if (!['object', 'function'].includes(typeof current)) return undefined
+    return current[segment]
+  }, payload)
+}
+
+const getMissingRequiredFields = (requiredFields = [], payload = {}) => {
+  if (!Array.isArray(requiredFields) || !requiredFields.length) return []
+  return requiredFields.filter((fieldPath) => {
+    const value = getPayloadValueByPath(payload, fieldPath)
+    return !hasMeaningfulPayloadValue(value)
+  })
+}
+
+const parseManufacturingActionPayload = (payloadText = '') => {
+  const text = String(payloadText ?? '').trim()
+  if (!text) return {}
+  return JSON.parse(text)
+}
+
+const executeManufacturingAction = async (message, actionCard, cardIndex = 0) => {
+  if (!message?.manufacturingData || !actionCard || actionCard.executing) return
+
+  const actionName = actionCard.name || `鍔ㄤ綔 ${cardIndex + 1}`
+  const targetApi = String(actionCard.targetApi || '').trim()
+  if (!targetApi) {
+    actionCard.executeError = true
+    actionCard.executeResult = '缂哄皯 targetApi锛屾棤娉曟墽琛屽姩浣�'
+    return
+  }
+
+  let payload = {}
+  try {
+    payload = parseManufacturingActionPayload(actionCard.payloadText)
+  } catch (err) {
+    actionCard.executeError = true
+    actionCard.executeResult = '璇锋眰鍙傛暟涓嶆槸鍚堟硶 JSON锛岃妫�鏌ュ悗閲嶈瘯'
+    return
+  }
+
+  const requiredFields = Array.isArray(actionCard.requiredFields) ? actionCard.requiredFields : []
+  if (requiredFields.length && !isPlainObject(payload)) {
+    actionCard.executeError = true
+    actionCard.executeResult = '蹇呭~瀛楁鏍¢獙澶辫触锛氳姹傚弬鏁板繀椤绘槸 JSON 瀵硅薄'
+    return
+  }
+
+  const missingFields = getMissingRequiredFields(requiredFields, payload)
+  if (missingFields.length) {
+    actionCard.executeError = true
+    const missingFieldLabels = missingFields.map(field => getStructuredPathLabel(field))
+    actionCard.executeResult = `缂哄皯蹇呭~瀛楁锛�${missingFieldLabels.join('銆�')}`
+    return
+  }
+
+  try {
+    await ElMessageBox.confirm(`纭鎵ц銆�${actionName}銆嶅悧锛焋, '鎵ц纭', {
+      confirmButtonText: '纭鎵ц',
+      cancelButtonText: '鍙栨秷',
+      type: 'warning'
+    })
+  } catch (err) {
+    return
+  }
+
+  actionCard.executing = true
+  actionCard.executeError = false
+  actionCard.executeResult = ''
+
+  const method = normalizeRequestMethod(actionCard.method)
+  const requestConfig = {
+    url: targetApi,
+    method: method.toLowerCase()
+  }
+
+  if (method === 'GET') {
+    requestConfig.params = payload
+  } else {
+    requestConfig.data = payload
+  }
+
+  try {
+    const res = await request(requestConfig)
+    const successMsg = res?.msg || `${actionName}鎵ц鎴愬姛`
+    actionCard.executed = true
+    actionCard.executeError = false
+    actionCard.executeResult = successMsg
+    ElMessage.success(successMsg)
+  } catch (err) {
+    actionCard.executed = false
+    actionCard.executeError = true
+    actionCard.executeResult = err?.message || `${actionName}鎵ц澶辫触锛岃绋嶅悗閲嶈瘯`
+  } finally {
+    actionCard.executing = false
+  }
 }
 
 // 鍘嗗彶浼氳瘽鐩稿叧
@@ -987,6 +1579,8 @@
           tableData: null,
           payloadTreeData: null,
           payloadHiddenData: null,
+          purchaseAnalysisData: null,
+          manufacturingData: null,
           localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : []
         }
 
@@ -1045,11 +1639,10 @@
 }
 
 onMounted(() => {
-  initUUID()
-  // 鍒濆娆㈣繋
-  if (messages.value.length === 0) {
-    hello()
+  if (props.autoOpen) {
+    visible.value = true
   }
+  initUUID()
   window.addEventListener('resize', handleWindowResize)
 })
 
@@ -1074,11 +1667,26 @@
   inputMessage.value = ''
   quickPromptStart.value = 0
   initUUID()
-  hello()
+})
+
+watch(() => props.defaultAssistant, (nextKey) => {
+  if (!nextKey || nextKey === selectedAssistantKey.value) return
+  if (!assistants.value.some(item => item.key === nextKey)) return
+  selectedAssistantKey.value = nextKey
+})
+
+watch(() => props.autoOpen, (nextValue) => {
+  if (nextValue) {
+    visible.value = true
+  }
 })
 
 const handleWindowResize = () => {
   windowWidth.value = window.innerWidth
+}
+
+const handleHeaderExtraAction = () => {
+  emit('header-extra-action')
 }
 
 const toggleSidebar = () => {
@@ -1089,10 +1697,12 @@
 }
 
 const handleClose = () => {
+  if (hideTrigger.value) return
   visible.value = false
 }
 
 const handleManualClose = () => {
+  if (hideTrigger.value) return
   if (isSending.value) {
     abortCurrentRequest()
   }
@@ -1108,10 +1718,6 @@
   uuid.value = storedUUID
 }
 
-const hello = () => {
-  sendRequest(currentAssistant.value.welcomeMessage || '浣犲ソ')
-}
-
 const newChat = () => {
   revokeMessageLocalFileSnapshots(messages.value)
   disposeCharts()
@@ -1123,7 +1729,6 @@
   quickPromptStart.value = 0
   localStorage.removeItem(currentAssistant.value.storageKey)
   initUUID()
-  hello()
 }
 
 const handleNewChat = () => {
@@ -1215,15 +1820,25 @@
 const applyStructuredMessageData = (messageObj, parsedData, msgIndex, shouldRenderCharts = true) => {
   if (!messageObj || !parsedData?.success) return
 
+  const previousManufacturingData = messageObj.manufacturingData
   messageObj.type = parsedData.type || ''
+  messageObj.tableData = null
+  messageObj.purchaseAnalysisData = null
+  messageObj.manufacturingData = null
 
   if (messageObj.type === 'todo_list' && parsedData.data) {
     messageObj.tableData = parsedData.data
   }
 
+  const manufacturingData = buildManufacturingStructuredData(parsedData, previousManufacturingData)
+  if (manufacturingData) {
+    messageObj.manufacturingData = manufacturingData
+  }
+
   if (parsedData.action === 'confirm_required' && parsedData.businessType) {
     messageObj.type = 'purchase_analysis_confirm'
     messageObj.purchaseAnalysisData = parsedData
+    messageObj.manufacturingData = null
     if (!Array.isArray(messageObj.payloadTreeData) || !messageObj.payloadTreeData.length) {
       initializePurchasePayloadTree(messageObj, parsedData.payload || {})
     }
@@ -1263,6 +1878,19 @@
   }
 
   return null
+}
+
+const getStructuredFallbackText = (parsedData) => {
+  if (!parsedData) return '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
+  if (parsedData.type === 'todo_list') return '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁銆�'
+  if (manufacturingStructuredTypeSet.has(parsedData.type)) {
+    if (parsedData.type === 'manufacturing_action_plan') return '宸蹭负鎮ㄧ敓鎴愬姙鐞嗗缓璁紝璇风‘璁ゅ姩浣滃悗鎵ц銆�'
+    if (parsedData.type === 'manufacturing_warning') return '宸蹭负鎮ㄧ敓鎴愬埗閫犻璀︾湅鏉裤��'
+    if (parsedData.type === 'manufacturing_analysis') return '宸蹭负鎮ㄧ敓鎴愬埗閫犲垎鏋愮粨鏋溿��'
+    return '宸茶繑鍥炲埗閫犳煡璇㈢粨鏋溿��'
+  }
+  if (parsedData.charts && Object.keys(parsedData.charts).length > 0) return '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛ㄣ��'
+  return '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
 }
 
 const buildPurchaseMaterialRankCharts = (parsedData) => {
@@ -1471,12 +2099,7 @@
   if (visibleValue === undefined || visibleValue === null) return clonePurchasePayloadValue(hiddenValue)
 
   if (Array.isArray(visibleValue) && Array.isArray(hiddenValue)) {
-    const maxLength = Math.max(visibleValue.length, hiddenValue.length)
-    const merged = []
-    for (let i = 0; i < maxLength; i++) {
-      merged[i] = mergePurchasePayloadWithHidden(visibleValue[i], hiddenValue[i])
-    }
-    return merged
+    return visibleValue.map((item, index) => mergePurchasePayloadWithHidden(item, hiddenValue[index]))
   }
 
   if (
@@ -1879,6 +2502,23 @@
     ''
 }
 
+const prunePurchaseProductRecord = (record) => {
+  if (!record || typeof record !== 'object' || Array.isArray(record)) return null
+  const normalizedRecord = normalizePurchaseProductRecord(record)
+  const hasVisibleFieldValue = Object.entries(normalizedRecord).some(([key, value]) => {
+    if (shouldHidePurchaseField(key)) return false
+    return hasMeaningfulPayloadValue(value)
+  })
+  return hasVisibleFieldValue ? normalizedRecord : null
+}
+
+const normalizeAndFilterPurchaseProductData = (value) => {
+  if (!Array.isArray(value)) return value
+  return value
+    .map(item => prunePurchaseProductRecord(item))
+    .filter(Boolean)
+}
+
 const mergeLegacyProductDataIntoLedgers = (payload) => {
   if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return payload
   if (!Array.isArray(payload.purchaseLedgers) || !Array.isArray(payload.productData) || !payload.productData.length) {
@@ -1887,13 +2527,11 @@
 
   const ledgers = payload.purchaseLedgers.map(ledger => ({
     ...ledger,
-    productData: Array.isArray(ledger.productData)
-      ? ledger.productData.map(normalizePurchaseProductRecord)
-      : []
+    productData: normalizeAndFilterPurchaseProductData(ledger.productData) || []
   }))
   const unmatchedProducts = []
 
-  payload.productData.map(normalizePurchaseProductRecord).forEach(product => {
+  normalizeAndFilterPurchaseProductData(payload.productData).forEach(product => {
     const productMatchKey = getPurchaseProductMatchKey(product)
     const matchedLedger = ledgers.find(ledger => {
       const ledgerKeys = [
@@ -1932,9 +2570,7 @@
   if (!record || typeof record !== 'object' || Array.isArray(record)) return record
   const normalizedRecord = {
     ...record,
-    productData: Array.isArray(record.productData)
-      ? record.productData.map(normalizePurchaseProductRecord)
-      : record.productData
+    productData: normalizeAndFilterPurchaseProductData(record.productData)
   }
   return Object.entries(normalizedRecord).reduce((result, [key, value]) => {
     if (purchaseLedgerAllowedFieldKeys.has(key)) {
@@ -2131,6 +2767,12 @@
   sanitized = normalizePurchasePayloadFieldTypes(sanitized)
   if (Array.isArray(sanitized.purchaseLedgers)) {
     sanitized.purchaseLedgers = sanitized.purchaseLedgers.map(filterPurchaseLedgerRecord)
+  }
+  if (Array.isArray(sanitized.productData)) {
+    sanitized.productData = normalizeAndFilterPurchaseProductData(sanitized.productData)
+  }
+  if (Array.isArray(sanitized.productData) && !sanitized.productData.length) {
+    delete sanitized.productData
   }
 
   purchaseApprovalFieldKeys.forEach(key => {
@@ -2374,7 +3016,9 @@
     type: '',
     tableData: null,
     payloadTreeData: null,
-    payloadHiddenData: null
+    payloadHiddenData: null,
+    purchaseAnalysisData: null,
+    manufacturingData: null
   })
 
   outputState.value[botMsgIndex] = {
@@ -2497,7 +3141,9 @@
     type: '',
     tableData: null,
     payloadTreeData: null,
-    payloadHiddenData: null
+    payloadHiddenData: null,
+    purchaseAnalysisData: null,
+    manufacturingData: null
   }
   messages.value.push(botMsg)
 
@@ -2663,13 +3309,7 @@
     }
 
     if (!display) {
-      if (parsed.type === 'todo_list') {
-        display = '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁銆�'
-      } else if (parsed.charts && Object.keys(parsed.charts).length > 0) {
-        display = '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛ㄣ��'
-      } else {
-        display = '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
-      }
+      display = getStructuredFallbackText(parsed)
     }
   } else if (startIdx !== -1) {
     const lastBraceIdx = output.lastIndexOf('}')
@@ -2691,13 +3331,7 @@
         }
 
         if (!display) {
-          if (parsed.type === 'todo_list') {
-            display = '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁锛�'
-          } else if (parsed.charts && Object.keys(parsed.charts).length > 0) {
-            display = '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛細'
-          } else {
-            display = '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
-          }
+          display = getStructuredFallbackText(parsed)
         }
       } catch (e) {
         // 瑙f瀽澶辫触锛岃鏄� JSON 杩樺湪浼犺緭涓垨鏍煎紡涓嶆纭�
@@ -3043,8 +3677,9 @@
     height: 100%;
   }
   :deep(.el-drawer__header) {
-    margin-bottom: 0;
-    padding: 0;
+    margin-bottom: 0 !important;
+    padding: 0 !important;
+    border-bottom: 1px solid rgba(255, 255, 255, 0.12);
     background: $gradient-dark;
     color: #fff;
   }
@@ -3055,7 +3690,7 @@
   justify-content: space-between;
   align-items: center;
   width: 100%;
-  padding: 18px 20px;
+  padding: 12px 18px;
   background: $gradient-dark;
   position: relative;
   overflow: hidden;
@@ -3187,6 +3822,15 @@
         opacity: 1;
       }
     }
+
+    :deep(.header-action-btn--text) {
+      width: auto !important;
+      min-width: 104px;
+      padding: 8px 14px !important;
+      font-size: 14px;
+      font-weight: 600;
+      white-space: nowrap;
+    }
   }
 
   .assistant-switcher {
@@ -3269,7 +3913,7 @@
     top: 0;
     left: 0;
     right: 0;
-    height: 240px;
+    height: 128px;
     background: linear-gradient(180deg, rgba(0, 85, 212, 0.06) 0%, transparent 100%);
     pointer-events: none;
   }
@@ -3707,6 +4351,171 @@
     min-width: 300px;
     --el-table-border-color: rgba(0, 122, 255, 0.08);
     --el-table-header-bg-color: $ice-white;
+  }
+}
+
+.manufacturing-card {
+  margin-top: 12px;
+  width: 100%;
+  background: #fff;
+  border: 1px solid rgba(0, 85, 212, 0.12);
+  border-radius: 12px;
+  box-shadow: $shadow-card;
+  padding: 14px;
+}
+
+.manufacturing-card__title {
+  font-size: 14px;
+  font-weight: 700;
+  color: $deep-blue;
+  margin-bottom: 10px;
+}
+
+.manufacturing-summary-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+  gap: 8px;
+  margin-bottom: 12px;
+}
+
+.manufacturing-summary-item {
+  border-radius: 10px;
+  padding: 10px 12px;
+  border: 1px solid rgba(0, 85, 212, 0.08);
+  background: linear-gradient(180deg, #f8fbff, #f1f7ff);
+  min-height: 66px;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  gap: 6px;
+}
+
+.manufacturing-summary-item--core {
+  border-color: rgba(30, 91, 255, 0.24);
+}
+
+.manufacturing-summary-label {
+  font-size: 12px;
+  color: #4b5563;
+}
+
+.manufacturing-summary-value {
+  font-size: 15px;
+  color: #1f2937;
+  line-height: 1.4;
+  word-break: break-all;
+}
+
+.manufacturing-warning-list {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  margin-bottom: 12px;
+}
+
+.manufacturing-warning-item {
+  border-radius: 10px;
+  border: 1px solid rgba(245, 158, 11, 0.22);
+  background: linear-gradient(135deg, rgba(255, 247, 237, 0.9), rgba(255, 255, 255, 0.98));
+  padding: 10px 12px;
+}
+
+.manufacturing-warning-item__head {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  color: #92400e;
+  font-size: 13px;
+}
+
+.manufacturing-warning-count {
+  margin-left: auto;
+  font-weight: 700;
+  color: #c2410c;
+}
+
+.manufacturing-warning-detail {
+  margin: 8px 0 0;
+  font-size: 12px;
+  line-height: 1.6;
+  color: #7c2d12;
+  word-break: break-all;
+}
+
+.manufacturing-table-wrapper {
+  margin-top: 10px;
+}
+
+.manufacturing-action-list {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  margin-top: 12px;
+}
+
+.manufacturing-action-card {
+  border: 1px solid rgba(0, 85, 212, 0.1);
+  border-radius: 10px;
+  padding: 10px 12px;
+  background: #f8fbff;
+}
+
+.manufacturing-action-card__head {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+  font-size: 13px;
+  color: #1f2937;
+  margin-bottom: 8px;
+}
+
+.manufacturing-action-card__meta {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  margin-bottom: 8px;
+  font-size: 12px;
+  color: #64748b;
+  word-break: break-all;
+}
+
+.manufacturing-action-card__desc {
+  margin: 0 0 8px;
+  font-size: 12px;
+  line-height: 1.6;
+  color: #475467;
+}
+
+.manufacturing-required-fields {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 6px;
+  margin-bottom: 8px;
+  font-size: 12px;
+  color: #7c2d12;
+}
+
+.manufacturing-action-footer {
+  margin-top: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  gap: 12px;
+}
+
+.manufacturing-action-result {
+  flex: 1;
+  font-size: 12px;
+  line-height: 1.5;
+
+  &.success {
+    color: #1f9d55;
+  }
+
+  &.error {
+    color: #d93025;
   }
 }
 
@@ -4196,75 +5005,59 @@
 
 .chat-hero {
   display: grid;
-  grid-template-columns: 164px minmax(0, 1fr);
-  gap: 18px;
-  align-items: start;
-  padding: 14px 18px 6px;
+  grid-template-columns: 176px minmax(0, 1fr);
+  gap: 14px;
+  align-items: stretch;
+  padding: 8px 18px 4px;
 
   &.compact {
-    grid-template-columns: 122px minmax(0, 1fr);
-    gap: 12px;
-    padding: 8px 18px 2px;
+    grid-template-columns: 132px minmax(0, 1fr);
+    gap: 10px;
+    padding: 4px 18px 2px;
   }
 }
 
 .assistant-stand {
   position: relative;
-  min-height: 252px;
+  min-height: 206px;
   display: flex;
   align-items: center;
   justify-content: center;
   flex-direction: column;
-  padding-top: 18px;
+  padding-top: 8px;
   overflow: hidden;
 
   &.compact {
-    min-height: 176px;
-    padding-top: 8px;
+    min-height: 160px;
+    padding-top: 4px;
   }
 
   &.thinking {
     .assistant-halo {
       opacity: 1;
-      transform: scale(1.08);
-      filter: blur(8px);
+      transform: scale(1.12);
+      filter: blur(9px);
     }
 
     .assistant-scan-ring {
-      opacity: 1;
-      animation-duration: 1.6s;
+      opacity: 0.95;
+      animation-duration: 1.5s;
     }
 
     .assistant-orbit {
-      opacity: 1;
+      opacity: 0.76;
     }
 
-    .assistant-bot {
-      transform: translateY(-4px) scale(1.02);
+    .assistant-model-shell {
+      transform: translateY(-5px) scale(1.02);
     }
 
-    .assistant-bot-head {
-      box-shadow: 0 0 30px rgba(80, 157, 255, 0.36);
+    .assistant-model-cut {
+      animation-duration: 2.2s;
     }
 
-    .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-model-img {
+      filter: saturate(1.06) drop-shadow(0 18px 20px rgba(22, 48, 80, 0.22));
     }
 
     .assistant-status {
@@ -4278,219 +5071,174 @@
       animation: thinkingDot 1s ease-in-out infinite;
     }
 
+    .assistant-base-lg {
+      animation-duration: 1.8s;
+    }
+
+    .assistant-base-md {
+      animation-duration: 1.5s;
+    }
+
     .assistant-base-sm {
-      box-shadow: 0 0 24px rgba(255, 93, 122, 0.48);
+      box-shadow: 0 0 24px rgba(30, 91, 255, 0.36);
+      animation-duration: 1.25s;
     }
   }
 }
 
 .assistant-halo {
   position: absolute;
-  top: 22px;
-  width: 130px;
-  height: 130px;
+  top: 24px;
+  width: 146px;
+  height: 146px;
   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%);
+  background: radial-gradient(circle, rgba(31, 122, 114, 0.26) 0%, rgba(30, 91, 255, 0.2) 42%, rgba(109, 65, 237, 0.12) 66%, transparent 80%);
   filter: blur(6px);
-  opacity: 0.82;
+  opacity: 0.78;
   transition: all 0.35s ease;
 }
 
 .assistant-scan-ring {
   position: absolute;
-  top: 40px;
-  width: 132px;
-  height: 132px;
+  top: 44px;
+  width: 136px;
+  height: 136px;
   border-radius: 50%;
-  border: 1px solid rgba(90, 159, 224, 0.22);
+  border: 1px solid rgba(67, 145, 223, 0.24);
   box-shadow: inset 0 0 16px rgba(255, 255, 255, 0.25);
-  opacity: 0.55;
+  opacity: 0.52;
   animation: scanRing 4s linear infinite;
 }
 
 .assistant-orbit {
   position: absolute;
   top: 52px;
-  width: 150px;
-  height: 150px;
+  width: 156px;
+  height: 156px;
   border-radius: 50%;
-  border: 1px dashed rgba(92, 135, 255, 0.22);
-  opacity: 0.45;
+  border: 1px dashed rgba(92, 135, 255, 0.24);
+  opacity: 0.42;
 }
 
 .assistant-orbit-a {
-  animation: orbitRotate 8s linear infinite;
+  animation: orbitRotate 8.6s linear infinite;
 }
 
 .assistant-orbit-b {
-  width: 118px;
-  height: 118px;
+  width: 124px;
+  height: 124px;
   top: 68px;
-  border-color: rgba(255, 108, 150, 0.22);
-  animation: orbitRotateReverse 5.6s linear infinite;
+  border-color: rgba(31, 122, 114, 0.24);
+  animation: orbitRotateReverse 6.2s linear infinite;
 }
 
-.assistant-bot {
+.assistant-model-shell {
   position: relative;
   z-index: 1;
+  width: 148px;
+  height: 178px;
   display: flex;
-  flex-direction: column;
-  align-items: center;
+  align-items: flex-end;
   justify-content: center;
-  margin-top: 12px;
+  margin-top: 4px;
   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%;
+    bottom: 2px;
+    width: 164px;
+    height: 42px;
     transform: translateX(-50%);
-    background: linear-gradient(135deg, #54bfff, #7a41ff);
-    box-shadow: 0 0 14px rgba(84, 191, 255, 0.65);
+    border-radius: 50%;
+    background: radial-gradient(
+      ellipse at center,
+      rgba(43, 126, 211, 0.32) 0%,
+      rgba(43, 126, 211, 0.14) 46%,
+      rgba(43, 126, 211, 0) 74%
+    );
+    filter: blur(2.6px);
+    animation: baseGlow 4.6s ease-in-out infinite;
+    z-index: 1;
+  }
+
+  &::after {
+    content: '';
+    position: absolute;
+    left: 50%;
+    bottom: 10px;
+    width: 138px;
+    height: 28px;
+    transform: translateX(-50%);
+    border-radius: 50%;
+    border: 1px solid rgba(36, 116, 198, 0.6);
+    box-shadow:
+      inset 0 0 0 1px rgba(255, 255, 255, 0.58),
+      0 0 22px rgba(42, 116, 196, 0.24);
+    animation: basePulse 3.2s ease-in-out infinite;
+    z-index: 4;
   }
 }
 
-.assistant-bot-antenna-left {
-  left: 36px;
-  transform: rotate(-14deg);
-}
-
-.assistant-bot-antenna-right {
-  right: 36px;
-  transform: rotate(14deg);
-}
-
-.assistant-bot-head {
+.assistant-model-cut {
   position: relative;
+  width: 132px;
+  height: 178px;
+  z-index: 6;
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
+  transform-origin: center 84%;
+  animation: avatarFloat 3.2s ease-in-out infinite;
+}
+
+.assistant-model-img {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+  object-position: center bottom;
+  display: block;
+  filter: saturate(1.03) drop-shadow(0 14px 18px rgba(22, 49, 79, 0.2));
+  transition: filter 0.35s ease;
+}
+
+.assistant-model-fallback {
   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);
+  height: 92px;
+  border-radius: 24px;
+  color: #fff;
+  background: linear-gradient(145deg, rgba(31, 122, 114, 0.9), rgba(30, 91, 255, 0.9));
+  border: 1px solid rgba(255, 255, 255, 0.3);
+  box-shadow: 0 12px 24px rgba(31, 85, 173, 0.22);
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 
-.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 {
+.assistant-base {
   position: absolute;
   left: 50%;
-  bottom: 16px;
-  width: 22px;
-  height: 4px;
+  bottom: 8px;
   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);
+  border: 1px solid rgba(36, 116, 198, 0.28);
+  background: radial-gradient(
+    ellipse at center,
+    rgba(255, 255, 255, 0.94) 0%,
+    rgba(81, 164, 233, 0.16) 58%,
+    rgba(30, 91, 255, 0.06) 100%
+  );
+  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
 }
 
 .assistant-status {
   position: relative;
   z-index: 1;
-  margin-top: 14px;
-  padding: 6px 12px;
+  margin-top: 7px;
+  padding: 5px 10px;
   border-radius: 999px;
-  font-size: 12px;
+  font-size: 11px;
   font-weight: 600;
   color: $deep-blue;
   background: rgba(255, 255, 255, 0.95);
@@ -4510,81 +5258,25 @@
 }
 
 .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;
+  pointer-events: none;
 }
 
 .assistant-base-md {
-  bottom: 6px;
-  width: 88px;
-  height: 20px;
-  border-color: rgba(255, 93, 122, 0.34);
+  bottom: 15px;
+  width: 104px;
+  height: 22px;
+  border-color: rgba(36, 116, 198, 0.48);
+  animation: basePulse 2.8s ease-in-out infinite;
 }
 
 .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));
+  bottom: 20px;
+  width: 68px;
+  height: 14px;
+  background: linear-gradient(90deg, rgba(31, 122, 114, 0.82), rgba(45, 124, 255, 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);
-  }
+  box-shadow: 0 0 18px rgba(45, 124, 255, 0.34);
+  animation: basePulse 2.2s ease-in-out infinite;
 }
 
 @keyframes orbitRotate {
@@ -4625,9 +5317,85 @@
   }
 }
 
+.assistant-base-lg {
+  width: 142px;
+  height: 32px;
+  animation: basePulse 3.4s ease-in-out infinite;
+
+  &::before {
+    content: '';
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    width: 130px;
+    height: 130px;
+    transform: translate(-50%, -50%);
+    border-radius: 50%;
+    background: conic-gradient(
+      from 180deg,
+      transparent 0deg,
+      rgba(36, 116, 198, 0.65) 48deg,
+      transparent 114deg,
+      rgba(36, 116, 198, 0.55) 212deg,
+      transparent 286deg,
+      rgba(31, 122, 114, 0.45) 334deg,
+      transparent 360deg
+    );
+    -webkit-mask: radial-gradient(circle, transparent 61%, #000 62%, #000 68%, transparent 70%);
+    mask: radial-gradient(circle, transparent 61%, #000 62%, #000 68%, transparent 70%);
+    opacity: 0.62;
+    animation: baseSpin 9s linear infinite;
+  }
+}
+
+@keyframes avatarFloat {
+  0%,
+  100% {
+    transform: translateY(0);
+  }
+  50% {
+    transform: translateY(-7px);
+  }
+}
+
+@keyframes basePulse {
+  0%,
+  100% {
+    transform: translateX(-50%) scale(1);
+    opacity: 0.88;
+  }
+  50% {
+    transform: translateX(-50%) scale(1.05);
+    opacity: 0.98;
+  }
+}
+
+@keyframes baseSpin {
+  from {
+    transform: translate(-50%, -50%) rotate(0deg);
+  }
+  to {
+    transform: translate(-50%, -50%) rotate(360deg);
+  }
+}
+
+@keyframes baseGlow {
+  0%,
+  100% {
+    transform: translateX(-50%) scaleX(1);
+    opacity: 0.82;
+  }
+  50% {
+    transform: translateX(-50%) scaleX(1.06);
+    opacity: 0.96;
+  }
+}
+
 .welcome-card {
   position: relative;
-  padding: 14px 14px 12px;
+  align-self: stretch;
+  min-height: 206px;
+  padding: 9px 10px 8px;
   border-radius: 16px;
   background:
     linear-gradient(#fff, #fff) padding-box,
@@ -4636,7 +5404,8 @@
   box-shadow: 0 16px 36px rgba(0, 85, 212, 0.12);
 
   &.compact {
-    padding: 10px 12px;
+    min-height: 160px;
+    padding: 8px 9px 7px;
     border-radius: 12px;
     box-shadow: 0 8px 16px rgba(0, 85, 212, 0.07);
 
@@ -4645,8 +5414,8 @@
     }
 
     .welcome-title {
-      font-size: 17px;
-      line-height: 1.3;
+      font-size: 16px;
+      line-height: 1.25;
 
       br {
         display: none;
@@ -4654,65 +5423,69 @@
     }
 
     .welcome-desc {
-      margin-top: 6px;
-      font-size: 12px;
-      line-height: 1.55;
+      margin-top: 4px;
+      font-size: 11px;
+      line-height: 1.5;
     }
 
     .quick-prompt-list {
-      margin-top: 10px;
-      gap: 6px;
+      margin-top: 8px;
+      gap: 5px;
     }
 
     .quick-prompt-btn {
-      padding: 8px 10px;
-      font-size: 12px;
+      padding: 7px 9px;
+      font-size: 11px;
       border-radius: 7px;
     }
 
     .more-prompts-btn {
-      margin-top: 8px;
-      font-size: 12px;
+      margin-top: 6px;
+      font-size: 11px;
     }
   }
 }
 
 .welcome-eyebrow {
-  font-size: 11px;
+  font-size: 10px;
   font-weight: 700;
   letter-spacing: 2px;
   color: rgba(0, 85, 212, 0.58);
-  margin-bottom: 8px;
+  margin-bottom: 5px;
 }
 
 .welcome-title {
   margin: 0;
-  font-size: 26px;
-  line-height: 1.2;
+  font-size: 20px;
+  line-height: 1.15;
   font-weight: 800;
   color: #172033;
+
+  br {
+    display: none;
+  }
 }
 
 .welcome-desc {
-  margin: 10px 0 0;
-  font-size: 13px;
-  line-height: 1.7;
+  margin: 5px 0 0;
+  font-size: 12px;
+  line-height: 1.5;
   color: #5f6980;
 }
 
 .quick-prompt-list {
   display: grid;
-  gap: 8px;
-  margin-top: 14px;
+  gap: 6px;
+  margin-top: 8px;
 }
 
 .quick-prompt-btn {
   width: 100%;
   border: none;
-  border-radius: 10px;
-  padding: 11px 14px;
+  border-radius: 9px;
+  padding: 7px 10px;
   text-align: left;
-  font-size: 13px;
+  font-size: 12px;
   font-weight: 600;
   color: #fff;
   cursor: pointer;
@@ -4760,14 +5533,14 @@
 }
 
 .more-prompts-btn {
-  margin-top: 10px;
-  padding: 0 12px;
-  height: 32px;
+  margin-top: 6px;
+  padding: 0 10px;
+  height: 26px;
   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-size: 12px;
   font-weight: 600;
   cursor: pointer;
   display: inline-flex;
@@ -4782,21 +5555,6 @@
     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));
   }
 }
 
@@ -4892,12 +5650,6 @@
 
   .welcome-title {
     font-size: 21px;
-  }
-
-  .hero-dot-grid {
-    grid-template-columns: repeat(12, 1fr);
-    gap: 6px;
-    padding: 0 14px 12px;
   }
 
   .message-list {

--
Gitblit v1.9.3