智能体应用1: 点外卖

38 阅读7分钟

一、系统架构总览

用户 (前端/App) 
    ↓ (自然语言指令)
[前端:智能体交互层]
    ↓ (包含会话ID的请求)
[中台:智能体编排与执行引擎] 
    ┌───────────┬───────────┬───────────┐
    ↓           ↓           ↓           ↓
[工具1:        [工具2:        [工具3:        [记忆服务:
 餐馆/菜单查询]  购物车操作]    地址管理]     会话状态存储]
    ↓           ↓           ↓           ↓
    └───────────┴─────┬─────┴───────────┘
                    ↓
[中台:智能体决策与规划]
    ↓ (结构化下一步指令或最终结果)
[外部服务API网关] → 美团/饿了么等外卖平台开放API
    ↓
[前端:渲染与用户确认界面]

二、工作流程:

完整工作流程示例

  1. 用户输入:“帮我点一份麦当劳巨无霸套餐,送到XX大厦,帮我到付款界面”

  2. 前端:发送请求到中台/api/agent/order-food,附带会话ID。

  3. 中台 - 第一轮

    • LLM分析意图:需要query_restaurant_menu工具。
    • 发送事件status: querying_menu 到前端。
    • 执行工具:调用菜单查询工具,找到附近的麦当劳和巨无霸套餐。
    • 发送事件message: “找到‘麦当劳(XX店)’,有巨无霸套餐,价格38元。确认加入购物车吗?” 同时发送action_preview: {action: “add_to_cart”, items: [...]}
  4. 前端:显示消息和确认卡片。用户点击“确认”。

  5. 中台 - 第二轮

    • 前端发送确认指令。
    • LLM分析:用户已确认,需要manage_cart工具。
    • 执行工具:将商品加入购物车,返回成功。
    • LLM发现地址信息“XX大厦”不明确,需要address工具解析或用户确认。
    • 发送事件message: “需要确认具体地址。XX大厦有以下地址选项...”
  6. 用户:在前端选择或确认具体地址。

  7. 中台 - 第三轮

    • LLM分析:所有信息(餐厅、商品、地址)齐备,用户最终目标是“到付款界面”。
    • 调用get_payment_page工具。
    • 发送事件action_preview: {action: “get_payment_page”, need_confirm: true}(高风险操作,必须确认)。
  8. 前端:弹出确认框“即将跳转支付,总价38元,请确认”。

  9. 用户:点击“确认支付”。

  10. 中台 - 最终轮

    • 调用支付工具,从外卖平台获取预填充好商品、地址、价格的支付页面URL(带临时token)
    • 发送事件final_result: {type: “payment_page”, url: “https://pay.ele.me/...”}
  11. 前端:在应用内嵌的WebView中加载该URL,用户看到的是外卖平台标准的支付页面,只需输入密码或验证指纹即可完成支付。


三、前端实现详解

1. 界面设计(关键组件)
  • 智能体对话面板:核心区域,显示与AI的对话历史。

  • 智能体状态可视化面板

    • 思考状态:显示“正在分析你的需求...”、“正在查询麦当劳菜单...”

    • 操作预览:以卡片形式展示智能体即将或正在执行的操作,例如:

      json

      {
        “action”: “add_to_cart”,
        “restaurant”: “麦当劳(XX店)”,
        “items”: [{“name”: “巨无霸套餐”, “quantity”: 1}],
        “need_confirm”: true // 需要用户确认
      }
      
    • 工具调用记录:一个小日志,显示“调用了【菜单查询工具】”、“调用了【地址校验工具】”。

  • 混合输入区:用户既可以继续用自然语言对话,也可以直接点击智能体推荐的选项进行确认或修改(例如,点击“更换套餐”或“确认加入购物车”)。

  • 最终结果嵌入区:当智能体完成“到付款界面”的任务后,这个区域将直接嵌入从外卖平台获取的、已填好所有信息的支付页面H5或WebView

  1. 主要功能

    • 自动创建和管理会话
    • 实时SSE通信显示智能体状态
    • 操作预览和确认机制
    • 支付页面嵌入
    • 完整的响应式UI
2. 前端核心逻辑(代码思路)
// 1. 初始化智能体会话
const agentSession = useAgentSession('外卖助手');
// 2. 发送用户消息
const handleUserMessage = async (userInput) => {
  // 2.1 将用户输入添加到对话历史,并显示“思考中”状态
  agentSession.addMessage({ role: 'user', content: userInput });
  agentSession.setStatus('thinking');
  
  // 2.2 调用中台智能体API(流式响应)
  const response = await fetch('/api/agent/order-food', {
    method: 'POST',
    body: JSON.stringify({
      session_id: agentSession.id,
      message: userInput,
      // 可以附加上下文,如前端当前的地理位置
      context: { user_location: userLocation }
    }),
    headers: { 'Content-Type': 'application/json' }
  });
  
  // 2.3 处理流式响应(中台返回的是Server-Sent Events)
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    const chunk = decoder.decode(value);
    const events = chunk.split('\n\n').filter(e => e);
    
    for (const event of events) {
      const [eventLine, dataLine] = event.split('\n');
      if (eventLine === 'event: status') {
        // 更新智能体状态
        agentSession.setStatus(JSON.parse(dataLine.replace('data: ', '')).status);
      } else if (eventLine === 'event: action_preview') {
        // 显示智能体将要执行的操作(需要用户确认的卡片)
        const action = JSON.parse(dataLine.replace('data: ', ''));
        renderActionPreviewCard(action);
      } else if (eventLine === 'event: message') {
        // 逐步显示智能体的回复文本
        agentSession.appendAgentMessage(JSON.parse(dataLine.replace('data: ', '')).content);
      } else if (eventLine === 'event: final_result') {
        // 收到最终结果,例如支付页面的URL或嵌入代码
        const result = JSON.parse(dataLine.replace('data: ', ''));
        if (result.type === 'payment_page') {
          embedPaymentPage(result.url, result.token); // 嵌入支付页面
        }
      }
    }
  }
  agentSession.setStatus('idle');
};

