import type { DemoCard, DemoLocale, DemoMode, DemoTabId, MockAgentResult, ProgressStep, ProgressStepStatus, TraceItem, } from './types'; interface WorkflowTemplate { label: string; detail: string; doneDetail: string; kind: TraceItem['kind']; durationMs: number; } interface RunMockAgentParams { tab: DemoTabId; input: string; locale: DemoLocale; mode: DemoMode; onWorkflowInit: (steps: ProgressStep[]) => void; onStepStatus: (stepId: string, status: ProgressStepStatus, detail?: string) => void; onTrace: (item: TraceItem) => void; } const WORKFLOWS: Record> = { en: { chat: [ { label: 'Understanding user intent', detail: 'Parsing the latest message and session context.', doneDetail: 'Intent and preferred action recognized.', kind: 'planner', durationMs: 600, }, { label: 'Planning tool route', detail: 'Selecting the best backend function and argument set.', doneDetail: 'Tool route and arguments prepared.', kind: 'planner', durationMs: 700, }, { label: 'Executing tool call', detail: 'Running product/store/statistics action.', doneDetail: 'Tool returned structured data.', kind: 'tool', durationMs: 900, }, { label: 'Rendering response', detail: 'Composing concise answer + rich cards.', doneDetail: 'Final message and cards generated.', kind: 'renderer', durationMs: 500, }, ], voice: [ { label: 'Normalizing transcript', detail: 'Detecting language and cleaning transcript text.', doneDetail: 'Transcript normalized.', kind: 'validator', durationMs: 600, }, { label: 'Choosing assistant tool', detail: 'Mapping spoken intent to product/store workflows.', doneDetail: 'Voice tool selected.', kind: 'planner', durationMs: 650, }, { label: 'Running voice tool', detail: 'Calling backend action and collecting UI payload.', doneDetail: 'Voice tool output ready.', kind: 'tool', durationMs: 900, }, { label: 'Synthesizing summary', detail: 'Producing short speech-safe answer.', doneDetail: 'Final voice summary composed.', kind: 'renderer', durationMs: 450, }, ], marketing: [ { label: 'Reading campaign brief', detail: 'Extracting tone, audience, and product context.', doneDetail: 'Campaign brief parsed.', kind: 'planner', durationMs: 700, }, { label: 'Generating marketing assets', detail: 'Streaming copy and image enhancement instructions.', doneDetail: 'Marketing draft generated.', kind: 'tool', durationMs: 1200, }, { label: 'Quality checks', detail: 'Verifying CTA clarity and consistency.', doneDetail: 'Draft passed validation.', kind: 'validator', durationMs: 550, }, { label: 'Preparing showcase output', detail: 'Building final campaign cards and metadata.', doneDetail: 'Marketing output ready.', kind: 'renderer', durationMs: 450, }, ], invoice: [ { label: 'Pre-processing invoice image', detail: 'Normalizing orientation and text contrast.', doneDetail: 'Image prepared for extraction.', kind: 'validator', durationMs: 700, }, { label: 'Extracting structured fields', detail: 'Detecting invoice number, totals, and dates.', doneDetail: 'Invoice fields extracted.', kind: 'tool', durationMs: 1100, }, { label: 'Verifying consistency', detail: 'Checking totals, currency, and item count.', doneDetail: 'Extraction validated.', kind: 'validator', durationMs: 650, }, { label: 'Formatting result view', detail: 'Generating clean JSON + summary cards.', doneDetail: 'Invoice output rendered.', kind: 'renderer', durationMs: 500, }, ], marketplace: [ { label: 'Interpreting filters', detail: 'Parsing category, location, and price limits.', doneDetail: 'Search filters resolved.', kind: 'planner', durationMs: 650, }, { label: 'Querying marketplace data', detail: 'Searching products and buyer/store entities.', doneDetail: 'Results retrieved.', kind: 'tool', durationMs: 950, }, { label: 'Ranking matches', detail: 'Sorting by relevance and availability.', doneDetail: 'Top matches selected.', kind: 'validator', durationMs: 600, }, { label: 'Composing answer', detail: 'Preparing concise brief with cards.', doneDetail: 'Marketplace summary ready.', kind: 'renderer', durationMs: 450, }, ], }, 'zh-TW': { chat: [ { label: '理解使用者意圖', detail: '解析最新訊息與對話上下文。', doneDetail: '意圖與偏好操作已辨識。', kind: 'planner', durationMs: 600, }, { label: '規劃工具路由', detail: '選擇最佳後端函式與參數組合。', doneDetail: '工具路徑與參數已準備。', kind: 'planner', durationMs: 700, }, { label: '執行工具呼叫', detail: '執行商品、店家或統計操作。', doneDetail: '工具已回傳結構化資料。', kind: 'tool', durationMs: 900, }, { label: '渲染回覆內容', detail: '整理精簡文字與卡片輸出。', doneDetail: '最終訊息與卡片已生成。', kind: 'renderer', durationMs: 500, }, ], voice: [ { label: '標準化語音轉寫', detail: '偵測語系並清理語音文字。', doneDetail: '語音文字已標準化。', kind: 'validator', durationMs: 600, }, { label: '選擇語音工具', detail: '將口語意圖映射到商品/市集流程。', doneDetail: '語音工具已選定。', kind: 'planner', durationMs: 650, }, { label: '執行語音工具', detail: '呼叫動作並收集 UI 載荷。', doneDetail: '語音工具輸出已就緒。', kind: 'tool', durationMs: 900, }, { label: '合成語音摘要', detail: '產生簡短可口語播報的回覆。', doneDetail: '最終語音摘要已完成。', kind: 'renderer', durationMs: 450, }, ], marketing: [ { label: '讀取行銷需求', detail: '擷取語氣、受眾與產品脈絡。', doneDetail: '活動需求解析完成。', kind: 'planner', durationMs: 700, }, { label: '生成行銷素材', detail: '串流輸出文案與圖像增強指令。', doneDetail: '行銷草案已生成。', kind: 'tool', durationMs: 1200, }, { label: '品質檢查', detail: '驗證 CTA 清晰度與內容一致性。', doneDetail: '草案通過檢查。', kind: 'validator', durationMs: 550, }, { label: '封裝展示輸出', detail: '建立最終活動卡片與執行資訊。', doneDetail: '行銷輸出已就緒。', kind: 'renderer', durationMs: 450, }, ], invoice: [ { label: '發票影像前處理', detail: '標準化方向與文字對比。', doneDetail: '影像已準備可供擷取。', kind: 'validator', durationMs: 700, }, { label: '擷取結構化欄位', detail: '辨識發票號碼、金額與日期。', doneDetail: '發票欄位擷取完成。', kind: 'tool', durationMs: 1100, }, { label: '一致性驗證', detail: '檢查總額、幣別與品項數。', doneDetail: '擷取結果驗證完成。', kind: 'validator', durationMs: 650, }, { label: '格式化結果檢視', detail: '生成乾淨 JSON 與摘要卡片。', doneDetail: '發票輸出已渲染。', kind: 'renderer', durationMs: 500, }, ], marketplace: [ { label: '解析篩選條件', detail: '整理品類、地區與價格限制。', doneDetail: '搜尋條件已完成解析。', kind: 'planner', durationMs: 650, }, { label: '查詢市集資料', detail: '搜尋商品與買家/店家實體。', doneDetail: '市集結果已取回。', kind: 'tool', durationMs: 950, }, { label: '匹配結果排序', detail: '依相關性與可用性排序。', doneDetail: '已選出最佳匹配項目。', kind: 'validator', durationMs: 600, }, { label: '組裝最終回覆', detail: '整理精簡摘要與結果卡片。', doneDetail: '市集摘要已就緒。', kind: 'renderer', durationMs: 450, }, ], }, }; const QUICK_PROMPTS: Record> = { en: { chat: [ 'Add eggs for NT$120 / box, stock 30', 'Show my latest products', 'Create a store in Hsinchu for organic buyers', ], voice: [ 'Find products below 200', 'Update egg stock to 48 boxes', 'Open analytics for this week', ], marketing: [ 'Generate a fresh campaign for free-range eggs', 'Create a weekend CTA with short hashtags', 'Polish this product photo for e-commerce', ], invoice: [ 'Extract invoice fields and confidence', 'Show invoice summary with line items', 'Validate invoice totals and due date', ], marketplace: [ 'Search fruits in Taoyuan below 250', 'Find nearby stores looking for eggs', 'Show marketplace demand snapshot', ], }, 'zh-TW': { chat: ['新增雞蛋 120/盒 庫存30', '列出我的最新產品', '新增新竹有機買家店家'], voice: ['找 200 以下產品', '把雞蛋庫存改成 48 箱', '開啟本週分析'], marketing: ['幫我做放牧雞蛋促銷活動', '產生週末 CTA 與 Hashtag', '優化這張產品圖成電商風格'], invoice: ['抽取發票欄位與信心值', '顯示發票摘要與明細', '驗證總額與到期日'], marketplace: ['搜尋桃園 250 以下水果', '找附近想買雞蛋的店家', '顯示市集供需快照'], }, }; let sequence = 0; function nextId(prefix: string): string { sequence += 1; return `${prefix}-${Date.now().toString(36)}-${sequence.toString(36)}`; } function nowLabel(): string { return new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); } function sleep(ms: number): Promise { return new Promise((resolve) => { setTimeout(resolve, ms); }); } function extractTopic(input: string): string { const trimmed = input.trim(); if (!trimmed) return 'seasonal farm products'; const chunk = trimmed.split(/\s+/).slice(0, 5).join(' '); return chunk || 'seasonal farm products'; } function buildToolPayload(tab: DemoTabId, input: string, mode: DemoMode): Record { const topic = extractTopic(input); const base = { request: topic, mode, latencyMs: Math.floor(620 + Math.random() * 330), tokens: Math.floor(280 + Math.random() * 150), }; switch (tab) { case 'chat': return { ...base, toolName: 'product_search_public', resultCount: 4, nextAction: 'refine_filter' }; case 'voice': return { ...base, toolName: 'product_update', updatedField: 'stock', followUp: 'confirm_changes' }; case 'marketing': return { ...base, toolName: 'marketing_generate_stream', assets: ['headline', 'caption', 'cta', 'image'] }; case 'invoice': return { ...base, toolName: 'invoice_scan', confidence: 0.93, fieldsExtracted: 11 }; case 'marketplace': return { ...base, toolName: 'marketplace_get_stats', productsMatched: 6, storesMatched: 3 }; default: return base; } } function buildCards(tab: DemoTabId, input: string, locale: DemoLocale): DemoCard[] { const topic = extractTopic(input); if (tab === 'marketing') { return [ { title: locale === 'zh-TW' ? '活動主題' : 'Campaign Theme', subtitle: topic, tags: locale === 'zh-TW' ? ['新品曝光', '週末檔期', '社群貼文'] : ['launch', 'weekend', 'social'], actions: locale === 'zh-TW' ? ['複製文案', '開啟素材面板'] : ['Copy copy', 'Open assets panel'], }, { title: locale === 'zh-TW' ? '成效預估' : 'Projected Impact', metrics: [ { label: locale === 'zh-TW' ? '互動率' : 'Engagement', value: '+24%' }, { label: locale === 'zh-TW' ? '點擊率' : 'CTR', value: '+11%' }, { label: locale === 'zh-TW' ? '轉換率' : 'Conversion', value: '+7%' }, ], }, ]; } if (tab === 'invoice') { return [ { title: locale === 'zh-TW' ? '發票摘要' : 'Invoice Summary', subtitle: locale === 'zh-TW' ? '已抽取主要欄位' : 'Core fields extracted', metrics: [ { label: locale === 'zh-TW' ? '總額' : 'Total', value: 'NT$ 12,860' }, { label: locale === 'zh-TW' ? '稅額' : 'Tax', value: 'NT$ 643' }, { label: locale === 'zh-TW' ? '信心值' : 'Confidence', value: '93%' }, ], actions: locale === 'zh-TW' ? ['建立發票紀錄', '匯出 CSV'] : ['Create record', 'Export CSV'], }, ]; } if (tab === 'marketplace') { return [ { title: locale === 'zh-TW' ? '放牧雞蛋' : 'Free-range Eggs', subtitle: locale === 'zh-TW' ? '新竹・可週配' : 'Hsinchu • weekly delivery', metrics: [ { label: locale === 'zh-TW' ? '單價' : 'Price', value: locale === 'zh-TW' ? 'NT$ 118 / 盒' : 'NT$ 118 / box' }, { label: locale === 'zh-TW' ? '庫存' : 'Stock', value: '42' }, ], tags: [topic, locale === 'zh-TW' ? '熱門' : 'trending'], }, { title: locale === 'zh-TW' ? '有機蔬菜組' : 'Organic Veg Pack', subtitle: locale === 'zh-TW' ? '桃園・當日採收' : 'Taoyuan • same-day harvest', metrics: [ { label: locale === 'zh-TW' ? '單價' : 'Price', value: locale === 'zh-TW' ? 'NT$ 220 / 組' : 'NT$ 220 / set' }, { label: locale === 'zh-TW' ? '庫存' : 'Stock', value: '18' }, ], }, ]; } if (tab === 'voice') { return [ { title: locale === 'zh-TW' ? '語音動作完成' : 'Voice Action Completed', subtitle: locale === 'zh-TW' ? '已套用您的口語指令' : 'Applied your spoken command', actions: locale === 'zh-TW' ? ['再次修改', '查看產品'] : ['Edit again', 'Open product'], }, ]; } return [ { title: locale === 'zh-TW' ? '代理回覆摘要' : 'Agent Response Snapshot', subtitle: topic, metrics: [ { label: locale === 'zh-TW' ? '路由' : 'Route', value: locale === 'zh-TW' ? '工具優先' : 'Tool-first' }, { label: locale === 'zh-TW' ? '狀態' : 'Status', value: locale === 'zh-TW' ? '已完成' : 'Completed' }, ], actions: locale === 'zh-TW' ? ['複製結果', '繼續追問'] : ['Copy result', 'Ask follow-up'], }, ]; } function buildPayload(tab: DemoTabId, locale: DemoLocale): Record { if (tab === 'invoice') { return { provider: 'gemini', model: 'gemini-2.5-flash', invoice: { invoiceNumber: 'FTL-2026-0312-09', counterpartyName: locale === 'zh-TW' ? '新竹青禾食堂' : 'Qinghe Kitchen', issueDate: '2026-03-11', dueDate: '2026-03-25', currency: 'TWD', total: 12860, paid: 0, }, confidence: 0.93, }; } if (tab === 'marketing') { return { headline: locale === 'zh-TW' ? '新鮮直送,今天就吃到安心蛋' : 'Fresh from farm, on your shelf today', cta: locale === 'zh-TW' ? '立即預購本週產地直送' : 'Pre-order this week\'s harvest now', hashtags: locale === 'zh-TW' ? ['#產地直送', '#放牧雞蛋', '#Farm2Market'] : ['#FarmFresh', '#DirectFromFarm', '#Farm2Market'], }; } if (tab === 'marketplace') { return { query: locale === 'zh-TW' ? '市集商品搜尋' : 'Marketplace search', stats: { sellers: 62, buyers: 87, products: 426, stores: 198, }, ranking: locale === 'zh-TW' ? '價格 + 庫存 + 地區' : 'price + stock + location', }; } return { mode: tab, summary: locale === 'zh-TW' ? '已完成工具流程並輸出結果。' : 'Tool workflow completed and response generated.', }; } function buildSummary(tab: DemoTabId, locale: DemoLocale, input: string): string { const topic = extractTopic(input); if (locale === 'zh-TW') { switch (tab) { case 'voice': return `已完成語音指令處理,並把「${topic}」轉成可執行動作。`; case 'marketing': return `行銷草案與素材已生成,主題聚焦在「${topic}」,可直接進行 A/B 測試。`; case 'invoice': return `發票欄位已抽取並完成一致性檢查,建議下一步可直接建立帳務紀錄。`; case 'marketplace': return `已完成市集搜尋與排序,先展示最符合「${topic}」條件的供應項目。`; default: return `流程已完成:我先做工具規劃,再回覆「${topic}」的可執行結果。`; } } switch (tab) { case 'voice': return `Voice command execution completed. "${topic}" was converted into an actionable workflow.`; case 'marketing': return `Marketing draft and assets are ready, tuned around "${topic}", and suitable for quick A/B testing.`; case 'invoice': return 'Invoice fields were extracted and validated. Next step can directly create an accounting record.'; case 'marketplace': return `Marketplace search and ranking finished. Top matches for "${topic}" are now prioritized.`; default: return `Completed the full tool-first workflow and produced an actionable response for "${topic}".`; } } function buildTraceItem( template: WorkflowTemplate, status: TraceItem['status'], detail: string, payload?: Record ): TraceItem { return { id: nextId('trace'), kind: template.kind, title: template.label, detail, status, payload, timestamp: nowLabel(), }; } export function getQuickPrompts(tab: DemoTabId, locale: DemoLocale): string[] { return QUICK_PROMPTS[locale][tab]; } export function buildWelcomeText(tab: DemoTabId, locale: DemoLocale): string { if (locale === 'zh-TW') { switch (tab) { case 'voice': return '語音代理已就緒。輸入一句口語指令,我會顯示推理進度與工具結果。'; case 'marketing': return '行銷工作台已就緒。可測試文案、CTA 與圖片優化流程。'; case 'invoice': return '發票 AI 已就緒。可展示 OCR 抽取與欄位驗證。'; case 'marketplace': return '市集代理已就緒。可示範搜尋、排序與統計摘要。'; default: return 'Farm2Market AI Demo 已就緒。輸入需求,我會顯示模型處理進度。'; } } switch (tab) { case 'voice': return 'Voice agent is ready. Send a spoken-style command and watch live step tracking.'; case 'marketing': return 'Marketing studio is ready. Test copy, CTA, and image-generation workflows.'; case 'invoice': return 'Invoice AI is ready. Demonstrate OCR extraction and validation steps.'; case 'marketplace': return 'Marketplace agent is ready. Demonstrate search, ranking, and stats summaries.'; default: return 'Farm2Market AI Demo is ready. Send a request to see model progress in real time.'; } } export async function runMockAgent(params: RunMockAgentParams): Promise { const { tab, input, locale, mode, onStepStatus, onTrace, onWorkflowInit } = params; const workflow = WORKFLOWS[locale][tab]; const steps: ProgressStep[] = workflow.map((item, index) => ({ id: `step-${index + 1}`, label: item.label, detail: item.detail, status: 'pending', })); onWorkflowInit(steps); for (let index = 0; index < workflow.length; index += 1) { const template = workflow[index]; const stepId = steps[index].id; onStepStatus(stepId, 'in_progress', template.detail); onTrace(buildTraceItem(template, 'running', template.detail)); await sleep(template.durationMs); const payload = template.kind === 'tool' ? buildToolPayload(tab, input, mode) : undefined; onStepStatus(stepId, 'completed', template.doneDetail); onTrace(buildTraceItem(template, 'ok', template.doneDetail, payload)); } return { summary: buildSummary(tab, locale, input), cards: buildCards(tab, input, locale), payload: buildPayload(tab, locale), }; }