# @keyueclaw/web-sdk

KeyueClaw Web SDK — 基于 Lit 的可嵌入 Web Component 集合，让外部系统一行代码接入 AI 对话、技能管理、接口管理和技能组件管理能力。

## 安装

```bash
npm install @keyueclaw/web-sdk
# 或
pnpm add @keyueclaw/web-sdk
```

## 模块一览

| 模块             | 元素标签                       | 用途                                                |
| ---------------- | ------------------------------ | --------------------------------------------------- |
| chat             | `<keyueclaw-chat>`             | AI 对话聊天                                         |
| skills           | `<keyueclaw-skills>`           | 技能管理（创建、编辑、AI 构建、版本发布）           |
| api-management   | `<keyueclaw-api-management>`   | 接口定义管理（CRUD、导入导出）                      |
| skill-components | `<keyueclaw-skill-components>` | 技能组件管理（创建、编辑、版本发布、变量/API 引用） |

## 快速开始

### ES Module

```html
<link rel="stylesheet" href="@keyueclaw/web-sdk/chat.css" />

<script type="module">
  import "@keyueclaw/web-sdk/chat";

  const el = document.createElement("keyueclaw-chat");
  el.setAttribute("ws-base-url", "ws://your-server/keyueclaw");
  el.setAttribute("http-base-url", "http://your-server");
  el.setAttribute("tenant-id", "your-tenant-id");
  el.setAttribute("bot-id", "your-bot-id");
  el.setAttribute("phone", "13800138000");
  document.getElementById("chat-root").appendChild(el);
</script>
```

### UMD (script 标签)

```html
<link rel="stylesheet" href="@keyueclaw/web-sdk/chat.css" />
<script src="@keyueclaw/web-sdk/chat.umd.cjs"></script>
<script>
  const el = document.createElement("keyueclaw-chat");
  el.setAttribute("ws-base-url", "ws://your-server/keyueclaw");
  el.setAttribute("http-base-url", "http://your-server");
  el.setAttribute("tenant-id", "your-tenant-id");
  el.setAttribute("bot-id", "your-bot-id");
  el.setAttribute("phone", "13800138000");
  document.getElementById("chat-root").appendChild(el);
</script>
```

### 按需引入

每个模块独立引入，互不影响：

```js
// 仅引入需要的模块
import "@keyueclaw/web-sdk/skills";
import "@keyueclaw/web-sdk/skills.css";
```

## 通用属性

所有元素共享以下属性（继承自 `EmbeddedGatewayElementBase`）：

| 属性            | 类型     | 必填 | 说明                                         |
| --------------- | -------- | ---- | -------------------------------------------- |
| `ws-base-url`   | `string` | 是   | WebSocket 网关地址，如 `ws://host/keyueclaw` |
| `http-base-url` | `string` | 是   | HTTP 服务地址，如 `http://host`              |
| `token`         | `string` | 否   | 认证令牌                                     |
| `theme`         | `string` | 否   | 主题名称，默认 `"dash"`                      |

## 组件属性

### `<keyueclaw-chat>`

| 属性          | 类型                      | 必填 | 说明                                                                                            |
| ------------- | ------------------------- | ---- | ----------------------------------------------------------------------------------------------- |
| `phone`       | `string`                  | 是   | 用户手机号，用于会话路由                                                                        |
| `tenant-id`   | `string`                  | 是   | 租户 ID                                                                                         |
| `tenant-name` | `string`                  | 否   | 租户名称                                                                                        |
| `bot-id`      | `string`                  | 是   | 机器人 ID                                                                                       |
| `operator-id` | `string`                  | 否   | 操作者 ID                                                                                       |
| `deploy-mode` | `"offline" \| "online"`   | 否   | 部署模式，默认 `"offline"`                                                                      |
| `variables`   | `Record<string, unknown>` | 否   | 外部注入的会话变量；仅当前会话 `/api/v2/dialog/variable` 返回的变量名会被下发，其他字段会被忽略 |

```html
<keyueclaw-chat
  ws-base-url="ws://localhost:18789/keyueclaw"
  http-base-url="http://localhost:18789"
  phone="13800138000"
  tenant-id="tenant-001"
  bot-id="bot-001"
  operator-id="op-001"
  deploy-mode="offline"
></keyueclaw-chat>
```

```ts
const el = document.createElement("keyueclaw-chat");
el.setAttribute("tenant-id", "tenant-001");
el.setAttribute("bot-id", "bot-001");
el.variables = {
  city: "北京",
  date: "2026-05-19",
};
```

### `<keyueclaw-skills>`

| 属性            | 类型     | 必填 | 说明       |
| --------------- | -------- | ---- | ---------- |
| `tenant-id`     | `string` | 是   | 租户 ID    |
| `tenant-name`   | `string` | 否   | 租户名称   |
| `bot-id`        | `string` | 否   | 机器人 ID  |
| `operator-id`   | `string` | 否   | 操作者 ID  |
| `operator-name` | `string` | 否   | 操作者名称 |

