@unif/react-native-chat-sdk 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,252 @@
1
+ # @unif/react-native-chat-sdk
2
+
3
+ AI 聊天 SDK — 流式通信、Provider 模式、状态管理,参考 [Ant Design X SDK](https://ant-design-x.antgroup.com/x-sdks/introduce) 架构设计。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ yarn add @unif/react-native-chat-sdk
9
+ ```
10
+
11
+ ### peerDependencies
12
+
13
+ ```bash
14
+ yarn add react react-native react-native-sse
15
+ ```
16
+
17
+ ## 架构
18
+
19
+ ```
20
+ ┌──────────────────────────────────────────┐
21
+ │ useXChat (Hook) │
22
+ │ 组合 Provider + store,输出 messages │
23
+ ├──────────────────────────────────────────┤
24
+ │ ChatProvider 层 │
25
+ │ AbstractChatProvider → OpenAI/DeepSeek │
26
+ ├──────────────────────────────────────────┤
27
+ │ 工具层 │
28
+ │ XRequest + XStream + SSEParser │
29
+ └──────────────────────────────────────────┘
30
+ ```
31
+
32
+ ## 快速开始
33
+
34
+ ### 方式一:ChatProvider 全局模式
35
+
36
+ ```tsx
37
+ import {
38
+ ChatProvider,
39
+ useChatContext,
40
+ OpenAIChatProvider,
41
+ XRequest,
42
+ } from '@unif/react-native-chat-sdk';
43
+ import AsyncStorage from '@react-native-async-storage/async-storage';
44
+
45
+ const request = new XRequest({
46
+ baseURL: 'https://api.openai.com',
47
+ getToken: async () => 'sk-...',
48
+ });
49
+ const provider = new OpenAIChatProvider({request, model: 'gpt-4o'});
50
+
51
+ function App() {
52
+ return (
53
+ <ChatProvider provider={provider} storage={AsyncStorage}>
54
+ <ChatScreen />
55
+ </ChatProvider>
56
+ );
57
+ }
58
+
59
+ function ChatScreen() {
60
+ const {messages, onRequest, requesting, abort} = useChatContext();
61
+ // 使用 messages 渲染列表,onRequest 发送消息...
62
+ }
63
+ ```
64
+
65
+ ### 方式二:直接使用 Hooks
66
+
67
+ ```tsx
68
+ import {useXChat, useXConversations, useXModel} from '@unif/react-native-chat-sdk';
69
+
70
+ function ChatScreen() {
71
+ const {messages, onRequest, abort, requesting} = useXChat({provider});
72
+ const {sessions, switchSession, newSession} = useXConversations({storage: AsyncStorage});
73
+ const {selectedModel, setSelectedModel} = useXModel({models: MODELS});
74
+ }
75
+ ```
76
+
77
+ ## API
78
+
79
+ ### Hooks
80
+
81
+ #### useXChat
82
+
83
+ 聊天操作核心 hook,消费 Provider 输出可渲染 messages。
84
+
85
+ ```typescript
86
+ const {
87
+ messages, // MessageInfo[] — 可渲染消息列表
88
+ requesting, // boolean — 是否请求中
89
+ suggestions, // SuggestionItem[] — 建议提示
90
+ error, // string | null — 错误信息
91
+ onRequest, // (input) => void — 发起请求
92
+ abort, // () => void — 中止请求
93
+ resetChat, // () => void — 重置聊天
94
+ loadSession, // (messages) => void — 加载历史会话
95
+ clearError, // () => void — 清除错误
96
+ } = useXChat({provider});
97
+ ```
98
+
99
+ #### useXConversations
100
+
101
+ 会话管理 hook,组合 session + history。
102
+
103
+ ```typescript
104
+ const {
105
+ sessions, // SessionSummary[]
106
+ activeId, // string
107
+ switchSession, // (id) => Promise<ChatMessage[]>
108
+ newSession, // () => void
109
+ deleteSession, // (id) => Promise<void>
110
+ archiveSession, // (id, title, messages) => Promise<void>
111
+ } = useXConversations({storage: AsyncStorage});
112
+ ```
113
+
114
+ #### useXModel
115
+
116
+ 模型选择 hook。
117
+
118
+ ```typescript
119
+ const {
120
+ selectedModel, // string
121
+ models, // ModelInfo[]
122
+ setSelectedModel, // (id) => void
123
+ } = useXModel({models: AVAILABLE_MODELS, defaultModel: 'gpt-4o'});
124
+ ```
125
+
126
+ ### Providers
127
+
128
+ #### AbstractChatProvider
129
+
130
+ 抽象基类,自定义 Provider 需继承并实现 3 个方法:
131
+
132
+ ```typescript
133
+ class MyProvider extends AbstractChatProvider {
134
+ // 将 useXChat.onRequest() 参数转为 HTTP 请求体
135
+ transformParams(input: RequestParams): Record<string, unknown>;
136
+
137
+ // 创建用户本地消息(立即显示在列表中)
138
+ transformLocalMessage(input: RequestParams): ChatMessage;
139
+
140
+ // 将 SSE 流数据转为 assistant 消息(流式累积)
141
+ transformMessage(output: SSEOutput, current?: ChatMessage): ChatMessage;
142
+ }
143
+ ```
144
+
145
+ #### OpenAIChatProvider
146
+
147
+ OpenAI 兼容 Provider,支持所有 OpenAI 标准 API。
148
+
149
+ ```typescript
150
+ const provider = new OpenAIChatProvider({
151
+ baseURL: 'https://api.openai.com',
152
+ model: 'gpt-4o',
153
+ getToken: async () => apiKey,
154
+ });
155
+ ```
156
+
157
+ #### DeepSeekChatProvider
158
+
159
+ DeepSeek Provider,继承 OpenAI,额外处理 `reasoning_content`。
160
+
161
+ ```typescript
162
+ const provider = new DeepSeekChatProvider({
163
+ model: 'deepseek-chat',
164
+ getToken: async () => apiKey,
165
+ });
166
+ ```
167
+
168
+ ### Tools
169
+
170
+ #### XStream
171
+
172
+ SSE 流适配器,基于 `react-native-sse` 的 RN 原生实现。
173
+
174
+ ```typescript
175
+ const stream = new XStream({
176
+ url: 'https://api.example.com/stream',
177
+ method: 'POST',
178
+ body: {message: 'hello'},
179
+ timeout: 120000,
180
+ });
181
+
182
+ stream.connect({
183
+ onMessage: (output) => console.log(output.data),
184
+ onError: (err) => console.error(err),
185
+ onComplete: () => console.log('done'),
186
+ });
187
+ ```
188
+
189
+ #### XRequest
190
+
191
+ 面向 LLM 的 HTTP 请求工具,组合 XStream + SSEParser。
192
+
193
+ ```typescript
194
+ const request = new XRequest({
195
+ baseURL: 'https://api.example.com',
196
+ endpoint: '/v1/chat/completions',
197
+ getToken: async () => token,
198
+ });
199
+
200
+ await request.create(params, {
201
+ onStream: (chunk) => handleChunk(chunk),
202
+ onSuccess: () => handleDone(),
203
+ onError: (err) => handleError(err),
204
+ });
205
+ ```
206
+
207
+ #### SSEParser
208
+
209
+ SSE 解析器,event_id 幂等去重 + seq 严格排序。
210
+
211
+ ```typescript
212
+ const parser = new SSEParser();
213
+ parser.reset(); // 每轮对话重置
214
+
215
+ const envelopes = parser.process(rawData); // 返回排序后的事件数组
216
+ ```
217
+
218
+ ### Types
219
+
220
+ ```typescript
221
+ // 消息类型
222
+ interface ChatMessage {
223
+ id: string;
224
+ text: string;
225
+ role: 'user' | 'assistant' | 'system';
226
+ status: 'local' | 'loading' | 'updating' | 'success' | 'error' | 'abort';
227
+ messageType: 'text' | 'card' | 'system';
228
+ createdAt: Date;
229
+ turnId: string;
230
+ cardType?: string;
231
+ cardData?: { data: Record<string, unknown>; actions: string[] };
232
+ extra?: Record<string, unknown>;
233
+ }
234
+
235
+ // 存储抽象
236
+ interface ChatStorage {
237
+ getItem: (key: string) => Promise<string | null>;
238
+ setItem: (key: string, value: string) => Promise<void>;
239
+ removeItem: (key: string) => Promise<void>;
240
+ }
241
+ ```
242
+
243
+ ## 兼容性
244
+
245
+ | @unif/react-native-chat-sdk | React Native | React | 状态 |
246
+ |-----------------------------|-------------|-------|------|
247
+ | 0.1.x | >= 0.71 | >= 18 | ✅ |
248
+ | 0.1.x | 0.74.x | 18.x | ✅ 已验证(PECPortal) |
249
+
250
+ ## 许可证
251
+
252
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unif/react-native-chat-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "UNIF React Native Chat SDK — 流式通信、Provider、状态管理",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -37,8 +37,12 @@ interface ChatContextValue<Message = ChatMessage> {
37
37
  messages: MessageInfo<Message>[];
38
38
  requesting: boolean;
39
39
  suggestions: SuggestionItem[];
40
+ error: string | null;
40
41
  onRequest: (input: unknown) => void;
41
42
  abort: () => void;
43
+ resetChat: () => void;
44
+ loadSession: (messages: ChatMessage[]) => void;
45
+ clearError: () => void;
42
46
  // useXConversations
43
47
  sessions: SessionSummary[];
44
48
  activeId: string;
@@ -54,7 +58,6 @@ interface ChatContextValue<Message = ChatMessage> {
54
58
 
55
59
  const ChatContext = createContext<ChatContextValue | null>(null);
56
60
 
57
- // 内存存储 fallback(无 storage 时使用)
58
61
  const memoryStorage: ChatStorage = {
59
62
  _data: new Map<string, string>(),
60
63
  async getItem(key: string) {
@@ -4,7 +4,7 @@
4
4
  * 消费 ChatProvider,输出可渲染的 messages 列表
5
5
  */
6
6
 
7
- import {useCallback, useEffect, useRef} from 'react';
7
+ import {useCallback, useEffect, useRef, useSyncExternalStore} from 'react';
8
8
  import {createChatStore} from '../stores/chatStore';
9
9
  import type {AbstractChatProvider} from '../providers/AbstractChatProvider';
10
10
  import type {ChatMessage, MessageStatus} from '../types/message';
@@ -36,8 +36,12 @@ export interface UseXChatReturn<
36
36
  messages: MessageInfo<Message>[];
37
37
  requesting: boolean;
38
38
  suggestions: SuggestionItem[];
39
+ error: string | null;
39
40
  onRequest: (input: Input) => void;
40
41
  abort: () => void;
42
+ resetChat: () => void;
43
+ loadSession: (messages: ChatMessage[]) => void;
44
+ clearError: () => void;
41
45
  }
42
46
 
43
47
  let _idCounter = 0;
@@ -55,27 +59,25 @@ export function useXChat<
55
59
  const storeRef = useRef(createChatStore());
56
60
  const store = storeRef.current;
57
61
 
58
- // 订阅 store 变化
59
- const state = store.getState();
60
-
61
- // 同步最新值(组件级别使用 zustand subscribe 或 useSyncExternalStore)
62
- // 简化实现:直接用 store.getState()
63
- const getState = useCallback(() => store.getState(), [store]);
62
+ // 使用 useSyncExternalStore 订阅 zustand store 变化
63
+ const state = useSyncExternalStore(
64
+ store.subscribe,
65
+ store.getState,
66
+ store.getState,
67
+ );
64
68
 
65
69
  const onRequest = useCallback(
66
70
  (input: Input) => {
67
- const s = getState();
68
- if (s.isRequesting) return; // 请求去重
71
+ const s = store.getState();
72
+ if (s.isRequesting) return;
69
73
 
70
74
  store.getState().setRequesting(true);
71
75
  store.getState().setError(null);
72
76
  store.getState().setSuggestions([]);
73
77
 
74
- // 添加用户本地消息
75
78
  const localMessage = provider.transformLocalMessage(input);
76
79
  store.getState().addMessage(localMessage as unknown as ChatMessage);
77
80
 
78
- // 发起请求
79
81
  provider.sendMessage(input, {
80
82
  onUpdate: (message) => {
81
83
  store
@@ -101,9 +103,12 @@ export function useXChat<
101
103
  .addMessage(fallbackMsg as unknown as ChatMessage);
102
104
  }
103
105
  },
106
+ onSuggestion: (items) => {
107
+ store.getState().setSuggestions(items);
108
+ },
104
109
  });
105
110
  },
106
- [provider, getState, store, config]
111
+ [provider, store, config]
107
112
  );
108
113
 
109
114
  const abort = useCallback(() => {
@@ -111,14 +116,27 @@ export function useXChat<
111
116
  store.getState().setRequesting(false);
112
117
  }, [provider, store]);
113
118
 
114
- // 清理
119
+ const resetChat = useCallback(() => {
120
+ store.getState().resetChat();
121
+ }, [store]);
122
+
123
+ const loadSession = useCallback(
124
+ (messages: ChatMessage[]) => {
125
+ store.getState().loadSession(messages);
126
+ },
127
+ [store]
128
+ );
129
+
130
+ const clearError = useCallback(() => {
131
+ store.getState().setError(null);
132
+ }, [store]);
133
+
115
134
  useEffect(() => {
116
135
  return () => {
117
136
  provider.abort();
118
137
  };
119
138
  }, [provider]);
120
139
 
121
- // 转换为 MessageInfo 格式
122
140
  const messages: MessageInfo<Message>[] = state.messages.map((m) => ({
123
141
  id: m.id,
124
142
  message: m as unknown as Message,
@@ -130,7 +148,11 @@ export function useXChat<
130
148
  messages,
131
149
  requesting: state.isRequesting,
132
150
  suggestions: state.suggestions,
151
+ error: state.error,
133
152
  onRequest,
134
153
  abort,
154
+ resetChat,
155
+ loadSession,
156
+ clearError,
135
157
  };
136
158
  }
package/src/index.ts CHANGED
@@ -20,6 +20,7 @@ export type {
20
20
  SSEErrorEvent,
21
21
  SSEDoneEvent,
22
22
  SSEEnvelope,
23
+ SuggestionItem,
23
24
  } from './types/sse';
24
25
 
25
26
  export type {
@@ -49,7 +50,7 @@ export type {ChatStorage} from './stores/storage';
49
50
 
50
51
  // Hooks
51
52
  export {useXChat} from './hooks/useXChat';
52
- export type {UseXChatConfig, MessageInfo} from './hooks/useXChat';
53
+ export type {UseXChatConfig, UseXChatReturn, MessageInfo} from './hooks/useXChat';
53
54
 
54
55
  export {useXConversations} from './hooks/useXConversations';
55
56
  export type {UseXConversationsOptions, SessionSummary} from './hooks/useXConversations';
@@ -7,6 +7,7 @@
7
7
  import {XStream} from './XStream';
8
8
  import {SSEParser} from './SSEParser';
9
9
  import type {SSEOutput} from '../types/provider';
10
+ import type {SSEEnvelope} from '../types/sse';
10
11
 
11
12
  export interface XRequestConfig {
12
13
  baseURL: string;
@@ -49,11 +50,12 @@ export class XRequest {
49
50
  } = this.config;
50
51
 
51
52
  // 注入 auth token
53
+ const reqHeaders = {...headers};
52
54
  if (getToken) {
53
55
  try {
54
56
  const token = await getToken();
55
57
  if (token) {
56
- headers['Authorization'] = `Bearer ${token}`;
58
+ reqHeaders['Authorization'] = `Bearer ${token}`;
57
59
  }
58
60
  } catch {
59
61
  // token 获取失败,继续无认证请求
@@ -65,14 +67,21 @@ export class XRequest {
65
67
  this.stream = new XStream({
66
68
  url,
67
69
  method: 'POST',
68
- headers,
70
+ headers: reqHeaders,
69
71
  body: params,
70
72
  timeout,
71
73
  });
72
74
 
73
75
  this.stream.connect({
74
76
  onMessage: (output: SSEOutput) => {
75
- callbacks.onStream?.(output);
77
+ // 通过 SSEParser 进行幂等去重和排序
78
+ const envelopes: SSEEnvelope[] = this.parser.process(output.data);
79
+ for (const envelope of envelopes) {
80
+ callbacks.onStream?.({
81
+ data: JSON.stringify(envelope),
82
+ event: envelope.type,
83
+ });
84
+ }
76
85
  },
77
86
  onError: (error: Error) => {
78
87
  callbacks.onError?.({
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import type {ChatMessage} from './message';
7
+ import type {SuggestionItem} from './sse';
7
8
 
8
9
  export interface RequestParams<T = ChatMessage> {
9
10
  message: T;
@@ -21,4 +22,5 @@ export interface ProviderCallbacks<Message = ChatMessage> {
21
22
  onUpdate: (message: Message) => void;
22
23
  onSuccess: (message: Message) => void;
23
24
  onError: (error: Error) => void;
25
+ onSuggestion?: (items: SuggestionItem[]) => void;
24
26
  }