解密openclaw底层pi-mono架构系列一:5. pi-web-ui

解密openclaw底层pi-mono架构系列一:5. pi-web-ui

>>关注,点赞,转发私信发代码<<

>>关注,点赞,转发私信发代码<<

>>关注,点赞,转发私信发代码<<

前四篇我们依次构建了 pi-ai(统一 LLM API)、pi-agent-core(智能体运行时)、pi-tui(终端 UI)、pi-coding-agent(终端编程助手)。它们都运行在 Node.js 或终端中。但如果想让普通用户也能用上 AI 能力呢?答案就是本篇主角------pi-web-ui,一套浏览器端的 AI 聊天界面组件。


目录

  1. [pi-web-ui 是什么](#pi-web-ui 是什么)
  2. 整体架构:从终端到浏览器
  3. ChatPanel:一步搞定聊天界面
  4. [自定义布局:ChatPanel + 外部框架](#自定义布局:ChatPanel + 外部框架)
  5. 浏览器存储系统
  6. [消息类型与 Artifact](#消息类型与 Artifact)
  7. 浏览器端工具
  8. [CORS 代理:跨域问题的解法](#CORS 代理:跨域问题的解法)
  9. 主题与样式
  10. [实战:AI 客服系统](#实战:AI 客服系统)
  11. 总结与下一步

1. pi-web-ui 是什么

一句话定义

pi-web-ui = 浏览器版的 pi-agent + Web Components UI

它把 pi-aipi-agent-core 的能力搬到了浏览器中,并提供一套开箱即用的聊天界面组件。无论你在做什么 Web 应用------电商、SaaS、内部工具------只要几行 TypeScript,就能嵌入 AI 对话能力。

类比理解

场景 类比 对应的包
终端里和 AI 聊天 在命令行里打电话 pi-coding-agent
浏览器里和 AI 聊天 在网页上开视频会议 pi-web-ui

就像把终端里的"电话"升级成了网页上的"视频会议"------功能一样,但用起来更直观、更适合普通用户。

核心 API

作用 来自
ChatPanel 完整聊天界面组件(Web Component) pi-web-ui
Agent 智能体运行时(管理模型、消息、工具循环) pi-agent-core
getModel() 获取 LLM 模型配置 pi-ai
AppStorage 统一存储管理(会话、Key、设置) pi-web-ui

使用方式 :创建 Agent → 创建 ChatPanel → 调用 chatPanel.setAgent(agent) 关联 → 用 Lit 渲染到页面。

初始化流程


2. 整体架构:从终端到浏览器

组件架构图

数据流架构图

技术栈

复制代码
Lit               Web Components 渲染(html`...` 模板语法)
Tailwind CSS v4   原子化样式
IndexedDB         浏览器本地存储(会话、设置、API Key)
Vite              构建工具(解析 bare imports、打包)

注意 :pi-web-ui 使用 bare imports(如 import { html } from "lit"),浏览器无法直接解析,必须通过 Vite 打包 。不支持 <script src="..."> 直接引入。

数据流

pi-web-ui 的数据流非常清晰:

复制代码
用户在浏览器输入
    │
    ▼
┌──────────────────┐
│    ChatPanel     │  ← UI 组件层(Web Component)
│  (Lit 渲染)      │
└────────┬─────────┘
         │ setAgent()
         ▼
┌──────────────────┐
│  pi-agent-core   │  ← Agent 运行时(浏览器版本)
│  (浏览器 bundle) │
└────────┬─────────┘
         │ 通过
         ▼
┌──────────────────┐
│     pi-ai        │  ← 统一 LLM API(浏览器版本)
│  (浏览器 bundle) │
└────────┬─────────┘
         │ HTTP 请求
         ▼
┌──────────────────┐
│   CORS 代理      │  ← 解决浏览器跨域限制
│  /?url=<encoded> │
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│    LLM API       │  ← MiniMax / Anthropic / OpenAI ...
│  (云端服务)      │
└──────────────────┘
         │ 流式响应
         ▼
    AgentEvent 流
         │
         ▼
    更新 UI 组件

关键区别 :和终端版的 pi-coding-agent 相比,浏览器版多了一层 CORS 代理,因为浏览器不允许直接跨域请求 LLM API。

源码结构

复制代码
packages/web-ui/src/
├── components/              # UI 组件
│   ├── ChatPanel.ts         # 高级:完整聊天界面
│   ├── AgentInterface.ts    # 低级:可定制的 Agent 聊天区域
│   ├── MessageList.ts       # 消息列表
│   ├── MessageItem.ts       # 单条消息
│   ├── InputBar.ts          # 输入框
│   └── ModelSelector.ts     # 模型选择器
│
├── storage/                 # IndexedDB 存储
│   ├── SessionsStore.ts     # 会话持久化
│   ├── ProviderKeysStore.ts # API Key(加密存储)
│   ├── SettingsStore.ts     # 用户设置(含代理配置)
│   └── CustomProvidersStore.ts # 自定义 LLM 提供商
│
├── tools/                   # 浏览器端工具
│   ├── js-repl.ts           # JavaScript 沙箱执行
│   └── artifacts.ts         # Artifact 渲染
│
├── dialogs/                 # 弹窗组件
│   ├── ApiKeyPromptDialog.ts # API Key 输入弹窗
│   └── SettingsDialog.ts     # 设置面板(代理/主题等)
│
└── utils/
    └── proxy-utils.ts       # CORS 代理工具函数

3. ChatPanel:一步搞定聊天界面

ChatPanel 是最高层的组件,开箱即用。它的 API 是纯 TypeScript,通过 Lit 渲染到 DOM。

最简示例

对应 examples/05-pi-web-ui/src/01-chat-panel.ts

第一步:创建 Vite 项目

bash 复制代码
# package.json 核心依赖
{
  "dependencies": {
    "@mariozechner/pi-ai": "...",
    "@mariozechner/pi-agent-core": "...",
    "@mariozechner/pi-web-ui": "...",
    "@tailwindcss/vite": "^4.1.17",
    "lit": "^3.3.1"
  }
}

第二步:CSS 入口 (src/app.css)

css 复制代码
@import "tailwindcss";
@import "@mariozechner/pi-web-ui/app.css";

注意路径是 @mariozechner/pi-web-ui/app.css(不是 /dist/app.css),对应 package.json 的 exports 配置。

第三步:HTML 页面 (01-chat-panel.html)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>ChatPanel 示例</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="./src/01-chat-panel.ts"></script>
</body>
</html>

第四步:TypeScript 代码 (src/01-chat-panel.ts)

typescript 复制代码
import { Agent } from "@mariozechner/pi-agent-core";
import { getModel } from "@mariozechner/pi-ai";
import {
  ApiKeyPromptDialog,
  AppStorage,
  ChatPanel,
  createJavaScriptReplTool,
  IndexedDBStorageBackend,
  ProviderKeysStore,
  SessionsStore,
  SettingsStore,
  CustomProvidersStore,
  setAppStorage,
} from "@mariozechner/pi-web-ui";
import { html, render } from "lit";
import "./app.css";

// ① 初始化存储(IndexedDB)
const settings = new SettingsStore();
const providerKeys = new ProviderKeysStore();
const sessions = new SessionsStore();
const customProviders = new CustomProvidersStore();

const backend = new IndexedDBStorageBackend({
  dbName: "pi-web-ui-example-01",
  version: 2,
  stores: [
    settings.getConfig(),
    SessionsStore.getMetadataConfig(),
    providerKeys.getConfig(),
    customProviders.getConfig(),
    sessions.getConfig(),
  ],
});

settings.setBackend(backend);
providerKeys.setBackend(backend);
customProviders.setBackend(backend);
sessions.setBackend(backend);

const storage = new AppStorage(settings, providerKeys, sessions, customProviders, backend);
setAppStorage(storage);

// 预配置 CORS 代理(浏览器跨域必需)
await settings.set("proxy.url", "http://localhost:3001");
await settings.set("proxy.enabled", true);

// ② 创建 Agent ------ 使用 MiniMax-M2.5 模型
const agent = new Agent({
  initialState: {
    systemPrompt: "你是一个友善的 AI 助手,用中文回答问题。回答简洁清晰。",
    model: getModel("minimax-cn", "MiniMax-M2.5"),
    thinkingLevel: "off",
    messages: [],
    tools: [],
  },
});

// ③ 创建 ChatPanel 并关联 Agent
const chatPanel = new ChatPanel();

await chatPanel.setAgent(agent, {
  // 当需要 API Key 时弹出输入框
  onApiKeyRequired: async (provider: string) => {
    return await ApiKeyPromptDialog.prompt(provider);
  },
  // 配置工具:JavaScript REPL
  toolsFactory: (_agent, _agentInterface, _artifactsPanel, runtimeProvidersFactory) => {
    const replTool = createJavaScriptReplTool();
    replTool.runtimeProvidersFactory = runtimeProvidersFactory;
    return [replTool];
  },
});

// ④ 渲染到页面
const app = document.getElementById("app")!;
render(
  html`
    <div class="w-full h-screen flex flex-col bg-background text-foreground">
      ${chatPanel}
    </div>
  `,
  app,
);

代码解析:四步走

复制代码
① 初始化存储
   创建 4 个 Store → 绑定 IndexedDB 后端 → 注册为全局 AppStorage
   这是 pi-web-ui 的固定模板代码,每个示例都需要。

② 创建 Agent
   Agent 来自 pi-agent-core,配置 systemPrompt、model、tools。
   getModel("minimax-cn", "MiniMax-M2.5") 从 pi-ai 获取模型配置。

③ 关联 ChatPanel 和 Agent
   chatPanel.setAgent(agent, config) 是核心 API。
   config 中可以配置 API Key 弹窗、工具工厂等。

④ 渲染
   ChatPanel 是 Web Component,直接用 Lit 的 html`` 模板嵌入。
   Tailwind CSS 类名控制全屏布局。

运行效果

ChatPanel 自动渲染了完整的聊天界面:消息列表、输入框、模型选择器(右下角 MiniMax-M2.5)、思考深度开关。

ChatPanel 内置了什么?

不要被简单的 ${chatPanel} 骗了,ChatPanel 内部其实包含了完整的功能链:

复制代码
ChatPanel
  ├── 模型选择器(切换 MiniMax/Claude/GPT...)
  ├── 思考深度选择器(off/brief/detailed)
  ├── 消息列表(自动滚动、Markdown 渲染)
  ├── 输入框(支持多行、文件拖放)
  ├── 会话管理(新建/切换/删除会话)
  ├── API Key 管理(安全存储在 IndexedDB)
  └── 设置面板(代理/主题/语言/默认模型)

4. 自定义布局:ChatPanel + 外部框架

当 ChatPanel 的默认布局不满足需求时,可以用自定义 HTML 把 ChatPanel 包裹起来,在外部添加顶栏、状态栏等。

对应 examples/05-pi-web-ui/src/02-agent-interface.ts

自定义顶栏 + 状态栏

typescript 复制代码
import { Agent } from "@mariozechner/pi-agent-core";
import { getModel } from "@mariozechner/pi-ai";
import {
  ApiKeyPromptDialog, AppStorage, ChatPanel, createJavaScriptReplTool,
  IndexedDBStorageBackend, ProviderKeysStore, SessionsStore, SettingsStore,
  CustomProvidersStore, setAppStorage,
} from "@mariozechner/pi-web-ui";
import { html, render } from "lit";
import "./app.css";

// ... 存储初始化(同上,省略) ...

// 状态变量
let tokenCount = 0;
let status = "就绪";

// 创建 Agent
const agent = new Agent({
  initialState: {
    systemPrompt: "你是一个专业的代码审查助手,用中文回答。",
    model: getModel("minimax-cn", "MiniMax-M2.5"),
    thinkingLevel: "off",
    messages: [],
    tools: [],
  },
});

// 监听 Agent 事件,更新自定义状态栏
agent.subscribe((event: any) => {
  if (event.type === "message_update") {
    status = "AI 正在回复...";
    renderApp();
  }
  if (event.type === "turn_end") {
    tokenCount += event.usage?.totalTokens ?? 0;
    status = "就绪";
    renderApp();
  }
});

// 创建 ChatPanel
const chatPanel = new ChatPanel();
await chatPanel.setAgent(agent, { /* ... */ });

// 渲染:自定义顶栏 + ChatPanel + 底部状态栏
const app = document.getElementById("app")!;

function renderApp() {
  render(
    html`
      <div class="w-full h-screen flex flex-col bg-background text-foreground overflow-hidden">
        <!-- 自定义顶栏 -->
        <div class="flex items-center justify-between px-5 py-3 border-b border-border shrink-0">
          <span class="text-sm font-semibold">AI 编程助手</span>
          <span class="text-xs px-3 py-1 rounded-full bg-secondary text-muted-foreground">
            MiniMax-M2.5
          </span>
        </div>

        <!-- 聊天区域(ChatPanel 提供完整功能) -->
        <div class="flex-1 min-h-0">
          ${chatPanel}
        </div>

        <!-- 自定义底部状态栏 -->
        <div class="flex items-center justify-between px-5 py-2 border-t border-border text-xs text-muted-foreground shrink-0">
          <span>${tokenCount} tokens</span>
          <span>${status}</span>
        </div>
      </div>
    `,
    app,
  );
}

renderApp();

运行效果

顶栏显示"AI 编程助手"和模型标签,底部状态栏实时显示 token 数和状态。

关键点

  1. ChatPanel 只负责聊天区域,外层布局完全由你的 HTML 控制
  2. agent.subscribe() 监听 Agent 事件,可以在自定义 UI 中实时更新 token 数、状态等
  3. renderApp() 函数 在状态变化时重新渲染整个布局(Lit 会做差分更新,性能无忧)

ChatPanel 快速上手 vs 自定义布局 对比

对比项 快速上手(示例 01) 自定义布局(示例 02)
布局 ChatPanel 占满全屏 自定义顶栏 + ChatPanel + 状态栏
代码量 ~50 行 ~80 行
状态监控 内置 agent.subscribe() 自定义
适合场景 快速原型、独立页面 嵌入现有系统

类比:快速上手像买精装房------拎包入住;自定义布局像买毛坯房------自己装修。


5. 浏览器存储系统

pi-web-ui 使用 IndexedDB 在浏览器本地持久化数据,关闭页面后数据不会丢失。

存储层架构

复制代码
IndexedDB(浏览器本地数据库)
  ├── SettingsStore          用户偏好(主题/语言/代理配置)
  ├── ProviderKeysStore      API Key(加密存储)
  ├── SessionsStore          会话记录(消息历史)
  └── CustomProvidersStore   自定义 LLM 提供商

初始化模板(每个示例都需要)

typescript 复制代码
import {
  AppStorage, IndexedDBStorageBackend,
  ProviderKeysStore, SessionsStore, SettingsStore,
  CustomProvidersStore, setAppStorage,
} from "@mariozechner/pi-web-ui";

// 创建 4 个 Store
const settings = new SettingsStore();
const providerKeys = new ProviderKeysStore();
const sessions = new SessionsStore();
const customProviders = new CustomProvidersStore();

// 创建 IndexedDB 后端,注册所有 Store 的配置
const backend = new IndexedDBStorageBackend({
  dbName: "my-app",       // 数据库名,不同应用用不同名字
  version: 2,
  stores: [
    settings.getConfig(),
    SessionsStore.getMetadataConfig(),
    providerKeys.getConfig(),
    customProviders.getConfig(),
    sessions.getConfig(),
  ],
});

// 绑定后端
settings.setBackend(backend);
providerKeys.setBackend(backend);
customProviders.setBackend(backend);
sessions.setBackend(backend);

// 注册为全局存储(ChatPanel 内部会通过 getAppStorage() 读取)
const storage = new AppStorage(settings, providerKeys, sessions, customProviders, backend);
setAppStorage(storage);

常用操作

typescript 复制代码
// --- 设置存储 ---
await settings.set("proxy.url", "http://localhost:3001");
await settings.set("proxy.enabled", true);
const proxyUrl = await settings.get<string>("proxy.url");

// --- API Key 存储 ---
await providerKeys.setKey("minimax-cn", "your-minimax-api-key");
const key = await providerKeys.getKey("minimax-cn");

安全说明:API Key 存储在 IndexedDB 中。但浏览器端存储的安全性有限,生产环境建议从后端获取临时 Token,不要在前端硬编码 Key。


6. 消息类型与 Artifact

三类消息

pi-web-ui 支持三类消息,覆盖文本、附件、可执行内容:

复制代码
消息类型系统
  ├── 标准消息        文本 / 思考内容 / 工具结果
  ├── 附件消息        图片 / PDF / 文件
  └── Artifact 消息   HTML / SVG / Markdown(可执行渲染)

标准消息

typescript 复制代码
// 用户消息
const userMsg = {
  type: "user",
  content: [{ type: "text", text: "你好!" }],
};

// 助手回复(带思考过程)
const assistantMsg = {
  type: "assistant",
  content: [
    { type: "thinking", thinking: "用户在打招呼,我应该友善回复..." },
    { type: "text", text: "你好!有什么可以帮你的吗?" },
  ],
};

附件消息

typescript 复制代码
// 带图片的消息(拖拽上传或粘贴)
const withImage = {
  type: "userWithAttachments",
  text: "这段代码有什么问题?",
  attachments: [{
    type: "image",
    mimeType: "image/png",
    data: base64EncodedData,
  }],
};

Artifact 消息(亮点功能)

Artifact 是 pi-web-ui 的特色功能------AI 可以生成 可执行的 HTML/SVG/Markdown 内容,直接在对话中渲染。

typescript 复制代码
// HTML Artifact:在安全 iframe 中渲染
const htmlArtifact = {
  type: "artifact",
  artifactType: "html",
  content: `
    <html>
    <body>
      <h1>动态图表</h1>
      <canvas id="chart"></canvas>
      <script>
        const ctx = document.getElementById('chart').getContext('2d');
        // 绘制图表...
      </script>
    </body>
    </html>
  `,
};

// SVG Artifact:直接渲染矢量图
const svgArtifact = {
  type: "artifact",
  artifactType: "svg",
  content: `<svg width="200" height="200">
    <circle cx="100" cy="100" r="80" fill="#6366f1"/>
    <text x="55" y="110" fill="white" font-size="20">Hello!</text>
  </svg>`,
};

使用场景

  • 让 AI 生成数据可视化图表
  • 让 AI 输出 SVG 图标/流程图
  • 让 AI 生成交互式 demo 页面

7. 浏览器端工具

和终端版的 pi-coding-agent 有 read/write/bash 工具一样,pi-web-ui 也有浏览器端的专属工具:

工具 功能 类比
js-repl 在沙箱中执行 JavaScript 浏览器版的 bash
artifacts 渲染 HTML/SVG/Markdown 浏览器版的 write

注册工具

工具通过 setAgent()toolsFactory 配置:

typescript 复制代码
await chatPanel.setAgent(agent, {
  onApiKeyRequired: async (provider) => ApiKeyPromptDialog.prompt(provider),
  toolsFactory: (_agent, _agentInterface, _artifactsPanel, runtimeProvidersFactory) => {
    const replTool = createJavaScriptReplTool();
    replTool.runtimeProvidersFactory = runtimeProvidersFactory;
    return [replTool];
  },
});

js-repl 示例

AI 可以写 JavaScript 代码并执行------比如你问"帮我计算斐波那契数列":

复制代码
用户: "帮我计算斐波那契数列前 10 项"

AI 自动调用 js-repl 工具:
┌──────────────────────────────────┐
│ function fibonacci(n) {          │
│   const r = [0, 1];              │
│   for (let i = 2; i < n; i++)    │
│     r.push(r[i-1] + r[i-2]);    │
│   return r.slice(0, n);          │
│ }                                │
│ fibonacci(10);                   │
├──────────────────────────────────┤
│ 结果: [0,1,1,2,3,5,8,13,21,34]  │
└──────────────────────────────────┘

AI: 前 10 项是 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

代码在安全沙箱(iframe sandbox)中执行,不会影响主页面。


8. CORS 代理:跨域问题的解法

为什么需要代理?

浏览器有同源策略(Same-Origin Policy),不允许网页直接请求不同域名的 API:

复制代码
你的网页 (localhost:5173)
    │
    ╳ 直接请求被浏览器拦截!
    │
    ▼
api.minimaxi.com  ← 不同域名

解决方案:加一层 CORS 代理转发请求:

复制代码
你的网页 (localhost:5173)
    │
    ✓ 请求代理(已配置 CORS headers)
    │
    ▼
CORS 代理 (localhost:3001)
    │
    ✓ 服务端请求,无跨域限制
    │
    ▼
api.minimaxi.com

CORS 代理请求流程

代理请求格式

pi-web-ui 内部使用查询参数格式 传递目标 URL(见 proxy-utils.ts):

复制代码
原始 URL:   https://api.minimaxi.com/anthropic
代理后 URL: http://localhost:3001/?url=https%3A%2F%2Fapi.minimaxi.com%2Fanthropic

SDK 会在代理 URL 后面追加 API 路径,所以最终的请求是:

复制代码
http://localhost:3001/?url=https%3A%2F%2Fapi.minimaxi.com%2Fanthropic/v1/messages

代理原理

对应 examples/05-pi-web-ui/03-cors-proxy.ts

复制代码
1. 收到请求:  GET /?url=https%3A%2F%2Fapi.minimaxi.com%2Fanthropic/v1/messages
2. 解析参数:  url=https://api.minimaxi.com/anthropic + /v1/messages
3. 拼接目标:  https://api.minimaxi.com/anthropic/v1/messages
4. 转发请求:  保留原始 headers 和 body
5. 返回响应:  添加 Access-Control-Allow-Origin: *
6. 流式管道:  SSE 响应实时透传(支持流式输出)

在代码中预启用代理

代理配置存储在 SettingsStore 中,可以在初始化时自动启用:

typescript 复制代码
// 在存储初始化后、创建 Agent 前
await settings.set("proxy.url", "http://localhost:3001");
await settings.set("proxy.enabled", true);

这样用户打开页面后无需手动去设置面板配置代理,开箱即用。

生产环境 :不要用通配符 *,应限制允许的来源域名。


9. 主题与样式

pi-web-ui 使用 Tailwind CSS v4 + CSS 变量的样式系统。

CSS 入口文件

每个页面都需要导入两个样式:

css 复制代码
/* src/app.css */
@import "tailwindcss";                        /* Tailwind 基础样式 */
@import "@mariozechner/pi-web-ui/app.css";   /* pi-web-ui 组件样式 + CSS 变量 */

内置 CSS 变量

pi-web-ui 的 app.css 定义了一套 CSS 变量(兼容 shadcn/ui 命名规范):

css 复制代码
:root {
  --background: ...;        /* 页面背景 */
  --foreground: ...;        /* 文字颜色 */
  --primary: ...;           /* 主色调 */
  --secondary: ...;         /* 次要色 */
  --border: ...;            /* 边框色 */
  --muted-foreground: ...;  /* 弱文字 */
}

在代码中使用对应的 Tailwind 类名:

html 复制代码
<div class="bg-background text-foreground border-border">
  <!-- 自动适配 pi-web-ui 的配色方案 -->
</div>

10. 实战:AI 客服系统

对应 examples/05-pi-web-ui/src/04-customer-service.ts

把上面学到的内容组合起来,做一个完整的 AI 客服悬浮窗:

运行效果

紫色渐变背景的产品页面,右下角蓝色头部的客服悬浮窗,用户发送"你好"后智能客服小云自动回复。

核心代码

typescript 复制代码
// ... 存储初始化 + 代理预配置(同前) ...

// 客服 Agent ------ 使用 MiniMax-M2.5(低成本、国内直连)
const agent = new Agent({
  initialState: {
    systemPrompt: `你是科技未来有限公司的 AI 客服助手。

你的职责:
1. 回答关于"云端数据管理平台"的产品问题
2. 帮助用户解决技术问题(登录、数据导入、API 对接等)
3. 处理常见咨询(价格、功能对比、免费试用)
4. 态度友善,用中文回答,回答简洁不超过 3 句话

产品信息:
- 价格:基础版 ¥99/月,专业版 ¥299/月,企业版定制
- 免费试用:14 天
- 客服时间:工作日 9:00-18:00(AI 全天候)`,
    model: getModel("minimax-cn", "MiniMax-M2.5"),
    thinkingLevel: "off",
    messages: [],
    tools: [],
  },
});

const chatPanel = new ChatPanel();
await chatPanel.setAgent(agent, { /* ... */ });

// 悬浮窗状态
let isOpen = true;

function renderApp() {
  render(
    html`
      <!-- 模拟产品页面背景 -->
      <div class="min-h-screen" style="background: linear-gradient(135deg, #667eea, #764ba2);">
        <div class="p-10 text-white">
          <h1 class="text-4xl font-bold">云端数据管理平台</h1>
          <p class="text-lg opacity-80">科技未来有限公司</p>
        </div>

        <!-- 客服悬浮窗 -->
        ${isOpen
          ? html`
            <div class="fixed bottom-6 right-6 w-[400px] h-[600px] rounded-2xl overflow-hidden flex flex-col"
                 style="box-shadow: 0 8px 32px rgba(0,0,0,0.3); z-index: 1000;">
              <!-- 蓝色头部 -->
              <div class="flex items-center gap-3 px-5 py-3 text-white shrink-0"
                   style="background: #0071e3;">
                <span>🤖</span>
                <div>
                  <div class="text-sm font-semibold">智能客服</div>
                  <div class="text-[11px] opacity-80">在线 · 通常秒回</div>
                </div>
                <button class="ml-auto text-xl bg-transparent border-none text-white cursor-pointer"
                        @click=${() => { isOpen = false; renderApp(); }}>×</button>
              </div>
              <!-- ChatPanel 聊天区域 -->
              <div class="flex-1 min-h-0 bg-background">
                ${chatPanel}
              </div>
            </div>
          `
          : html`
            <!-- 关闭后:圆形按钮 -->
            <button class="fixed bottom-6 right-6 w-14 h-14 rounded-full border-none text-2xl text-white cursor-pointer"
                    style="background: #0071e3; box-shadow: 0 4px 16px rgba(0,113,227,0.4); z-index: 1000;"
                    @click=${() => { isOpen = true; renderApp(); }}>💬</button>
          `
        }
      </div>
    `,
    app,
  );
}

亮点分析

  1. Lit 模板语法html\...`+@click事件绑定 +${表达式}` 条件渲染
  2. ChatPanel 复用:同一个 ChatPanel 实例,在悬浮窗打开/关闭间切换
  3. 零额外框架:不需要 React/Vue,Lit 的模板就够用

生产环境注意事项

事项 说明
API Key 从后端接口获取临时 Token,不要前端硬编码
模型选择 客服场景用 MiniMax-M2.5 等轻量模型,国内直连低成本
CORS 代理 限制允许的域名,避免代理被滥用
System Prompt 限定角色和回答范围,防止 AI 越界
错误处理 配置友好的错误提示,用户体验优先

11. 总结与下一步

pi-web-ui 的核心价值

能力 说明
TypeScript API new ChatPanel() + setAgent() 关联 Agent
存储系统 IndexedDB 持久化会话、Key、设置、代理配置
消息丰富 文本 / 附件 / Artifact(可执行 HTML)
工具系统 js-repl / artifacts
CORS 代理 查询参数格式 /?url=<encoded> 自动处理跨域
Vite 构建 必须通过 Vite 打包,不支持纯 HTML 引入

一句话记住

pi-coding-agent 是终端里的 AI 编程搭档------给程序员用的。
pi-web-ui 是浏览器里的 AI 对话组件------给所有用户用的。

回顾整个系列

复制代码
第 1 章  pi-ai              统一 LLM API,一套代码调 20+ 模型
第 2 章  pi-agent-core       智能体运行时,工具循环 + 事件系统
第 3 章  pi-tui              终端 UI 框架,差分渲染 + 组件化
第 4 章  pi-coding-agent     终端 AI 编程助手 = ai + agent + tui
第 5 章  pi-web-ui           浏览器 AI 聊天组件 = ai + agent + Web Components

附录:运行示例的完整步骤

前提条件

bash 复制代码
# 1. 进入示例目录并安装依赖
cd examples/05-pi-web-ui
npm install

# 2. 设置 API Key(推荐 MiniMax 国内版)
export MINIMAX_CN_API_KEY=你的key
# 也可以不设环境变量,在页面弹出的对话框中手动输入

步骤 1:启动 CORS 代理

bash 复制代码
# 在终端窗口 1(从 examples 目录)
cd examples
npm run 05:proxy

# 看到输出:
# 🌐 CORS 代理已启动: http://localhost:3001
# 使用方式(查询参数格式,匹配 pi-web-ui proxy-utils):
#   原始请求:  https://api.minimaxi.com/anthropic
#   代理请求:  http://localhost:3001/?url=https%3A%2F%2Fapi.minimaxi.com%2Fanthropic

上方:CORS 代理启动后转发 API 请求的日志。下方:Vite 开发服务器就绪。

步骤 2:启动 Vite 开发服务器

bash 复制代码
# 在终端窗口 2
cd examples/05-pi-web-ui
npm run dev

# 看到输出:
# VITE vX.X.X  ready in XXXms
# ➜ Local:   http://localhost:5173/

步骤 3:打开浏览器

bash 复制代码
# 首页导航
open http://localhost:5173

# 示例 1:ChatPanel 快速上手
open http://localhost:5173/01-chat-panel.html

# 示例 2:自定义布局
open http://localhost:5173/02-agent-interface.html

# 示例 4:AI 客服系统
open http://localhost:5173/04-customer-service.html

步骤 4:开始对话

  1. 页面加载后,代理已自动配置(代码中预设了 proxy.urlproxy.enabled
  2. 在输入框发送消息
  3. 首次使用时会弹出 API Key 输入框,输入你的 MiniMax API Key
  4. AI 开始流式回复
相关推荐
郝学胜-神的一滴2 小时前
系统设计与面向对象设计:两大设计思想的深度剖析
java·前端·c++·ue5·软件工程
徐同保2 小时前
openclaw插件
前端
摸鱼仙人~2 小时前
前端面试最常考、最容易被问崩的 50 道八股精简版
前端·面试·职场和发展
2501_948114242 小时前
星链4SAPI + OpenClaw实战:给GPT-5.4与Claude 4.6装上“职业传送门”
python·gpt·架构
恪愚2 小时前
three | 材质 Material
前端·javascript·材质
zuoerjinshu2 小时前
WebSpoon9.0(KETTLE的WEB版本)编译 + tomcatdocker部署 + 远程调试教程
前端
架构师沉默2 小时前
为什么很多大厂 API 不再使用 PUT 和 DELETE?
java·后端·架构
qq_454245032 小时前
AI模块化工作流的基石:三要素双向生成与可信存储机制
人工智能·架构
lxmyzzs2 小时前
解决Windows安装OpenClaw报错:无法加载npm.ps1,禁止运行脚本
前端·windows·npm·openclaw