上一篇 下一篇 分享链接 返回 返回顶部

AI 浏览器卡顿、启动慢、响应慢?这套性能优化方案和源码够用了

发布人:慈云数据-客服中心 发布时间:3小时前 阅读量:0

AI浏览器 性能优化教程|附源码

适用场景:你正在开发一款带有 AI 助手能力的浏览器、AI 搜索浏览器、网页总结浏览器、RAG 浏览器插件,或基于 Electron / Chromium / WebView 封装的智能浏览器应用。
本文将从 启动速度、页面加载、AI 推理、流式响应、缓存、内存管理、前端渲染、接口并发、源码实现 等角度,系统讲解 AI 浏览器的性能优化方案,并附带可直接参考的源码示例。


一、什么是 AI 浏览器?

AI 浏览器并不只是“浏览器 + 聊天窗口”的简单组合。

一个完整的 AI 浏览器,通常包含以下能力:

  1. 普通网页浏览能力

    • 地址栏访问网页
    • 标签页管理
    • 历史记录
    • 收藏夹
    • 下载管理
  2. AI 助手能力

    • 网页内容总结
    • 网页问答
    • 当前页面翻译
    • 智能搜索
    • 文档解析
    • 表单自动填写
    • 代码解释
    • 网页内容对话
  3. 上下文感知能力

    • 自动读取当前网页正文
    • 识别标题、作者、发布时间
    • 提取页面结构
    • 分析用户当前任务
  4. 本地与云端协同能力

    • 本地缓存
    • 云端大模型推理
    • 本地向量数据库
    • RAG 检索增强生成

但是,AI 浏览器相比传统浏览器更容易遇到性能问题。原因也很明显:除了浏览网页,它还需要处理网页解析、文本提取、AI 请求、流式响应、上下文拼接、向量检索、前端渲染等额外任务。

如果不做优化,用户会明显感受到:

  • 启动慢;
  • 页面卡顿;
  • AI 响应延迟高;
  • 内存占用不断上涨;
  • 多标签页时 CPU 飙升;
  • 网页总结经常超时;
  • AI 面板输入时掉帧;
  • 流式输出不流畅;
  • 请求大模型接口成本过高。

因此,AI 浏览器的性能优化不是某个点的优化,而是一套系统工程。


二、AI 浏览器常见性能瓶颈

在优化之前,先要明确瓶颈在哪里。

1. 启动阶段性能瓶颈

如果你的 AI 浏览器基于 Electron 开发,启动慢通常来自以下几个方面:

  • 主进程加载过多模块;
  • 启动时初始化 AI SDK;
  • 启动时读取大量历史记录;
  • 启动时加载多个窗口;
  • 启动时创建数据库连接;
  • 首屏渲染依赖过多 JS 文件;
  • 首屏加载 AI 面板资源过大。

很多开发者会在应用启动时做太多事情,例如:

import OpenAI from 'openai'
import sqlite3 from 'sqlite3'
import vectorDB from './vector-db'
import historyService from './history'
import bookmarkService from './bookmark'
import aiSummaryService from './ai-summary'

这些模块一旦在启动阶段同步加载,就会拖慢应用启动速度。


2. 网页内容提取瓶颈

AI 浏览器经常需要读取当前网页内容,例如用于总结文章、问答或翻译。

常见问题包括:

  • 直接读取整个 document.body.innerText
  • 没有过滤导航栏、广告、评论区;
  • 一次性传输大量文本到主进程;
  • 文本过长导致大模型请求缓慢;
  • 页面还没加载完成就开始解析;
  • 对 SPA 页面重复解析。

错误示例:

const text = document.body.innerText

这行代码看似简单,但在复杂网页中可能会提取几十万字符,既影响页面性能,又增加 AI 请求成本。


3. AI 请求延迟瓶颈

AI 浏览器的核心体验之一是“快”。

用户点击“总结当前网页”后,如果等待 10 秒才看到结果,体验会很差。

AI 请求慢通常有这些原因:

  • prompt 太长;
  • 上下文没有压缩;
  • 没有流式输出;
  • 没有请求缓存;
  • 多个 AI 请求串行执行;
  • 模型选择不合理;
  • 没有超时控制;
  • 没有取消机制;
  • 没有分层任务设计。

4. 前端渲染瓶颈

AI 浏览器通常有一个侧边栏或浮窗,用于展示 AI 对话。

