npm package: @lingshuai/chat-uniapp
Description: UniApp chat SDK for AI-powered platform
Author: LingShuai
Latest version: 1.2.6

# @lingshuai/chat-uniapp

UniApp 聊天 SDK，支持流式对话、会话管理、离线存储，兼容 Vue 2 和 Vue 3。

## 平台支持

| 平台 | 流式传输 | 说明 |
|------|---------|------|
| 微信小程序 | ✅ 支持 | 使用 `enableChunked` + `onChunkReceived` |
| H5 | ✅ 支持 | 使用浏览器原生 `fetch` + `ReadableStream` |
| App (iOS/Android) | ✅ 支持 | 优先使用 RenderJS 真流式，降级为 `plus.net` 模拟流式 |
| 支付宝/百度/抖音小程序 | ⚠️ 降级 | 自动降级为非流式传输 |

## 安装

```bash
npm install @lingshuai/chat-uniapp
# 或
pnpm add @lingshuai/chat-uniapp
```

**Vue 2 项目额外依赖**：
```bash
npm install @vue/composition-api
```

## 快速开始

### Vue 3 项目

```vue
<template>
  <view class="chat-container">
    <scroll-view class="messages" scroll-y>
      <view v-for="msg in messages" :key="msg.id" class="message">
        <text class="role">{{ msg.role }}:</text>
        <text class="content">{{ msg.content }}</text>
      </view>
    </scroll-view>

    <view class="input-area">
      <input v-model="input" placeholder="输入消息..." />
      <button @click="send" :disabled="loading">
        {{ loading ? '发送中...' : '发送' }}
      </button>
      <button @click="reset">新对话</button>
    </view>

    <view v-if="error" class="error">{{ error }}</view>
  </view>
</template>

<script setup>
import { ref } from 'vue';
import { useChat } from '@lingshuai/chat-uniapp';

const input = ref('');

const { messages, loading, error, sendMessage, resetConversation } = useChat({
  apiKey: 'sk_live_xxx',
  appId: 'your-project-id',
  baseUrl: 'https://pinyou.xin/api' // 可选
});

const send = () => {
  if (input.value.trim()) {
    sendMessage(input.value);
    input.value = '';
  }
};

const reset = () => {
  resetConversation();
};
</script>

<style>
.chat-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.messages {
  flex: 1;
  padding: 20rpx;
}

.message {
  margin-bottom: 20rpx;
}

.role {
  font-weight: bold;
  margin-right: 10rpx;
}

.input-area {
  display: flex;
  padding: 20rpx;
  border-top: 1px solid #eee;
}

.error {
  color: red;
  padding: 20rpx;
}
</style>
```

### Vue 2 项目

```vue
<template>
  <view class="chat-container">
    <scroll-view class="messages" scroll-y>
      <view v-for="msg in messages" :key="msg.id" class="message">
        <text class="role">{{ msg.role }}:</text>
        <text class="content">{{ msg.content }}</text>
      </view>
    </scroll-view>

    <view class="input-area">
      <input v-model="input" placeholder="输入消息..." />
      <button @click="send" :disabled="loading">
        {{ loading ? '发送中...' : '发送' }}
      </button>
    </view>
  </view>
</template>

<script>
import Vue from 'vue';
import VueCompositionAPI from '@vue/composition-api';
import { useChat } from '@lingshuai/chat-uniapp/vue2';

Vue.use(VueCompositionAPI);

export default {
  setup() {
    const input = ref('');

    const { messages, loading, error, sendMessage, resetConversation } = useChat({
      apiKey: 'sk_live_xxx',
      appId: 'your-project-id'
    });

    const send = () => {
      if (input.value.trim()) {
        sendMessage(input.value);
        input.value = '';
      }
    };

    return {
      input,
      messages,
      loading,
      error,
      send,
      resetConversation
    };
  }
};
</script>
```

## 会话管理

SDK 自动管理会话：
- 首次对话时，后端返回 `conversationId`
- SDK 自动保存并在后续请求中带上
- 会话历史自动持久化到本地存储
- 重启应用后自动恢复上次会话