// 3. 用户确认智能体操作
const handleConfirmAction = (actionId, confirmedData) => {
  // 发送确认信息回中台,例如:确认加入购物车的商品和数量
  fetch('/api/agent/confirm-action', {
    method: 'POST',
    body: JSON.stringify({
      session_id: agentSession.id,
      action_id: actionId,
      user_confirmation: confirmedData
    })
  });
};
3. 前端完整代码--更新中,可参考
## 整前端实现:基于SSEJavaScriptAI外卖助手
class AIFoodOrderingAssistant {
    constructor() {
        // 应用状态
        this.currentSessionId = null;
        this.eventSource = null;
        this.isConnected = false;
        this.isProcessing = false;
        this.pendingActionId = null;
        
        // DOM元素
        this.elements = {
            userInput: document.getElementById('userInput'),
            sendButton: document.getElementById('sendButton'),
            messagesContainer: document.getElementById('messagesContainer'),
            statusContainer: document.getElementById('statusContainer'),
            sessionId: document.getElementById('sessionId'),
            connectionStatus: document.getElementById('connectionStatus'),
            paymentSection: document.getElementById('paymentSection'),
            paymentFrame: document.getElementById('paymentFrame')
        };
        
        // 初始化
        this.init();
    }
    
    /**
     * 初始化应用
     */
    async init() {
        // 1. 创建或加载会话
        await this.initializeSession();
        
        // 2. 设置事件监听器
        this.setupEventListeners();
        
        // 3. 建立SSE连接
        this.connectToAgent();
        
        // 4. 显示欢迎消息
        this.showWelcomeMessage();
    }
    