如果流式响应每来一个 token 就更新一次 React 状态,会导致频繁渲染。

错误示例:

reader.onmessage = (token) => {
  setContent(prev => prev + token)
}

当 token 很多时,React 会频繁触发更新,导致页面卡顿。


5. 内存泄漏问题

AI 浏览器常见内存泄漏来源:

  • 标签页关闭后事件监听未移除;
  • WebSocket 没有关闭;
  • AbortController 没有释放;
  • 缓存无限增长;
  • DOM 引用被长期持有;
  • 大文本内容被存储在内存中;
  • 每个标签页都创建独立 AI 上下文。

尤其是 Electron 应用,如果不控制内存,很容易出现运行一段时间后占用几个 GB 的情况。


三、整体优化思路

AI 浏览器性能优化可以分为以下几个层级:

优化层级 重点方向
启动优化 延迟加载、按需初始化、减少首屏 JS
页面优化 内容提取、DOM 过滤、节流防抖
AI 优化 Prompt 压缩、流式输出、缓存、模型路由
渲染优化 批量更新、虚拟列表、Web Worker
网络优化 请求合并、超时取消、并发控制
内存优化 LRU 缓存、资源释放、标签页生命周期
架构优化 主进程/渲染进程隔离、任务队列、插件化

下面开始进入实战。


四、启动性能优化

1. AI SDK 延迟加载

不要在应用启动时立即加载 AI SDK,而是在用户第一次使用 AI 功能时再加载。

优化前

import OpenAI from 'openai'

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
})

优化后

let aiClient = null

async function getAIClient() {
  if (aiClient) return aiClient

  const { default: OpenAI } = await import('openai')

  aiClient = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY
  })

  return aiClient
}

module.exports = {
  getAIClient
}

这样可以避免 AI SDK 在启动阶段参与打包和初始化,减少首屏压力。


2. 历史记录懒加载

浏览器历史记录通常数据量很大,启动时不应该全部读取。

class HistoryService {
  constructor(db) {
    this.db = db
  }

  async getRecentHistory(limit = 50) {
    return this.db.all(
      `SELECT title, url, visit_time 
       FROM history 
       ORDER BY visit_time DESC 
       LIMIT ?`,
      [limit]
    )
  }

  async searchHistory(keyword, limit = 20) {
    return this.db.all(
      `SELECT title, url, visit_time 
       FROM history 
       WHERE title LIKE ? OR url LIKE ?
       ORDER BY visit_time DESC 
       LIMIT ?`,
      [`%${keyword}%`, `%${keyword}%`, limit]
    )
  }
}

原则是:

  • 首页只加载最近 50 条;
  • 搜索时再查询数据库;
  • 不要一次性把全部历史记录塞进内存;
  • 长列表使用分页或虚拟列表。

五、网页内容提取优化

AI 浏览器的关键能力之一是理解网页。
但“提取网页正文”一定要做精细化处理,不能粗暴读取整个页面。

1. 内容提取脚本

下面是一个可注入网页的内容提取脚本。

function extractPageContent() {
  const clone = document.body.cloneNode(true)

  const removeSelectors = [
    'script',
    'style',
    'noscript',
    'iframe',
    'svg',
    'canvas',
    'nav',
    'footer',
    'header',
    'aside',
    '.ad',
    '.ads',
    '.advertisement',
    '.comment',
    '.comments',
    '.sidebar',
    '.recommend',
    '[role="navigation"]',
    '[aria-hidden="true"]'
  ]

  removeSelectors.forEach(selector => {
    clone.querySelectorAll(selector).forEach(el => el.remove())
  })

  const title = document.title || ''
  const headings = Array.from(clone.querySelectorAll('h1,h2,h3'))
    .map(el => el.innerText.trim())
    .filter(Boolean)
    .slice(0, 20)

  const paragraphs = Array.from(clone.querySelectorAll('p,article,main,section'))
    .map(el => el.innerText.trim())
    .filter(text => text.length > 30)

  const content = paragraphs.join('\n\n')
    .replace(/\n{3,}/g, '\n\n')
    .replace(/[ \t]{2,}/g, ' ')
    .trim()

  return {
    title,
    url: location.href,
    headings,
    content: content.slice(0, 30000),
    length: content.length
  }
}

这个脚本做了几件事:

  • 克隆 document.body,避免直接修改页面;
  • 删除无用节点;
  • 提取标题与正文;
  • 过滤过短文本;
  • 限制最大字符长度;
  • 返回结构化数据。

