/** * IDE Command Center — logistics advisor (mock scripts + cards) */ (function () { 'use strict'; const STORAGE_THREADS = 'ide-ai-threads'; const STORAGE_ACTIVE_THREAD = 'ide-ai-active-thread'; const STORAGE_SESSION = 'ide-ai-session'; const PHONE = '18123959245'; const EMAIL = 'bd01@1st56.com'; const WECHAT = 'ydgjzzh'; const ICP = '粤ICP备2025463970号-2'; const ADDRESS_ZH = '深圳宝安区福永怀德翠岗5区36栋'; const ADDRESS_EN = 'Bldg 36, Cuigang 5, Fuyong, Bao’an, Shenzhen'; const HERO_STATS = [ { id: 'coverage', interactive: true, value: { zh: '30+', en: '30+' }, label: { zh: '覆盖国家/地区', en: 'Countries & regions' }, }, { value: { zh: '8,000+', en: '8,000+' }, label: { zh: '服务跨境卖家', en: 'SMB sellers served' }, }, { value: { zh: '1,200万+', en: '12M+' }, label: { zh: '累计运送包裹', en: 'Parcels shipped' }, }, ]; const COPY = { en: { welcomeTitle: 'IDE, a digital one-stop cross-border logistics provider', welcomeTitleShort: 'IDE · Cross-border logistics', welcomeSubLead: 'Parcels, express, dropship, and 9610 clearance — ', welcomeSubAccent: 'built for SMB cross-border sellers', welcomeSubTail: '.', placeholder: 'Ask about lanes, tracking, or 9610…', sendLabel: 'Ask', human: 'Talk to a human advisor', you: 'You', historyTitle: 'Chat history', newChat: 'New chat', emptyHistory: 'No conversations yet', fallback: 'Quotes are in the customer system after you register. You can also talk to an advisor.', trustPhone: PHONE, trustAddress: ADDRESS_EN, trustWechat: 'WeChat ' + WECHAT, trustEmail: EMAIL, trustIcp: ICP, guide9610Title: 'Ship first, tax second', guide9610Kicker: 'Services', guide9610Sub: 'Pick a lane. Tax and filings come after the parcel can leave.', loginTrack: 'Sign in to track', registerNow: 'Register to quote', loginExisting: 'Already registered? Sign in', heroCta: 'Ship now', statsAria: 'Service highlights', statsCoverageHint: 'Tap to view', mapZoomHint: 'Tap a country to pin it on the map', composeLead: 'Ask about a lane, or pick a shortcut below', }, zh: { welcomeTitle: 'IDE,全球数字化一站式跨境物流服务商', welcomeTitleShort: 'IDE · 一站式跨境物流', welcomeSubLead: '专线小包、国际快递、一件代发与 9610 报关,', welcomeSubAccent: '专为中小跨境卖家而生', welcomeSubTail: '。', placeholder: '问线路、查轨迹、9610 政策…', sendLabel: '提问', human: '联系人工顾问', you: '你', historyTitle: '历史记录', newChat: '新会话', emptyHistory: '暂无会话', fallback: '官网不提供询价。完成注册登记后,即可在客户系统里看明码价。也可以先联系顾问。', trustPhone: PHONE, trustAddress: ADDRESS_ZH, trustWechat: '微信 ' + WECHAT, trustEmail: EMAIL, trustIcp: ICP, guide9610Title: '先发货,再办税', guide9610Kicker: '服务', guide9610Sub: '先选怎么走货。税务和备案跟在后面,点一张卡就能问。', loginTrack: '登录查真实轨迹', registerNow: '去注册登记', loginExisting: '已有账号,去登录', heroCta: '立即发货', statsAria: '服务数据', statsCoverageHint: '点我查看', mapZoomHint: '点击国家,地图上会用定位图标标出', composeLead: '先问线路,或选快捷入口', }, }; const GUIDE_SERVICES = [ { icon: 'lane', tag: { en: 'Ship', zh: '发货' }, en: { title: 'US / EU / SEA parcels', body: 'US, Mexico, Canada, EU 27, the UK, Australia, New Zealand, and Southeast Asia parcels. Tracked end to end.' }, zh: { title: '欧美 / 东南亚专线', body: '美国、墨西哥、加拿大、欧盟 27 国及英国、澳大利亚、新西兰,以及东南亚小包。全程可追踪。' }, ask: { en: 'I need a line to the US or Southeast Asia', zh: '我要走欧美或东南亚专线' }, intent: 'quote', }, { icon: 'warehouse', tag: { en: 'Warehouse', zh: '仓储' }, en: { title: 'Warehouse dropship', body: 'Inbound, pick and pack, ship when the order lands. SMB sellers do not need their own warehouse.' }, zh: { title: '仓储一件代发', body: '入仓、拣货打包,订单来了就发出去。中小卖家不用自己养仓。' }, ask: { en: 'I need warehouse dropshipping', zh: '我要仓储一件代发' }, intent: 'warehouse', }, { icon: 'clearance', tag: { en: 'Ship', zh: '发货' }, en: { title: '9610 clearance', body: 'List release, consolidated declaration, and three-document match — so this parcel can actually leave.' }, zh: { title: '9610 报关出货', body: '清单核放、汇总申报、三单对碰。先保证这票货出得去。' }, ask: { en: 'I need to ship a 9610 parcel', zh: '我有一票 9610 要出货' }, intent: 'hard', }, { icon: 'advise', tag: { en: 'Ship', zh: '发货' }, en: { title: 'Tough cargo', body: 'Battery, oversize, no invoice, or declined elsewhere. We pick the lane and the tax path first.' }, zh: { title: '疑难货怎么发', body: '带电、超长、无票、别人不接的单。先帮你选线路和税务路径。' }, ask: { en: 'Others declined this cargo. Can you take it?', zh: '别人不接的货,你们能发吗?' }, intent: 'hard', }, { icon: 'refund', tag: { en: 'Tax', zh: '税务' }, en: { title: 'Export VAT refund', body: 'If you have special VAT invoices: we assemble the refund pack and file it on the e-tax bureau.' }, zh: { title: '出口退税', body: '有增值税专票:整理退税资料,并在电子税务局申报。进项税按退税率退还。' }, ask: { en: 'Can you handle export tax refund?', zh: '出口退税可以代办吗?' }, intent: 'hard', }, { icon: 'exempt', tag: { en: 'Tax', zh: '税务' }, en: { title: 'Exemption without invoice', body: 'No special invoice: pilot-zone filing and exemption registration, so export VAT is waived.' }, zh: { title: '无票免征', body: '没有专票:综试区备案和免税登记。出口环节增值税免掉。' }, ask: { en: 'Help me register for VAT-free export', zh: '帮我办无票免征登记' }, intent: 'hard', }, { icon: 'filing', tag: { en: 'Filing', zh: '备案' }, en: { title: 'Customs credentials', body: 'Importer/exporter filing, cross-border seller status, and e-port card — when volume is ready to scale.' }, zh: { title: '海关资质', body: '进出口收发货人、跨境电商资质、电子口岸卡。量起来再办也不迟。' }, ask: { en: 'I need customs credentials filed', zh: '我要办海关资质备案' }, intent: 'hard', }, ]; const MAP_COORDS = { eu: [10.5, 50.5], us: [-98.5, 39.8], mx: [-102.5, 23.6], ca: [-106.3, 56.1], au: [134.5, -25.7], nz: [174.8, -41.3], de: [10.45, 51.16], fr: [2.35, 46.85], it: [12.5, 42.85], es: [-3.7, 40.4], nl: [5.3, 52.15], be: [4.35, 50.85], at: [14.55, 47.52], ie: [-7.7, 53.35], gb: [-2.5, 54.0], pt: [-8.6, 39.4], gr: [23.73, 37.98], se: [18.07, 59.33], dk: [12.57, 55.68], fi: [24.94, 60.17], pl: [21.01, 52.23], cz: [14.44, 50.08], hu: [19.05, 47.5], ro: [24.97, 45.94], bg: [23.32, 42.7], sk: [17.11, 48.15], si: [14.51, 46.05], hr: [15.98, 45.81], lt: [25.28, 54.69], lv: [24.11, 56.95], ee: [24.75, 59.44], lu: [6.13, 49.61], mt: [14.51, 35.9], cy: [33.38, 35.18], my: [101.98, 4.21], sg: [103.82, 1.35], ph: [121.77, 12.88], th: [100.5, 13.75], vn: [105.85, 21.03], id: [113.92, -0.79], }; const COVER_REGIONS = [ { id: 'eu', dest: { zh: '欧盟', en: 'European Union' }, label: { zh: '欧盟 27 国 + 英国', en: 'EU 27 + UK' }, expand: true }, { id: 'us', dest: { zh: '美国', en: 'United States' }, label: { zh: '美国', en: 'United States' } }, { id: 'mx', dest: { zh: '墨西哥', en: 'Mexico' }, label: { zh: '墨西哥', en: 'Mexico' } }, { id: 'ca', dest: { zh: '加拿大', en: 'Canada' }, label: { zh: '加拿大', en: 'Canada' } }, { id: 'au', dest: { zh: '澳大利亚', en: 'Australia' }, label: { zh: '澳大利亚', en: 'Australia' } }, { id: 'nz', dest: { zh: '新西兰', en: 'New Zealand' }, label: { zh: '新西兰', en: 'New Zealand' } }, ]; const COVER_EU = [ { mapId: 'de', zh: '德国', en: 'Germany' }, { mapId: 'fr', zh: '法国', en: 'France' }, { mapId: 'it', zh: '意大利', en: 'Italy' }, { mapId: 'es', zh: '西班牙', en: 'Spain' }, { mapId: 'nl', zh: '荷兰', en: 'Netherlands' }, { mapId: 'be', zh: '比利时', en: 'Belgium' }, { mapId: 'at', zh: '奥地利', en: 'Austria' }, { mapId: 'ie', zh: '爱尔兰', en: 'Ireland' }, { mapId: 'gb', zh: '英国', en: 'United Kingdom' }, { mapId: 'pt', zh: '葡萄牙', en: 'Portugal' }, { mapId: 'gr', zh: '希腊', en: 'Greece' }, { mapId: 'se', zh: '瑞典', en: 'Sweden' }, { mapId: 'dk', zh: '丹麦', en: 'Denmark' }, { mapId: 'fi', zh: '芬兰', en: 'Finland' }, { mapId: 'pl', zh: '波兰', en: 'Poland' }, { mapId: 'cz', zh: '捷克', en: 'Czechia' }, { mapId: 'hu', zh: '匈牙利', en: 'Hungary' }, { mapId: 'ro', zh: '罗马尼亚', en: 'Romania' }, { mapId: 'bg', zh: '保加利亚', en: 'Bulgaria' }, { mapId: 'sk', zh: '斯洛伐克', en: 'Slovakia' }, { mapId: 'si', zh: '斯洛文尼亚', en: 'Slovenia' }, { mapId: 'hr', zh: '克罗地亚', en: 'Croatia' }, { mapId: 'lt', zh: '立陶宛', en: 'Lithuania' }, { mapId: 'lv', zh: '拉脱维亚', en: 'Latvia' }, { mapId: 'ee', zh: '爱沙尼亚', en: 'Estonia' }, { mapId: 'lu', zh: '卢森堡', en: 'Luxembourg' }, { mapId: 'mt', zh: '马耳他', en: 'Malta' }, { mapId: 'cy', zh: '塞浦路斯', en: 'Cyprus' }, ]; const COVER_SEA = [ { mapId: 'my', zh: '马来西亚', en: 'Malaysia' }, { mapId: 'sg', zh: '新加坡', en: 'Singapore' }, { mapId: 'ph', zh: '菲律宾', en: 'Philippines' }, { mapId: 'th', zh: '泰国', en: 'Thailand' }, { mapId: 'vn', zh: '越南', en: 'Vietnam' }, { mapId: 'id', zh: '印度尼西亚', en: 'Indonesia' }, ]; const INTENTS = [ { id: 'quote', icon: 'quote', en: { title: 'Clear rates', hint: 'Tailored solutions — better, faster. Register to inquire.', label: 'I need a shipping quote' }, zh: { title: '明码报价', hint: '量身定制解决方案,更优更快等你来询', label: '我要报价' }, }, { id: 'track', icon: 'track', en: { title: 'Track a shipment', hint: 'End-to-end tracking, timely exception alerts', label: 'Track my shipment' }, zh: { title: '轨迹追踪', hint: '轨迹全程可追踪,异常及时提醒', label: '我想查这票货到哪了' }, }, { id: 'hard', icon: 'hard', en: { title: 'Tough orders', hint: 'Refund, exemption, 3C battery, special & cosmetic goods', label: 'Can you take a difficult or 9610 order?' }, zh: { title: '疑难杂单', hint: '退税、免征、3C带电、特殊品、化妆品', label: '疑难杂单或 9610 怎么发?' }, }, { id: 'human', icon: 'human', en: { title: 'Your advisor', hint: 'One person from quote to delivery', label: 'Talk to a human advisor' }, zh: { title: '专属顾问', hint: '报价跟到签收,售后有人认', label: '联系专属顾问' }, }, ]; function iconMarkup(name) { const art = { quote: '' + '' + '' + '', track: '' + '' + '' + '' + '', hard: '' + '' + '' + '', human: '' + '' + '' + '' + '' + '', lane: '' + '' + '', clearance: '' + '' + '' + '', advise: '' + '' + '', refund: '' + '' + '' + '', exempt: '' + '', filing: '' + '' + '' + '', warehouse: '' + '' + '' + '', }; const body = art[name] || art.lane; return ( '' ); } function resolveInitialLang() { const saved = localStorage.getItem('ide-language'); if (saved === 'zh' || saved === 'en') return saved; return 'zh'; } let lang = resolveInitialLang(); let isBusy = false; let sessionMessages = []; let hasUserMessage = false; let threads = []; let activeThreadId = null; let flow = null; let shell; let welcomeEl; let messagesEl; let followupEl; let inputEl; let sendBtn; let intentGridEl; let mainScrollEl; let historyListEl; let globeController = null; let coverageOpen = false; const MAP_REGION_EU = new Set(['eu'].concat(COVER_EU.map((item) => item.mapId))); const MAP_REGION_AM = new Set(['us', 'mx', 'ca']); const MAP_REGION_AU = new Set(['au', 'nz']); const MAP_REGION_SEA = new Set(COVER_SEA.map((item) => item.mapId)); function mapRegionForId(mapId) { if (MAP_REGION_EU.has(mapId)) return 'eu'; if (MAP_REGION_AM.has(mapId)) return 'am'; if (MAP_REGION_AU.has(mapId)) return 'au'; if (MAP_REGION_SEA.has(mapId)) return 'sea'; return 'cn'; } function preferOmsH5() { const ua = navigator.userAgent || ''; return /MicroMessenger/i.test(ua) || /Android|iPhone|iPod|iPad|Mobile/i.test(ua); } function resolveOmsOrigin() { const configured = window.IDE_OMS_ORIGIN || localStorage.getItem('ide-oms-origin'); if (configured) return String(configured).replace(/\/$/, ''); const path = (window.location.pathname || '').replace(/\\/g, '/'); const port = window.location.port; const isFile = window.location.protocol === 'file:'; const standalone = isFile || /\/IDE官网(\/|$)/.test(path) || (port && !['80', '443', '5173', '4173'].includes(port) && !document.getElementById('app')); if (standalone) { const host = window.location.hostname || '127.0.0.1'; return `${isFile ? 'http:' : window.location.protocol}//${host}:5173`; } return window.location.origin; } function omsUrl(pathname) { return `${resolveOmsOrigin()}${pathname}`; } function loginTrackUrl(number) { const target = preferOmsH5() ? '/h5/login' : '/login'; const redirect = preferOmsH5() ? '/h5/orders' : '/home/workplace'; const url = new URL(omsUrl(target)); url.searchParams.set('redirect', redirect); if (number) url.searchParams.set('track', number); return url.toString(); } function loginPortalUrl() { return omsUrl(preferOmsH5() ? '/h5/login' : '/login'); } function registerUrl(dest) { const url = new URL(omsUrl(preferOmsH5() ? '/wechat/register' : '/register')); url.searchParams.set('from', 'site'); if (dest) url.searchParams.set('dest', String(dest)); return url.toString(); } function registerActions(dest) { const actions = el('div', 'ai-card__actions'); const reg = el('a', 'ai-card-link is-primary', t('registerNow')); reg.href = registerUrl(dest); const login = el('a', 'ai-card-link', t('loginExisting')); login.href = loginPortalUrl(); actions.appendChild(reg); actions.appendChild(login); return actions; } function isHomePage() { const path = (window.location.pathname || '').replace(/\\/g, '/'); return ( /(?:^|\/)index\.html?$/.test(path) || path.endsWith('/') || path === '' || /\/IDE官网\/?$/.test(path) ); } function t(key) { return COPY[lang][key] || COPY.en[key] || key; } function setTrustLabel(selector, text) { const label = shell.querySelector(selector + ' .ai-trust-label'); if (label) label.textContent = text; } function intentCopy(intent) { return lang === 'zh' ? intent.zh : intent.en; } function newThreadId() { return 't_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8); } function deriveThreadTitle(messages) { const firstUser = messages.find((m) => m.role === 'user'); if (!firstUser) return t('newChat'); const text = String(firstUser.text || '').trim(); return text.length > 28 ? `${text.slice(0, 28)}…` : text; } function formatThreadTime(ts) { const date = new Date(ts); const now = new Date(); const sameDay = date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate(); if (sameDay) { return date.toLocaleTimeString(lang === 'zh' ? 'zh-CN' : 'en-US', { hour: '2-digit', minute: '2-digit', }); } return date.toLocaleDateString(lang === 'zh' ? 'zh-CN' : 'en-US', { month: 'short', day: 'numeric', }); } function getActiveThread() { return threads.find((thread) => thread.id === activeThreadId) || null; } function syncActiveThreadMessages() { const thread = getActiveThread(); if (!thread) return; thread.messages = sessionMessages.map((msg) => ({ role: msg.role, text: msg.text, card: msg.card || null, })); thread.flow = flow ? { ...flow, data: { ...(flow.data || {}) } } : null; thread.updatedAt = Date.now(); if (thread.messages.some((m) => m.role === 'user')) { thread.title = deriveThreadTitle(thread.messages); } } function saveThreads() { syncActiveThreadMessages(); localStorage.setItem(STORAGE_THREADS, JSON.stringify(threads)); if (activeThreadId) { localStorage.setItem(STORAGE_ACTIVE_THREAD, activeThreadId); } } function applyThreadMessages(thread) { sessionMessages = thread?.messages ? thread.messages.map((msg) => ({ role: msg.role, text: msg.text, card: msg.card || null })) : []; hasUserMessage = sessionMessages.some((m) => m.role === 'user'); flow = thread?.flow ? { ...thread.flow, data: { ...(thread.flow.data || {}) } } : null; } function createThread(setActive) { const thread = { id: newThreadId(), title: t('newChat'), updatedAt: Date.now(), messages: [], flow: null, }; threads.unshift(thread); if (setActive !== false) { activeThreadId = thread.id; applyThreadMessages(thread); } saveThreads(); return thread; } function loadThreads() { try { const raw = localStorage.getItem(STORAGE_THREADS); threads = raw ? JSON.parse(raw) : []; if (!Array.isArray(threads)) threads = []; } catch { threads = []; } activeThreadId = localStorage.getItem(STORAGE_ACTIVE_THREAD); if (threads.length === 0) { try { const legacy = sessionStorage.getItem(STORAGE_SESSION); const legacyMessages = legacy ? JSON.parse(legacy) : []; if (Array.isArray(legacyMessages) && legacyMessages.length > 0) { const thread = createThread(true); thread.messages = legacyMessages; thread.title = deriveThreadTitle(legacyMessages); applyThreadMessages(thread); saveThreads(); sessionStorage.removeItem(STORAGE_SESSION); return; } } catch { /* ignore */ } createThread(true); return; } if (!activeThreadId || !threads.some((thread) => thread.id === activeThreadId)) { activeThreadId = threads[0].id; } applyThreadMessages(getActiveThread()); } function syncHeaderOffset() { const header = document.getElementById('header'); const height = header ? Math.round(header.getBoundingClientRect().height) : 72; document.documentElement.style.setProperty('--ai-header-h', `${height}px`); } function el(tag, className, text) { const node = document.createElement(tag); if (className) node.className = className; if (text != null) node.textContent = text; return node; } function renderCard(card) { if (!card || !card.type) return null; const wrap = el('div', `ai-card ai-card--${card.type}`); const zh = lang === 'zh'; if (card.type === 'choice') { if (card.kicker) wrap.appendChild(el('p', 'ai-card__kicker', card.kicker)); if (card.title) wrap.appendChild(el('p', 'ai-card__title', card.title)); const actions = el('div', 'ai-card__actions'); (card.options || []).forEach((opt) => { const btn = el('button', opt.primary ? 'ai-card-opt is-primary' : 'ai-card-opt', opt.label); btn.type = 'button'; btn.dataset.choice = opt.id; btn.dataset.flow = card.flowId || ''; btn.dataset.label = opt.label; actions.appendChild(btn); }); wrap.appendChild(actions); return wrap; } if (card.type === 'quote') { wrap.appendChild(el('p', 'ai-card__kicker', zh ? '询价' : 'Quote')); wrap.appendChild(el('p', 'ai-card__title', zh ? '注册登记后即可询价' : 'Register to see rates')); wrap.appendChild( el( 'p', 'ai-card__body', card.dest ? zh ? '发到' + card.dest + '可以走。官网不提供询价,完成注册后在客户系统里按线路看明码价。' : 'We ship to ' + card.dest + '. Quotes are not on this site — register, then see list rates in the customer system.' : zh ? '官网不提供询价。完成注册登记后,即可在客户系统里按线路看明码价,一口价不砍。' : 'This site does not quote. Register first, then see list rates by lane in the customer system — no haggling.' ) ); wrap.appendChild(registerActions(card.dest)); return wrap; } if (card.type === 'track') { wrap.appendChild(el('p', 'ai-card__kicker', zh ? '真实轨迹' : 'Live tracking')); wrap.appendChild(el('p', 'ai-card__title', t('loginTrack'))); wrap.appendChild( el( 'p', 'ai-card__body', card.number ? zh ? `单号 ${card.number} 已记下。登录后看本票真实节点。` : `Got ${card.number}. Sign in to see live scans for this shipment.` : zh ? '有单号请登录查看。询价需先注册登记。' : 'Sign in if you have a number. Quotes require registration.' ) ); const actions = el('div', 'ai-card__actions'); const login = el('a', 'ai-card-link is-primary', t('loginTrack')); login.href = loginTrackUrl(card.number || ''); actions.appendChild(login); wrap.appendChild(actions); wrap.appendChild(intentActions(['quote', 'human'])); return wrap; } if (card.type === 'hard-refund' || card.type === 'hard-exempt' || card.type === 'hard-basic') { const meta = { 'hard-refund': { kicker: zh ? '9610 · 利润最优' : '9610 · best margin', title: zh ? '出口退税(免抵退)' : 'Export VAT refund', body: zh ? '有进项专票:汇总正式报关、完整 10 位 HS、报关单退税联 + 专票。进项税额按退税率退还。' : 'You have special VAT invoices: formal consolidated declaration, full 10-digit HS, declaration refund copy + invoice. Input VAT is refunded at the rebate rate.', }, 'hard-exempt': { kicker: zh ? '9610 · 合规推荐' : '9610 · compliant', title: zh ? '无票免征' : 'Exemption without invoice', body: zh ? '无专票但要免税:简易/汇总均可,HS 前 4 位,综服平台登记。出口环节增值税/消费税免征。' : 'No special invoice, but you want exemption: simplified or consolidated filing, first 4 HS digits, comprehensive-service platform. Export VAT/excise is exempted.', }, 'hard-basic': { kicker: zh ? '9610 · 快捷出货' : '9610 · fastest exit', title: zh ? '基础通关服务' : 'Basic clearance', body: zh ? '仅需出货:物流商统一简易申报。出得快,但税务未规范可能被视同内销(增值税 + 所得税 + 滞纳金)。量起来后建议升到免征或退税,IDE 可评估路径。' : 'Ship-only: carrier files a simplified declaration. Fast, but unstructured tax handling can be treated as domestic sales. We can review an upgrade path when volume grows.', }, }[card.type]; if (card.type === 'hard-basic') wrap.classList.add('ai-card--warn'); wrap.appendChild(el('p', 'ai-card__kicker', meta.kicker)); wrap.appendChild(el('p', 'ai-card__title', meta.title)); wrap.appendChild(el('p', 'ai-card__body', meta.body)); wrap.appendChild(intentActions(['human'])); return wrap; } if (card.type === 'contact') { wrap.appendChild(el('p', 'ai-card__kicker', zh ? '人工顾问' : 'Human advisor')); wrap.appendChild(el('p', 'ai-card__title', zh ? '专人跟,不排队工单' : 'One advisor — not a ticket queue')); const rows = el('div', 'ai-card__rows'); [ [zh ? '电话' : 'Phone', PHONE], [zh ? '微信' : 'WeChat', WECHAT], [zh ? '邮箱' : 'Email', EMAIL], [zh ? '地址' : 'Address', zh ? ADDRESS_ZH : ADDRESS_EN], ].forEach(([label, value]) => { const row = el('div', 'ai-card__row'); row.appendChild(el('span', '', label)); row.appendChild(el('strong', '', value)); rows.appendChild(row); }); wrap.appendChild(rows); wrap.appendChild( el( 'p', 'ai-card__body', zh ? '要询价请先注册登记,或直接打电话、加微信。' : 'Register first to quote, or call / WeChat us directly.' ) ); wrap.appendChild(contactActions()); return wrap; } if (card.type === 'company') { wrap.appendChild(el('p', 'ai-card__kicker', zh ? '关于我们' : 'About IDE')); wrap.appendChild(el('p', 'ai-card__title', zh ? '中小卖家的跨境物流顾问' : 'Cross-border logistics for SMB sellers')); wrap.appendChild( el( 'p', 'ai-card__body', zh ? '深圳宝安运营,备案主体可核(' + ICP + ')。主做美欧与东南亚小包。接小批量、带电、别人不接的 9610。一人从报价跟到售后。' : 'Operated from Bao’an, Shenzhen (ICP ' + ICP + '). US, EU and Southeast Asia parcels. We take low volume, battery, and 9610 others decline. One advisor from quote to after-sales.' ) ); appendCardItems(wrap, [ { title: zh ? '主线:美加墨 / 欧盟 / 澳新 / 东南亚' : 'Lanes: US-CA-MX / EU / AU-NZ / SEA', hint: zh ? '欧盟 27 国及英国,东南亚列马来、新、菲、泰、越、印尼。' : 'EU 27 and the UK, plus Malaysia, Singapore, Philippines, Thailand, Vietnam, Indonesia.' }, { title: zh ? '难单先给出货路径' : 'Tough orders get a ship path first', hint: zh ? '别人不接的带电、无票、超长,先保证出得去,再谈退税或免征。' : 'Battery, no invoice, oversize — we get it out, then pick refund or exemption.' }, { title: zh ? '微信 ' + WECHAT : 'WeChat ' + WECHAT, hint: zh ? '卖家常用这一个号找顾问,底栏也可复制。' : 'The number sellers use. Copy it from the bar below.' }, ]); wrap.appendChild(intentActions(['quote', 'human'])); return wrap; } if (card.type === 'pricing') { wrap.appendChild(el('p', 'ai-card__kicker', zh ? '价格' : 'Pricing')); wrap.appendChild(el('p', 'ai-card__metric', zh ? '按线路明码报价,一口价不砍' : 'Lane list rates — no haggling')); wrap.appendChild( el( 'p', 'ai-card__body', zh ? '中小卖家按计费重看价即可,不磨、不砍。税怎么走取决于有没有专票。询价请先注册登记。' : 'SMB sellers see the list rate by chargeable weight — no haggling. Tax savings still depend on invoices. Register first to quote.' ) ); appendCardItems(wrap, [ { title: zh ? '计费重 = 实重和体积重取大' : 'Chargeable weight = max(actual, volumetric)', hint: zh ? '体积重按渠道系数折算,超长另计。' : 'Volumetric uses the lane factor; oversize is extra.' }, { title: zh ? '明码一口价' : 'Published list rate', hint: zh ? '注册登记后,在客户系统里按线路看价,不用讨价还价。' : 'After you register, see list rates by lane in the customer system. No bargaining.' }, { title: zh ? '有票退税,无票免征或基础通关' : 'Refund / exemption / basic clearance', hint: zh ? '差在税,不在口头运费。带电、敏感货加收。' : 'The gap is tax, not a spoken freight. Battery and sensitive cargo surcharge.' }, ]); wrap.appendChild(registerActions()); return wrap; } if (card.type === 'warehouse') { wrap.appendChild(el('p', 'ai-card__kicker', zh ? '仓储' : 'Warehouse')); wrap.appendChild(el('p', 'ai-card__title', zh ? '仓储一件代发' : 'Warehouse dropship')); wrap.appendChild( el( 'p', 'ai-card__body', zh ? '入仓、拣货打包,订单来了就发出去。中小卖家不用自己养仓。开通请先注册登记,在客户系统里操作。' : 'Inbound, pick and pack, ship when the order lands. Register first — warehouse dropship runs in the customer system.' ) ); appendCardItems(wrap, [ { title: zh ? '入仓管理' : 'Inbound', hint: zh ? '收货上架,库存可查。' : 'Receive, put away, inventory you can see.' }, { title: zh ? '一件代发' : 'Dropship', hint: zh ? '订单过来就拣货打包发出。' : 'Pick, pack, and ship as orders come in.' }, { title: zh ? '对接出库' : 'Outbound', hint: zh ? '和专线、9610 出货打通。' : 'Tied to parcel lanes and 9610 exit.' }, ]); wrap.appendChild(registerActions()); return wrap; } return wrap; } function appendCardItems(wrap, items) { const list = el('div', 'ai-card__list'); items.forEach((item) => { const row = el('div', 'ai-card__item'); row.appendChild(el('strong', '', item.title)); if (item.hint) row.appendChild(el('span', '', item.hint)); list.appendChild(row); }); wrap.appendChild(list); } function intentActions(ids) { const zh = lang === 'zh'; const labels = { quote: zh ? '去注册询价 →' : 'Register to quote →', hard: zh ? '疑难单 / 9610 →' : 'Tough / 9610 →', human: zh ? '联系顾问 →' : 'Talk to advisor →', }; const actions = el('div', 'ai-card__actions'); ids.forEach((id, index) => { if (id === 'quote') { const link = el('a', index === 0 ? 'ai-card-opt is-primary' : 'ai-card-opt', labels.quote); link.href = registerUrl(); actions.appendChild(link); return; } const btn = el('button', index === 0 ? 'ai-card-opt is-primary' : 'ai-card-opt', labels[id] || id); btn.type = 'button'; btn.dataset.intent = id; actions.appendChild(btn); }); return actions; } function contactActions() { const actions = el('div', 'ai-card__actions'); const tel = el('a', 'ai-card-link is-primary', PHONE); tel.href = `tel:${PHONE}`; const mail = el('a', 'ai-card-link', EMAIL); mail.href = `mailto:${EMAIL}`; const wx = el('span', 'ai-card-link', (lang === 'zh' ? '微信 ' : 'WeChat ') + WECHAT); actions.appendChild(tel); actions.appendChild(wx); actions.appendChild(mail); return actions; } function renderShell() { const existing = document.getElementById('ai-advisor-shell'); if (existing) { shell = existing; return; } if (!isHomePage()) return; shell = document.createElement('div'); shell.id = 'ai-advisor-shell'; shell.innerHTML = ` `; document.body.appendChild(shell); welcomeEl = shell.querySelector('.ai-welcome'); messagesEl = shell.querySelector('.ai-messages'); followupEl = shell.querySelector('.ai-followup'); inputEl = shell.querySelector('.ai-composer-input'); sendBtn = shell.querySelector('.ai-composer-send'); intentGridEl = shell.querySelector('.ai-intent-grid'); mainScrollEl = shell.querySelector('.ai-app-main'); historyListEl = shell.querySelector('.ai-history-list'); bindMainScroll(); bindStageChrome(); shell.querySelector('.ai-new-chat-btn').addEventListener('click', () => startNewChat()); shell.querySelector('.ai-new-chat-ghost').addEventListener('click', () => startNewChat()); shell.querySelector('.ai-sidebar-toggle').addEventListener('click', () => toggleHistorySidebar()); shell.querySelector('.ai-history-backdrop').addEventListener('click', () => closeDrawers()); shell.querySelector('.ai-guide-panel__close').addEventListener('click', () => close9610Panel()); shell.querySelector('.ai-guide-panel__cards').addEventListener('click', (e) => { const card = e.target.closest('[data-guide-ask]'); if (!card || isBusy) return; close9610Panel(); sendUserMessage(card.dataset.guideAsk, { intentId: card.dataset.guideIntent || 'hard' }); }); shell.querySelector('.ai-trust-wechat').addEventListener('click', async () => { const btn = shell.querySelector('.ai-trust-wechat'); const label = btn.querySelector('.ai-trust-label'); try { await navigator.clipboard.writeText(WECHAT); if (label) label.textContent = lang === 'zh' ? '微信已复制' : 'WeChat copied'; } catch { if (label) label.textContent = WECHAT; } window.setTimeout(() => { if (label) label.textContent = t('trustWechat'); }, 1600); }); shell.querySelector('.ai-composer-form').addEventListener('submit', (e) => { e.preventDefault(); sendUserMessage(inputEl.value.trim()); }); inputEl.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendUserMessage(inputEl.value.trim()); } }); inputEl.addEventListener('input', () => { inputEl.style.height = 'auto'; inputEl.style.height = Math.min(inputEl.scrollHeight, 160) + 'px'; syncComposerSendState(); }); syncComposerSendState(); messagesEl.addEventListener('click', (e) => { const intentBtn = e.target.closest('[data-intent]'); if (intentBtn && !isBusy) { const intent = INTENTS.find((item) => item.id === intentBtn.dataset.intent); const label = intent ? intentCopy(intent).label : intentBtn.textContent; sendUserMessage(label, { intentId: intentBtn.dataset.intent }); return; } const btn = e.target.closest('[data-choice]'); if (!btn || isBusy) return; sendUserMessage(btn.dataset.label || btn.textContent, { kind: 'choice', flowId: btn.dataset.flow, choice: btn.dataset.choice, }); }); } function buildCoverageMarkers() { const markers = []; const pushMarker = (id, label) => { const coords = MAP_COORDS[id]; if (!coords) return; markers.push({ id, coords, label, regionId: mapRegionForId(id) }); }; COVER_REGIONS.forEach((region) => { const label = lang === 'zh' ? region.label.zh : region.label.en; pushMarker(region.id, label); }); COVER_EU.forEach((item) => { pushMarker(item.mapId, coverName(item)); }); COVER_SEA.forEach((item) => { pushMarker(item.mapId, coverName(item)); }); return markers; } function setCoverageOpen(open) { coverageOpen = !!open; const panel = shell?.querySelector('.ai-cover-panel'); const trigger = shell?.querySelector('.ai-welcome-stats__trigger'); const block = shell?.querySelector('.ai-welcome-stats-block'); if (panel) { if (coverageOpen) panel.removeAttribute('hidden'); else panel.setAttribute('hidden', ''); } if (trigger) trigger.setAttribute('aria-expanded', coverageOpen ? 'true' : 'false'); if (block) block.classList.toggle('is-open', coverageOpen); if (!globeController) return; globeController.setActive(coverageOpen); if (coverageOpen) { globeController.setMarkers(buildCoverageMarkers()); globeController.resetView(); } else { globeController.setMarkers([]); globeController.resetView(); } window.requestAnimationFrame(syncMainOverflow); } function focusCoverageOnMap(mapId) { if (!mapId) return; if (!coverageOpen) setCoverageOpen(true); if (!globeController) return; globeController.setMarkers(buildCoverageMarkers()); globeController.focusMarker(mapId); shell?.querySelectorAll('.ai-cover-chip.is-map-active').forEach((node) => { node.classList.remove('is-map-active'); }); const chip = shell?.querySelector('.ai-cover-chip[data-map-id="' + mapId + '"]'); if (chip) chip.classList.add('is-map-active'); } function renderWelcomeStats() { if (!shell) return; const listEl = shell.querySelector('.ai-welcome-stats'); if (!listEl) return; listEl.setAttribute('aria-label', t('statsAria')); listEl.innerHTML = HERO_STATS.map((item) => { const value = lang === 'zh' ? item.value.zh : item.value.en; const label = lang === 'zh' ? item.label.zh : item.label.en; if (item.interactive) { return ( '
  • ' + '
  • ' ); } return ( '
  • ' + '' + value + '' + '' + label + '
  • ' ); }).join(''); const trigger = listEl.querySelector('.ai-welcome-stats__trigger'); if (trigger && !trigger.dataset.bound) { trigger.dataset.bound = '1'; trigger.addEventListener('click', () => { setCoverageOpen(!coverageOpen); }); } } function applyWelcomeTitle() { if (!shell) return; const titleEl = shell.querySelector('.ai-welcome-title'); if (!titleEl) return; const useShort = window.matchMedia('(max-width: 640px)').matches; titleEl.textContent = useShort ? t('welcomeTitleShort') : t('welcomeTitle'); } function renderStageGlobe() { if (!shell || !window.IDEStageGlobe) return; const el = shell.querySelector('.ai-stage-maps'); const cnHost = shell.querySelector('.ai-welcome-plane'); globeController = window.IDEStageGlobe.render(el, { lang, cnHost }) || el?.__globeApi || null; if (coverageOpen && globeController) { globeController.setActive(true); globeController.setMarkers(buildCoverageMarkers()); } } function bindStageChrome() { if (!shell || shell.dataset.chromeBound === '1') return; shell.dataset.chromeBound = '1'; } function renderIntentGrid() { if (!intentGridEl) return; intentGridEl.innerHTML = ''; INTENTS.forEach((intent) => { const copy = intentCopy(intent); const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'ai-intent-door' + (intent.id === 'quote' ? ' is-primary' : ''); btn.dataset.intent = intent.id; btn.innerHTML = ` ${iconMarkup(intent.icon)} `; btn.querySelector('.ai-intent-door__title').textContent = copy.title; btn.querySelector('.ai-intent-door__hint').textContent = copy.hint; btn.addEventListener('click', () => onIntentClick(intent)); intentGridEl.appendChild(btn); }); renderCoverage(); } function coverName(item) { return lang === 'zh' ? item.zh : item.en; } function coverChip(label, dest, extraClass, mapId) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = extraClass ? 'ai-cover-chip ' + extraClass : 'ai-cover-chip'; btn.textContent = label; if (dest) btn.dataset.dest = dest; if (mapId) btn.dataset.mapId = mapId; return btn; } function renderCoverage() { if (!welcomeEl) return; const chipsEl = welcomeEl.querySelector('.ai-cover__chips'); const euEl = welcomeEl.querySelector('.ai-cover__eu'); const seaChips = welcomeEl.querySelector('.ai-cover__sea-chips'); if (!chipsEl || !euEl || !seaChips) return; chipsEl.innerHTML = ''; euEl.innerHTML = ''; seaChips.innerHTML = ''; COVER_REGIONS.forEach((region) => { const label = lang === 'zh' ? region.label.zh : region.label.en; const dest = lang === 'zh' ? region.dest.zh : region.dest.en; if (region.expand) { const btn = coverChip(label, '', 'is-eu', region.id); btn.setAttribute('aria-expanded', euEl.hidden ? 'false' : 'true'); btn.addEventListener('click', () => { const open = euEl.hasAttribute('hidden'); if (open) euEl.removeAttribute('hidden'); else euEl.setAttribute('hidden', ''); btn.setAttribute('aria-expanded', open ? 'true' : 'false'); focusCoverageOnMap(region.id); window.requestAnimationFrame(syncMainOverflow); }); chipsEl.appendChild(btn); return; } const btn = coverChip(label, dest, '', region.id); btn.addEventListener('click', () => { focusCoverageOnMap(region.id); startQuoteTo(dest); }); chipsEl.appendChild(btn); }); COVER_EU.forEach((item) => { const name = coverName(item); const btn = coverChip(name, name, '', item.mapId); btn.addEventListener('click', () => { focusCoverageOnMap(item.mapId); startQuoteTo(name); }); euEl.appendChild(btn); }); COVER_SEA.forEach((item) => { const name = coverName(item); const btn = coverChip(name, name, '', item.mapId); btn.addEventListener('click', () => { focusCoverageOnMap(item.mapId); startQuoteTo(name); }); seaChips.appendChild(btn); }); } function startQuoteTo(dest) { if (isBusy || !dest) return; const label = lang === 'zh' ? '发到' + dest : 'Ship to ' + dest; sendUserMessage(label, { intentId: 'quote', dest }); } function renderFollowupChips() { if (!followupEl) return; followupEl.innerHTML = ''; INTENTS.forEach((intent) => { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'ai-followup-chip'; btn.textContent = intentCopy(intent).title; btn.addEventListener('click', () => onIntentClick(intent)); followupEl.appendChild(btn); }); } function placeComposer(chatting) { if (!shell) return; const wrap = shell.querySelector('.ai-composer-wrap'); const welcomeSlot = shell.querySelector('.ai-composer-slot--welcome'); const chatSlot = shell.querySelector('.ai-composer-slot--chat'); if (!wrap || !welcomeSlot || !chatSlot) return; const target = chatting ? chatSlot : welcomeSlot; if (wrap.parentElement !== target) { target.appendChild(wrap); } wrap.classList.toggle('is-welcome', !chatting); wrap.classList.toggle('is-docked', chatting); const leadEl = shell.querySelector('.ai-welcome-compose__lead'); if (leadEl) leadEl.hidden = !!chatting; } function updateViewMode() { const chatting = hasUserMessage || sessionMessages.some((m) => m.role === 'user'); if (welcomeEl) welcomeEl.classList.toggle('is-hidden', chatting); if (messagesEl) messagesEl.classList.toggle('is-active', chatting); if (followupEl) followupEl.classList.toggle('is-visible', chatting); placeComposer(chatting); window.requestAnimationFrame(syncMainOverflow); } function renderHistorySidebar() { if (!historyListEl) return; const sorted = [...threads].sort((a, b) => b.updatedAt - a.updatedAt); historyListEl.innerHTML = ''; if (sorted.length === 0) { historyListEl.appendChild(el('p', 'ai-history-empty', t('emptyHistory'))); return; } sorted.forEach((thread) => { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'ai-history-item'; btn.setAttribute('role', 'listitem'); if (thread.id === activeThreadId) btn.classList.add('is-active'); btn.innerHTML = ` `; btn.querySelector('.ai-history-item__title').textContent = thread.title || t('newChat'); btn.querySelector('.ai-history-item__time').textContent = formatThreadTime(thread.updatedAt); btn.addEventListener('click', () => switchThread(thread.id)); historyListEl.appendChild(btn); }); } function switchThread(threadId) { if (isBusy || threadId === activeThreadId) { closeHistorySidebar(); return; } syncActiveThreadMessages(); activeThreadId = threadId; applyThreadMessages(getActiveThread()); saveThreads(); renderHistorySidebar(); renderMessages(); updateViewMode(); closeHistorySidebar(); inputEl.focus(); } function startNewChat() { if (isBusy) return; syncActiveThreadMessages(); const current = getActiveThread(); if (current && current.messages.length === 0) { closeHistorySidebar(); inputEl.focus(); return; } createThread(true); renderHistorySidebar(); renderMessages(); updateViewMode(); closeHistorySidebar(); setNavActive('home'); inputEl.focus(); } function toggleHistorySidebar() { close9610Panel(); shell?.classList.toggle('ai-history-open'); } function closeHistorySidebar() { shell?.classList.remove('ai-history-open'); } function open9610Panel() { closeHistorySidebar(); closeMobileNav(); shell?.classList.add('ai-guide-open'); setNavActive('services'); if (location.hash !== '#services') { history.replaceState(null, '', '#services'); } } function close9610Panel() { shell?.classList.remove('ai-guide-open'); if (location.hash === '#services' || location.hash === '#9610') { history.replaceState(null, '', location.pathname + location.search); setNavActive('home'); } } function closeDrawers() { closeHistorySidebar(); close9610Panel(); } function render9610Panel() { if (!shell) return; const titleEl = shell.querySelector('.ai-guide-panel__title'); const subEl = shell.querySelector('.ai-guide-panel__sub'); const cardsEl = shell.querySelector('.ai-guide-panel__cards'); if (!titleEl || !subEl || !cardsEl) return; const kickerEl = shell.querySelector('.ai-guide-panel__kicker'); titleEl.textContent = t('guide9610Title'); if (kickerEl) kickerEl.textContent = t('guide9610Kicker'); subEl.textContent = t('guide9610Sub'); cardsEl.innerHTML = GUIDE_SERVICES.map((item) => { const copy = lang === 'zh' ? item.zh : item.en; const tag = lang === 'zh' ? item.tag.zh : item.tag.en; const ask = lang === 'zh' ? item.ask.zh : item.ask.en; return ( '' ); }).join(''); } function renderWelcomeSub() { const subEl = shell?.querySelector('.ai-welcome-sub'); if (!subEl) return; subEl.innerHTML = t('welcomeSubLead') + '' + t('welcomeSubAccent') + '' + t('welcomeSubTail'); } function renderStaticCopy() { if (!shell) return; shell.querySelector('.ai-history-sidebar__title').textContent = t('historyTitle'); shell.querySelector('.ai-new-chat-btn__label').textContent = t('newChat'); shell.querySelector('.ai-new-chat-ghost__label').textContent = t('newChat'); applyWelcomeTitle(); renderWelcomeSub(); const heroCta = shell.querySelector('.ai-welcome-cta'); if (heroCta) { heroCta.textContent = t('heroCta'); heroCta.href = loginPortalUrl(); } renderWelcomeStats(); const mapHint = shell.querySelector('.ai-cover-panel__hint'); if (mapHint) mapHint.textContent = t('mapZoomHint'); renderCoverage(); if (coverageOpen) { const panel = shell.querySelector('.ai-cover-panel'); const block = shell.querySelector('.ai-welcome-stats-block'); if (panel) panel.removeAttribute('hidden'); if (block) block.classList.add('is-open'); if (globeController) { globeController.setActive(true); globeController.setMarkers(buildCoverageMarkers()); } } setTrustLabel('.ai-trust-phone', t('trustPhone')); setTrustLabel('.ai-trust-address', t('trustAddress')); setTrustLabel('.ai-trust-wechat', t('trustWechat')); setTrustLabel('.ai-trust-email', t('trustEmail')); setTrustLabel('.ai-trust-icp', t('trustIcp')); shell.querySelector('.ai-trust-bar__human').textContent = t('human') + ' →'; const composeLead = shell.querySelector('.ai-welcome-compose__lead'); if (composeLead) composeLead.textContent = t('composeLead'); inputEl.placeholder = t('placeholder'); if (sendBtn) sendBtn.setAttribute('aria-label', t('sendLabel')); render9610Panel(); renderIntentGrid(); renderFollowupChips(); renderHistorySidebar(); renderMessages(); updateViewMode(); renderStageGlobe(); } function renderMessages() { if (!messagesEl) return; messagesEl.innerHTML = ''; sessionMessages.forEach((msg) => appendMessageRow(msg.role, msg.text, msg.card, false)); scrollToEnd(); } function appendMessageRow(role, text, card, save) { const row = el('div', `ai-msg-row ai-msg-row--${role}`); const avatar = el('div', 'ai-msg-avatar'); avatar.innerHTML = role === 'assistant' ? '' : ''; if (role === 'user') avatar.textContent = t('you').charAt(0).toUpperCase(); const bubble = el('div', 'ai-msg-bubble'); if (text) bubble.appendChild(el('p', 'ai-msg-text', text)); if (card) { const cardNode = renderCard(card); if (cardNode) bubble.appendChild(cardNode); } row.appendChild(avatar); row.appendChild(bubble); messagesEl.appendChild(row); if (save) { sessionMessages.push({ role, text: text || '', card: card || null }); if (role === 'user') hasUserMessage = true; saveThreads(); updateViewMode(); renderHistorySidebar(); } scrollToEnd(); return row; } function showTyping() { const row = el('div', 'ai-msg-row ai-msg-row--assistant ai-msg-row--typing'); row.innerHTML = `
    `; messagesEl.appendChild(row); scrollToEnd(); return row; } function scrollToEnd() { if (!mainScrollEl) return; mainScrollEl.scrollTop = mainScrollEl.scrollHeight; syncMainOverflow(); } function syncMainOverflow() { const el = mainScrollEl; if (!el) return; const max = el.scrollHeight - el.clientHeight; const canScroll = max > 12; el.classList.toggle('is-scrolled', canScroll && el.scrollTop > 12); el.classList.toggle('is-overflowing', canScroll && el.scrollTop < max - 12); } function bindMainScroll() { if (!mainScrollEl || mainScrollEl.dataset.scrollBound === '1') return; mainScrollEl.dataset.scrollBound = '1'; mainScrollEl.addEventListener('scroll', syncMainOverflow, { passive: true }); window.addEventListener('resize', syncMainOverflow); window.requestAnimationFrame(syncMainOverflow); } function looksLikeTracking(text) { if (typeof text !== 'string') return false; return /^[A-Za-z0-9][A-Za-z0-9\-_]{7,}$/.test(text.replace(/\s+/g, '')); } function detectIntent(text) { const lower = text.toLowerCase(); if (/track|tracking|物流|单号|追踪/.test(lower) || looksLikeTracking(text)) return 'track'; if (/9610|退税|专票|免税|疑难|小批量|不接/.test(lower)) return 'hard'; if (/一件代发|代发|dropship|fulfillment/.test(lower)) return 'warehouse'; if (/仓储|清关|空运|海运|服务|warehous|customs|services/.test(lower)) return 'guide'; if (/定价|收费|价目|pricing/.test(lower)) return 'pricing'; if (/price|cost|quote|报价|价格|多少钱/.test(lower)) return 'quote'; if (/human|人工|顾问|电话|联系我们|联系/.test(lower)) return 'human'; if (/who are you|about ide|你们是谁|关于我们|关于|公司|靠谱/.test(lower)) return 'company'; return null; } function replyForIntent(intentId, payload) { const zh = lang === 'zh'; if (intentId === 'human') { flow = null; return { text: zh ? '直接打给我们或加微信。询价请先注册登记。' : 'Call or WeChat us. Register first if you need a quote.', card: { type: 'contact' }, }; } if (intentId === 'company') { flow = null; return { text: zh ? '先看这张公司卡。要询价请先注册登记。' : 'Here is who we are. Register first when you are ready to quote.', card: { type: 'company' }, }; } if (intentId === 'guide') { flow = null; open9610Panel(); return { text: zh ? '服务在右侧,点一张卡继续。' : 'Services are on the right — pick a card to continue.', card: null, }; } if (intentId === 'pricing') { flow = null; return { text: zh ? '计费重取实重和体积重较大值;有票走退税,无票走免征。询价请先注册登记。' : 'Chargeable weight is the greater of actual and volumetric. Refund if you have invoices, exemption if not. Register first to quote.', card: { type: 'pricing' }, }; } if (intentId === 'quote') { flow = null; const dest = payload && payload.dest ? String(payload.dest) : ''; return { text: dest ? zh ? '发到' + dest + '可以走。官网不提供询价,请先注册登记。' : 'We ship to ' + dest + '. Quotes are not on this site — please register first.' : zh ? '官网不提供询价。完成注册登记后,即可在客户系统里看明码价。' : 'This site does not quote. Register first, then see list rates in the customer system.', card: { type: 'quote', dest }, }; } if (intentId === 'warehouse') { flow = null; return { text: zh ? '仓储一件代发要先注册登记,入仓和代发在客户系统里操作。' : 'Warehouse dropship needs an account. Inbound and outbound run in the customer system.', card: { type: 'warehouse' }, }; } if (intentId === 'track') { flow = null; const number = payload && looksLikeTracking(payload) ? String(payload).replace(/\s+/g, '') : ''; if (number) { try { sessionStorage.setItem('ide-pending-track', number); } catch { /* ignore */ } } return { text: number ? zh ? '单号已记下。请登录客户系统看本票真实节点。' : 'Number saved. Sign in to see live scans for this shipment.' : zh ? '有单号请登录看真实轨迹。询价需先注册登记。' : 'Sign in if you have a tracking number. Quotes require registration.', card: { type: 'track', number }, }; } if (intentId === 'hard') { flow = { id: 'hard', step: 0, data: {} }; return { text: zh ? '别人不接的单我们接。如果走 9610,先确认一张票,再给你对应方案。' : 'We take orders others decline. For 9610, one tax question first — then the matching scheme.', card: { type: 'choice', flowId: 'hard', kicker: '9610', title: zh ? '货物能取得增值税专用发票(进项票)吗?' : 'Can you obtain special VAT invoices (input invoices)?', options: [ { id: 'vat-yes', label: zh ? '能取得专票' : 'Yes, we have invoices', primary: true }, { id: 'vat-no', label: zh ? '无法取得专票' : 'No special invoices' }, ], }, }; } return { text: t('fallback'), card: { type: 'contact' } }; } function continueFlow(userText, extra) { const zh = lang === 'zh'; if (flow.id === 'quote') { flow = null; return replyForIntent('quote'); } if (flow.id === 'track') { return replyForIntent('track', userText); } if (flow.id === 'hard') { const choice = extra && extra.kind === 'choice' ? extra.choice : null; const normalized = (choice || userText || '').toLowerCase(); if (flow.step === 0) { const yes = choice === 'vat-yes' || /能|有票|yes|invoice/.test(normalized); if (yes) { flow = null; return { text: zh ? '有专票,走退税最划算。' : 'With special invoices, refund is the strongest path.', card: { type: 'hard-refund' }, }; } flow.step = 1; return { text: zh ? '没有专票的话,还想不想用出口免税?' : 'Without special invoices — do you still want export tax exemption?', card: { type: 'choice', flowId: 'hard', title: zh ? '是否希望享受出口免税政策?' : 'Do you want the export tax exemption?', options: [ { id: 'exempt-yes', label: zh ? '希望免税' : 'Yes, exemption', primary: true }, { id: 'exempt-no', label: zh ? '仅需出货' : 'Just need to ship' }, ], }, }; } const wantExempt = choice === 'exempt-yes' || /免税|希望|yes|exempt/.test(normalized); flow = null; if (wantExempt) { return { text: zh ? '无票免征,合规且能免出口环节税。' : 'Exemption without invoice — compliant, and export tax is waived.', card: { type: 'hard-exempt' }, }; } return { text: zh ? '可以先出货,但税务风险要心里有数。' : 'We can ship first — keep the tax risk in view.', card: { type: 'hard-basic' }, }; } flow = null; return { text: t('fallback'), card: { type: 'contact' } }; } function buildReply(userText, extra) { if (extra && extra.intentId) { return replyForIntent(extra.intentId, extra); } if (flow) { return continueFlow(userText, extra); } const guessed = detectIntent(userText); if (guessed === 'track' && looksLikeTracking(userText)) { return replyForIntent('track', userText); } if (guessed) return replyForIntent(guessed, userText); return { text: t('fallback'), card: { type: 'contact' } }; } function syncComposerSendState() { if (!sendBtn || !inputEl) return; const hasText = Boolean(inputEl.value.trim()); sendBtn.classList.toggle('is-empty', !hasText && !isBusy); sendBtn.disabled = isBusy; } function setBusy(busy) { isBusy = busy; if (inputEl) inputEl.disabled = busy; shell?.querySelectorAll('.ai-intent-door, .ai-followup-chip, .ai-card-opt, .ai-cover-chip').forEach((node) => { node.disabled = busy; }); syncComposerSendState(); } function onIntentClick(intent) { if (isBusy) return; sendUserMessage(intentCopy(intent).label, { intentId: intent.id }); } function sendUserMessage(text, extra) { if (!text || isBusy || !shell) return; appendMessageRow('user', text, null, true); if (inputEl) { inputEl.value = ''; inputEl.style.height = 'auto'; syncComposerSendState(); } setBusy(true); const typing = showTyping(); const reply = buildReply(text, extra || null); window.setTimeout(() => { typing.remove(); appendMessageRow('assistant', reply.text, reply.card, true); setBusy(false); inputEl.focus(); }, 520 + Math.random() * 280); } function markLangNavItem() { document.getElementById('language-toggle-btn')?.closest('li')?.classList.add('site-nav-tools'); } const NAV_TOPICS = { about: { intentId: 'company', hash: 'about' }, pricing: { intentId: 'pricing', hash: 'pricing' }, contact: { intentId: 'human', hash: 'contact' }, }; function closeMobileNav() { document.body.classList.remove('mobile-nav-active'); document.querySelectorAll('.mobile-nav-show').forEach((node) => node.classList.remove('d-none')); document.querySelectorAll('.mobile-nav-hide').forEach((node) => node.classList.add('d-none')); } function setNavActive(topic) { document.querySelectorAll('#navbar ul li > a').forEach((link) => { if (link.id === 'language-toggle-btn') return; const href = link.getAttribute('href') || ''; if (!topic || topic === 'home') { link.classList.toggle('active', href === '/' || href === 'index.html'); return; } link.classList.toggle('active', href === `#${topic}` || href.endsWith(`#${topic}`)); }); } function openNavTopic(topic) { const spec = NAV_TOPICS[topic]; if (!spec || isBusy || !shell) return; const intent = INTENTS.find((item) => item.id === spec.intentId); const labels = { company: lang === 'zh' ? '关于我们' : 'About IDE', pricing: lang === 'zh' ? '价格怎么算?' : 'How does pricing work?', human: lang === 'zh' ? '联系人工顾问' : 'Talk to a human advisor', }; setNavActive(topic); if (location.hash !== `#${spec.hash}`) { history.replaceState(null, '', `#${spec.hash}`); } sendUserMessage(labels[spec.intentId], { intentId: spec.intentId }); void intent; } function bindHomeReset() { const reset = (e) => { if (!isHomePage()) return; e.preventDefault(); startNewChat(); closeDrawers(); setNavActive('home'); if (location.hash) history.replaceState(null, '', location.pathname + location.search); }; document.querySelector('#header .logo')?.addEventListener('click', reset); document.querySelector('#navbar a[href="/"], #navbar a[href="index.html"]')?.addEventListener('click', reset); document.querySelectorAll('#navbar a[href^="#"], #navbar a[href*="#"]').forEach((link) => { const href = link.getAttribute('href') || ''; const topic = href.split('#')[1]; if (!NAV_TOPICS[topic]) return; link.addEventListener('click', (e) => { if (!isHomePage()) return; e.preventDefault(); closeMobileNav(); closeDrawers(); openNavTopic(topic); }); }); document.querySelectorAll('#navbar a[href="#services"], #navbar a[href="#9610"], #navbar a[href="/9610.html"]').forEach((link) => { link.addEventListener('click', (e) => { e.preventDefault(); open9610Panel(); }); }); shell?.querySelector('.ai-trust-bar__human')?.addEventListener('click', (e) => { e.preventDefault(); openNavTopic('contact'); }); } function init() { if (!isHomePage()) return; document.body.classList.add('command-home'); loadThreads(); renderShell(); markLangNavItem(); bindHomeReset(); renderStaticCopy(); syncHeaderOffset(); window.addEventListener('resize', () => { syncHeaderOffset(); applyWelcomeTitle(); }); window.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeDrawers(); }); const bootTopic = (location.hash || '').replace('#', ''); if (bootTopic === 'services' || bootTopic === '9610') { open9610Panel(); } else if (NAV_TOPICS[bootTopic]) { openNavTopic(bootTopic); } } window.IDEAdvisor = { setLanguage(nextLang) { lang = nextLang === 'zh' ? 'zh' : 'en'; renderStaticCopy(); }, open: () => { if (!isHomePage()) window.location.href = '/'; }, close: () => closeDrawers(), }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();