前言

在前两篇文章中,我们介绍了AI UI Web系统的基本架构、提示词工程和组件设计。很多读者在体验后发现:系统不仅能理解单次请求,还能记住上下文,通过多轮对话逐步完善界面。

“帮我生成一个用户管理页面”
“再添加一个订单管理菜单”
“为用户管理页面添加一个表格”

这种连续的对话体验是如何实现的?今天,我将深入剖析AI UI Web系统的多轮对话实现机制,分享从增量更新、智能合并到token优化的完整技术方案。


一、多轮对话的核心挑战

1.1 为什么多轮对话这么难?

在实际开发中,我们遇到了以下核心问题:

挑战一:Token超限问题
第一次请求:生成完整系统(2000 tokens)
第二次请求:AI需要知道当前UI状态(2000 tokens)
第三次请求:对话历史 + 当前UI状态(4000+ tokens)
结果:超过API的token限制,导致请求失败或数据丢失
挑战二:数据合并问题
用户:"添加一个用户管理菜单"
AI返回:{ "menuItems": [{ "index": "users", "title": "用户管理" }] }

用户:"为用户管理添加表格"
AI返回:{ "pages": { "users": { "type": "table", ... } } }

问题:如何将这两次返回的数据正确合并?
挑战三:增量更新识别
用户:"修改用户管理的标题"
AI应该返回:{ "pages": { "users": { "title": "新标题" } } }
而不是:{ "pages": { "users": { ...完整配置 } } }

如何让AI理解"只返回变更部分"?

1.2 我们的解决方案

前端发送请求
    ↓
后端检测当前UI的token数量
    ↓
token > 1500?使用摘要:使用完整UI
    ↓
构建多轮对话提示词
    ↓
调用AI API
    ↓
AI返回增量更新
    ↓
智能合并数据(deepMerge + mergeArrays)
    ↓
保存到数据库
    ↓
返回合并后的完整UI

二、前端状态管理

2.1 Pinia Store实现

// frontend/src/stores/chat.js
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { generateUI, conversationApi } from '../api'