2. 防止频繁解析页面

SPA 网站内容可能不断变化,如果每次 DOM 变化都立即解析,会严重影响性能。

可以使用防抖:

function debounce(fn, delay = 500) {
  let timer = null

  return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay)
  }
}

const handlePageChanged = debounce(() => {
  const data = extractPageContent()
  window.postMessage({
    type: 'AI_BROWSER_PAGE_CONTENT',
    payload: data
  })
}, 1000)

const observer = new MutationObserver(handlePageChanged)

observer.observe(document.body, {
  childList: true,
  subtree: true
})

同时,在标签页关闭或页面卸载时必须释放监听:

window.addEventListener('beforeunload', () => {
  observer.disconnect()
})

六、Prompt 压缩优化

AI 浏览器很容易把整篇网页内容直接塞给大模型,但这会造成三个问题:

  1. 请求变慢;
  2. token 成本升高;
  3. 模型容易忽略重点。

推荐做法是先压缩上下文。

1. 基础文本压缩函数

function compressText(text, maxLength = 8000) {
  if (!text) return ''

  const cleanText = text
    .replace(/\s+/g, ' ')
    .replace(/相关推荐|广告|分享到|登录|注册/g, '')
    .trim()

  if (cleanText.length <= maxLength) {
    return cleanText
  }

  const start = cleanText.slice(0, Math.floor(maxLength * 0.45))
  const middleStart = Math.floor(cleanText.length * 0.45)
  const middle = cleanText.slice(middleStart, middleStart + Math.floor(maxLength * 0.25))
  const end = cleanText.slice(-Math.floor(maxLength * 0.3))

  return `${start}\n\n[中间内容已压缩]\n\n${middle}\n\n[部分内容省略]\n\n${end}`
}

这种方式虽然简单,但对于网页总结类任务已经有明显效果。


2. 结构化 Prompt

不要写过于宽泛的 Prompt,例如:

请总结这个网页

更推荐结构化 Prompt:

function buildSummaryPrompt(page) {
  const content = compressText(page.content, 8000)

  return `
你是一个专业的网页阅读助手。请根据下面的网页内容生成中文总结。

要求:
1. 用简洁清晰的语言总结;
2. 输出 5 个核心要点;
3. 如果网页包含教程步骤,请按步骤整理;
4. 如果内容中存在数据、结论或观点,请单独列出;
5. 不要编造网页中不存在的信息。

网页标题:${page.title}
网页地址:${page.url}

网页正文:
${content}

请按以下格式输出:

## 一句话总结
...

## 核心要点
1. ...
2. ...
3. ...
4. ...
5. ...

## 重要细节
- ...

## 适合继续追问的问题
- ...
`
}

结构化 Prompt 的好处是:

  • 模型输出更稳定;
  • 减少无效废话;
  • 降低二次修正成本;
  • 前端更容易渲染结果。

七、AI 流式响应优化

流式输出是 AI 浏览器体验优化的关键。

用户不一定要求总耗时最低,但希望“尽快看到内容出现”。
因此,AI 接口应该优先使用 stream。

下面以 Node.js 服务端为例。

async function streamAIResponse({ prompt, res }) {
  const client = await getAIClient()

  const stream = await client.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [
      {
        role: 'user',
        content: prompt
      }
    ],
    temperature: 0.3,
    stream: true
  })

  res.setHeader('Content-Type', 'text/event-stream; charset=utf-8')
  res.setHeader('Cache-Control', 'no-cache')
  res.setHeader('Connection', 'keep-alive')

  for await (const chunk of stream) {
    const text = chunk.choices?.[0]?.delta?.content || ''
    if (text) {
      res.write(`data: ${JSON.stringify({ text })}\n\n`)
    }
  }

  res.write(`data: ${JSON.stringify({ done: true })}\n\n`)
  res.end()
}

前端接收:

async function requestSummary(prompt, onText) {
  const response = await fetch('/api/ai/summary', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ prompt })
  })

  const reader = response.body.getReader()
  const decoder = new TextDecoder('utf-8')

  while (true) {
    const { value, done } = await reader.read()
    if (done) break

    const chunk = decoder.decode(value, { stream: true })
    const lines = chunk.split('\n\n').filter(Boolean)

    for (const line of lines) {
      if (!line.startsWith('data:')) continue

      const json = line.replace(/^data:\s*/, '')
      const data = JSON.parse(json)

      if (data.text) {
        onText(data.text)
      }
    }
  }
}

