From 5470429a79313630a7ddef601de1d89e7dada754 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期五, 22 五月 2026 09:14:12 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_NEW_pro' into dev-new_pro_OA

---
 src/components/AIChatSidebar/index.vue |  757 +++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 693 insertions(+), 64 deletions(-)

diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
index e3a9e3c..3006ce7 100644
--- a/src/components/AIChatSidebar/index.vue
+++ b/src/components/AIChatSidebar/index.vue
@@ -4,7 +4,7 @@
     <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>
+          <el-icon :size="22" color="#fff"><component :is="currentAssistant.icon" /></el-icon>
         </div>
       </el-tooltip>
     </div>
@@ -356,6 +356,244 @@
                         </el-button>
                       </div>
                     </div>
+                  </div>
+                </div>
+
+                <div v-if="message.salesData" class="sales-structured-card">
+                  <div class="sales-structured-card__title">{{ getSalesTypeLabel(message.type) }}</div>
+
+                  <div v-if="message.salesData.summaryEntries?.length" class="sales-summary-grid">
+                    <div
+                        v-for="(entry, entryIndex) in message.salesData.summaryEntries"
+                        :key="`sales-summary-${entry.key}-${entryIndex}`"
+                        class="sales-summary-item"
+                    >
+                      <span class="sales-summary-label">{{ entry.label }}</span>
+                      <strong class="sales-summary-value">{{ entry.value }}</strong>
+                    </div>
+                  </div>
+
+                  <div v-if="message.type === 'sales_customer_churn_risk' && message.salesData.listItems?.length" class="sales-focus-list">
+                    <div
+                        v-for="(item, itemIndex) in message.salesData.listItems"
+                        :key="`risk-${item.customerName || itemIndex}`"
+                        class="sales-focus-item"
+                    >
+                      <div class="sales-focus-item__head">
+                        <strong>{{ formatStructuredValue(item.customerName) }}</strong>
+                        <div class="sales-focus-tags">
+                          <el-tag size="small" :type="getSalesLevelTagType(item.riskLevel)">
+                            {{ getSalesLevelLabel(item.riskLevel, 'risk') }}
+                          </el-tag>
+                          <el-tag size="small" type="warning">椋庨櫓鍒� {{ formatStructuredValue(item.riskScore) }}</el-tag>
+                        </div>
+                      </div>
+                      <div class="sales-focus-metrics">
+                        <span>寰呭洖娆撅細{{ formatStructuredValue(item.pendingAmount) }}</span>
+                        <span>寰呭洖娆惧崰姣旓細{{ formatStructuredValue(item.pendingRate) }}</span>
+                        <span>璺濅笂娆′笅鍗曪細{{ formatStructuredValue(item.daysSinceLastOrder) }}</span>
+                      </div>
+                      <div v-if="toStructuredStringArray(item.riskReasons).length" class="sales-focus-reasons">
+                        <el-tag
+                            v-for="(reason, reasonIndex) in toStructuredStringArray(item.riskReasons)"
+                            :key="`${item.customerName || itemIndex}-reason-${reasonIndex}`"
+                            size="small"
+                            type="danger"
+                            effect="plain"
+                        >
+                          {{ reason }}
+                        </el-tag>
+                      </div>
+                    </div>
+                  </div>
+
+                  <div v-if="message.type === 'sales_collection_quote_strategy' && message.salesData.listItems?.length" class="sales-focus-list">
+                    <div
+                        v-for="(item, itemIndex) in message.salesData.listItems"
+                        :key="`strategy-${item.customerName || itemIndex}`"
+                        class="sales-focus-item sales-focus-item--strategy"
+                    >
+                      <div class="sales-focus-item__head">
+                        <strong>{{ formatStructuredValue(item.customerName) }}</strong>
+                        <div class="sales-focus-tags">
+                          <el-tag size="small" :type="getSalesLevelTagType(item.priority)">
+                            {{ getSalesLevelLabel(item.priority, 'priority') }}
+                          </el-tag>
+                          <el-tag size="small" type="success">杞寲鐜� {{ formatStructuredValue(item.quoteConversionRate) }}</el-tag>
+                        </div>
+                      </div>
+                      <div class="sales-focus-metrics">
+                        <span>寰呭洖娆撅細{{ formatStructuredValue(item.pendingAmount) }}</span>
+                        <span v-if="item.nextAction">涓嬩竴姝ワ細{{ formatStructuredValue(item.nextAction) }}</span>
+                      </div>
+                      <p v-if="item.collectionStrategy" class="sales-strategy-line">
+                        <strong>鍥炴绛栫暐锛�</strong>{{ formatStructuredValue(item.collectionStrategy) }}
+                      </p>
+                      <p v-if="item.quotationStrategy" class="sales-strategy-line">
+                        <strong>鎶ヤ环绛栫暐锛�</strong>{{ formatStructuredValue(item.quotationStrategy) }}
+                      </p>
+                    </div>
+                  </div>
+
+                  <div
+                      v-if="message.salesData.listItems?.length && message.salesData.columns?.length && !isSalesFocusType(message.type)"
+                      class="table-wrapper manufacturing-table-wrapper"
+                  >
+                    <el-table :data="message.salesData.listItems" border stripe size="small" style="width: 100%">
+                      <el-table-column
+                          v-for="col in message.salesData.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.salesData.topCustomers?.length && message.salesData.topCustomerColumns?.length" class="table-wrapper manufacturing-table-wrapper">
+                    <div class="sales-section-title">閲嶇偣瀹㈡埛</div>
+                    <el-table :data="message.salesData.topCustomers" border stripe size="small" style="width: 100%">
+                      <el-table-column
+                          v-for="col in message.salesData.topCustomerColumns"
+                          :key="`top-customer-${col}`"
+                          :label="getStructuredFieldLabel(col)"
+                          min-width="120"
+                          show-overflow-tooltip
+                      >
+                        <template #default="{ row }">
+                          {{ formatStructuredValue(row[col]) }}
+                        </template>
+                      </el-table-column>
+                    </el-table>
+                  </div>
+
+                  <div v-if="message.salesData.contractTrend?.length && message.salesData.contractTrendColumns?.length" class="table-wrapper manufacturing-table-wrapper">
+                    <div class="sales-section-title">鍚堝悓瓒嬪娍</div>
+                    <el-table :data="message.salesData.contractTrend" border stripe size="small" style="width: 100%">
+                      <el-table-column
+                          v-for="col in message.salesData.contractTrendColumns"
+                          :key="`contract-trend-${col}`"
+                          :label="getStructuredFieldLabel(col)"
+                          min-width="120"
+                          show-overflow-tooltip
+                      >
+                        <template #default="{ row }">
+                          {{ formatStructuredValue(row[col]) }}
+                        </template>
+                      </el-table-column>
+                    </el-table>
+                  </div>
+                </div>
+
+                <div v-if="message.purchaseData" class="sales-structured-card">
+                  <div class="sales-structured-card__title">{{ getPurchaseTypeLabel(message.type) }}</div>
+
+                  <div v-if="message.purchaseData.summaryEntries?.length" class="sales-summary-grid">
+                    <div
+                        v-for="(entry, entryIndex) in message.purchaseData.summaryEntries"
+                        :key="`purchase-summary-${entry.key}-${entryIndex}`"
+                        class="sales-summary-item"
+                    >
+                      <span class="sales-summary-label">{{ entry.label }}</span>
+                      <strong class="sales-summary-value">{{ entry.value }}</strong>
+                    </div>
+                  </div>
+
+                  <div
+                      v-if="message.purchaseData.listItems?.length && message.purchaseData.columns?.length"
+                      class="table-wrapper manufacturing-table-wrapper"
+                  >
+                    <el-table :data="message.purchaseData.listItems" border stripe size="small" style="width: 100%">
+                      <el-table-column
+                          v-for="col in message.purchaseData.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>
+
+                <div v-if="message.financeData" class="sales-structured-card">
+                  <div class="sales-structured-card__title">{{ getFinancialTypeLabel(message.type) }}</div>
+
+                  <div v-if="message.financeData.summaryEntries?.length" class="sales-summary-grid">
+                    <div
+                        v-for="(entry, entryIndex) in message.financeData.summaryEntries"
+                        :key="`finance-summary-${entry.key}-${entryIndex}`"
+                        class="sales-summary-item"
+                    >
+                      <span class="sales-summary-label">{{ entry.label }}</span>
+                      <strong class="sales-summary-value">{{ entry.value }}</strong>
+                    </div>
+                  </div>
+
+                  <div v-if="message.financeData.headline" class="finance-headline">
+                    {{ message.financeData.headline }}
+                  </div>
+
+                  <div v-if="message.financeData.conclusions?.length" class="finance-text-section">
+                    <div class="sales-section-title">鏍稿績缁撹</div>
+                    <ul>
+                      <li v-for="(item, idx) in message.financeData.conclusions" :key="`finance-conclusion-${idx}`">
+                        {{ item }}
+                      </li>
+                    </ul>
+                  </div>
+
+                  <div v-if="message.financeData.riskSuggestions?.length" class="finance-text-section">
+                    <div class="sales-section-title">椋庨櫓寤鸿</div>
+                    <ul>
+                      <li v-for="(item, idx) in message.financeData.riskSuggestions" :key="`finance-risk-${idx}`">
+                        {{ item }}
+                      </li>
+                    </ul>
+                  </div>
+
+                  <div
+                      v-for="section in message.financeData.sections"
+                      :key="`finance-section-${section.key}`"
+                      class="table-wrapper manufacturing-table-wrapper"
+                  >
+                    <div class="sales-section-title">{{ section.title }}</div>
+                    <el-table :data="section.items" border stripe size="small" style="width: 100%">
+                      <el-table-column
+                          v-for="col in section.columns"
+                          :key="`finance-${section.key}-${col}`"
+                          :label="getStructuredFieldLabel(col)"
+                          min-width="120"
+                          show-overflow-tooltip
+                      >
+                        <template #default="{ row }">
+                          {{ formatStructuredValue(row[col]) }}
+                        </template>
+                      </el-table-column>
+                    </el-table>
+                  </div>
+                </div>
+
+                <div v-if="message.purchaseIntentData?.quickPrompts?.length" class="purchase-intent-quick-prompt-wrap">
+                  <div class="purchase-intent-quick-prompt-title">璇曡瘯浠ヤ笅鎻愰棶</div>
+                  <div class="quick-prompt-list purchase-intent-quick-prompt-list">
+                    <button
+                        v-for="prompt in message.purchaseIntentData.quickPrompts"
+                        :key="`purchase-intent-${prompt}`"
+                        type="button"
+                        class="quick-prompt-btn"
+                        :disabled="isSending"
+                        @click="sendQuickPrompt(prompt)"
+                    >
+                      {{ prompt }}
+                    </button>
                   </div>
                 </div>
 
