| | |
| | | </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.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> |
| | | |
| | | <div v-if="message.purchaseAnalysisData" class="purchase-confirm-card"> |
| | | <div class="purchase-confirm-header"> |
| | | <span>{{ businessTypeLabelMap[message.purchaseAnalysisData.businessType] || message.purchaseAnalysisData.businessType || '采购业务' }}</span> |
| | |
| | | sales_customer_churn_risk: '客户流失风险分析', |
| | | sales_collection_quote_strategy: '回款与报价策略建议' |
| | | } |
| | | const purchaseTypeLabelMap = { |
| | | purchase_material_rank: '采购物料金额排行', |
| | | purchase_pending_payment_list: '待付款采购单' |
| | | } |
| | | const manufacturingStructuredTypeSet = new Set([ |
| | | 'manufacturing_site_snapshot', |
| | | 'manufacturing_plan_list', |
| | |
| | | contractAmountTotal: '合同总额', |
| | | receivedAmountTotal: '已回款金额', |
| | | pendingAmountTotal: '待回款总额', |
| | | shipRate: '发货率' |
| | | shipRate: '发货率', |
| | | pendingOrderCount: '待付款订单数', |
| | | totalContractAmount: '待付款合同总额', |
| | | totalPaidAmount: '已付款总额', |
| | | totalPendingAmount: '待付款总额' |
| | | }) |
| | | const purchasePayloadFieldLabelMap = { |
| | | purchaseLedgers: '采购台账', |
| | |
| | | |
| | | 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 isSalesFocusType = (type = '') => salesFocusTypeSet.has(String(type || '')) |
| | | |
| | | const getSalesLevelTagType = (level = '') => { |
| | |
| | | .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 = '') => { |
| | |
| | | purchaseAnalysisData: null, |
| | | manufacturingData: null, |
| | | salesData: null, |
| | | purchaseData: null, |
| | | purchaseIntentData: null, |
| | | localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : [] |
| | | } |
| | | |
| | |
| | | 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, |
| | |
| | | } |
| | | |
| | | 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.purchaseAnalysisData = null |
| | | messageObj.manufacturingData = null |
| | | messageObj.salesData = null |
| | | messageObj.purchaseData = null |
| | | messageObj.purchaseIntentData = 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 |
| | |
| | | messageObj.manufacturingData = manufacturingData |
| | | } |
| | | |
| | | const purchaseData = buildPurchaseStructuredData(parsedData) |
| | | if (purchaseData) { |
| | | messageObj.purchaseData = purchaseData |
| | | } |
| | | |
| | | if (parsedData.action === 'confirm_required' && parsedData.businessType) { |
| | | messageObj.type = 'purchase_analysis_confirm' |
| | | messageObj.purchaseAnalysisData = parsedData |
| | | messageObj.manufacturingData = null |
| | | messageObj.salesData = null |
| | | messageObj.purchaseData = null |
| | | if (!Array.isArray(messageObj.payloadTreeData) || !messageObj.payloadTreeData.length) { |
| | | initializePurchasePayloadTree(messageObj, parsedData.payload || {}) |
| | | } |
| | |
| | | const getStructuredFallbackText = (parsedData) => { |
| | | if (!parsedData) return '正在为您展示分析结果...' |
| | | 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 === 'manufacturing_analysis') return '已为您生成制造分析结果。' |
| | | return '已返回制造查询结果。' |
| | | } |
| | | if (String(parsedData.type || '').startsWith('purchase_')) return '已返回采购查询结果。' |
| | | if (parsedData.charts && Object.keys(parsedData.charts).length > 0) return '已为您生成分析图表。' |
| | | return '正在为您展示分析结果...' |
| | | } |
| | |
| | | payloadHiddenData: null, |
| | | purchaseAnalysisData: null, |
| | | manufacturingData: null, |
| | | salesData: null |
| | | salesData: null, |
| | | purchaseData: null, |
| | | purchaseIntentData: null |
| | | }) |
| | | |
| | | outputState.value[botMsgIndex] = { |
| | |
| | | payloadHiddenData: null, |
| | | purchaseAnalysisData: null, |
| | | manufacturingData: null, |
| | | salesData: null |
| | | salesData: null, |
| | | purchaseData: null, |
| | | purchaseIntentData: null |
| | | } |
| | | messages.value.push(botMsg) |
| | | |
| | |
| | | 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) |
| | |
| | | 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') { |
| | |
| | | color: $deep-blue; |
| | | } |
| | | |
| | | .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%; |