八、流式渲染防卡顿优化

如果每次收到一个 token 都更新页面,会导致 React 或 Vue 频繁渲染。

推荐使用缓冲区,每 50ms 或 100ms 批量更新一次。

React 示例源码

import React, { useRef, useState, useEffect } from 'react'

export default function AIStreamPanel() {
  const [content, setContent] = useState('')
  const bufferRef = useRef('')
  const timerRef = useRef(null)

  function appendText(text) {
    bufferRef.current += text

    if (!timerRef.current) {
      timerRef.current = setTimeout(() => {
        setContent(prev => prev + bufferRef.current)
        bufferRef.current = ''
        timerRef.current = null
      }, 80)
    }
  }

  async function startSummary() {
    setContent('')

    await requestSummary('请总结当前网页', appendText)
  }

  useEffect(() => {
    return () => {
      if (timerRef.current) {
        clearTimeout(timerRef.current)
      }
    }
  }, [])

  return (
    
{content}
) }

这样可以把几十次甚至几百次状态更新合并成较少的渲染更新,大幅减少卡顿。


九、请求缓存优化

对于网页总结、翻译、解释类任务,同一个页面经常会被重复请求。
可以用缓存减少接口调用。

1. 生成缓存 key

import crypto from 'crypto'

function createCacheKey({ url, task, content }) {
  const hash = crypto
    .createHash('sha256')
    .update(url + task + content.slice(0, 5000))
    .digest('hex')

  return `ai:${task}:${hash}`
}

2. 简单内存缓存

class LRUCache {
  constructor(max = 100) {
    this.max = max
    this.cache = new Map()
  }

  get(key) {
    if (!this.cache.has(key)) return null

    const value = this.cache.get(key)
    this.cache.delete(key)
    this.cache.set(key, value)

    return value
  }

  set(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key)
    }

    this.cache.set(key, {
      value,
      time: Date.now()
    })

    if (this.cache.size > this.max) {
      const firstKey = this.cache.keys().next().value
      this.cache.delete(firstKey)
    }
  }
}

const aiCache = new LRUCache(200)

3. 使用缓存

async function summarizePage(page) {
  const prompt = buildSummaryPrompt(page)

  const key = createCacheKey({
    url: page.url,
    task: 'summary',
    content: page.content
  })

  const cached = aiCache.get(key)
  if (cached) {
    return {
      fromCache: true,
      result: cached.value
    }
  }

  const result = await callAI(prompt)

  aiCache.set(key, result)

  return {
    fromCache: false,
    result
  }
}

需要注意的是,流式接口不太适合直接返回完整缓存。实际开发中可以这样处理:

  • 如果有缓存,直接展示完整结果;
  • 如果无缓存,使用流式生成;
  • 流式结束后把完整结果写入缓存。

十、并发控制与请求取消

AI 浏览器中用户可能连续点击“总结”“翻译”“解释”。
如果不处理,会产生多个无意义请求。

1. 前端 AbortController

let currentController = null

async function requestAI(prompt, onText) {
  if (currentController) {
    currentController.abort()
  }

  currentController = new AbortController()

  try {
    const response = await fetch('/api/ai', {
      method: 'POST',
      signal: currentController.signal,
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ prompt })
    })

    const reader = response.body.getReader()
    const decoder = new TextDecoder()

    while (true) {
      const { value, done } = await reader.read()
      if (done) break
      onText(decoder.decode(value))
    }
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('AI 请求已取消')
    } else {
      throw err
    }
  }
}

2. 服务端并发队列

class TaskQueue {
  constructor(limit = 3) {
    this.limit = limit
    this.running = 0
    this.queue = []
  }

  add(task) {
    return new Promise((resolve, reject) => {
      this.queue.push({
        task,
        resolve,
        reject
      })
      this.next()
    })
  }

  next() {
    if (this.running >= this.limit) return
    const item = this.queue.shift()
    if (!item) return

    this.running++

    item.task()
      .then(item.resolve)
      .catch(item.reject)
      .finally(() => {
        this.running--
        this.next()
      })
  }
}

const aiQueue = new TaskQueue(5)

使用方式:

app.post('/api/ai/summary', async (req, res) => {
  await aiQueue.add(() => streamAIResponse({
    prompt: req.body.prompt,
    res
  }))
})

