@unif/react-native-chat-sdk 0.6.1 → 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.
- package/lib/commonjs/hooks/ChatProvider.js +26 -8
- package/lib/commonjs/hooks/ChatProvider.js.map +1 -1
- package/lib/commonjs/hooks/useXChat.js +76 -32
- package/lib/commonjs/hooks/useXChat.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/providers/AbstractChatProvider.js +8 -4
- package/lib/commonjs/providers/AbstractChatProvider.js.map +1 -1
- package/lib/commonjs/providers/DeepSeekChatProvider.js +7 -4
- package/lib/commonjs/providers/DeepSeekChatProvider.js.map +1 -1
- package/lib/commonjs/providers/OpenAIChatProvider.js +6 -1
- package/lib/commonjs/providers/OpenAIChatProvider.js.map +1 -1
- package/lib/commonjs/stores/chatStore.js +83 -70
- package/lib/commonjs/stores/chatStore.js.map +1 -1
- package/lib/module/hooks/ChatProvider.js +26 -8
- package/lib/module/hooks/ChatProvider.js.map +1 -1
- package/lib/module/hooks/useXChat.js +77 -33
- package/lib/module/hooks/useXChat.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/providers/AbstractChatProvider.js +8 -4
- package/lib/module/providers/AbstractChatProvider.js.map +1 -1
- package/lib/module/providers/DeepSeekChatProvider.js +7 -4
- package/lib/module/providers/DeepSeekChatProvider.js.map +1 -1
- package/lib/module/providers/OpenAIChatProvider.js +6 -1
- package/lib/module/providers/OpenAIChatProvider.js.map +1 -1
- package/lib/module/stores/chatStore.js +83 -68
- package/lib/module/stores/chatStore.js.map +1 -1
- package/lib/typescript/commonjs/hooks/ChatProvider.d.ts +10 -3
- package/lib/typescript/commonjs/hooks/ChatProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/useXChat.d.ts +10 -4
- package/lib/typescript/commonjs/hooks/useXChat.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +2 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/providers/AbstractChatProvider.d.ts +6 -6
- package/lib/typescript/commonjs/providers/AbstractChatProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/providers/DeepSeekChatProvider.d.ts +3 -2
- package/lib/typescript/commonjs/providers/DeepSeekChatProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/providers/OpenAIChatProvider.d.ts +3 -2
- package/lib/typescript/commonjs/providers/OpenAIChatProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/stores/chatStore.d.ts +24 -14
- package/lib/typescript/commonjs/stores/chatStore.d.ts.map +1 -1
- package/lib/typescript/commonjs/types/provider.d.ts +14 -1
- package/lib/typescript/commonjs/types/provider.d.ts.map +1 -1
- package/lib/typescript/module/hooks/ChatProvider.d.ts +10 -3
- package/lib/typescript/module/hooks/ChatProvider.d.ts.map +1 -1
- package/lib/typescript/module/hooks/useXChat.d.ts +10 -4
- package/lib/typescript/module/hooks/useXChat.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +2 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/providers/AbstractChatProvider.d.ts +6 -6
- package/lib/typescript/module/providers/AbstractChatProvider.d.ts.map +1 -1
- package/lib/typescript/module/providers/DeepSeekChatProvider.d.ts +3 -2
- package/lib/typescript/module/providers/DeepSeekChatProvider.d.ts.map +1 -1
- package/lib/typescript/module/providers/OpenAIChatProvider.d.ts +3 -2
- package/lib/typescript/module/providers/OpenAIChatProvider.d.ts.map +1 -1
- package/lib/typescript/module/stores/chatStore.d.ts +24 -14
- package/lib/typescript/module/stores/chatStore.d.ts.map +1 -1
- package/lib/typescript/module/types/provider.d.ts +14 -1
- package/lib/typescript/module/types/provider.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/hooks/ChatProvider.tsx +34 -17
- package/src/hooks/useXChat.ts +111 -65
- package/src/index.ts +3 -0
- package/src/providers/AbstractChatProvider.ts +17 -9
- package/src/providers/DeepSeekChatProvider.ts +5 -6
- package/src/providers/OpenAIChatProvider.ts +10 -2
- package/src/stores/chatStore.ts +53 -41
- package/src/types/provider.ts +19 -1
package/src/hooks/useXChat.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* useXChat — 聊天操作
|
|
2
|
+
* useXChat — 聊天操作 Hook
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 支持 isRequesting / parser 消息展开 / defaultMessages 初始消息
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
91
|
-
store.getState().addMessage(localChatMessage);
|
|
128
|
+
store.getState().addMessage(localMessage);
|
|
92
129
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
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:
|
|
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
|
|
156
|
+
store.getState().upsertMessage(message);
|
|
118
157
|
},
|
|
119
158
|
onSuccess: (message) => {
|
|
120
159
|
removeLoading();
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
...
|
|
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
|
|
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
|
|
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
|
|
211
|
+
store.getState().upsertMessage(message);
|
|
177
212
|
},
|
|
178
213
|
onSuccess: (message) => {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
...
|
|
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
|
|
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
|
|
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
|
|
245
|
-
status: m
|
|
246
|
-
extra: m
|
|
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<
|
|
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,20 +293,34 @@ export function useXChat<
|
|
|
273
293
|
};
|
|
274
294
|
}, [provider]);
|
|
275
295
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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]);
|
|
286
320
|
|
|
287
321
|
return {
|
|
288
322
|
messages,
|
|
289
|
-
|
|
323
|
+
isRequesting: state.isRequesting,
|
|
290
324
|
suggestions: state.suggestions,
|
|
291
325
|
error: state.error,
|
|
292
326
|
onRequest,
|
|
@@ -298,3 +332,15 @@ export function useXChat<
|
|
|
298
332
|
removeMessage,
|
|
299
333
|
};
|
|
300
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
|
-
*
|
|
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
|
-
*
|
|
30
|
+
* 新签名:接收 options 参数(含 messages 等上下文)
|
|
29
31
|
*/
|
|
30
|
-
abstract transformParams(
|
|
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
|
-
*
|
|
45
|
+
* 新签名:接收 info 对象(含 output + current)
|
|
41
46
|
*/
|
|
42
|
-
abstract transformMessage(
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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(
|
|
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 {
|
|
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(
|
|
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 结束标记
|
package/src/stores/chatStore.ts
CHANGED
|
@@ -1,64 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Chat 消息状态管理(Zustand)
|
|
3
|
-
*
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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:
|
|
18
|
-
updateMessage: (
|
|
19
|
-
|
|
20
|
-
|
|
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<
|
|
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:
|
|
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
|
-
|
|
59
|
-
|
|
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
|
+
}
|
package/src/types/provider.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Provider 协议类型
|
|
3
|
-
*
|
|
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
|
+
}
|