@@ -750,6 +988,56 @@
   purchase_return_order: '閲囪喘閫�璐у崟',
   unknown: '鏈煡閲囪喘涓氬姟'
 }
+const salesStructuredTypeSet = new Set([
+  'sales_customer_profile_list',
+  'sales_quotation_list',
+  'sales_ledger_list',
+  'sales_return_list',
+  'sales_customer_interaction_list',
+  'sales_shipping_list',
+  'sales_dashboard',
+  'sales_customer_churn_risk',
+  'sales_collection_quote_strategy'
+])
+const salesFocusTypeSet = new Set([
+  'sales_customer_churn_risk',
+  'sales_collection_quote_strategy'
+])
+const salesTypeLabelMap = {
+  sales_customer_profile_list: '瀹㈡埛妗f',
+  sales_quotation_list: '閿�鍞姤浠�',
+  sales_ledger_list: '閿�鍞彴璐�',
+  sales_return_list: '閿�鍞��璐�',
+  sales_customer_interaction_list: '瀹㈡埛寰�鏉�',
+  sales_shipping_list: '鍙戣揣鍙拌处',
+  sales_dashboard: '閿�鍞寚鏍囩粺璁�',
+  sales_customer_churn_risk: '瀹㈡埛娴佸け椋庨櫓鍒嗘瀽',
+  sales_collection_quote_strategy: '鍥炴涓庢姤浠风瓥鐣ュ缓璁�'
+}
+const purchaseTypeLabelMap = {
+  purchase_material_rank: '閲囪喘鐗╂枡閲戦鎺掕',
+  purchase_pending_payment_list: '寰呬粯娆鹃噰璐崟'
+}
+const financialStructuredTypeSet = new Set([
+  'financial_cost_accounting',
+  'financial_order_profit_analysis',
+  'financial_inventory_capital_analysis',
+  'financial_cashflow_forecast',
+  'financial_business_anomaly_warning',
+  'financial_business_cockpit',
+  'financial_operation_report',
+  'financial_rag_knowledge'
+])
+const financialTypeLabelMap = {
+  financial_cost_accounting: '鎴愭湰鏍哥畻',
+  financial_order_profit_analysis: '璁㈠崟鍒╂鼎鍒嗘瀽',
+  financial_inventory_capital_analysis: '搴撳瓨璧勯噾鍒嗘瀽',
+  financial_cashflow_forecast: '鐜伴噾娴侀娴�',
+  financial_business_anomaly_warning: '缁忚惀寮傚父棰勮',
+  financial_business_cockpit: '缁忚惀椹鹃┒鑸�',
+  financial_operation_report: '缁忚惀鎶ュ憡',
+  financial_rag_knowledge: '璐㈠姟鐭ヨ瘑妫�绱�'
+}
 const manufacturingStructuredTypeSet = new Set([
   'manufacturing_site_snapshot',
   'manufacturing_plan_list',
@@ -819,6 +1107,54 @@
   materialName: '鐗╂枡鍚嶇О',
   stockQty: '搴撳瓨閲�'
 }
+Object.assign(structuredFieldLabelMap, {
+  customerName: '瀹㈡埛鍚嶇О',
+  riskLevel: '椋庨櫓绛夌骇',
+  riskScore: '椋庨櫓璇勫垎',
+  riskReasons: '椋庨櫓鍘熷洜',
+  pendingAmount: '寰呭洖娆鹃噾棰�',
+  pendingRate: '寰呭洖娆惧崰姣�',
+  daysSinceLastOrder: '璺濅笂娆′笅鍗曞ぉ鏁�',
+  priority: '浼樺厛绾�',
+  quoteConversionRate: '鎶ヤ环杞寲鐜�',
+  collectionStrategy: '鍥炴绛栫暐',
+  quotationStrategy: '鎶ヤ环绛栫暐',
+  nextAction: '涓嬩竴姝ュ姩浣�',
+  salesContractNo: '閿�鍞悎鍚屽彿',
+  revenue: '鏀跺叆',
+  materialCost: '鏉愭枡鎴愭湰',
+  laborCost: '浜哄伐鎴愭湰',
+  depreciationCost: '鎶樻棫鎴愭湰',
+  scrapCost: '鎶ュ簾鎴愭湰',
+  totalCost: '鎬绘垚鏈�',
+  profit: '鍒╂鼎',
+  profitRate: '鍒╂鼎鐜�',
+  reasons: '鍘熷洜',
+  suggestion: '寤鸿',
+  productName: '浜у搧鍚嶇О',
+  model: '鍨嬪彿',
+  quantity: '鏁伴噺',
+  inventoryValue: '搴撳瓨璧勯噾',
+  stagnantDays: '鍛嗘粸澶╂暟',
+  overstock: '鏄惁瓒呭偍',
+  month: '鏈堜唤',
+  income: '鏀跺叆',
+  expense: '鏀嚭',
+  netFlow: '鍑�鐜伴噾娴�',
+  message: '棰勮淇℃伅',
+  headline: '鎶ュ憡鏍囬',
+  conclusions: '鏍稿績缁撹',
+  riskSuggestions: '椋庨櫓寤鸿',
+  orderProfitTop: '鍒╂鼎Top',
+  contractAmountTotal: '鍚堝悓鎬婚',
+  receivedAmountTotal: '宸插洖娆鹃噾棰�',
+  pendingAmountTotal: '寰呭洖娆炬�婚',
+  shipRate: '鍙戣揣鐜�',
+  pendingOrderCount: '寰呬粯娆捐鍗曟暟',
+  totalContractAmount: '寰呬粯娆惧悎鍚屾�婚',
+  totalPaidAmount: '宸蹭粯娆炬�婚',
+  totalPendingAmount: '寰呬粯娆炬�婚'
+})
 const purchasePayloadFieldLabelMap = {
   purchaseLedgers: '閲囪喘鍙拌处',
   productData: '浜у搧鏄庣粏',
@@ -1203,6 +1539,147 @@
 
 const getManufacturingTypeLabel = (type = '') => manufacturingTypeLabelMap[String(type || '')] || '鍒堕�犵粨鏋�'
 
+const inferSalesColumns = (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 normalizeSalesListItems = (items) => {
+  if (!Array.isArray(items)) return []
+  return items.filter(item => isPlainObject(item))
+}
+
+const buildSalesStructuredData = (parsedData) => {
+  const type = String(parsedData?.type || '')
+  if (!salesStructuredTypeSet.has(type)) return null
+
+  const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {}
+  const listItems = normalizeSalesListItems(rawData.items)
+  const topCustomers = normalizeSalesListItems(rawData.topCustomers)
+  const contractTrend = normalizeSalesListItems(rawData.contractTrend)
+
+  return {
+    type,
+    summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary),
+    listItems,
+    columns: inferSalesColumns(listItems),
+    topCustomers,
+    topCustomerColumns: inferSalesColumns(topCustomers),
+    contractTrend,
+    contractTrendColumns: inferSalesColumns(contractTrend)
+  }
+}
+
+const getSalesTypeLabel = (type = '') => salesTypeLabelMap[String(type || '')] || '閿�鍞煡璇㈢粨鏋�'
+
+const buildPurchaseStructuredData = (parsedData) => {
+  if (parsedData?.success !== true) return null
+
+  const type = String(parsedData?.type || '')
+  if (!type.startsWith('purchase_')) return null
+
+  const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {}
+  const listItems = normalizeSalesListItems(rawData.items)
+
+  return {
+    type,
+    summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary),
+    listItems,
+    columns: inferSalesColumns(listItems)
+  }
+}
+
+const getPurchaseTypeLabel = (type = '') => purchaseTypeLabelMap[String(type || '')] || '閲囪喘鏌ヨ缁撴灉'
+
+const financialDataSectionConfig = [
+  { key: 'orders', title: '璁㈠崟鏄庣粏' },
+  { key: 'items', title: '鏄庣粏鍒楄〃' },
+  { key: 'actualMonthly', title: '鍘嗗彶鐜伴噾娴�' },
+  { key: 'forecastMonthly', title: '棰勬祴鐜伴噾娴�' },
+  { key: 'receivableRiskTop', title: '搴旀敹椋庨櫓Top' },
+  { key: 'payablePressureTop', title: '搴斾粯鍘嬪姏Top' },
+  { key: 'orderProfitTop', title: '鍒╂鼎Top' }
+]
+
+const buildFinancialStructuredSections = (rawData = {}) => {
+  const sections = []
+  financialDataSectionConfig.forEach((section) => {
+    const items = normalizeSalesListItems(rawData?.[section.key])
+    if (!items.length) return
+    sections.push({
+      key: section.key,
+      title: section.title,
+      items,
+      columns: inferSalesColumns(items)
+    })
+  })
+  return sections
+}
+
+const buildFinancialStructuredData = (parsedData) => {
+  const type = String(parsedData?.type || '')
+  if (!financialStructuredTypeSet.has(type) || parsedData?.success !== true) return null
+
+  const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {}
+  const headline = String(rawData?.headline || '').trim()
+
+  return {
+    type,
+    summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary),
+    headline,
+    conclusions: toStructuredStringArray(rawData?.conclusions),
+    riskSuggestions: toStructuredStringArray(rawData?.riskSuggestions),
+    sections: buildFinancialStructuredSections(rawData)
+  }
+}
+
+const getFinancialTypeLabel = (type = '') => financialTypeLabelMap[String(type || '')] || '璐㈠姟鏌ヨ缁撴灉'
+
+const isSalesFocusType = (type = '') => salesFocusTypeSet.has(String(type || ''))
+
+const getSalesLevelTagType = (level = '') => {
+  const normalizedLevel = String(level || '').toLowerCase()
+  if (normalizedLevel === 'high') return 'danger'
+  if (normalizedLevel === 'medium') return 'warning'
+  if (normalizedLevel === 'low') return 'success'
+  return 'info'
+}
+
+const getSalesLevelLabel = (level = '', mode = 'risk') => {
+  const normalizedLevel = String(level || '').toLowerCase()
+  const suffix = mode === 'priority' ? '浼樺厛绾�' : '椋庨櫓'
+  if (normalizedLevel === 'high') return `楂�${suffix}`
+  if (normalizedLevel === 'medium') return `涓�${suffix}`
+  if (normalizedLevel === 'low') return `浣�${suffix}`
+  if (!normalizedLevel) return mode === 'priority' ? '鏈垎绾�' : '鏈瘎浼�'
+  return normalizedLevel.toUpperCase()
+}
+
+const toStructuredStringArray = (value) => {
+  if (Array.isArray(value)) {
+    return value.map(item => String(item || '').trim()).filter(Boolean)
+  }
+  if (typeof value === 'string') {
+    return value
+      .split(/[,\uFF0C\u3001;\uFF1B\n]/)
+      .map(item => item.trim())
+      .filter(Boolean)
+  }
+  return []
+}
+
+const normalizePurchaseIntentNotRecognizedData = (parsedData) => {
+  if (String(parsedData?.type || '') !== 'purchase_intent_not_recognized') return null
+  const quickPrompts = toStructuredStringArray(parsedData?.data?.quickPrompts)
+  if (!quickPrompts.length) return null
+  return { quickPrompts }
+}
+
 const getManufacturingWarningLevelType = (level = '') => {
   const normalizedLevel = String(level || '').toLowerCase()
   if (normalizedLevel === 'high') return 'danger'
@@ -1581,6 +2058,10 @@
           payloadHiddenData: null,
           purchaseAnalysisData: null,
           manufacturingData: null,
+          salesData: null,
+          purchaseData: null,
+          purchaseIntentData: null,
+          financeData: null,
           localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : []
         }
 