并发控制可以避免:

  • 服务端瞬间打满;
  • API 额度快速消耗;
  • 多个请求互相抢占资源;
  • AI 浏览器整体响应变慢。

十一、模型路由优化

并不是所有任务都需要使用最强模型。

AI 浏览器常见任务可以按复杂度分层:

任务类型 推荐模型策略
网页标题生成 小模型
网页摘要 中小模型
翻译 中小模型
代码解释 中等模型
多网页对比 大模型
长文档分析 大模型 + RAG
本地命令理解 小模型或规则引擎

示例代码:

function selectModel(task, contentLength) {
  if (task === 'title') {
    return 'gpt-4o-mini'
  }

  if (task === 'translate') {
    return contentLength > 10000
      ? 'gpt-4o'
      : 'gpt-4o-mini'
  }

  if (task === 'summary') {
    return contentLength > 20000
      ? 'gpt-4o'
      : 'gpt-4o-mini'
  }

  if (task === 'compare') {
    return 'gpt-4o'
  }

  return 'gpt-4o-mini'
}

模型路由的核心目标是:

  • 简单任务用便宜快模型;
  • 复杂任务用强模型;
  • 控制响应速度;
  • 控制成本;
  • 避免“大炮打蚊子”。

十二、Web Worker 优化文本处理

网页正文清洗、分词、摘要预处理、向量切片等任务,如果放在主线程执行,可能导致界面卡顿。

可以使用 Web Worker。

worker.js

self.onmessage = function (event) {
  const { type, payload } = event.data

  if (type === 'COMPRESS_TEXT') {
    const result = compressText(payload.text, payload.maxLength)
    self.postMessage({
      type: 'COMPRESS_TEXT_DONE',
      payload: result
    })
  }
}

function compressText(text, maxLength = 8000) {
  if (!text) return ''

  const cleanText = text
    .replace(/\s+/g, ' ')
    .trim()

  if (cleanText.length <= maxLength) {
    return cleanText
  }

  return cleanText.slice(0, maxLength)
}

主线程使用

function compressTextInWorker(text) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('/worker.js')

    worker.postMessage({
      type: 'COMPRESS_TEXT',
      payload: {
        text,
        maxLength: 8000
      }
    })

    worker.onmessage = event => {
      if (event.data.type === 'COMPRESS_TEXT_DONE') {
        resolve(event.data.payload)
        worker.terminate()
      }
    }

    worker.onerror = err => {
      reject(err)
      worker.terminate()
    }
  })
}

对于高频任务,建议复用 Worker,而不是每次都创建新 Worker。


十三、标签页生命周期优化

AI 浏览器通常支持多个标签页。
每个标签页都可能注入内容脚本、AI 上下文、页面监听器。
如果没有生命周期管理,内存会持续上涨。

可以设计一个 TabManager。

class TabManager {
  constructor() {
    this.tabs = new Map()
  }

  createTab(id, webview) {
    this.tabs.set(id, {
      id,
      webview,
      aiContext: null,
      lastActiveTime: Date.now(),
      status: 'active'
    })
  }

  activateTab(id) {
    for (const tab of this.tabs.values()) {
      tab.status = 'background'
    }

    const tab = this.tabs.get(id)
    if (tab) {
      tab.status = 'active'
      tab.lastActiveTime = Date.now()
    }
  }

  closeTab(id) {
    const tab = this.tabs.get(id)
    if (!tab) return

    if (tab.webview) {
      tab.webview.remove()
    }

    tab.aiContext = null
    this.tabs.delete(id)
  }

  cleanupInactiveTabs(maxIdleTime = 30 * 60 * 1000) {
    const now = Date.now()

    for (const [id, tab] of this.tabs.entries()) {
      if (
        tab.status === 'background' &&
        now - tab.lastActiveTime > maxIdleTime
      ) {
        tab.aiContext = null
      }
    }
  }
}

优化原则:

  • 当前标签页保留完整上下文;
  • 后台标签页降低更新频率;
  • 长时间未使用标签页释放 AI 上下文;
  • 关闭标签页时释放 DOM、事件、请求;
  • 避免每个标签页都持有大文本。

十四、RAG 检索优化

如果 AI 浏览器支持“问当前网页”或“问多个网页”,就会用到 RAG。

基本流程:

  1. 提取网页正文;
  2. 文本切片;
  3. 生成向量;
  4. 存储向量;
  5. 用户提问时检索相关片段;
  6. 把相关片段交给大模型回答。