```html
<keyueclaw-skills
  ws-base-url="ws://localhost:18789/keyueclaw"
  http-base-url="http://localhost:18789"
  tenant-id="tenant-001"
  operator-id="op-001"
  operator-name="admin"
></keyueclaw-skills>
```

### `<keyueclaw-api-management>`

| 属性          | 类型     | 必填 | 说明                                      |
| ------------- | -------- | ---- | ----------------------------------------- |
| `service-url` | `string` | 否   | API 服务地址（默认从 http-base-url 推导） |
| `tenant-id`   | `string` | 否   | 租户 ID（用于租户级接口过滤）             |

```html
<keyueclaw-api-management
  ws-base-url="ws://localhost:18789/keyueclaw"
  http-base-url="http://localhost:18789"
  service-url="http://localhost:18789"
  tenant-id="tenant-001"
></keyueclaw-api-management>
```

### `<keyueclaw-skill-components>`

| 属性        | 类型     | 必填 | 说明    |
| ----------- | -------- | ---- | ------- |
| `tenant-id` | `string` | 是   | 租户 ID |

```html
<keyueclaw-skill-components
  ws-base-url="ws://localhost:18789/keyueclaw"
  http-base-url="http://localhost:18789"
  tenant-id="tenant-001"
></keyueclaw-skill-components>
```

## 自定义事件

| 元素                           | 事件名                       | Detail                                      | 说明               |
| ------------------------------ | ---------------------------- | ------------------------------------------- | ------------------ |
| `<keyueclaw-skill-components>` | `keyueclaw-component-detail` | `{ tenantId: string, componentId: string }` | 新建组件成功后触发 |

```js
document
  .querySelector("keyueclaw-skill-components")
  .addEventListener("keyueclaw-component-detail", (e) => {
    console.log("新组件:", e.detail.componentId);
  });
```

## 编写与构建

### 项目结构

```
packages/web-sdk/
├── src/
│   ├── chat.ts                          # chat 入口
│   ├── skills.ts                        # skills 入口
│   ├── api-management.ts                # api-management 入口
│   ├── skill-components.ts              # skill-components 入口
│   ├── elements/
│   │   ├── chat-element.ts              # <keyueclaw-chat> 实现
│   │   ├── skills-element.ts            # <keyueclaw-skills> 实现
│   │   ├── api-management-element.ts    # <keyueclaw-api-management> 实现
│   │   └── skill-components-element.ts  # <keyueclaw-skill-components> 实现
│   └── shared/
│       ├── embedded-gateway-element.ts  # 共享基类（WebSocket 连接、主题同步）
│       ├── connection.ts                # URL 解析、token 注入
│       └── theme.ts                     # 主题同步
├── postcss.chat.config.js               # chat 的 PostCSS 配置
├── postcss.skills.config.js
├── postcss.api-management.config.js
├── postcss.skill-components.config.js
├── postcss-prefix-vars.js               # CSS 变量前缀插件
├── vite.config.ts                       # Vite 构建配置
└── dist/                                # 构建产物
    ├── chat.js / chat.umd.cjs / chat.css
    ├── skills.js / skills.umd.cjs / skills.css
    ├── api-management.js / api-management.umd.cjs / api-management.css
    └── skill-components.js / skill-components.umd.cjs / skill-components.css
```

### 新增/修改组件

1. 在 `src/elements/` 下创建或修改 `xxx-element.ts`
2. 继承 `EmbeddedGatewayElementBase`，使用 `@customElement("keyueclaw-xxx")` 注册
3. 用 `@property()` 声明 HTML 属性，`@state()` 声明内部状态
4. 重写 `startEmbeddedGatewayConnection()` 处理连接建立后的逻辑
5. 在 `src/xxx.ts` 入口文件中导出元素类并引入 `@ui/styles.css`

### 构建命令

默认按 `bash` 执行。修改 `src/` 后，需重新构建 `dist/`，页面不会自动同步源码变更。

```bash
# 安装依赖（首次）
pnpm install

# 构建所有模块
pnpm build

# 单独构建某个模块
pnpm build:chat
pnpm build:skills
pnpm build:api-management
pnpm build:skill-components

# 开发模式（watch，文件修改后自动重新构建）
pnpm dev:chat
pnpm dev:skills
pnpm dev:api-management
pnpm dev:skill-components
```

### 更新 dist

```bash
cd packages/web-sdk

# 更新全部 dist 产物
pnpm build

# 只更新某一个模块
pnpm build:chat
pnpm build:skills
pnpm build:api-management
pnpm build:skill-components

# 构建完成后检查 dist 时间戳
ls -l dist
```

如果本地页面仍表现为旧逻辑，强制刷新浏览器即可；`dist/` 更新后不会自动替换已缓存的 SDK 模块。

### 构建产物

每个模块产出三个文件：

| 文件          | 格式 | 用途                                                 |
| ------------- | ---- | ---------------------------------------------------- |
| `xxx.js`      | ESM  | 现代打包工具 / `<script type="module">`              |
| `xxx.umd.cjs` | UMD  | 传统 `<script>` 标签，挂载到 `window.KeyueClawXxxUI` |
| `xxx.css`     | CSS  | 组件样式，需手动引入                                 |

