@unif/react-native-chat-sdk 0.6.0 → 1.0.0

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.
Files changed (67) hide show
  1. package/lib/commonjs/hooks/ChatProvider.js +26 -8
  2. package/lib/commonjs/hooks/ChatProvider.js.map +1 -1
  3. package/lib/commonjs/hooks/useXChat.js +76 -32
  4. package/lib/commonjs/hooks/useXChat.js.map +1 -1
  5. package/lib/commonjs/index.js.map +1 -1
  6. package/lib/commonjs/providers/AbstractChatProvider.js +8 -4
  7. package/lib/commonjs/providers/AbstractChatProvider.js.map +1 -1
  8. package/lib/commonjs/providers/DeepSeekChatProvider.js +7 -4
  9. package/lib/commonjs/providers/DeepSeekChatProvider.js.map +1 -1
  10. package/lib/commonjs/providers/OpenAIChatProvider.js +6 -1
  11. package/lib/commonjs/providers/OpenAIChatProvider.js.map +1 -1
  12. package/lib/commonjs/stores/chatStore.js +83 -70
  13. package/lib/commonjs/stores/chatStore.js.map +1 -1
  14. package/lib/module/hooks/ChatProvider.js +26 -8
  15. package/lib/module/hooks/ChatProvider.js.map +1 -1
  16. package/lib/module/hooks/useXChat.js +77 -33
  17. package/lib/module/hooks/useXChat.js.map +1 -1
  18. package/lib/module/index.js.map +1 -1
  19. package/lib/module/providers/AbstractChatProvider.js +8 -4
  20. package/lib/module/providers/AbstractChatProvider.js.map +1 -1
  21. package/lib/module/providers/DeepSeekChatProvider.js +7 -4
  22. package/lib/module/providers/DeepSeekChatProvider.js.map +1 -1
  23. package/lib/module/providers/OpenAIChatProvider.js +6 -1
  24. package/lib/module/providers/OpenAIChatProvider.js.map +1 -1
  25. package/lib/module/stores/chatStore.js +83 -68
  26. package/lib/module/stores/chatStore.js.map +1 -1
  27. package/lib/typescript/commonjs/hooks/ChatProvider.d.ts +10 -3
  28. package/lib/typescript/commonjs/hooks/ChatProvider.d.ts.map +1 -1
  29. package/lib/typescript/commonjs/hooks/useXChat.d.ts +10 -4
  30. package/lib/typescript/commonjs/hooks/useXChat.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/index.d.ts +2 -1
  32. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/providers/AbstractChatProvider.d.ts +6 -6
  34. package/lib/typescript/commonjs/providers/AbstractChatProvider.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/providers/DeepSeekChatProvider.d.ts +3 -2
  36. package/lib/typescript/commonjs/providers/DeepSeekChatProvider.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/providers/OpenAIChatProvider.d.ts +3 -2
  38. package/lib/typescript/commonjs/providers/OpenAIChatProvider.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/stores/chatStore.d.ts +24 -14
  40. package/lib/typescript/commonjs/stores/chatStore.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/types/provider.d.ts +14 -1
  42. package/lib/typescript/commonjs/types/provider.d.ts.map +1 -1
  43. package/lib/typescript/module/hooks/ChatProvider.d.ts +10 -3
  44. package/lib/typescript/module/hooks/ChatProvider.d.ts.map +1 -1
  45. package/lib/typescript/module/hooks/useXChat.d.ts +10 -4
  46. package/lib/typescript/module/hooks/useXChat.d.ts.map +1 -1
  47. package/lib/typescript/module/index.d.ts +2 -1
  48. package/lib/typescript/module/index.d.ts.map +1 -1
  49. package/lib/typescript/module/providers/AbstractChatProvider.d.ts +6 -6
  50. package/lib/typescript/module/providers/AbstractChatProvider.d.ts.map +1 -1
  51. package/lib/typescript/module/providers/DeepSeekChatProvider.d.ts +3 -2
  52. package/lib/typescript/module/providers/DeepSeekChatProvider.d.ts.map +1 -1
  53. package/lib/typescript/module/providers/OpenAIChatProvider.d.ts +3 -2
  54. package/lib/typescript/module/providers/OpenAIChatProvider.d.ts.map +1 -1
  55. package/lib/typescript/module/stores/chatStore.d.ts +24 -14
  56. package/lib/typescript/module/stores/chatStore.d.ts.map +1 -1
  57. package/lib/typescript/module/types/provider.d.ts +14 -1
  58. package/lib/typescript/module/types/provider.d.ts.map +1 -1
  59. package/package.json +1 -1
  60. package/src/hooks/ChatProvider.tsx +34 -17
  61. package/src/hooks/useXChat.ts +111 -61
  62. package/src/index.ts +3 -0
  63. package/src/providers/AbstractChatProvider.ts +17 -9
  64. package/src/providers/DeepSeekChatProvider.ts +5 -6
  65. package/src/providers/OpenAIChatProvider.ts +10 -2
  66. package/src/stores/chatStore.ts +53 -41
  67. package/src/types/provider.ts +19 -1