    /**
     * 初始化会话
     */
    async initializeSession() {
        try {
            // 尝试从localStorage获取现有会话ID
            const savedSessionId = localStorage.getItem('agent_session_id');
            const savedSessionTime = localStorage.getItem('agent_session_time');
            
            // 检查会话是否过期(超过1小时)
            const sessionExpired = savedSessionTime && 
                                 (Date.now() - parseInt(savedSessionTime)) > 3600000;
            
            if (savedSessionId && !sessionExpired) {
                this.currentSessionId = savedSessionId;
                this.elements.sessionId.textContent = savedSessionId.substring(0, 8) + '...';
            } else {
                // 创建新会话
                const response = await fetch('/api/agent/session', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    }
                });
                
                const data = await response.json();
                this.currentSessionId = data.session_id;
                
                // 保存到localStorage
                localStorage.setItem('agent_session_id', this.currentSessionId);
                localStorage.setItem('agent_session_time', Date.now().toString());
                
                this.elements.sessionId.textContent = this.currentSessionId.substring(0, 8) + '...';
            }
        } catch (error) {
            console.error('初始化会话失败:', error);
            // 生成一个临时会话ID
            this.currentSessionId = 'temp_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
            this.elements.sessionId.textContent = '临时会话';
        }
    }
    
    /**
     * 设置事件监听器
     */
    setupEventListeners() {
        // 发送按钮点击事件
        this.elements.sendButton.addEventListener('click', () => this.sendMessage());
        
        // 输入框回车事件
        this.elements.userInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                this.sendMessage();
            }
        });
        
        // 输入框输入事件(启用/禁用发送按钮)
        this.elements.userInput.addEventListener('input', () => {
            const hasText = this.elements.userInput.value.trim().length > 0;
            this.elements.sendButton.disabled = !hasText || this.isProcessing;
        });
        
        // 页面卸载时关闭SSE连接
        window.addEventListener('beforeunload', () => {
            if (this.eventSource) {
                this.eventSource.close();
            }
        });
        
        // 断线重连
        window.addEventListener('online', () => {
            if (!this.isConnected) {
                this.connectToAgent();
            }
        });
    }
    
    /**
     * 连接到智能体SSE流
     */
    connectToAgent() {
        if (this.eventSource) {
            this.eventSource.close();
        }
        
        const sseUrl = `/api/agent/stream?session_id=${this.currentSessionId}`;
        this.eventSource = new EventSource(sseUrl);
        
        this.eventSource.onopen = () => {
            console.log('SSE连接已建立');
            this.isConnected = true;
            this.updateConnectionStatus('🟢 已连接');
        };
        
        this.eventSource.onerror = (error) => {
            console.error('SSE连接错误:', error);
            this.isConnected = false;
            this.updateConnectionStatus('🔴 连接断开');
            
            // 尝试重新连接
            setTimeout(() => {
                if (!this.isConnected) {
                    this.connectToAgent();
                }
            }, 5000);
        };
        
        // 处理不同的SSE事件
        this.eventSource.addEventListener('status', this.handleStatusEvent.bind(this));
        this.eventSource.addEventListener('action_preview', this.handleActionPreviewEvent.bind(this));
        this.eventSource.addEventListener('message', this.handleMessageEvent.bind(this));
        this.eventSource.addEventListener('final_result', this.handleFinalResultEvent.bind(this));
        this.eventSource.addEventListener('error', this.handleErrorEvent.bind(this));
    }
    
    /**
     * 更新连接状态显示
     */
    updateConnectionStatus(status) {
        this.elements.connectionStatus.textContent = status;
    }
    
    /**
     * 处理状态事件
     */
    handleStatusEvent(event) {
        const data = JSON.parse(event.data);
        this.addStatusCard(data.status, data.details);
        
        // 更新UI状态
        if (data.status === 'thinking') {
            this.showTypingIndicator();
            this.isProcessing = true;
            this.updateUIState();
        } else if (data.status === 'executing_tool') {
            this.addMessage('assistant', `正在执行: ${data.tool_name}...`, false);
            this.isProcessing = true;
            this.updateUIState();
        } else if (data.status === 'idle') {
            this.removeTypingIndicator();
            this.isProcessing = false;
            this.updateUIState();
        }
    }
    
    /**
     * 处理操作预览事件
     */
    handleActionPreviewEvent(event) {
        const data = JSON.parse(event.data);
        this.pendingActionId = data.action_id;
        this.showActionPreview(data);
    }
    
    /**
     * 处理消息事件
     */
    handleMessageEvent(event) {
        const data = JSON.parse(event.data);
        this.removeTypingIndicator();
        this.addMessage('assistant', data.content, true);
    }
    
    /**
     * 处理最终结果事件
     */
    handleFinalResultEvent(event) {
        const data = JSON.parse(event.data);
        
        if (data.type === 'payment_page') {
            this.showPaymentPage(data.url, data.token);
            this.addMessage('assistant', '已为您跳转到支付页面,请确认订单信息并完成支付。', true);
        } else if (data.type === 'order_created') {
            this.addMessage('assistant', `订单创建成功!订单号: ${data.order_id},总价: ¥${data.total_price}`, true);
        }
        
        this.isProcessing = false;
        this.updateUIState();
    }
    
    /**
     * 处理错误事件
     */
    handleErrorEvent(event) {
        const data = JSON.parse(event.data);
        this.addMessage('assistant', `❌ 错误: ${data.message}`, true);
        this.addStatusCard('error', data.message);
        this.isProcessing = false;
        this.updateUIState();
    }
    
    /**
     * 发送用户消息
     */
    async sendMessage() {
        const userInput = this.elements.userInput.value.trim();
        
        if (!userInput || this.isProcessing) return;
        
        // 添加用户消息到界面
        this.addMessage('user', userInput, true);
        
        // 清空输入框
        this.elements.userInput.value = '';
        this.elements.sendButton.disabled = true;
        this.isProcessing = true;
        this.updateUIState();
        
        try {
            // 发送消息到服务器
            const response = await fetch('/api/agent/process', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    session_id: this.currentSessionId,
                    message: userInput,
                    context: {
                        user_location: await this.getUserLocation(),
                        timestamp: new Date().toISOString()
                    }
                })
            });
            
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            
            // 显示思考中的状态
            this.addStatusCard('thinking', '正在分析您的需求...');
            this.showTypingIndicator();
            
        } catch (error) {
            console.error('发送消息失败:', error);
            this.addMessage('assistant', '抱歉,发送消息时出现错误,请稍后重试。', true);
            this.isProcessing = false;
            this.updateUIState();
        }
    }
    
    /**
     * 确认智能体操作
     */
    async confirmAction(confirmedData = null) {
        if (!this.pendingActionId) return;
        
        try {
            const response = await fetch('/api/agent/confirm-action', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    session_id: this.currentSessionId,
                    action_id: this.pendingActionId,
                    user_confirmation: confirmedData
                })
            });
            
            if (response.ok) {
                this.removeActionPreview();
                this.pendingActionId = null;
            }
        } catch (error) {
            console.error('确认操作失败:', error);
        }
    }
    
    /**
     * 修改操作
     */
    modifyAction() {
        // 在实际应用中,这里可以打开一个修改界面
        const modification = prompt('请输入修改内容(例如:换大杯可乐,加一份薯条):');
        if (modification) {
            this.addMessage('user', `修改:${modification}`, true);
            this.removeActionPreview();
            this.sendModification(modification);
        }
    }
    
    /**
     * 发送修改请求
     */
    async sendModification(modification) {
        try {
            const response = await fetch('/api/agent/modify', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    session_id: this.currentSessionId,
                    modification: modification
                })
            });
            
            if (!response.ok) {
                throw new Error('修改请求失败');
            }
        } catch (error) {
            console.error('发送修改失败:', error);
        }
    }
    
    /**
     * 显示欢迎消息
     */
    showWelcomeMessage() {
        const welcomeMessages = [
            "👋 你好!我是AI外卖助手,可以帮你完成从选餐到支付的全流程。",
            "💡 你可以这样对我说:",
            "• \"帮我点一份麦当劳巨无霸套餐,送到XX大厦\"",
            "• \"我想吃披萨,送到公司地址\"",
            "• \"点一份外卖,预算50元左右\"",
            "我会一步步引导你完成订单,并在最后跳转到支付页面。"
        ];
        
        setTimeout(() => {
            welcomeMessages.forEach((msg, index) => {
                setTimeout(() => {
                    this.addMessage('assistant', msg, true);
                }, index * 300);
            });
        }, 1000);
    }
    
    /**
     * 添加消息到聊天界面
     */
    addMessage(role, content, animate = true) {
        const messageDiv = document.createElement('div');
        messageDiv.className = `message ${role}-message`;
        
        const bubble = document.createElement('div');
        bubble.className = 'message-bubble';
        bubble.textContent = content;
        
        messageDiv.appendChild(bubble);
        
        if (animate) {
            messageDiv.style.opacity = '0';
            messageDiv.style.transform = 'translateY(10px)';
        }
        
        this.elements.messagesContainer.appendChild(messageDiv);
        
        if (animate) {
            requestAnimationFrame(() => {
                messageDiv.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
                messageDiv.style.opacity = '1';
                messageDiv.style.transform = 'translateY(0)';
            });
        }
        
        // 滚动到底部
        this.scrollToBottom();
    }
    
    /**
     * 显示输入指示器
     */
    showTypingIndicator() {
        this.removeTypingIndicator(); // 先移除可能存在的现有指示器
        
        const typingDiv = document.createElement('div');
        typingDiv.className = 'message agent-message typing-indicator';
        typingDiv.id = 'typingIndicator';
        
        const typingBubble = document.createElement('div');
        typingBubble.className = 'message-typing';
        
        const dots = document.createElement('div');
        dots.className = 'typing-dots';
        dots.innerHTML = '<span></span><span></span><span></span>';
        
        typingBubble.appendChild(dots);
        typingDiv.appendChild(typingBubble);
        this.elements.messagesContainer.appendChild(typingDiv);
        
        this.scrollToBottom();
    }
    
    /**
     * 移除输入指示器
     */
    removeTypingIndicator() {
        const existingIndicator = document.getElementById('typingIndicator');
        if (existingIndicator) {
            existingIndicator.remove();
        }
    }
    
    /**
     * 添加状态卡片
     */
    addStatusCard(status, details) {
        const statusCard = document.createElement('div');
        statusCard.className = 'status-card';
        
        const now = new Date();
        const timeString = now.toLocaleTimeString('zh-CN', { 
            hour: '2-digit', 
            minute: '2-digit',
            second: '2-digit'
        });
        
        let statusIcon = '🔵';
        let statusText = '处理中';
        
        switch(status) {
            case 'thinking':
                statusIcon = '🤔';
                statusText = '思考中';
                break;
            case 'querying_menu':
                statusIcon = '📋';
                statusText = '查询菜单';
                break;
            case 'executing_tool':
                statusIcon = '⚙️';
                statusText = '执行工具';
                break;
            case 'waiting_confirmation':
                statusIcon = '⏳';
                statusText = '等待确认';
                break;
            case 'error':
                statusIcon = '❌';
                statusText = '错误';
                break;
            case 'completed':
                statusIcon = '✅';
                statusText = '已完成';
                break;
        }
        
        statusCard.innerHTML = `
            <h4>${statusIcon} ${statusText}</h4>
            <p>${details}</p>
            <div class="timestamp">${timeString}</div>
        `;
        
        this.elements.statusContainer.appendChild(statusCard);
        
        // 滚动状态容器到底部
        this.elements.statusContainer.scrollTop = this.elements.statusContainer.scrollHeight;
    }
    
    /**
     * 显示操作预览
     */
    showActionPreview(data) {
        const actionCard = document.createElement('div');
        actionCard.className = 'action-preview-card';
        actionCard.id = 'actionPreviewCard';
        
        let actionDetails = '';
        let actionTitle = '请确认操作';
        
        switch(data.action) {
            case 'add_to_cart':
                actionTitle = '🛒 添加到购物车';
                actionDetails = `
                    <div><strong>餐厅:</strong>${data.params.restaurant || '未知'}</div>
                    <div><strong>商品:</strong></div>
                    <ul>
                        ${(data.params.items || []).map(item => 
                            `<li>${item.name} × ${item.quantity}</li>`
                        ).join('')}
                    </ul>
                `;
                break;
                
            case 'select_address':
                actionTitle = '📍 选择地址';
                actionDetails = `
                    <div><strong>请选择收货地址:</strong></div>
                    ${(data.params.options || []).map((addr, idx) => `
                        <div style="margin: 8px 0; padding: 8px; border: 1px solid #e5e7eb; border-radius: 6px;">
                            <label>
                                <input type="radio" name="address" value="${addr.id}" ${idx === 0 ? 'checked' : ''}>
                                ${addr.name} (${addr.address})
                            </label>
                        </div>
                    `).join('')}
                `;
                break;
                
            case 'get_payment_page':
                actionTitle = '💰 前往支付';
                actionDetails = `
                    <div><strong>订单摘要:</strong></div>
                    <div>总金额:¥${data.params.total_price || '0.00'}</div>
                    <div>配送地址:${data.params.address || '请确认地址'}</div>
                    <div>预计送达:${data.params.estimated_delivery || '30分钟'}</div>
                `;
                break;
        }
        
        actionCard.innerHTML = `
            <h3>${actionTitle}</h3>
            <div class="action-details">${actionDetails}</div>
            <div class="action-buttons">
                <button onclick="assistant.confirmAction(getConfirmedData())" class="btn btn-confirm">确认</button>
                <button onclick="assistant.modifyAction()" class="btn btn-modify">修改</button>
            </div>
        `;
        
        this.elements.statusContainer.appendChild(actionCard);
        this.elements.statusContainer.scrollTop = this.elements.statusContainer.scrollHeight;
    }
    
    /**
     * 移除操作预览
     */
    removeActionPreview() {
        const existingCard = document.getElementById('actionPreviewCard');
        if (existingCard) {
            existingCard.remove();
        }
    }
    
    /**
     * 显示支付页面
     */
    showPaymentPage(url, token) {
        // 构建完整的支付URL(包含token)
        const paymentUrl = `${url}${url.includes('?') ? '&' : '?'}token=${token}&embedded=true`;
        
        // 更新iframe源
        this.elements.paymentFrame.src = paymentUrl;
        
        // 显示支付容器
        this.elements.paymentSection.style.display = 'block';
        
        // 滚动到支付区域
        setTimeout(() => {
            this.elements.paymentSection.scrollIntoView({ behavior: 'smooth' });
        }, 500);
    }
    
    /**
     * 隐藏支付页面
     */
    hidePaymentSection() {
        this.elements.paymentSection.style.display = 'none';
        this.elements.paymentFrame.src = '';
    }
    
    /**
     * 获取用户位置(模拟)
     */
    async getUserLocation() {
        return new Promise((resolve) => {
            if (navigator.geolocation) {
                navigator.geolocation.getCurrentPosition(
                    (position) => {
                        resolve({
                            latitude: position.coords.latitude,
                            longitude: position.coords.longitude,
                            accuracy: position.coords.accuracy
                        });
                    },
                    () => {
                        // 获取失败时使用默认位置
                        resolve({
                            latitude: 39.9042,
                            longitude: 116.4074,
                            accuracy: 1000,
                            city: '北京市'
                        });
                    },
                    { timeout: 5000 }
                );
            } else {
                resolve({
                    latitude: 39.9042,
                    longitude: 116.4074,
                    city: '北京市'
                });
            }
        });
    }
    
    /**
     * 更新UI状态
     */
    updateUIState() {
        const hasText = this.elements.userInput.value.trim().length > 0;
        this.elements.sendButton.disabled = !hasText || this.isProcessing;
        this.elements.userInput.disabled = this.isProcessing;
        
        if (this.isProcessing) {
            this.elements.userInput.placeholder = '智能体正在处理中,请稍候...';
        } else {
            this.elements.userInput.placeholder = '告诉我你想吃什么?例如:帮我点一份麦当劳巨无霸套餐,送到XX大厦,帮我到付款界面';
        }
    }
    
    /**
     * 滚动到底部
     */
    scrollToBottom() {
        requestAnimationFrame(() => {
            this.elements.messagesContainer.scrollTop = this.elements.messagesContainer.scrollHeight;
        });
    }
}