### PostCSS 处理

构建时 PostCSS 自动执行两个操作：

1. **CSS 变量前缀**：`--bg` → `--keyueclaw-bg`，避免与宿主页面样式冲突
2. **选择器作用域**：所有样式限定在 `keyueclaw-xxx .keyueclaw-xxx-scope` 下

### 类型检查

```bash
cd packages/web-sdk
npx tsc --noEmit
```

> 已有的 `TS5097` 警告（`.ts` 扩展名导入）可通过 tsconfig 中的 `allowImportingTsExtensions` 抑制。其余来自 `extensions/` 目录的错误与 web-sdk 无关。

## 调试

### 方式一：本地 Vite Dev Server（推荐）

1. 启动后端网关服务（确保 `http://127.0.0.1:18789` 可访问）

2. 启动 Vite Dev Server：

   ```bash
   cd ui
   pnpm dev
   ```

   访问 http://localhost:5173/

3. 修改 web-sdk 源码后，手动重新构建对应模块：
   ```bash
   cd packages/web-sdk
   npx cross-env TARGET=skill-components vite build
   ```
   刷新浏览器即可看到变化（Vite 的 HMR 不覆盖 web-sdk dist，需手动 build）

### 方式二：Python HTTP Server（静态预览）

```bash
# 构建所有模块
cd packages/web-sdk && pnpm build

# 在项目根目录启动 HTTP 服务
cd ../..
python -m http.server 8080
```

访问 http://localhost:8080/ui/index.html

> 注意：Python server 下 index.html 的路径需使用 `../packages/web-sdk/dist/...`（相对路径）

### 方式三：嵌入到自己的项目

```html
<!-- 开发时直接指向 dist 目录 -->
<link rel="stylesheet" href="./node_modules/@keyueclaw/web-sdk/dist/chat.css" />
<script type="module" src="./node_modules/@keyueclaw/web-sdk/dist/chat.js"></script>
```

### 浏览器调试技巧

1. **打开开发者工具**（F12），在 Console 中查看元素注册和连接日志

2. **检查自定义元素**：在 Elements 面板中搜索 `keyueclaw-chat` 等标签，查看属性和状态

3. **检查网络请求**：在 Network 面板筛选 `keyueclaw`，查看 API 调用和 WebSocket 连接

4. **常见问题排查**：

   | 现象                     | 原因                    | 解决                                      |
   | ------------------------ | ----------------------- | ----------------------------------------- |
   | 元素显示空白             | CSS 文件未引入          | 检查 `<link>` 标签路径                    |
   | 控制台报 404             | dist 文件路径错误       | 检查 `src` / `href` 路径                  |
   | "加载中…" 一直不消失     | WebSocket 连接失败      | 检查 `ws-base-url` 是否正确、网关是否运行 |
   | API 请求 404             | `http-base-url` 错误    | 检查 HTTP 地址，确认后端服务运行          |
   | 样式错乱                 | CSS 变量冲突            | 确认引入了对应的 `.css` 文件              |
   | `@` / `&` 触发面板不弹出 | 没有 API 定义或变量数据 | 检查后端是否有对应数据                    |

5. **Lit DevTools**：安装 [Lit DevTools](https://chrome.google.com/webstore/detail/lit-devtools) 浏览器扩展，可以查看 Lit 元素的 `@state` 和 `@property` 值

### 发布到 npm

```bash
cd packages/web-sdk
pnpm build
npm publish --access public
```

发布前确认：

- `package.json` 中 `version` 已更新
- `dist/` 目录包含所有四个模块的产物
- `exports` 字段与实际产物一致

## 技术架构

```
┌─────────────────────────────────────────────┐
│              外部页面                         │
│  <keyueclaw-chat>  <keyueclaw-skills>  ...  │
└──────────────┬──────────────────────────────┘
               │ Lit Web Components
┌──────────────▼──────────────────────────────┐
│         packages/web-sdk                    │
│  ┌─────────────────────────────────────┐    │
│  │  EmbeddedGatewayElementBase         │    │
│  │  (WebSocket 连接、主题同步)          │    │
│  └─────────────────────────────────────┘    │
│  ┌────────┐ ┌────────┐ ┌─────┐ ┌────────┐  │
│  │  chat  │ │ skills │ │ api │ │ compo- │  │
│  │element │ │element │ │ mgmt│ │ nents  │  │
│  └────┬───┘ └────┬───┘ └──┬──┘ └───┬────┘  │
└───────┼──────────┼────────┼────────┼────────┘
        │          │        │        │
┌───────▼──────────▼────────▼────────▼────────┐
│              @ui (共享代码)                   │
│  views/  controllers/  api-client/  types/  │
└─────────────────────────────────────────────┘
```

- **web-sdk** 是薄封装层，管理元素状态和生命周期
- **@ui** 提供业务逻辑、渲染函数、API 客户端和类型定义
- CSS 通过 PostCSS 自动加 `keyueclaw-` 前缀，避免样式冲突
- 每个模块独立构建，产物包含 ESM (`.js`) + UMD (`.umd.cjs`) + CSS

## License

MIT