@@ -1,11 +1,18 @@
1
1
  /**
2
- * useXChat — 聊天操作 hook
2
+ * useXChat — 聊天操作 Hook
3
3
  *
4
- * 消费 ChatProvider,输出可渲染的 messages 列表
4
+ * 支持 isRequesting / parser 消息展开 / defaultMessages 初始消息
5
5
  */
6
6
 
7
- import { useCallback, useEffect, useMemo, useSyncExternalStore } from 'react';
7
+ import {
8
+ useCallback,
9
+ useEffect,
10
+ useMemo,
11
+ useRef,
12
+ useSyncExternalStore,
13
+ } from 'react';
8
14
  import { getOrCreateChatStore } from '../stores/chatStore';
15
+ import type { StoreMessage } from '../stores/chatStore';
9
16
  import type { AbstractChatProvider } from '../providers/AbstractChatProvider';
10
17
  import type { ChatMessage, MessageStatus } from '../types/message';
11
18
  import type { RequestParams, SSEOutput } from '../types/provider';
@@ -25,6 +32,10 @@ export interface UseXChatConfig<
25
32
  > {
26
33
  conversationKey: string;
27
34
  provider: AbstractChatProvider<Message, Input, Output>;
35
+ /** 将单条消息解析为多条展示消息 */
36
+ parser?: (message: Message) => Message[];
37
+ /** 初始消息(同步数组或异步 Promise) */
38
+ defaultMessages?: Message[] | (() => Promise<Message[]>);
28
39
  requestPlaceholder?: (input: Input) => Message;
29
40
  requestFallback?: (input: Input, info: { error: Error }) => Message;
30
41
  generateId?: () => string;
@@ -35,7 +46,8 @@ export interface UseXChatReturn<
35
46
  Input = RequestParams<Message>,
36
47
  > {
37
48
  messages: MessageInfo<Message>[];
38
- requesting: boolean;
49
+ /** 是否正在请求 */
50
+ isRequesting: boolean;
39
51
  suggestions: SuggestionItem[];
40
52
  error: string | null;
41
53
  onRequest: (input: Input) => void;
@@ -56,21 +68,47 @@ export interface UseXChatReturn<
56
68
  removeMessage: (id: string) => void;
57
69
  }
58
70
 
71
+ /** 从 Message 中安全读取 status 字段 */
72
+ function getStatus<M>(m: M): MessageStatus {
73
+ return ((m as Record<string, unknown>).status as MessageStatus) ?? 'local';
74
+ }
75
+
76
+ /** 从 Message 中安全读取 extra 字段 */
77
+ function getExtra<M>(m: M): Record<string, unknown> | undefined {
78
+ return (m as Record<string, unknown>).extra as
79
+ | Record<string, unknown>
80
+ | undefined;
81
+ }
82
+
59
83
  export function useXChat<
60
- Message = ChatMessage,
84
+ Message extends StoreMessage = ChatMessage,
61
85
  Input = RequestParams<Message>,
62
86
  Output = SSEOutput,
63
87
  >(
64
88
  config: UseXChatConfig<Message, Input, Output>
65
89
  ): UseXChatReturn<Message, Input> {
66
- const { provider, conversationKey } = config;
90
+ const { provider, conversationKey, parser, defaultMessages } = config;
91
+ const defaultMessagesLoadedRef = useRef(false);
67
92
 
68
93
  const store = useMemo(
69
- () => getOrCreateChatStore(conversationKey),
94
+ () => getOrCreateChatStore<Message>(conversationKey),
70
95
  [conversationKey]
71
96
  );
72
97
 
73
- // 使用 useSyncExternalStore 订阅 zustand store 变化
98
+ // 首次挂载时加载 defaultMessages
99
+ useEffect(() => {
100
+ if (!defaultMessages || defaultMessagesLoadedRef.current) return;
101
+ defaultMessagesLoadedRef.current = true;
102
+
103
+ if (Array.isArray(defaultMessages)) {
104
+ store.getState().setMessages(defaultMessages);
105
+ } else {
106
+ defaultMessages().then((msgs) => {
107
+ store.getState().setMessages(msgs);
108
+ });
109
+ }
110
+ }, [defaultMessages, store]);
111
+
74
112
  const state = useSyncExternalStore(
75
113
  store.subscribe,
76
114
  store.getState,
@@ -87,20 +125,21 @@ export function useXChat<
87
125
  store.getState().setSuggestions([]);
88
126
 
89
127
  const localMessage = provider.transformLocalMessage(input);
90
- const localChatMessage = localMessage as unknown as ChatMessage;
91
- store.getState().addMessage(localChatMessage);
128
+ store.getState().addMessage(localMessage);
92
129
 
93
- // 注入 loading 占位消息(显示"正在思考"动画)
94
- const loadingMessageId = `loading-${localChatMessage.id}`;
95
- const loadingMessage: ChatMessage = {
130
+ // loading 占位消息:合成临时对象,收到首条响应后立即移除。
131
+ // 这里无法直接构造 Message 泛型实例,因此使用 unknown 中转,
132
+ // 这是整个 SDK 中唯一一处不可避免的类型断言。
133
+ const loadingMessageId = `loading-${localMessage.id}`;
134
+ const loadingMessage = {
96
135
  id: loadingMessageId,
97
136
  text: '',
98
137
  role: 'assistant',
99
138
  createdAt: new Date(),
100
139
  messageType: 'text',
101
- status: 'loading',
102
- turnId: localChatMessage.turnId,
103
- };
140
+ status: 'loading' as MessageStatus,
141
+ turnId: (localMessage as Record<string, unknown>).turnId ?? '',
142
+ } as unknown as Message;
104
143
  store.getState().addMessage(loadingMessage);
105
144
 
106
145
  let loadingRemoved = false;
@@ -114,15 +153,13 @@ export function useXChat<
114
153
  provider.sendMessage(input, {
115
154
  onUpdate: (message) => {
116
155
  removeLoading();
117
- store.getState().upsertMessage(message as unknown as ChatMessage);
156
+ store.getState().upsertMessage(message);
118
157
  },
119
158
  onSuccess: (message) => {
120
159
  removeLoading();
121
- const m = message as unknown as ChatMessage;
122
- store.getState().upsertMessage({
123
- ...m,
124
- status: 'success',
125
- });
160
+ store
161
+ .getState()
162
+ .upsertMessage({ ...message, status: 'success' } as Message);
126
163
  store.getState().setRequesting(false);
127
164
  },
128
165
  onError: (error) => {
@@ -132,7 +169,7 @@ export function useXChat<
132
169
 
133
170
  if (config.requestFallback) {
134
171
  const fallbackMsg = config.requestFallback(input, { error });
135
- store.getState().addMessage(fallbackMsg as unknown as ChatMessage);
172
+ store.getState().addMessage(fallbackMsg);
136
173
  }
137
174
  },
138
175
  onSuggestion: (items) => {
@@ -145,9 +182,8 @@ export function useXChat<
145
182
 
146
183
  const abort = useCallback(() => {
147
184
  provider.abort();
148
- // 清理可能存在的 loading 占位消息
149
185
  const msgs = store.getState().messages;
150
- const loadingMsg = msgs.find((m) => m.status === 'loading');
186
+ const loadingMsg = msgs.find((m) => getStatus(m) === 'loading');
151
187
  if (loadingMsg) {
152
188
  store.getState().removeMessage(loadingMsg.id);
153
189
  }
@@ -163,7 +199,6 @@ export function useXChat<
163
199
  const s = store.getState();
164
200
  if (s.isRequesting) return;
165
201
 
166
- // 将目标消息标记为 loading,移除其后的 assistant 消息
167
202
  const targetIdx = s.messages.findIndex((m) => m.id === id);
168
203
  if (targetIdx < 0) return;
169
204
 
@@ -173,14 +208,12 @@ export function useXChat<
173
208
 
174
209
  provider.sendMessage(input, {
175
210
  onUpdate: (message) => {
176
- store.getState().upsertMessage(message as unknown as ChatMessage);
211
+ store.getState().upsertMessage(message);
177
212
  },
178
213
  onSuccess: (message) => {
179
- const m = message as unknown as ChatMessage;
180
- store.getState().upsertMessage({
181
- ...m,
182
- status: 'success',
183
- });
214
+ store
215
+ .getState()
216
+ .upsertMessage({ ...message, status: 'success' } as Message);
184
217
  store.getState().setRequesting(false);
185
218
  },
186
219
  onError: (error) => {
@@ -189,7 +222,7 @@ export function useXChat<
189
222
 
190
223
  if (config.requestFallback) {
191
224
  const fallbackMsg = config.requestFallback(input, { error });
192
- store.getState().addMessage(fallbackMsg as unknown as ChatMessage);
225
+ store.getState().addMessage(fallbackMsg);
193
226
  }
194
227
  },
195
228
  onSuggestion: (items) => {
@@ -208,24 +241,11 @@ export function useXChat<
208
241
  ) => {
209
242
  if (typeof messagesOrFn === 'function') {
210
243
  const current = store.getState().messages;
211
- const currentInfos: MessageInfo<Message>[] = current.map((m) => ({
212
- id: m.id,
213
- message: m as unknown as Message,
214
- status: m.status,
215
- extra: m.extra as Record<string, unknown> | undefined,
216
- }));
244
+ const currentInfos = toMessageInfoList(current);
217
245
  const next = messagesOrFn(currentInfos);
218
- store
219
- .getState()
220
- .setMessages(
221
- next.map((info) => info.message as unknown as ChatMessage)
222
- );
246
+ store.getState().setMessages(next.map((info) => info.message));
223
247
  } else {
224
- store
225
- .getState()
226
- .setMessages(
227
- messagesOrFn.map((info) => info.message as unknown as ChatMessage)
228
- );
248
+ store.getState().setMessages(messagesOrFn.map((info) => info.message));
229
249
  }
230
250
  },
231
251
  [store]
@@ -241,18 +261,18 @@ export function useXChat<
241
261
  store.getState().setMessage(id, (m) => {
242
262
  const info: MessageInfo<Message> = {
243
263
  id: m.id,
244
- message: m as unknown as Message,
245
- status: m.status,
246
- extra: m.extra as Record<string, unknown> | undefined,
264
+ message: m,
265
+ status: getStatus(m),
266
+ extra: getExtra(m),
247
267
  };
248
268
  const partial =
249
269
  typeof partialOrFn === 'function' ? partialOrFn(info) : partialOrFn;
250
- const updated: Partial<ChatMessage> = {};
270
+ const updated: Partial<Message> = {};
251
271
  if (partial.message !== undefined) {
252
272
  Object.assign(updated, partial.message);
253
273
  }
254
274
  if (partial.status !== undefined) {
255
- updated.status = partial.status;
275
+ (updated as Record<string, unknown>).status = partial.status;
256
276
  }
257
277
  return updated;
258
278
  });
@@ -273,16 +293,34 @@ export function useXChat<
273
293
  };
274
294
  }, [provider]);
275
295
 
276
- const messages: MessageInfo<Message>[] = state.messages.map((m) => ({
277
- id: m.id,
278
- message: m as unknown as Message,
279
- status: m.status,
280
- extra: m.extra as Record<string, unknown> | undefined,
281
- }));
296
+ // 应用 parser(一对多展开)
297
+ const messages = useMemo<MessageInfo<Message>[]>(() => {
298
+ const rawInfos = toMessageInfoList(state.messages);
299
+
300
+ if (!parser) return rawInfos;
301
+
302
+ const expanded: MessageInfo<Message>[] = [];
303
+ for (const info of rawInfos) {
304
+ const parsed = parser(info.message);
305
+ if (parsed.length === 0) continue;
306
+ if (parsed.length === 1) {
307
+ expanded.push({ ...info, message: parsed[0]! });
308
+ } else {
309
+ parsed.forEach((p, i) => {
310
+ expanded.push({
311
+ ...info,
312
+ id: `${info.id}-${i}`,
313
+ message: p,
314
+ });
315
+ });
316
+ }
317
+ }
318
+ return expanded;
319
+ }, [state.messages, parser]);
282
320
 
283
321
  return {
284
322
  messages,
285
- requesting: state.isRequesting,
323
+ isRequesting: state.isRequesting,
286
324
  suggestions: state.suggestions,
287
325
  error: state.error,
288
326
  onRequest,
@@ -294,3 +332,15 @@ export function useXChat<
294
332
  removeMessage,
295
333
  };
296
334
  }
335
+
336
+ /** store messages → MessageInfo 列表 */
337
+ function toMessageInfoList<M extends StoreMessage>(
338
+ messages: M[]
339
+ ): MessageInfo<M>[] {
340
+ return messages.map((m) => ({
341
+ id: m.id,
342
+ message: m,
343
+ status: getStatus(m),
344
+ extra: getExtra(m),
345
+ }));
346
+ }
package/src/index.ts CHANGED
@@ -27,6 +27,8 @@ export type {
27
27
  RequestParams,
28
28
  SSEOutput,
29
29
  ProviderCallbacks,
30
+ TransformMessageInfo,
31
+ TransformParamsOptions,
30
32
  } from './types/provider';
31
33
 
32
34
  // Tools
@@ -47,6 +49,7 @@ export type { DeepSeekChatProviderConfig } from './providers/DeepSeekChatProvide
47
49
 
48
50
  // Stores
49
51
  export type { ChatStorage } from './stores/storage';
52
+ export type { StoreMessage, ChatStoreApi } from './stores/chatStore';
50
53
  export {
51
54
  getOrCreateChatStore,
52
55
  deleteChatStore,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * AbstractChatProvider — Provider 抽象基类
3
3
  *
4
- * 3 个抽象方法:transformParams / transformLocalMessage / transformMessage
4
+ * 定义 transformMessage / transformParams / transformLocalMessage 抽象方法
5
5
  */
6
6
 
7
7
  import type { XRequest, XRequestCallbacks } from '../tools/XRequest';
@@ -10,6 +10,8 @@ import type {
10
10
  RequestParams,
11
11
  SSEOutput,
12
12
  ProviderCallbacks,
13
+ TransformMessageInfo,
14
+ TransformParamsOptions,
13
15
  } from '../types/provider';
14
16
 
15
17
  export abstract class AbstractChatProvider<
@@ -25,9 +27,12 @@ export abstract class AbstractChatProvider<
25
27
 
26
28
  /**
27
29
  * 转换请求参数
28
- * useXChat.onRequest() 传入的参数转换为 XRequest 需要的格式
30
+ * 新签名:接收 options 参数(含 messages 等上下文)
29
31
  */
30
- abstract transformParams(input: Input): Record<string, unknown>;
32
+ abstract transformParams(
33
+ input: Input,
34
+ options?: TransformParamsOptions<Message>
35
+ ): Record<string, unknown>;
31
36
 
32
37
  /**
33
38
  * 转换本地消息
@@ -37,9 +42,11 @@ export abstract class AbstractChatProvider<
37
42
 
38
43
  /**
39
44
  * 转换响应消息
40
- * XRequest 返回的流数据转换为 ChatMessage(用于 assistant 消息渲染)
45
+ * 新签名:接收 info 对象(含 output + current)
41
46
  */
42
- abstract transformMessage(output: Output, current?: Message): Message;
47
+ abstract transformMessage(
48
+ info: TransformMessageInfo<Message, Output>
49
+ ): Message;
43
50
 
44
51
  /**
45
52
  * 发起请求(内部调用 XRequest)
@@ -50,10 +57,11 @@ export abstract class AbstractChatProvider<
50
57
 
51
58
  const requestCallbacks: XRequestCallbacks = {
52
59
  onStream: (chunk) => {
53
- currentMessage = this.transformMessage(
54
- chunk as unknown as Output,
55
- currentMessage
56
- );
60
+ // XRequest 回调类型是 SSEOutput,与 Output 泛型默认一致
61
+ currentMessage = this.transformMessage({
62
+ output: chunk as Output,
63
+ current: currentMessage,
64
+ });
57
65
  callbacks.onUpdate(currentMessage);
58
66
  },
59
67
  onSuccess: () => {
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { OpenAIChatProvider } from './OpenAIChatProvider';
9
9
  import type { ChatMessage } from '../types/message';
10
- import type { SSEOutput } from '../types/provider';
10
+ import type { SSEOutput, TransformMessageInfo } from '../types/provider';
11
11
 
12
12
  export interface DeepSeekChatProviderConfig {
13
13
  apiKey?: string;
@@ -28,11 +28,12 @@ export class DeepSeekChatProvider extends OpenAIChatProvider {
28
28
 
29
29
  /**
30
30
  * 覆写 transformMessage,处理 DeepSeek reasoning_content
31
+ * 新签名:info.output + info.current
31
32
  */
32
33
  override transformMessage(
33
- output: SSEOutput,
34
- current?: ChatMessage
34
+ info: TransformMessageInfo<ChatMessage, SSEOutput>
35
35
  ): ChatMessage {
36
+ const { output, current } = info;
36
37
  const raw = output.data;
37
38
 
38
39
  if (raw.trim() === '[DONE]') {
@@ -62,7 +63,6 @@ export class DeepSeekChatProvider extends OpenAIChatProvider {
62
63
 
63
64
  // 将 reasoning_content 映射为 <think> 标签包裹
64
65
  if (reasoningContent) {
65
- // 如果还没有开始 think 标签,添加开始标签
66
66
  if (!prevText.includes('<think>')) {
67
67
  newText += '<think>';
68
68
  }
@@ -70,7 +70,6 @@ export class DeepSeekChatProvider extends OpenAIChatProvider {
70
70
  }
71
71
 
72
72
  if (content) {
73
- // 如果有未关闭的 think 标签,先关闭
74
73
  if (newText.includes('<think>') && !newText.includes('</think>')) {
75
74
  newText += '</think>';
76
75
  }
@@ -96,7 +95,7 @@ export class DeepSeekChatProvider extends OpenAIChatProvider {
96
95
  turnId: current?.turnId ?? `turn-${Date.now()}`,
97
96
  };
98
97
  } catch {
99
- return current ?? super.transformMessage(output, current);
98
+ return current ?? super.transformMessage(info);
100
99
  }
101
100
  }
102
101
  }
@@ -7,7 +7,11 @@
7
7
  import { AbstractChatProvider } from './AbstractChatProvider';
8
8
  import { XRequest } from '../tools/XRequest';
9
9
  import type { ChatMessage, MessageStatus } from '../types/message';
10
- import type { RequestParams, SSEOutput } from '../types/provider';
10
+ import type {
11
+ RequestParams,
12
+ SSEOutput,
13
+ TransformMessageInfo,
14
+ } from '../types/provider';
11
15
 
12
16
  export interface OpenAIChatProviderConfig {
13
17
  baseURL: string;
@@ -82,8 +86,12 @@ export class OpenAIChatProvider extends AbstractChatProvider<
82
86
 
83
87
  /**
84
88
  * 将 SSE delta 转为 ChatMessage(累积 content)
89
+ * 新签名:info.output + info.current
85
90
  */
86
- transformMessage(output: SSEOutput, current?: ChatMessage): ChatMessage {
91
+ transformMessage(
92
+ info: TransformMessageInfo<ChatMessage, SSEOutput>
93
+ ): ChatMessage {
94
+ const { output, current } = info;
87
95
  const raw = output.data;
88
96
 
89
97
  // OpenAI 结束标记
@@ -1,64 +1,44 @@
1
1
  /**
2
2
  * Chat 消息状态管理(Zustand)
3
- * 内部 store,通过 useXChat hook 封装后对外暴露
3
+ *
4
+ * 泛型 store,通过 useXChat hook 封装后对外暴露。
5
+ * M 约束:必须包含 id 字段用于查找/去重。
4
6
  */
5
7
 
6
8
  import { create } from 'zustand';
7
- import type { ChatMessage } from '../types/message';
8
9
  import type { SuggestionItem } from '../types/sse';
9
10
 
10
- interface ChatState {
11
- messages: ChatMessage[];
12
- streamingText: string;
11
+ /** store 中消息的最小约束:必须有 id 用于查找/去重 */
12
+ export interface StoreMessage {
13
+ id: string;
14
+ }
15
+
16
+ interface ChatState<M extends StoreMessage> {
17
+ messages: M[];
13
18
  suggestions: SuggestionItem[];
14
19
  isRequesting: boolean;
15
20
  error: string | null;
16
21
 
17
- addMessage: (message: ChatMessage) => void;
18
- updateMessage: (
19
- id: string,
20
- updater: (msg: ChatMessage) => ChatMessage
21
- ) => void;
22
- upsertMessage: (message: ChatMessage) => void;
23
- setMessages: (
24
- messages: ChatMessage[] | ((prev: ChatMessage[]) => ChatMessage[])
25
- ) => void;
22
+ addMessage: (message: M) => void;
23
+ updateMessage: (id: string, updater: (msg: M) => M) => void;
24
+ upsertMessage: (message: M) => void;
25
+ setMessages: (messages: M[] | ((prev: M[]) => M[])) => void;
26
26
  setMessage: (
27
27
  id: string,
28
- partial: Partial<ChatMessage> | ((msg: ChatMessage) => Partial<ChatMessage>)
28
+ partial: Partial<M> | ((msg: M) => Partial<M>)
29
29
  ) => void;
30
30
  removeMessage: (id: string) => void;
31
31
  setSuggestions: (items: SuggestionItem[]) => void;
32
32
  setRequesting: (v: boolean) => void;
33
33
  setError: (error: string | null) => void;
34
34
  resetChat: () => void;
35
- loadSession: (messages: ChatMessage[]) => void;
36
- }
37
-
38
- // 全局 store 管理器 — 多会话内存常驻
39
- const chatStoreMap = new Map<string, ReturnType<typeof createChatStore>>();
40
-
41
- export function getOrCreateChatStore(key: string) {
42
- let store = chatStoreMap.get(key);
43
- if (!store) {
44
- store = createChatStore();
45
- chatStoreMap.set(key, store);
46
- }
47
- return store;
48
- }
49
-
50
- export function deleteChatStore(key: string) {
51
- chatStoreMap.delete(key);
52
- }
53
-
54
- export function getChatStoreMessages(key: string) {
55
- return chatStoreMap.get(key)?.getState()?.messages;
35
+ loadSession: (messages: M[]) => void;
56
36
  }
57
37
 
58
- export const createChatStore = () =>
59
- create<ChatState>((set, get) => ({
38
+ /** 创建一个泛型 chat store 实例 */
39
+ export function createChatStore<M extends StoreMessage>() {
40
+ return create<ChatState<M>>((set, get) => ({
60
41
  messages: [],
61
- streamingText: '',
62
42
  suggestions: [],
63
43
  isRequesting: false,
64
44
  error: null,
@@ -116,7 +96,6 @@ export const createChatStore = () =>
116
96
  resetChat: () =>
117
97
  set({
118
98
  messages: [],
119
- streamingText: '',
120
99
  suggestions: [],
121
100
  isRequesting: false,
122
101
  error: null,
@@ -125,9 +104,42 @@ export const createChatStore = () =>
125
104
  loadSession: (messages) =>
126
105
  set({
127
106
  messages,
128
- streamingText: '',
129
107
  suggestions: [],
130
108
  isRequesting: false,
131
109
  error: null,
132
110
  }),
133
111
  }));
112
+ }
113
+
114
+ /** store 实例类型 */
115
+ export type ChatStoreApi<M extends StoreMessage> = ReturnType<
116
+ typeof createChatStore<M>
117
+ >;
118
+
119
+ // 全局 store 管理器 — 多会话内存常驻
120
+
121
+ const chatStoreMap = new Map<string, ChatStoreApi<any>>();
122
+
123
+ /** 获取或创建指定会话的 store */
124
+ export function getOrCreateChatStore<M extends StoreMessage>(
125
+ key: string
126
+ ): ChatStoreApi<M> {
127
+ let store = chatStoreMap.get(key);
128
+ if (!store) {
129
+ store = createChatStore<M>();
130
+ chatStoreMap.set(key, store);
131
+ }
132
+ return store;
133
+ }
134
+
135
+ /** 删除指定会话的 store */
136
+ export function deleteChatStore(key: string) {
137
+ chatStoreMap.delete(key);
138
+ }
139
+
140
+ /** 读取指定会话的消息列表 */
141
+ export function getChatStoreMessages<M extends StoreMessage>(
142
+ key: string
143
+ ): M[] | undefined {
144
+ return chatStoreMap.get(key)?.getState()?.messages;
145
+ }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Provider 协议类型
3
- * 定义 ChatProvider ↔ useXChat 之间的数据契约
3
+ *
4
+ * 定义 ChatProvider 与 useXChat 之间的数据契约
4
5
  */
5
6
 
6
7
  import type { ChatMessage } from './message';
@@ -24,3 +25,20 @@ export interface ProviderCallbacks<Message = ChatMessage> {
24
25
  onError: (error: Error) => void;
25
26
  onSuggestion?: (items: SuggestionItem[]) => void;
26
27
  }
28
+
29
+ /** TransformMessage 接收 info 对象而非位置参数 */
30
+ export interface TransformMessageInfo<
31
+ Message = ChatMessage,
32
+ Output = SSEOutput,
33
+ > {
34
+ /** 当前 SSE 输出块 */
35
+ output: Output;
36
+ /** 已累积的消息(首块时为 undefined) */
37
+ current?: Message;
38
+ }
39
+
40
+ /** TransformParams 选项 */
41
+ export interface TransformParamsOptions<Message = ChatMessage> {
42
+ /** 会话中的全部消息 */
43
+ messages?: Message[];
44
+ }