```typescript
const { conversationId, resetConversation } = useChat(config);

// 开始新对话
resetConversation();

// 当前会话 ID
console.log(conversationId.value);
```

## API 参考

### useChat(config)

**参数 (ChatConfig)**：

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `apiKey` | string | ✅ | API 密钥 (sk_live_xxx) |
| `appId` | string | ✅ | 项目 ID |
| `baseUrl` | string | ❌ | API 基础地址（默认：https://pinyou.xin/api） |
| `timeout` | number | ❌ | 请求超时时间（毫秒，默认：30000） |
| `userId` | string | ❌ | 用户标识 |
| `sessionId` | string | ❌ | 会话标识（自动生成） |
| `maxRetries` | number | ❌ | 请求失败最大重试次数（默认：3） |
| `retryDelay` | number | ❌ | 重试初始延迟毫秒数，指数退避（默认：1000） |
| `maxStoredMessages` | number | ❌ | 本地存储最大消息数（默认：100） |
| `autoCleanup` | boolean | ❌ | 超出限制时自动清理旧消息（默认：true） |

**返回值 (UseChatReturn)**：

| 属性 | 类型 | 说明 |
|------|------|------|
| `messages` | Ref<Message[]> | 消息列表 |
| `loading` | Ref<boolean> | 加载状态 |
| `error` | Ref<string \| null> | 错误信息 |
| `conversationId` | Ref<string \| null> | 当前会话 ID |
| `sendMessage` | (content: string) => Promise<void> | 发送消息 |
| `resetConversation` | () => Promise<void> | 开始新对话 |
| `clearHistory` | () => Promise<void> | 清除当前会话的本地历史记录 |
| `getStorageInfo` | () => Promise<StorageInfo> | 获取存储信息（消息数、大小等） |
| `exportMessages` | () => Promise<string> | 导出消息为 JSON 字符串 |
| `importMessages` | (jsonString: string) => Promise<boolean> | 从 JSON 字符串导入消息 |
| `abort` | () => void | 中断流式传输 |

### Message 类型

```typescript
interface Message {
  id: string;                    // 消息 ID
  conversationId: string;        // 会话 ID
  role: 'user' | 'assistant';    // 角色
  content: string;               // 内容
  status: 'sending' | 'streaming' | 'success' | 'error';  // 状态
  createdAt: Date;               // 创建时间
  error?: string;                // 错误信息（status 为 error 时）
}
```

## 平台差异说明

### 微信小程序
- ✅ 完整支持流式传输（`enableChunked` + `onChunkReceived`）
- 需要在 `manifest.json` 中配置域名白名单：
  ```json
  {
    "mp-weixin": {
      "setting": {
        "urlCheck": false
      },
      "permission": {
        "scope.userLocation": {
          "desc": "你的位置信息将用于小程序位置接口的效果展示"
        }
      }
    }
  }
  ```

### H5
- ✅ 使用浏览器原生 `fetch` + `ReadableStream`
- 注意跨域问题（需要后端配置 CORS）

### App 端
- ✅ 优先使用 RenderJS 实现真正的流式传输（`fetch` + `ReadableStream`）
- 如未挂载 `RenderStreamWorker` 组件，自动降级为 `plus.net.XMLHttpRequest` 模拟流式
- 需要在 `main.js` 中全局注册组件（见下方说明）

#### App 端启用真流式（推荐）

App 端默认使用 `plus.net` 模拟流式（先拉取全部数据再逐字显示）。要启用真正的流式传输，需注册 `RenderStreamWorker` 组件：

**1. 在 `main.js` 中全局注册：**

```javascript
import RenderStreamWorker from '@lingshuai/chat-uniapp/components/RenderStreamWorker';
Vue.component('RenderStreamWorker', RenderStreamWorker);
```

**2. 在使用聊天的页面模板中放置组件：**

```vue
<template>
  <view>
    <RenderStreamWorker />
    <!-- 你的聊天 UI -->
  </view>
</template>
```