@@ -1799,7 +2280,7 @@
           const candidate = text.slice(i, j + 1)
           try {
             const parsed = JSON.parse(candidate)
-            if (parsed?.success === true) {
+            if (typeof parsed?.success === 'boolean') {
               return {
                 data: parsed,
                 startIdx: i,
@@ -1818,16 +2299,33 @@
 }
 
 const applyStructuredMessageData = (messageObj, parsedData, msgIndex, shouldRenderCharts = true) => {
-  if (!messageObj || !parsedData?.success) return
+  const isPurchaseIntentNotRecognized = String(parsedData?.type || '') === 'purchase_intent_not_recognized'
+  if (!messageObj || (parsedData?.success !== true && !isPurchaseIntentNotRecognized)) return
 
   const previousManufacturingData = messageObj.manufacturingData
   messageObj.type = parsedData.type || ''
   messageObj.tableData = null
   messageObj.purchaseAnalysisData = null
   messageObj.manufacturingData = null
+  messageObj.salesData = null
+  messageObj.purchaseData = null
+  messageObj.purchaseIntentData = null
+  messageObj.financeData = null
+
+  if (isPurchaseIntentNotRecognized) {
+    messageObj.purchaseIntentData = normalizePurchaseIntentNotRecognizedData(parsedData)
+    messageObj.chartOptions = null
+    messageObj.chartRenderReady = false
+    return
+  }
 
   if (messageObj.type === 'todo_list' && parsedData.data) {
     messageObj.tableData = parsedData.data
+  }
+
+  const salesData = buildSalesStructuredData(parsedData)
+  if (salesData) {
+    messageObj.salesData = salesData
   }
 
   const manufacturingData = buildManufacturingStructuredData(parsedData, previousManufacturingData)
@@ -1835,10 +2333,23 @@
     messageObj.manufacturingData = manufacturingData
   }
 
+  const purchaseData = buildPurchaseStructuredData(parsedData)
+  if (purchaseData) {
+    messageObj.purchaseData = purchaseData
+  }
+
+  const financeData = buildFinancialStructuredData(parsedData)
+  if (financeData) {
+    messageObj.financeData = financeData
+  }
+
   if (parsedData.action === 'confirm_required' && parsedData.businessType) {
     messageObj.type = 'purchase_analysis_confirm'
     messageObj.purchaseAnalysisData = parsedData
     messageObj.manufacturingData = null
+    messageObj.salesData = null
+    messageObj.purchaseData = null
+    messageObj.financeData = null
     if (!Array.isArray(messageObj.payloadTreeData) || !messageObj.payloadTreeData.length) {
       initializePurchasePayloadTree(messageObj, parsedData.payload || {})
     }
@@ -1883,12 +2394,21 @@
 const getStructuredFallbackText = (parsedData) => {
   if (!parsedData) return '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
   if (parsedData.type === 'todo_list') return '宸蹭负鎮ㄦ暣鐞嗗ソ鐩稿叧鏁版嵁銆�'
+  if (parsedData.type === 'purchase_intent_not_recognized') return '鏈瘑鍒埌鍙墽琛岀殑閲囪喘鏌ヨ鏉′欢锛岃琛ュ厖鏌ヨ鏉′欢鍚庡啀璇曘��'
+  if (salesStructuredTypeSet.has(parsedData.type)) {
+    if (parsedData.type === 'sales_customer_churn_risk') return '宸蹭负鎮ㄧ敓鎴愬鎴锋祦澶遍闄╁垎鏋愩��'
+    if (parsedData.type === 'sales_collection_quote_strategy') return '宸蹭负鎮ㄧ敓鎴愬洖娆句笌鎶ヤ环绛栫暐寤鸿銆�'
+    if (parsedData.type === 'sales_dashboard') return '宸蹭负鎮ㄧ敓鎴愰攢鍞寚鏍囩粺璁°��'
+    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 (financialStructuredTypeSet.has(parsedData.type)) return '宸茶繑鍥炶储鍔″垎鏋愮粨鏋溿��'
+  if (String(parsedData.type || '').startsWith('purchase_')) return '宸茶繑鍥為噰璐煡璇㈢粨鏋溿��'
   if (parsedData.charts && Object.keys(parsedData.charts).length > 0) return '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛ㄣ��'
   return '姝e湪涓烘偍灞曠ず鍒嗘瀽缁撴灉...'
 }
@@ -3018,7 +3538,11 @@
     payloadTreeData: null,
     payloadHiddenData: null,
     purchaseAnalysisData: null,
-    manufacturingData: null
+    manufacturingData: null,
+    salesData: null,
+    purchaseData: null,
+    purchaseIntentData: null,
+    financeData: null
   })
 
   outputState.value[botMsgIndex] = {
@@ -3143,7 +3667,11 @@
     payloadTreeData: null,
     payloadHiddenData: null,
     purchaseAnalysisData: null,
-    manufacturingData: null
+    manufacturingData: null,
+    salesData: null,
+    purchaseData: null,
+    purchaseIntentData: null,
+    financeData: null
   }
   messages.value.push(botMsg)
 
@@ -3175,28 +3703,6 @@
           const extracted = extractEmbeddedSuccessJson(fullText)
           if (extracted) {
             applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex)
-          } else {
-            const extractJson = (text) => {
-              const startIdx = text.indexOf('{"success": true')
-              if (startIdx === -1) return null
-
-              // 浠庡悗寰�鍓嶆壘鏈�鍚庝竴涓� '}'
-              const lastBraceIdx = text.lastIndexOf('}')
-              if (lastBraceIdx === -1 || lastBraceIdx < startIdx) return null
-
-              const potentialJson = text.substring(startIdx, lastBraceIdx + 1)
-              try {
-                return JSON.parse(potentialJson)
-              } catch (err) {
-                return null
-              }
-            }
-
-            const parsedData = extractJson(fullText)
-            if (parsedData) {
-              applyStructuredMessageData(currentMsg, parsedData, botMsgIndex, true)
-            }
-
           }
 
           updateOutputState(fullText, botMsgIndex)
@@ -3214,24 +3720,6 @@
     const extracted = extractEmbeddedSuccessJson(currentMsg.content)
     if (extracted) {
       applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex)
-    } else {
-      const extractJson = (text) => {
-        const startIdx = text.indexOf('{"success": true')
-        if (startIdx === -1) return null
-        const lastBraceIdx = text.lastIndexOf('}')
-        if (lastBraceIdx === -1 || lastBraceIdx < startIdx) return null
-        const potentialJson = text.substring(startIdx, lastBraceIdx + 1)
-        try {
-          return JSON.parse(potentialJson)
-        } catch (err) {
-          return null
-        }
-      }
-
-      const finalParsed = extractJson(currentMsg.content)
-      if (finalParsed) {
-        applyStructuredMessageData(currentMsg, finalParsed, botMsgIndex)
-      }
     }
   }).catch(err => {
     if (err.name === 'CanceledError' || err.name === 'AbortError') {
@@ -3589,30 +4077,30 @@
 .ai-chat-trigger {
   pointer-events: auto;
   position: fixed;
-  right: 24px;
-  bottom: 100px;
-  width: 56px;
-  height: 56px;
+  right: 10px;
+  bottom: 12px;
+  width: 40px;
+  height: 40px;
   background: $gradient-dark;
   border-radius: 50%;
   display: flex;
   align-items: center;
   justify-content: center;
   cursor: pointer;
-  box-shadow: $shadow-deep, 0 0 0 2px rgba(0, 85, 212, 0.3) inset, 0 0 30px rgba(0, 119, 232, 0.2);
-  transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
-  z-index: 2001;
-  animation: triggerPulse 3s ease-in-out infinite;
+  box-shadow: 0 8px 18px rgba(0, 68, 170, 0.32), 0 0 0 1px rgba(0, 136, 232, 0.32) inset;
+  transition: transform 0.22s ease, box-shadow 0.22s ease, opacity 0.22s ease;
+  z-index: 1020;
+  opacity: 0.9;
 
   &::before {
     content: '';
     position: absolute;
-    inset: -6px;
+    inset: -4px;
     background: linear-gradient(135deg, rgba(0, 85, 212, 0.4), rgba(0, 136, 232, 0.3), rgba(90, 159, 224, 0.2));
     border-radius: 50%;
     z-index: -1;
-    filter: blur(16px);
-    animation: glowPulse 2s ease-in-out infinite alternate;
+    filter: blur(10px);
+    opacity: 0.35;
   }
 
   &::after {
@@ -3625,16 +4113,12 @@
   }
 
   &:hover {
-    transform: scale(1.12) translateY(-4px);
-    box-shadow: $shadow-deep, 0 0 0 3px rgba(0, 136, 232, 0.4) inset, 0 0 50px rgba(0, 136, 232, 0.3);
-
-    &::before {
-      animation: glowPulse 1s ease-in-out infinite alternate;
-    }
+    transform: scale(1.05) translateY(-1px);
+    box-shadow: 0 10px 22px rgba(0, 68, 170, 0.38), 0 0 0 1px rgba(0, 136, 232, 0.42) inset;
 
     .trigger-icon {
-      transform: rotate(-8deg) scale(1.05);
-      filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.5));
+      transform: scale(1.03);
+      filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.42));
     }
   }
 