/**
 * 获取确认数据(用于表单数据)
 */
function getConfirmedData() {
    const actionCard = document.getElementById('actionPreviewCard');
    if (!actionCard) return null;
    
    // 检查是否有地址选择
    const addressRadio = actionCard.querySelector('input[name="address"]:checked');
    if (addressRadio) {
        return { address_id: addressRadio.value };
    }
    
    return null;
}

// 创建助手实例
const assistant = new AIFoodOrderingAssistant();

// 使助手全局可用(用于按钮点击事件)
window.assistant = assistant;
window.hidePaymentSection = () => assistant.hidePaymentSection();
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI外卖助手</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }
        
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        
        .app-container {
            max-width: 800px;
            margin: 0 auto;
            background: white;
            border-radius: 20px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            overflow: hidden;
        }
        
        .header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 24px;
            text-align: center;
        }
        
        .header h1 {
            font-size: 28px;
            margin-bottom: 8px;
            font-weight: 600;
        }
        
        .header p {
            opacity: 0.9;
            font-size: 14px;
        }
        
        .main-content {
            display: flex;
            height: 70vh;
        }
        
        /* 左侧:对话区 */
        .chat-section {
            flex: 3;
            border-right: 1px solid #e5e7eb;
            display: flex;
            flex-direction: column;
        }
        
        .chat-header {
            padding: 16px;
            border-bottom: 1px solid #e5e7eb;
            font-weight: 600;
            color: #374151;
        }
        
        .messages-container {
            flex: 1;
            overflow-y: auto;
            padding: 20px;
            background: #f9fafb;
        }
        
        .message {
            margin-bottom: 20px;
            animation: fadeIn 0.3s ease;
        }
        
        .user-message {
            text-align: right;
        }
        
        .agent-message {
            text-align: left;
        }
        
        .message-bubble {
            display: inline-block;
            max-width: 70%;
            padding: 12px 16px;
            border-radius: 18px;
            word-wrap: break-word;
            line-height: 1.4;
        }
        
        .user-message .message-bubble {
            background: #3b82f6;
            color: white;
            border-bottom-right-radius: 4px;
        }
        
        .agent-message .message-bubble {
            background: white;
            color: #374151;
            border: 1px solid #e5e7eb;
            border-bottom-left-radius: 4px;
        }
        
        .message-typing {
            display: inline-flex;
            align-items: center;
            padding: 12px 16px;
            background: white;
            border: 1px solid #e5e7eb;
            border-radius: 18px;
            border-bottom-left-radius: 4px;
        }
        
        .typing-dots {
            display: flex;
            gap: 4px;
        }
        
        .typing-dots span {
            width: 6px;
            height: 6px;
            background: #9ca3af;
            border-radius: 50%;
            animation: typing 1.4s infinite ease-in-out;
        }
        
        .typing-dots span:nth-child(2) {
            animation-delay: 0.2s;
        }
        
        .typing-dots span:nth-child(3) {
            animation-delay: 0.4s;
        }
        
        /* 右侧:智能体状态和操作区 */
        .status-section {
            flex: 2;
            display: flex;
            flex-direction: column;
            background: white;
        }
        
        .status-header {
            padding: 16px;
            border-bottom: 1px solid #e5e7eb;
            font-weight: 600;
            color: #374151;
        }
        
        .status-container {
            flex: 1;
            overflow-y: auto;
            padding: 20px;
        }
        
        .status-card {
            background: #f3f4f6;
            border-radius: 12px;
            padding: 16px;
            margin-bottom: 16px;
            border-left: 4px solid #3b82f6;
            animation: slideIn 0.3s ease;
        }
        
        .status-card h4 {
            color: #374151;
            margin-bottom: 8px;
            font-size: 14px;
            font-weight: 600;
        }
        
        .status-card p {
            color: #6b7280;
            font-size: 13px;
            margin-bottom: 4px;
        }
        
        .status-card .timestamp {
            font-size: 11px;
            color: #9ca3af;
            margin-top: 8px;
        }
        
        /* 操作预览卡片 */
        .action-preview-card {
            background: #fef3c7;
            border: 2px solid #f59e0b;
            border-radius: 12px;
            padding: 20px;
            margin-bottom: 20px;
            animation: pulse 2s infinite;
        }
        
        .action-preview-card h3 {
            color: #92400e;
            margin-bottom: 12px;
            font-size: 16px;
        }
        
        .action-details {
            background: white;
            border-radius: 8px;
            padding: 12px;
            margin-bottom: 16px;
            font-size: 14px;
        }
        
        .action-buttons {
            display: flex;
            gap: 12px;
        }
        
        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 8px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s ease;
            font-size: 14px;
        }
        
        .btn-confirm {
            background: #10b981;
            color: white;
            flex: 1;
        }
        
        .btn-confirm:hover {
            background: #059669;
        }
        
        .btn-modify {
            background: #f3f4f6;
            color: #374151;
            flex: 1;
        }
        
        .btn-modify:hover {
            background: #e5e7eb;
        }
        
        /* 输入区 */
        .input-section {
            padding: 20px;
            border-top: 1px solid #e5e7eb;
            background: white;
        }
        
        .input-container {
            display: flex;
            gap: 12px;
        }
        
        #userInput {
            flex: 1;
            padding: 14px 20px;
            border: 2px solid #e5e7eb;
            border-radius: 12px;
            font-size: 16px;
            transition: border-color 0.2s ease;
        }
        
        #userInput:focus {
            outline: none;
            border-color: #3b82f6;
        }
        
        .btn-send {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            border-radius: 12px;
            padding: 0 28px;
            font-weight: 600;
            cursor: pointer;
            transition: transform 0.2s ease;
        }
        
        .btn-send:hover:not(:disabled) {
            transform: translateY(-2px);
        }
        
        .btn-send:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }
        
        /* 支付页面容器 */
        .payment-container {
            margin-top: 20px;
            border: 2px dashed #10b981;
            border-radius: 12px;
            padding: 20px;
            background: #f0fdf4;
            animation: fadeIn 0.5s ease;
        }
        
        .payment-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 16px;
        }
        
        .payment-header h3 {
            color: #065f46;
            font-size: 16px;
        }
        
        .payment-frame {
            width: 100%;
            height: 400px;
            border: 1px solid #d1d5db;
            border-radius: 8px;
            overflow: hidden;
        }
        
        /* 动画 */
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }
        
        @keyframes slideIn {
            from { opacity: 0; transform: translateX(-10px); }
            to { opacity: 1; transform: translateX(0); }
        }
        
        @keyframes typing {
            0%, 60%, 100% { transform: translateY(0); }
            30% { transform: translateY(-4px); }
        }
        
        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.8; }
        }
        
        /* 会话管理 */
        .session-info {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 12px;
            color: #9ca3af;
            margin-top: 8px;
        }
        
        .session-id {
            background: rgba(255,255,255,0.2);
            padding: 2px 8px;
            border-radius: 10px;
            font-family: monospace;
        }
        
        /* 响应式设计 */
        @media (max-width: 768px) {
            .main-content {
                flex-direction: column;
                height: auto;
            }
            
            .status-section {
                height: 300px;
            }
            
            body {
                padding: 10px;
            }
            
            .message-bubble {
                max-width: 85%;
            }
        }
    </style>
