import { FormEvent, useEffect, useMemo, useRef, useState } from 'react'; import { buildWelcomeText, getQuickPrompts, runMockAgent } from './mockAgent'; import type { DemoLocale, DemoMessage, DemoMode, DemoTabId, ProgressStep, TraceItem } from './types'; import VoiceDemoPanel from './VoiceDemoPanel'; import MarketingStudioPanel from './MarketingStudioPanel'; import InvoiceDemoPanel from './InvoiceDemoPanel'; import MarketplaceDemoPanel from './MarketplaceDemoPanel'; import ChatFeatureDemo from './ChatFeatureDemo'; import { runLiveAgent } from './liveAgent'; const TAB_DEFINITIONS: Record> = { en: [ { id: 'chat', label: 'Chat Agent', subtitle: 'Tool-first orchestration' }, { id: 'voice', label: 'Voice Agent', subtitle: 'Speech-style actions' }, { id: 'marketing', label: 'Marketing Studio', subtitle: 'Copy + visual workflow' }, { id: 'invoice', label: 'Invoice AI', subtitle: 'OCR + extraction demo' }, { id: 'marketplace', label: 'Marketplace', subtitle: 'Search + ranking + stats' }, ], 'zh-TW': [ { id: 'chat', label: '聊天代理', subtitle: '工具優先協作流程' }, { id: 'voice', label: '語音代理', subtitle: '語音式操作流程' }, { id: 'marketing', label: '行銷工作室', subtitle: '文案 + 視覺素材流程' }, { id: 'invoice', label: '發票 AI', subtitle: 'OCR + 欄位擷取示範' }, { id: 'marketplace', label: '市集探索', subtitle: '搜尋 + 排名 + 統計' }, ], }; const APP_COPY = { en: { eyebrow: 'Farm2Market Demo', title: 'Agent Workspace', statusRunning: 'Running', statusIdle: 'Idle', hideControls: 'Hide Controls', showControls: 'Show Controls', tabAria: 'Agent tabs', controlsTitle: 'Controls', controlsSubtitle: 'Switch scenario and prompt packs.', demoMode: 'Demo Mode', modeMock: 'Mock (stable)', modeLive: 'Live (backend)', language: 'Language', english: 'English', traditionalChinese: 'Traditional Chinese', tryPrompts: 'Try Prompts', progressTitle: 'Model Progress', progressRunning: 'In progress', progressDone: 'Completed', structuredOutput: 'Structured Output', workingMessage: 'Working through the request…', composerPlaceholder: 'Describe what to demo, e.g. search eggs below 200', clearPanels: 'Clear Panels', runningAction: 'Running…', runDemo: 'Run Demo', traceTitle: 'Agent Trace', traceLive: 'Live events', traceLast: 'Last run summary', tracePayload: 'payload', traceWaiting: 'Waiting for trace events…', unknownError: 'Unknown runtime error', roleLabels: { user: 'user', assistant: 'assistant', system: 'system', }, kindLabels: { planner: 'planner', tool: 'tool', validator: 'validator', renderer: 'renderer', }, }, 'zh-TW': { eyebrow: 'Farm2Market 示範', title: '代理工作台', statusRunning: '執行中', statusIdle: '待命', hideControls: '隱藏控制', showControls: '顯示控制', tabAria: '代理分頁', controlsTitle: '控制面板', controlsSubtitle: '切換情境與提示組合。', demoMode: '示範模式', modeMock: '模擬(穩定)', modeLive: '即時(後端)', language: '語言', english: '英文', traditionalChinese: '繁體中文', tryPrompts: '快速提示', progressTitle: '模型處理進度', progressRunning: '執行中', progressDone: '完成', structuredOutput: '結構化輸出', workingMessage: '正在處理中,請稍候…', composerPlaceholder: '描述你要示範的功能,例如:幫我搜尋 200 以下雞蛋', clearPanels: '清除面板', runningAction: '執行中…', runDemo: '執行示範', traceTitle: '代理追蹤', traceLive: '即時事件', traceLast: '最近一次摘要', tracePayload: '載荷', traceWaiting: '等待追蹤事件…', unknownError: '未知執行錯誤', roleLabels: { user: '使用者', assistant: '助理', system: '系統', }, kindLabels: { planner: '規劃', tool: '工具', validator: '驗證', renderer: '輸出', }, }, } as const; function timestampLabel(): string { return new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } function createMessage( role: DemoMessage['role'], text: string, extra?: Pick ): DemoMessage { return { id: `${role}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`, role, text, timestamp: timestampLabel(), cards: extra?.cards, jsonPayload: extra?.jsonPayload, }; } function statusIcon(status: ProgressStep['status']): string { if (status === 'completed') return '✓'; if (status === 'error') return '!'; if (status === 'in_progress') return '●'; return '○'; } function App() { const [activeTab, setActiveTab] = useState('chat'); const [mode, setMode] = useState('mock'); const [locale, setLocale] = useState('en'); const [input, setInput] = useState(''); const [isWorking, setIsWorking] = useState(false); const [progressSteps, setProgressSteps] = useState([]); const [traceItems, setTraceItems] = useState([]); const [progressVisible, setProgressVisible] = useState(false); const [mobileControlsOpen, setMobileControlsOpen] = useState(false); const [queuedVoicePrompt, setQueuedVoicePrompt] = useState(null); const [queuedMarketingPrompt, setQueuedMarketingPrompt] = useState(null); const [queuedInvoicePrompt, setQueuedInvoicePrompt] = useState(null); const [queuedMarketplacePrompt, setQueuedMarketplacePrompt] = useState(null); const [messages, setMessages] = useState([ createMessage('system', buildWelcomeText('chat', 'en')), ]); const firstRenderRef = useRef(true); const threadRef = useRef(null); const copy = APP_COPY[locale]; const tabs = TAB_DEFINITIONS[locale]; const quickPrompts = useMemo(() => getQuickPrompts(activeTab, locale), [activeTab, locale]); const showTracePanel = isWorking || traceItems.length > 0; useEffect(() => { if (firstRenderRef.current) { firstRenderRef.current = false; return; } setMessages((prev) => [...prev, createMessage('system', buildWelcomeText(activeTab, locale))]); setProgressSteps([]); setTraceItems([]); setInput(''); setQueuedVoicePrompt(null); setQueuedMarketingPrompt(null); setQueuedInvoicePrompt(null); setQueuedMarketplacePrompt(null); setIsWorking(false); }, [activeTab, locale]); useEffect(() => { if (isWorking) { setProgressVisible(true); return; } if (!progressSteps.length) { setProgressVisible(false); return; } const timer = window.setTimeout(() => { setProgressVisible(false); }, 2000); return () => window.clearTimeout(timer); }, [isWorking, progressSteps]); useEffect(() => { if (activeTab === 'voice' || activeTab === 'marketing' || activeTab === 'invoice' || activeTab === 'marketplace') return; if (!threadRef.current) return; threadRef.current.scrollTo({ top: threadRef.current.scrollHeight, behavior: 'smooth' }); }, [activeTab, messages, progressSteps, isWorking]); const updateStepStatus = (stepId: string, status: ProgressStep['status'], detail?: string) => { setProgressSteps((prev) => prev.map((step) => (step.id === stepId ? { ...step, status, detail: detail ?? step.detail } : step)) ); }; const pushTrace = (item: TraceItem) => { setTraceItems((prev) => [...prev, item].slice(-20)); }; const clearPanels = () => { setTraceItems([]); setProgressSteps([]); }; async function handleSubmit(event: FormEvent) { event.preventDefault(); if (isWorking) return; const trimmed = input.trim(); if (!trimmed) return; setMessages((prev) => [...prev, createMessage('user', trimmed)]); setInput(''); setTraceItems([]); setIsWorking(true); try { const result = mode === 'live' ? await runLiveAgent({ tab: activeTab, input: trimmed, locale, onWorkflowInit: (steps) => { setProgressSteps(steps); }, onStepStatus: (stepId, status, detail) => { updateStepStatus(stepId, status, detail); }, onTrace: (item) => { pushTrace(item); }, }) : await runMockAgent({ tab: activeTab, input: trimmed, locale, mode, onWorkflowInit: (steps) => { setProgressSteps(steps); }, onStepStatus: (stepId, status, detail) => { updateStepStatus(stepId, status, detail); }, onTrace: (item) => { pushTrace(item); }, }); setMessages((prev) => [ ...prev, createMessage('assistant', result.summary, { cards: result.cards, jsonPayload: result.payload, }), ]); } catch (error) { const message = error instanceof Error ? error.message : copy.unknownError; setMessages((prev) => [ ...prev, createMessage( 'assistant', locale === 'zh-TW' ? `執行時發生錯誤:${message}` : `The demo run failed with an error: ${message}` ), ]); setProgressSteps((prev) => prev.map((step) => (step.status === 'in_progress' ? { ...step, status: 'error', detail: message } : step)) ); } finally { setIsWorking(false); } } return (

{copy.eyebrow}

{copy.title}

{isWorking ? copy.statusRunning : copy.statusIdle}
{progressVisible && progressSteps.length > 0 ? (

{copy.progressTitle}

{isWorking ? copy.progressRunning : copy.progressDone}
    {progressSteps.map((step) => (
  • {statusIcon(step.status)}
    {step.label} {step.detail}
  • ))}
) : null} {activeTab === 'voice' ? ( setQueuedVoicePrompt(null)} /> ) : activeTab === 'marketing' ? ( setQueuedMarketingPrompt(null)} /> ) : activeTab === 'invoice' ? ( setQueuedInvoicePrompt(null)} /> ) : activeTab === 'marketplace' ? ( setQueuedMarketplacePrompt(null)} /> ) : ( <>
{messages.map((message) => (
{copy.roleLabels[message.role]}

{message.text}

{message.cards && message.cards.length > 0 ? (
{message.cards.map((card) => (

{card.title}

{card.subtitle ?

{card.subtitle}

: null} {card.metrics && card.metrics.length > 0 ? (
{card.metrics.map((metric) => (
{metric.label} {metric.value}
))}
) : null} {card.tags && card.tags.length > 0 ? (
{card.tags.map((tag) => ( #{tag} ))}
) : null} {card.actions && card.actions.length > 0 ? (
{card.actions.map((action) => ( ))}
) : null}
))}
) : null} {message.jsonPayload ? (
{copy.structuredOutput}
{JSON.stringify(message.jsonPayload, null, 2)}
) : null}
))} {isWorking ? (
{copy.roleLabels.assistant}

{copy.workingMessage}

) : null}