@@ -4519,6 +5003,151 @@
   }
 }
 
+.sales-structured-card {
+  margin-top: 12px;
+  width: 100%;
+  background: #fff;
+  border: 1px solid rgba(31, 122, 114, 0.2);
+  border-radius: 12px;
+  box-shadow: $shadow-card;
+  padding: 14px;
+}
+
+.sales-structured-card__title {
+  font-size: 14px;
+  font-weight: 700;
+  color: #1f5ddf;
+  margin-bottom: 10px;
+}
+
+.sales-summary-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+  gap: 8px;
+  margin-bottom: 12px;
+}
+
+.sales-summary-item {
+  border-radius: 10px;
+  padding: 10px 12px;
+  border: 1px solid rgba(30, 91, 255, 0.12);
+  background: linear-gradient(180deg, #f7fbff, #edf6ff);
+  min-height: 66px;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  gap: 6px;
+}
+
+.sales-summary-label {
+  font-size: 12px;
+  color: #4b5563;
+}
+
+.sales-summary-value {
+  font-size: 15px;
+  color: #1f2937;
+  line-height: 1.4;
+  word-break: break-all;
+}
+
+.sales-focus-list {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.sales-focus-item {
+  border-radius: 10px;
+  border: 1px solid rgba(30, 91, 255, 0.14);
+  background: #f8fbff;
+  padding: 10px 12px;
+}
+
+.sales-focus-item--strategy {
+  border-color: rgba(31, 122, 114, 0.22);
+  background: linear-gradient(180deg, #f7fcfb, #edf9f6);
+}
+
+.sales-focus-item__head {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  gap: 10px;
+  font-size: 13px;
+  color: #1f2937;
+}
+
+.sales-focus-tags {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  flex-wrap: wrap;
+  justify-content: flex-end;
+}
+
+.sales-focus-metrics {
+  margin-top: 8px;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+  font-size: 12px;
+  color: #475467;
+}
+
+.sales-focus-reasons {
+  margin-top: 8px;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 6px;
+}
+
+.sales-strategy-line {
+  margin: 8px 0 0;
+  font-size: 12px;
+  line-height: 1.6;
+  color: #334155;
+}
+
+.sales-section-title {
+  margin: 4px 0 8px;
+  font-size: 13px;
+  font-weight: 700;
+  color: $deep-blue;
+}
+
+.finance-headline {
+  margin-top: 4px;
+  font-size: 13px;
+  line-height: 1.7;
+  color: #344054;
+}
+
+.finance-text-section {
+  margin-top: 10px;
+
+  ul {
+    margin: 6px 0 0;
+    padding-left: 18px;
+    font-size: 13px;
+    line-height: 1.7;
+    color: #344054;
+  }
+}
+
+.purchase-intent-quick-prompt-wrap {
+  margin-top: 12px;
+}
+
+.purchase-intent-quick-prompt-title {
+  font-size: 12px;
+  color: #4b5563;
+}
+
+.purchase-intent-quick-prompt-list {
+  margin-top: 8px;
+}
+
 .purchase-confirm-card {
   margin-top: 12px;
   width: 100%;

--
Gitblit v1.9.3