</head>
<body>
    <div class="app-container">
        <div class="header">
            <h1>🤖 AI外卖助手</h1>
            <p>智能点餐,一句话完成从选餐到支付的全流程</p>
            <div class="session-info">
                <span>会话ID: <span id="sessionId" class="session-id">loading...</span></span>
                <span>状态: <span id="connectionStatus">🟡 连接中...</span></span>
            </div>
        </div>
        
        <div class="main-content">
            <!-- 左侧:对话区 -->
            <div class="chat-section">
                <div class="chat-header">
                    对话记录
                </div>
                <div class="messages-container" id="messagesContainer">
                    <!-- 消息会动态添加到这里 -->
                </div>
            </div>
            
            <!-- 右侧:智能体状态区 -->
            <div class="status-section">
                <div class="status-header">
                    智能体状态与操作
                </div>
                <div class="status-container" id="statusContainer">
                    <!-- 状态卡片和操作预览会动态添加到这里 -->
                </div>
            </div>
        </div>
        
        <!-- 支付页面容器(初始隐藏) -->
        <div id="paymentSection" class="payment-container" style="display: none;">
            <div class="payment-header">
                <h3>💰 支付页面</h3>
                <button onclick="hidePaymentSection()" class="btn" style="background: #ef4444; color: white; padding: 8px 16px;">关闭</button>
            </div>
            <iframe id="paymentFrame" class="payment-frame" src="" title="支付页面"></iframe>
        </div>
        
        <!-- 输入区 -->
        <div class="input-section">
            <div class="input-container">
                <input 
                    type="text" 
                    id="userInput" 
                    placeholder="告诉我你想吃什么?例如:帮我点一份麦当劳巨无霸套餐,送到XX大厦,帮我到付款界面"
                    autocomplete="off"
                >
                <button id="sendButton" class="btn-send" onclick="sendMessage()">发送</button>
            </div>
        </div>
    </div>

    <script src="app.js"></script>
