/** AI 助手 · 三大 MCP 能力所需信息引导 */ export type McpCapabilityId = 'price' | 'guide' | 'track' export type McpFieldHint = { key: string required: boolean /** i18n key under nanaAssistant.mcp.* */ labelKey: string } export type McpCapabilityGuide = { id: McpCapabilityId toolKey: string /** 快捷入口 i18n */ titleKey: string descKey: string fields: McpFieldHint[] /** 引导话术 i18n */ coachKey: string /** 输入框示例 */ exampleKey: string /** 预填到输入框的示例文案 i18n(可编辑后再发) */ draftKey: string } export const MCP_CAPABILITY_GUIDES: McpCapabilityGuide[] = [ { id: 'price', toolKey: 'oms.price_check', titleKey: 'nanaAssistant.mcp.priceTitle', descKey: 'nanaAssistant.mcp.priceDesc', coachKey: 'nanaAssistant.mcp.priceCoach', exampleKey: 'nanaAssistant.mcp.priceExample', draftKey: 'nanaAssistant.mcp.priceDraft', fields: [ { key: 'country', required: true, labelKey: 'nanaAssistant.mcp.field.country' }, { key: 'weight', required: true, labelKey: 'nanaAssistant.mcp.field.weight' }, { key: 'postal', required: false, labelKey: 'nanaAssistant.mcp.field.postal' }, { key: 'dims', required: false, labelKey: 'nanaAssistant.mcp.field.dims' }, { key: 'cargoType', required: false, labelKey: 'nanaAssistant.mcp.field.cargoType' }, ], }, { id: 'guide', toolKey: 'oms.channel_guide', titleKey: 'nanaAssistant.mcp.guideTitle', descKey: 'nanaAssistant.mcp.guideDesc', coachKey: 'nanaAssistant.mcp.guideCoach', exampleKey: 'nanaAssistant.mcp.guideExample', draftKey: 'nanaAssistant.mcp.guideDraft', fields: [ { key: 'product', required: true, labelKey: 'nanaAssistant.mcp.field.product' }, { key: 'cargoName', required: true, labelKey: 'nanaAssistant.mcp.field.cargoName' }, ], }, { id: 'track', toolKey: 'oms.track_query', titleKey: 'nanaAssistant.mcp.trackTitle', descKey: 'nanaAssistant.mcp.trackDesc', coachKey: 'nanaAssistant.mcp.trackCoach', exampleKey: 'nanaAssistant.mcp.trackExample', draftKey: 'nanaAssistant.mcp.trackDraft', fields: [ { key: 'orderNo', required: true, labelKey: 'nanaAssistant.mcp.field.orderNo' }, ], }, ] const COUNTRY_HINT = /美国|英國|英国|德国|法国|澳洲|澳大利亚|加拿大|日本|韩国|意大利|西班牙|荷兰|墨西哥|巴西|新西兰|\b(US|GB|UK|DE|FR|AU|CA|JP|KR|IT|ES|NL|MX|BR|NZ)\b/i /** 0.5kg / 0.5 kg / 重量0.5 / 大概0.5公斤 — 避免把运单号里的纯数字当成重量 */ const WEIGHT_HINT = /(\d+(\.\d+)?)\s*(kg|KG|公斤|千克)|(?:重量|实重)\s*[::]?\s*\d+(\.\d+)?|(?:约|大概)\s*\d+(\.\d+)?\s*(kg|KG|公斤|千克)?/i /** IDE 运单、跟踪号等 */ const ORDER_NO_LOOSE = /[A-Za-z][A-Za-z0-9\-_]{5,}/ const ORDER_NO_STRICT = /\b(IDE\d{8,}|(?:YD|SF|YT|NF|LT)[A-Za-z0-9]{6,}|(?:[A-Z]{2,}\d{8,}))\b/i const PRODUCT_HINT = /产品|渠道|线路|运输方式|[A-Z]{2,}[_-][A-Z0-9_]+/i const CARGO_HINT = /品名|货物|普货|敏感|衣服|服装|鞋|化妆品|lipstick|手机|电池|粉末|液体|食品|寄|发/i export function extractOrderNo(text: string): string | null { const strict = text.match(ORDER_NO_STRICT) if (strict?.[1] || strict?.[0]) return (strict[1] || strict[0]).trim() // 「运单号:XXXX」类 const labeled = text.match( /(?:运单号|跟踪号|客户单号|物流单号|单号)[::\s]*([A-Za-z0-9][A-Za-z0-9\-_]{5,})/i, ) if (labeled?.[1]) return labeled[1].trim() const loose = text.match(ORDER_NO_LOOSE) return loose?.[0]?.trim() || null } function joinedContext(text: string, priorTexts?: string[]) { const parts = [...(priorTexts ?? []), text].map((s) => String(s || '').trim()).filter(Boolean) return parts.join('\n') } /** 粗判用户这句话还缺哪些必填信息(用于先追问再调工具);会合并多轮用户原文 */ export function detectMissingFields( capability: McpCapabilityId, text: string, opts?: { hasWaybillContext?: boolean; priorTexts?: string[] }, ): string[] { const t = joinedContext(text, opts?.priorTexts) const missing: string[] = [] if (capability === 'price') { if (!COUNTRY_HINT.test(t)) missing.push('country') if (!WEIGHT_HINT.test(t)) missing.push('weight') } else if (capability === 'guide') { if (!PRODUCT_HINT.test(t)) missing.push('product') if (!CARGO_HINT.test(t) && t.replace(/\s/g, '').length < 4) missing.push('cargoName') } else if (capability === 'track') { if (!opts?.hasWaybillContext && !extractOrderNo(t)) missing.push('orderNo') } return missing } export function inferCapabilityFromText(text: string): McpCapabilityId | null { const raw = text.trim() if (!raw) return null const lower = raw.toLowerCase() // 轨迹优先:关键词或明显单号 if (/track|tracking|轨迹|物流|运单|查单|单号|查下|查一下/.test(lower)) return 'track' if (extractOrderNo(raw) && !COUNTRY_HINT.test(raw)) return 'track' if (ORDER_NO_STRICT.test(raw) && raw.length < 40) return 'track' if (/品名|能发|禁运|可寄|对客说明|承运|敏感货/.test(lower)) return 'guide' if (/price|cost|quote|报价|价格|多少钱|运费|查价|怎么走|走得动/.test(lower)) return 'price' // 隐式查价:目的国 + 重量 / 「走|寄|发」+ 国家 if (COUNTRY_HINT.test(raw) && WEIGHT_HINT.test(raw)) return 'price' if (/(?:走|寄|发往|寄到|发到|到)/.test(raw) && COUNTRY_HINT.test(raw)) return 'price' return null } export function getCapability(id: McpCapabilityId): McpCapabilityGuide { return MCP_CAPABILITY_GUIDES.find((c) => c.id === id)! }