组件不可见，不影响布局。如果不放置该组件，SDK 会自动降级为 `plus.net` 模拟流式，功能不受影响。

### 其他小程序
- ⚠️ 自动降级为非流式传输
- 需要在各平台后台配置域名白名单

## 高级用法

### 直接使用 ApiClient

如果不需要 Vue 响应式，可以直接使用底层 `ApiClient`：

```typescript
import { ApiClient, EventEmitter } from '@lingshuai/chat-uniapp';

const events = new EventEmitter();
const client = new ApiClient({
  apiKey: 'sk_live_xxx',
  appId: 'your-project-id'
}, events);

// 监听事件
events.on('message:stream:chunk', (data) => {
  console.log('收到消息块:', data.chunk);
});

// 发送消息
await client.sendMessage({
  content: 'Hello',
  stream: true
});

// 重置会话
client.resetConversation();
```

### 自定义存储

```typescript
import { UniAppStorage } from '@lingshuai/chat-uniapp';

const storage = new UniAppStorage(200); // 最多存储 200 条消息

// 保存会话（自动清理旧消息）
await storage.saveConversation('conv_123', messages);

// 加载会话
const messages = await storage.getConversation('conv_123');

// 保存当前会话 ID
await storage.saveCurrentConversationId('conv_123');

// 获取当前会话 ID
const currentId = await storage.getCurrentConversationId();

// 清除指定会话
await storage.clearConversation('conv_123');

// 获取存储信息
const info = await storage.getStorageInfo('conv_123');
console.log(`消息数: ${info.messageCount}, 占用: ${info.estimatedSize}`);

// 导出/导入消息
const json = await storage.exportMessages('conv_123');
await storage.importMessages('conv_123', json);
```

### 平台检测

```typescript
import { detectPlatform, getPlatformCapabilities } from '@lingshuai/chat-uniapp';

const platform = detectPlatform();
console.log('当前平台:', platform); // 'weixin-mp' | 'h5' | 'app' | ...

const capabilities = getPlatformCapabilities(platform);
console.log('是否支持流式:', capabilities.supportsStream);
console.log('是否支持 enableChunked:', capabilities.supportsChunked);
```

## 注意事项

1. **域名白名单**：小程序平台需要在后台配置 API 域名白名单
2. **存储限制**：小程序本地存储有大小限制（通常 10MB），可通过 `maxStoredMessages` 控制消息数量，开启 `autoCleanup` 自动清理旧消息
3. **网络环境**：弱网环境下建议增加超时时间，SDK 内置指数退避重试机制（可通过 `maxRetries` 和 `retryDelay` 配置）
4. **Vue 2 项目**：需要安装 `@vue/composition-api` 并在 `main.js` 中注册
5. **中文字符**：微信小程序的 ArrayBuffer 解码已处理 UTF-8 多字节字符（包括跨分片的不完整字节序列）
6. **跨域问题**：H5 端需要后端配置 CORS 允许跨域请求

## 故障排查

### 微信小程序无法连接

1. 检查域名白名单配置
2. 开发阶段可在开发者工具中关闭域名校验
3. 检查 API Key 和 App ID 是否正确

### H5 跨域错误

后端需要配置 CORS：
```java
@CrossOrigin(origins = "*")
@RestController
public class ChatController {
    // ...
}
```

### App 端流式传输问题

App 端优先使用 RenderJS 实现真流式，如遇问题：
1. 确认已在 `main.js` 中全局注册 `RenderStreamWorker` 组件
2. 确认聊天页面模板中已放置 `<RenderStreamWorker />` 标签
3. 如未使用 RenderJS，SDK 会降级为 `plus.net` 模拟流式，确认 `plus` 对象可用（需在 `plus ready` 之后）
4. 确认网络请求域名已配置为安全域名

### Vue 2 响应式问题

确保使用 Vue 2 兼容的数组操作：
```javascript
// ✅ 正确
messages.value.push(newMessage);

// ❌ 错误（Vue 2 无法检测）
messages.value[0] = newMessage;
```

## 许可证

MIT

## 支持

如有问题，请提交 Issue 或联系技术支持。