</body>
</html>

四、配套的Node.js Express服务器示例

// server.js - 简化的服务器示例
const express = require('express');
const cors = require('cors');
const { v4: uuidv4 } = require('uuid');

const app = express();
app.use(cors());
app.use(express.json());
app.use(express.static('public')); // 静态文件服务

// 存储会话状态(生产环境中应该使用Redis)
const sessions = new Map();

// 创建新会话
app.post('/api/agent/session', (req, res) => {
    const sessionId = uuidv4();
    sessions.set(sessionId, {
        created_at: new Date(),
        messages: [],
        state: {
            step: 'initial',
            selected_restaurant: null,
            cart_id: null,
            address_id: null
        }
    });
    
    res.json({ session_id: sessionId });
});

// 处理用户消息
app.post('/api/agent/process', (req, res) => {
    const { session_id, message, context } = req.body;
    
    if (!sessions.has(session_id)) {
        return res.status(404).json({ error: '会话不存在' });
    }
    
    // 这里在实际应用中应该触发智能体处理流程
    // 我们只是模拟一个响应
    res.json({ 
        success: true, 
        message_id: uuidv4() 
    });
});

// SSE流端点
app.get('/api/agent/stream', (req, res) => {
    const sessionId = req.query.session_id;
    
    if (!sessionId) {
        return res.status(400).json({ error: '缺少会话ID' });
    }
    
    // 设置SSE头
    res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'Access-Control-Allow-Origin': '*'
    });
    
    // 发送初始状态
    res.write(`event: status\ndata: ${JSON.stringify({ status: 'connected' })}\n\n`);
    
    // 模拟一些事件(实际应用中这些事件来自智能体处理流程)
    const sendSimulatedEvents = () => {
        setTimeout(() => {
            res.write(`event: status\ndata: ${JSON.stringify({ 
                status: 'thinking', 
                details: '正在分析您的需求...' 
            })}\n\n`);
        }, 1000);
        
        setTimeout(() => {
            res.write(`event: status\ndata: ${JSON.stringify({ 
                status: 'querying_menu', 
                details: '查询麦当劳菜单中...' 
            })}\n\n`);
        }, 2000);
        
        setTimeout(() => {
            res.write(`event: message\ndata: ${JSON.stringify({ 
                content: '找到"麦当劳(国贸店)",有巨无霸套餐,价格38元。确认加入购物车吗?' 
            })}\n\n`);
        }, 3000);
        
        setTimeout(() => {
            res.write(`event: action_preview\ndata: ${JSON.stringify({
                action_id: uuidv4(),
                action: 'add_to_cart',
                params: {
                    restaurant: '麦当劳(国贸店)',
                    items: [
                        { name: '巨无霸套餐', quantity: 1, price: 38 }
                    ],
                    need_confirm: true
                }
            })}\n\n`);
        }, 3500);
    };
    
    // 开始发送模拟事件
    sendSimulatedEvents();
    
    // 保持连接
    req.on('close', () => {
        console.log(`客户端断开连接: ${sessionId}`);
        res.end();
    });
});

// 确认操作
app.post('/api/agent/confirm-action', (req, res) => {
    const { session_id, action_id, user_confirmation } = req.body;
    
    // 在实际应用中,这里会更新会话状态并继续智能体流程
    res.json({ 
        success: true,
        message: '操作已确认'
    });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`服务器运行在 http://localhost:${PORT}`);
});
```