export const useChatStore = defineStore('chat', () => {
  const messages = ref([])
  const loading = ref(false)
  const uiData = ref(null)
  const inputMessage = ref('')
  const progress = ref('')
  const currentConversationId = ref(null)
  const conversations = ref([])

  const addMessage = (role, content) => {
    messages.value.push({ role, content })
  }

  const setLoading = (state) => {
    loading.value = state
  }

  const setUIData = (ui) => {
    uiData.value = ui
  }

  const setProgress = (p) => {
    progress.value = p
  }

  const setCurrentConversationId = (id) => {
    currentConversationId.value = id
  }

  const setConversations = (list) => {
    conversations.value = list
  }

2.2 深度合并算法

  const deepMerge = (target, source) => {
    const result = { ...target }
    
    for (const key in source) {
      if (source[key] instanceof Object && key in target && target[key] instanceof Object && !Array.isArray(source[key])) {
        result[key] = deepMerge(target[key], source[key])
      } else if (Array.isArray(source[key]) && Array.isArray(target[key])) {
        result[key] = mergeArrays(target[key], source[key], key)
      } else {
        result[key] = source[key]
      }
    }
    
    return result
  }

  const mergeArrays = (targetArray, sourceArray, key) => {
    if (sourceArray.length === 0) {
      return targetArray
    }
    
    if (targetArray.length === 0) {
      return sourceArray
    }
    
    const result = [...targetArray]
    
    for (const sourceItem of sourceArray) {
      let existingIndex = -1
      
      if (sourceItem.prop !== undefined) {
        existingIndex = result.findIndex(item => item.prop === sourceItem.prop)
      } else if (sourceItem.index !== undefined) {
        existingIndex = result.findIndex(item => item.index === sourceItem.index)
      } else if (sourceItem.name !== undefined) {
        existingIndex = result.findIndex(item => item.name === sourceItem.name)
      } else if (sourceItem.type !== undefined) {
        existingIndex = result.findIndex(item => item.type === sourceItem.type)
      }
      
      if (existingIndex !== -1) {
        result[existingIndex] = deepMerge(result[existingIndex], sourceItem)
      } else {
        result.push(sourceItem)
      }
    }
    
    return result
  }

  const mergeUIData = (newUI) => {
    if (!uiData.value) {
      uiData.value = newUI
      return
    }

    const merged = deepMerge(uiData.value, newUI)
    uiData.value = merged
  }

2.3 发送消息的核心逻辑

  const sendMessage = async (message) => {
    try {
      console.log('chatStore sendMessage called with:', message)
      setLoading(true)
      
      const conversationHistory = messages.value.map(msg => ({
        role: msg.role,
        content: msg.content
      }))

      console.log('Calling generateUI with:', {
        message,
        conversationHistory,
        currentUI: uiData.value,
        conversationId: currentConversationId.value
      })

      const result = await generateUI(message, conversationHistory, uiData.value, currentConversationId.value)
      console.log('generateUI result:', result)

      if (result) {
        console.log('Received UI data:', result)
        mergeUIData(result)
        
        addMessage('assistant', '界面生成成功')
      } else {
        console.log('No UI data received')
        addMessage('assistant', '生成失败: 未收到UI数据')
      }
    } catch (error) {
      console.error('生成UI失败:', error)
      
      let errorMessage = '生成失败,请重试'
      
      if (error.code === 'ECONNABORTED' || error.message?.includes('timeout')) {
        errorMessage = '请求超时,AI API 响应时间过长。请稍后重试或简化您的需求。'
      } else if (error.response?.status === 504) {
        errorMessage = '请求超时,AI API 响应时间过长。请稍后重试或简化您的需求。'
      } else if (error.response?.data?.error) {
        errorMessage = error.response.data.error
      } else if (error.message) {
        errorMessage = `生成失败: ${error.message}`
      }
      
      addMessage('assistant', errorMessage)
      throw error
    } finally {
      setLoading(false)
      setProgress('')
    }
  }

三、后端核心实现

3.1 Token检测机制

// backend/server.js

function estimateTokens(text) {
  if (!text) return 0;
  return Math.ceil(text.length / 4);
}

function generateUISummary(uiData) {
  if (!uiData) return null;

  const summary = {
    type: uiData.type,
    title: uiData.title,
    description: uiData.description
  };

  if (uiData.menuItems) {
    summary.menuSummary = {
      count: uiData.menuItems.length,
      indices: uiData.menuItems.map(m => m.index),
      structure: uiData.menuItems.map(m => ({
        index: m.index,
        title: m.title,
        hasChildren: !!(m.children && m.children.length > 0),
        children: m.children ? m.children.map(c => c.index) : []
      }))
    };
  }

  if (uiData.pages) {
    summary.pagesSummary = {
      count: Object.keys(uiData.pages).length,
      keys: Object.keys(uiData.pages),
      pageTypes: Object.fromEntries(
        Object.entries(uiData.pages).map(([key, page]) => [key, page.type])
      )
    };
  }

  if (uiData.contentType) {
    summary.contentType = uiData.contentType;
  }

  if (uiData.contentTitle) {
    summary.contentTitle = uiData.contentTitle;
  }

  return summary;
}

3.2 多轮对话API实现

app.post('/api/generate-ui', authMiddleware, requirePermission('chat'), async (req, res) => {
  const { userQuery, conversationHistory, currentUI, conversationId, targetPage } = req.body;

  console.log('[Generate UI] Request received:', {
    userQuery,
    conversationHistoryLength: conversationHistory?.length,
    hasCurrentUI: !!currentUI,
    conversationId,
    targetPage
  });

  const startTime = Date.now();

  let messages = [
    {
      role: 'system',
      content: `You are an AI UI generator for admin management systems. Your task is to generate complete admin interface STRUCTURES based on user queries.

IMPORTANT: You should ONLY generate UI STRUCTURES and CONFIGURATIONS. DO NOT include any actual business data or mock data in your response. The data will be fetched separately through a dedicated mock data API.

CRITICAL GENERATION RULES - MUST FOLLOW:
1. LIMIT TO ONE PAGE PER REQUEST: Always generate ONLY ONE page at a time
2. FIRST REQUEST (no existing UI): Generate admin system framework with ONLY dashboard page and menu structure
3. SUBSEQUENT REQUESTS: Generate ONLY the specific page requested by the user
4. NEVER generate multiple pages in a single response - this will cause token overflow and data loss
5. Always include "generationStatus" field when there are remaining pages to generate

CRITICAL INSTRUCTION - INCREMENTAL GENERATION FOR MULTI-TURN CONVERSATIONS:
This is a multi-turn conversation system. When currentUISummary is provided (meaning this is NOT the first request), you MUST follow these rules:

1. NEVER return the complete UI structure - this will cause token overflow and data loss
2. ONLY return CHANGED/ADDED/UPDATED parts
3. The backend will automatically merge your response with the existing UI state

When currentUISummary is provided:
- For adding a new menu item: return only { "menuItems": [...new item] }
- For updating an existing menu: return only { "menuItems": [{ "index": "existing_id", ...updated fields }] }
- For adding a new page: return only { "pages": { "menu_index": {...} } }
- For updating an existing page: return only { "pages": { "menu_index": {...updated data} } }
- For modifying a specific field: return only that field
- For deleting a menu/page: return { "menuItems": [] } or { "pages": {} } with the item removed

When t ecified:
- Generate ONLY that specific page: { "pages": { "targetPage": {...} } }
- Do NOT include other pages or menu items unless explicitly requested

Example responses when currentUISummary is provided:
- "添加一个订单管理菜单" → { "menuItems": [{ "index": "orders", "title": "订单管理", "icon": "Tickets" }] }
- "为用户管理页面添加表格" → { "pages": { "users": { "title": "用户管理", "type": "table", "data": {...config_only...} } } }
- "修改仪表盘的标题" → { "pages": { "dashboard": { "title": "新标题" } } }
- "删除商品管理菜单" → { "menuItems": [] } (with products removed)
      `.trim()
    }
  ];

  if (conversationHistory && conversationHistory.length > 0) {
    messages = messages.concat(conversationHistory);
  }

  if (currentUI) {
    const currentUITokens = estimateTokens(JSON.stringify(currentUI));
    console.log('[Generate UI] Current UI tokens:', currentUITokens);

    if (currentUITokens > 1500) {
      console.log('[Generate UI] Current UI too large, using summary');
      const summary = generateUISummary(currentUI);
      messages.push({
        role: 'system',
        content: `Current UI summary: ${JSON.stringify(summary)}.\n\nCRITICAL: This is a multi-turn conversation. Generate ONLY the CHANGES needed based on the user query. DO NOT return the complete UI structure.\n\nExisting structure:\n- Type: ${summary.type}\n- Menu items: ${summary.menuSummary?.count || 0} (${summary.menuSummary?.indices?.join(', ') || 'none'})\n- Pages: ${summary.pagesSummary?.count || 0} (${summary.pagesSummary?.keys?.join(', ') || 'none'})\n\nWhen the user asks to add/modify something, return ONLY the changed parts (e.g., just the new menu item, just the updated page, etc.). The backend will merge your response with the existing UI state.`
      });
    } else {
      messages.push({
        role: 'system',
        content: `Current UI state: ${JSON.stringify(currentUI)}.\n\nCRITICAL: This is a multi-turn conversation. Generate ONLY the CHANGES needed based on the user query. DO NOT return the complete UI structure. The backend will merge your response with the existing UI state.`
      });
    }
  }

  messages.push({
    role: 'user',
    content: userQuery
  });

  try {
    const completion = await openai.chat.completions.create({
      model: 'qwen-plus',
      messages: messages,
      temperature: 0.7,
      max_tokens: 4000
    });

    const aiResponse = completion.choices[0].message.content;
    console.log('[Generate UI] AI response length:', aiResponse.length);

    const parsedData = parseJSONFromContent(aiResponse);
    console.log('[Generate UI] Parsed data:', parsedData);

    let finalUIData = parsedData;

    if (currentUI) {
      console.log('[Generate UI] Merging with existing UI');
      finalUIData = deepMerge(currentUI, parsedData);
      console.log('[Generate UI] Merged UI data:', finalUIData);
    }

    if (conversationId) {
      const existingMessages = await dao.getMessages(conversationId);
      if (!existingMessages || existingMessages.length === 0) {
        await dao.updateConversation(conversationId, { title: userQuery });
      }
      await dao.addMessage(conversationId, 'user', userQuery);
      await dao.addMessage(conversationId, 'assistant', '界面生成成功');

      const existingUIData = await dao.getUIData(conversationId);
      if (existingUIData) {
        const changedPages = identifyChangedPages(existingUIData, finalUIData);
        console.log('[Generate UI] Changed pages:', changedPages);

        if (changedPages.length > 0) {
          for (const pageKey of changedPages) {
            if (pageKey === '*') {
              console.log('[Generate UI] Clearing all mock data for conversation:', conversationId);
              await dao.deleteAllComponentMockData(conversationId);
            } else {
              console.log('[Generate UI] Clearing mock data for page:', pageKey);
              await dao.deleteComponentMockDataByPage(conversationId, pageKey);
            }
          }
        }
      }

      await dao.updateUIData(conversationId, finalUIData);
    }

    res.json(finalUIData);
  } catch (error) {
    console.error('[Generate UI] Error:', error);
    res.status(500).json({ error: 'Failed to generate UI', details: error.message });
  }
});

3.3 深度合并实现

function deepMerge(target, source) {
  const result = { ...target };
  
  for (const key in source) {
    if (source[key] instanceof Object && key in target && target[key] instanceof Object && !Array.isArray(source[key])) {
      result[key] = deepMerge(target[key], source[key]);
    } else if (Array.isArray(source[key]) && Array.isArray(target[key])) {
      result[key] = mergeArrays(target[key], source[key], key);
    } else {
      result[key] = source[key];
    }
  }
  
  return result;
}

function mergeArrays(targetArray, sourceArray, key) {
  if (sourceArray.length === 0) {
    return targetArray;
  }

  if (targetArray.length === 0) {
    return sourceArray.filter(item => item !== undefined && item !== null);
  }

  const result = [...targetArray];

  for (const sourceItem of sourceArray) {
    if (sourceItem === undefined || sourceItem === null) {
      continue;
    }

    let existingIndex = -1;

    if (typeof sourceItem === 'string') {
      existingIndex = result.findIndex(item => typeof item === 'string' && item === sourceItem);
    } else if (sourceItem.prop !== undefined) {
      existingIndex = result.findIndex(item => item && item.prop === sourceItem.prop);
    } else if (sourceItem.index !== undefined) {
      existingIndex = result.findIndex(item => item && item.index === sourceItem.index);
    } else if (sourceItem.name !== undefined) {
      existingIndex = result.findIndex(item => item && item.name === sourceItem.name);
    } else if (sourceItem.type !== undefined) {
      existingIndex = result.findIndex(item => item && item.type === sourceItem.type);
    } else if (sourceItem.value !== undefined) {
      existingIndex = result.findIndex(item => item && item.value === sourceItem.value);
    }

    if (existingIndex !== -1) {
      result[existingIndex] = deepMerge(result[existingIndex], sourceItem);
    } else {
      result.push(sourceItem);
    }
  }

  return result.filter(item => item !== undefined && item !== null);
}

3.4 变更页面识别

function identifyChangedPages(existingData, newData) {
  const changedPages = [];
  
  if (!existingData) {
    console.log('[identifyChangedPages] No existing data, all new pages are changed');
    return Object.keys(newData.pages || {});
  }
  
  const existingPages = existingData.pages || {};
  const newPages = newData.pages || {};
  
  for (const pageKey of Object.keys(newPages)) {
    const existingPage = existingPages[pageKey];
    const newPage = newPages[pageKey];
    
    if (!existingPage) {
      console.log('[identifyChangedPages] New page added:', pageKey);
      changedPages.push(pageKey);
    } else {
      const existingPageStr = JSON.stringify(existingPage);
      const newPageStr = JSON.stringify(newPage);
      
      if (existingPageStr !== newPageStr) {
        console.log('[identifyChangedPages] Page modified:', pageKey, {
          existingKeys: Object.keys(existingPage),
          newKeys: Object.keys(newPage)
        });
        changedPages.push(pageKey);
      }
    }
  }
  
  if (newData.data && !newData.pages) {
    console.log('[identifyChangedPages] Top-level data changed, clearing all mock data for conversation');
    return ['*'];
  }
  
  return changedPages;
}

四、多轮对话的完整流程

4.1 第一次请求(创建系统)

// 前端发送
{
  userQuery: "帮我生成一个电商后台管理系统",
  conversationHistory: [],
  currentUI: null,
  conversationId: null
}

// 后端处理
1. 检测到currentUI为null
2. 不使用摘要,直接发送system prompt
3. AI返回完整系统结构

// AI返回
{
  "type": "admin",
  "title": "电商后台管理系统",
  "menuItems": [
    { "index": "dashboard", "title": "仪表盘", "icon": "Odometer" },
    { "index": "products", "title": "商品管理", "icon": "Goods" }
  ],
  "pages": {
    "dashboard": {
      "title": "仪表盘",
      "type": "dashboard",
      "data": { ... }
    }
  },
  "generationStatus": {
    "status": "partial",
    "message": "已生成基础框架和仪表盘页面,可以继续生成其他页面",
    "remainingPages": ["products"]
  }
}

// 后端保存
1. 保存到数据库
2. 返回完整UI数据

4.2 第二次请求(添加页面)

// 前端发送
{
  userQuery: "为商品管理页面添加表格",
  conversationHistory: [
    { role: "user", content: "帮我生成一个电商后台管理系统" },
    { role: "assistant", content: "界面生成成功" }
  ],
  currentUI: { ...完整UI数据 },
  conversationId: "conv_123"
}

// 后端处理
1. 检测到currentUI存在
2. 计算token数量:假设为1200(小于15003. 不使用摘要,发送完整UI状态
4. 在system prompt中明确说明:只返回变更部分

// AI返回(只返回变更部分)
{
  "pages": {
    "products": {
      "title": "商品管理",
      "type": "table",
      "data": {
        "contentConfig": {
          "cols": [
            { "prop": "id", "label": "商品ID" },
            { "prop": "name", "label": "商品名称" },
            { "prop": "price", "label": "价格" }
          ]
        }
      }
    }
  }
}

// 后端处理
1. 使用deepMerge将AI返回的增量数据合并到现有UI
2. 识别变更的页面:["products"]
3. 清除products页面的mock数据
4. 保存合并后的完整UI到数据库
5. 返回合并后的完整UI

4.3 第三次请求(token超限情况)

// 前端发送
{
  userQuery: "添加一个订单管理菜单",
  conversationHistory: [ ... ],
  currentUI: { ...大型UI数据 },
  conversationId: "conv_123"
}

// 后端处理
1. 检测到currentUI存在
2. 计算token数量:假设为2500(大于15003. 生成UI摘要

// UI摘要
{
  "type": "admin",
  "title": "电商后台管理系统",
  "menuSummary": {
    "count": 2,
    "indices": ["dashboard", "products"],
    "structure": [
      { "index": "dashboard", "title": "仪表盘", "hasChildren": false },
      { "index": "products", "title": "商品管理", "hasChildren": false }
    ]
  },
  "pagesSummary": {
    "count": 2,
    "keys": ["dashboard", "products"],
    "pageTypes": {
      "dashboard": "dashboard",
      "products": "table"
    }
  }
}

// 发送摘要而非完整UI
messages.push({
  role: 'system',
  content: `Current UI summary: ${JSON.stringify(summary)}.\n\nCRITICAL: This is a multi-turn conversation. Generate ONLY the CHANGES needed based on the user query.`
});

// AI返回(只返回变更部分)
{
  "menuItems": [
    { "index": "orders", "title": "订单管理", "icon": "Tickets" }
  ]
}

// 后端处理
1. 使用mergeArrays将新菜单项合并到现有menuItems
2. 保存合并后的完整UI到数据库
3. 返回合并后的完整UI

五、关键技术点总结

5.1 Token优化策略

// Token检测阈值
const UI_TOKEN_THRESHOLD = 1500;

// 摘要生成
function generateUISummary(uiData) {
  // 只保留关键信息:
  // 1. 类型、标题、描述
  // 2. 菜单项数量和索引
  // 3. 页面数量和类型
  // 4. 内容类型和标题
}

// 摘要效果
完整UI2000+ tokens
摘要UI300-500 tokens
节省:75%+ tokens

5.2 增量更新提示词

// 关键提示词内容
"CRITICAL INSTRUCTION - INCREMENTAL GENERATION FOR MULTI-TURN CONVERSATIONS:
This is a multi-turn conversation system. When currentUISummary is provided (meaning this is NOT the first request), you MUST follow these rules:

1. NEVER return the complete UI structure - this will cause token overflow and data loss
2. ONLY return CHANGED/ADDED/UPDATED parts
3. The backend will automatically merge your response with the existing UI state

When currentUISummary is provided:
- For adding a new menu item: return only { "menuItems": [...new item] }
- For updating an existing menu: return only { "menuItems": [{ "index": "existing_id", ...updated fields }] }
- For adding a new page: return only { "pages": { "menu_index": {...} } }
- For updating an existing page: return only { "pages": { "menu_index": {...updated data} } }
- For modifying a specific field: return only that field
- For deleting a menu/page: return { "menuItems": [] } or { "pages": {} } with the item removed"

5.3 智能合并算法

// 深度合并:递归合并对象
deepMerge(target, source)

// 数组合并:基于唯一标识合并
mergeArrays(targetArray, sourceArray, key)

// 唯一标识优先级:
1. prop(表格列、表单字段)
2. index(菜单项、页面索引)
3. name(组件名称)
4. type(组件类型)
5. value(值)

// 合并策略:
- 如果找到匹配项:深度合并
- 如果未找到匹配项:追加到数组

5.4 变更页面识别

// 识别变更的页面
function identifyChangedPages(existingData, newData) {
  // 1. 检查新增的页面
  // 2. 检查修改的页面
  // 3. 返回变更的页面列表
}

// 清除变更页面的mock数据
for (const pageKey of changedPages) {
  await dao.deleteComponentMockDataByPage(conversationId, pageKey);
}

// 原因:
// 页面结构变化后,原有的mock数据可能不匹配
// 需要重新生成mock数据

六、实战案例演示

6.1 完整对话流程

// 第1轮:创建系统
用户:"帮我生成一个电商后台管理系统"
AI返回:完整系统框架
前端状态:uiData = { type: "admin", menuItems: [...], pages: {...} }

// 第2轮:添加页面
用户:"为商品管理页面添加表格"
AI返回:{ "pages": { "products": { ...table配置 } } }
前端状态:uiData = deepMerge(原数据, 新数据)

// 第3轮:添加菜单
用户:"添加一个订单管理菜单"
AI返回:{ "menuItems": [{ "index": "orders", ... }] }
前端状态:uiData = deepMerge(原数据, 新数据)

// 第4轮:修改页面
用户:"修改仪表盘的标题为数据总览"
AI返回:{ "pages": { "dashboard": { "title": "数据总览" } } }
前端状态:uiData = deepMerge(原数据, 新数据)

6.2 Token使用对比

// 不使用摘要优化1次请求:2000 tokens(完整UI)
第2次请求:2000 tokens(完整UI+ 100 tokens(对话历史)
第3次请求:2000 tokens(完整UI+ 200 tokens(对话历史)
第4次请求:2000 tokens(完整UI+ 300 tokens(对话历史)
总计:8600 tokens

// 使用摘要优化1次请求:2000 tokens(完整UI)
第2次请求:1200 tokens(完整UI+ 100 tokens(对话历史)
第3次请求:500 tokens(摘要UI+ 200 tokens(对话历史)
第4次请求:500 tokens(摘要UI+ 300 tokens(对话历史)
总计:4600 tokens

节省:46.5% tokens

七、总结与展望

本文深入介绍了AI UI Web系统的多轮对话实现机制,从增量更新、智能合并到token优化的完整技术方案。

核心技术要点:

  1. 前端状态管理

    • 使用Pinia Store管理对话状态
    • 维护messages、uiData、currentConversationId
    • 实现deepMerge和mergeArrays算法
  2. Token优化策略

    • Token检测机制(estimateTokens)
    • UI摘要生成(generateUISummary)
    • 动态选择完整UI或摘要
  3. 增量更新机制

    • 在system prompt中明确说明"只返回变更部分"
    • 提供具体的返回格式示例
    • 避免AI返回完整UI结构
  4. 智能合并算法

    • deepMerge:递归合并对象
    • mergeArrays:基于唯一标识合并数组
    • 支持多种唯一标识(prop、index、name、type、value)
  5. 变更页面识别

    • 识别新增和修改的页面
    • 清除变更页面的mock数据
    • 确保数据一致性

实际效果:

  • Token使用量减少46.5%
  • 支持无限轮对话
  • 数据一致性保证
  • 用户体验流畅

未来优化方向:

  • 引入更智能的摘要算法
  • 优化合并策略,支持更多场景
  • 增加对话历史压缩
  • 支持并行页面生成

立即体验点击体验

技术交流:欢迎一起探讨AI前端开发的未来!
在这里插入图片描述


本文为AI UI Web系列技术分享第三篇,下一篇将介绍数据库设计、API接口设计和部署运维等内容。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