性能优化重点在切片和检索。

文本切片源码

function splitText(text, chunkSize = 800, overlap = 100) {
  const chunks = []
  let start = 0

  while (start < text.length) {
    const end = Math.min(start + chunkSize, text.length)
    const chunk = text.slice(start, end)

    chunks.push({
      text: chunk,
      start,
      end
    })

    start += chunkSize - overlap
  }

  return chunks
}

简单关键词检索示例

如果暂时不接入向量数据库,也可以先用关键词评分实现轻量检索。

function keywordSearch(query, chunks, topK = 5) {
  const words = query
    .toLowerCase()
    .split(/\s+/)
    .filter(Boolean)

  return chunks
    .map(chunk => {
      const text = chunk.text.toLowerCase()
      let score = 0

      for (const word of words) {
        if (text.includes(word)) {
          score += 1
        }
      }

      return {
        ...chunk,
        score
      }
    })
    .filter(item => item.score > 0)
    .sort((a, b) => b.score - a.score)
    .slice(0, topK)
}

构建问答 Prompt

function buildQAPrompt(question, chunks) {
  const context = chunks
    .map((item, index) => `片段 ${index + 1}:\n${item.text}`)
    .join('\n\n')

  return `
你是一个网页问答助手。请只根据提供的网页片段回答问题。

要求:
1. 如果片段中没有答案,请回答“当前网页内容中没有找到明确答案”;
2. 不要编造信息;
3. 回答要简洁;
4. 必要时引用片段中的关键句。

网页片段:
${context}

用户问题:
${question}

请给出答案:
`
}

RAG 的好处是不用每次把整篇网页都传给模型,可以显著降低 token 消耗和响应延迟。


十五、接口完整示例源码

下面给出一个简化版 AI 浏览器后端接口,包括总结、缓存、模型选择和流式响应。

import express from 'express'
import crypto from 'crypto'
import OpenAI from 'openai'

const app = express()
app.use(express.json({ limit: '2mb' }))

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
})

class LRUCache {
  constructor(max = 100) {
    this.max = max
    this.cache = new Map()
  }

  get(key) {
    if (!this.cache.has(key)) return null
    const value = this.cache.get(key)
    this.cache.delete(key)
    this.cache.set(key, value)
    return value
  }

  set(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key)
    }

    this.cache.set(key, value)

    if (this.cache.size > this.max) {
      const firstKey = this.cache.keys().next().value
      this.cache.delete(firstKey)
    }
  }
}

const cache = new LRUCache(200)

function createCacheKey(page, task) {
  return crypto
    .createHash('sha256')
    .update(task + page.url + page.content.slice(0, 5000))
    .digest('hex')
}

function compressText(text, maxLength = 8000) {
  if (!text) return ''

  const cleanText = text
    .replace(/\s+/g, ' ')
    .trim()

  if (cleanText.length <= maxLength) return cleanText

  return cleanText.slice(0, maxLength)
}

function buildPrompt(page) {
  return `
请总结以下网页内容。

要求:
1. 输出一句话总结;
2. 输出 5 个核心要点;
3. 输出重要结论;
4. 不要编造原文没有的信息。

标题:${page.title}
地址:${page.url}

正文:
${compressText(page.content, 8000)}
`
}

function selectModel(contentLength) {
  return contentLength > 20000 ? 'gpt-4o' : 'gpt-4o-mini'
}

app.post('/api/summary', async (req, res) => {
  const page = req.body.page

  if (!page || !page.content) {
    res.status(400).json({
      error: '缺少 page.content'
    })
    return
  }

  const cacheKey = createCacheKey(page, 'summary')
  const cached = cache.get(cacheKey)

  if (cached) {
    res.json({
      fromCache: true,
      result: cached
    })
    return
  }

  const prompt = buildPrompt(page)
  const model = selectModel(page.content.length)

  res.setHeader('Content-Type', 'text/event-stream; charset=utf-8')
  res.setHeader('Cache-Control', 'no-cache')
  res.setHeader('Connection', 'keep-alive')

  let fullText = ''

  try {
    const stream = await client.chat.completions.create({
      model,
      messages: [
        {
          role: 'user',
          content: prompt
        }
      ],
      temperature: 0.3,
      stream: true
    })

    for await (const chunk of stream) {
      const text = chunk.choices?.[0]?.delta?.content || ''

      if (text) {
        fullText += text
        res.write(`data: ${JSON.stringify({ text })}\n\n`)
      }
    }

    cache.set(cacheKey, fullText)

    res.write(`data: ${JSON.stringify({ done: true })}\n\n`)
    res.end()
  } catch (err) {
    res.write(`data: ${JSON.stringify({
      error: err.message || 'AI 请求失败'
    })}\n\n`)
    res.end()
  }
})

app.listen(3000, () => {
  console.log('AI Browser Server running at http://localhost:3000')
})

十六、性能监控指标

优化不能只靠感觉,必须有指标。

AI 浏览器建议重点监控以下指标:

指标 含义
App Startup Time 应用启动时间
First Paint 首屏绘制时间
Webview Load Time 网页加载时间
AI First Token Time AI 首 token 时间
AI Total Time AI 完整响应时间
Memory Usage 内存占用
CPU Usage CPU 使用率
Cache Hit Rate 缓存命中率
Token Usage Token 消耗
Error Rate AI 请求失败率

前端可以简单记录:

const perf = {
  start: performance.now()
}

function mark(name) {
  perf[name] = performance.now()
}

function report() {
  console.table({
    firstToken: perf.firstToken - perf.start,
    finished: perf.finished - perf.start
  })
}

在 AI 流式请求中:

let hasFirstToken = false

requestAI(prompt, text => {
  if (!hasFirstToken) {
    hasFirstToken = true
    mark('firstToken')
  }

  appendText(text)
}).then(() => {
  mark('finished')
  report()
})

十七、推荐优化清单

最后给出一份可直接用于项目排查的优化清单。

启动优化

  • [ ] AI SDK 是否延迟加载;
  • [ ] 数据库是否懒初始化;
  • [ ] 首屏是否加载过多组件;
  • [ ] 是否启用代码分割;
  • [ ] 是否避免启动时读取全部历史记录。

网页解析优化

  • [ ] 是否过滤广告、导航、评论;
  • [ ] 是否限制最大正文长度;
  • [ ] 是否对 DOM 变化做防抖;
  • [ ] 是否在页面卸载时释放监听;
  • [ ] 是否避免频繁跨进程传输大文本。

AI 请求优化

  • [ ] 是否使用流式响应;
  • [ ] 是否有 Prompt 压缩;
  • [ ] 是否有缓存;
  • [ ] 是否支持取消请求;
  • [ ] 是否有超时机制;
  • [ ] 是否做模型路由。

渲染优化

  • [ ] 流式输出是否批量更新;
  • [ ] 长对话是否使用虚拟列表;
  • [ ] Markdown 渲染是否做缓存;
  • [ ] 大文本处理是否放到 Worker;
  • [ ] 是否避免无意义的全局状态更新。

内存优化

  • [ ] 标签页关闭是否释放资源;
  • [ ] WebSocket 是否关闭;
  • [ ] MutationObserver 是否断开;
  • [ ] 缓存是否设置上限;
  • [ ] 后台标签页是否降低活跃度。

十八、总结

AI 浏览器的性能优化,本质上是浏览器工程、前端工程、后端服务和大模型工程的综合优化。

不要把性能问题简单归因于“大模型慢”。
在实际项目中,很多卡顿和延迟来自:

  • 启动阶段加载过多模块;
  • 网页正文提取过于粗暴;
  • Prompt 未压缩;
  • 流式响应没有合理渲染;
  • 请求没有取消;
  • 缓存没有上限;
  • 标签页生命周期管理缺失;
  • 多任务并发失控。

一款体验优秀的 AI 浏览器,应该做到:

  1. 启动快:AI 能力按需加载;
  2. 解析准:只提取有价值内容;
  3. 响应快:流式输出,尽快返回首 token;
  4. 成本低:缓存、压缩、模型路由;
  5. 不卡顿:批量渲染、Worker 处理重任务;
  6. 可持续运行:控制内存、释放资源、管理标签页生命周期。

如果你正在开发 AI 浏览器,建议优先落地以下五个优化:

  • 网页正文清洗与长度限制;
  • AI 流式响应;
  • 流式渲染批量更新;
  • LRU 缓存;
  • AbortController 请求取消。

这五项优化投入不高,但对用户体验提升非常明显。
后续再逐步引入 RAG、向量检索、模型路由、标签页资源回收和性能监控系统,就可以构建一款真正稳定、快速、低成本的 AI 浏览器。

目录结构
全文