@unif/react-native-chat-sdk 0.1.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/package.json +49 -0
- package/src/hooks/ChatProvider.tsx +131 -0
- package/src/hooks/useXChat.ts +136 -0
- package/src/hooks/useXConversations.ts +90 -0
- package/src/hooks/useXModel.ts +33 -0
- package/src/index.md +188 -0
- package/src/index.ts +61 -0
- package/src/providers/AbstractChatProvider.ts +74 -0
- package/src/providers/DeepSeekChatProvider.ts +105 -0
- package/src/providers/OpenAIChatProvider.ts +130 -0
- package/src/stores/chatStore.ts +79 -0
- package/src/stores/historyStore.ts +117 -0
- package/src/stores/modelStore.ts +17 -0
- package/src/stores/sessionStore.ts +21 -0
- package/src/stores/storage.ts +10 -0
- package/src/tools/SSEParser.ts +52 -0
- package/src/tools/XRequest.ts +99 -0
- package/src/tools/XStream.ts +112 -0
- package/src/types/message.ts +26 -0
- package/src/types/provider.ts +24 -0
- package/src/types/sse.ts +93 -0
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unif/react-native-chat-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "UNIF React Native Chat SDK — 流式通信、Provider、状态管理",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"source": "./src/index.ts",
|
|
10
|
+
"types": "./src/index.ts",
|
|
11
|
+
"default": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"lib",
|
|
18
|
+
"!**/__tests__",
|
|
19
|
+
"!**/__mocks__",
|
|
20
|
+
"!**/.*"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "bob build",
|
|
24
|
+
"clean": "del-cli lib",
|
|
25
|
+
"typecheck": "tsc"
|
|
26
|
+
},
|
|
27
|
+
"keywords": ["react-native", "chat", "sdk", "sse", "streaming"],
|
|
28
|
+
"author": "qq382724935 <liulijun@pec.com.cn>",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"registry": "https://registry.npmjs.org/"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"zustand": "^4.5.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": "*",
|
|
38
|
+
"react-native": "*",
|
|
39
|
+
"react-native-sse": ">=1.0.0"
|
|
40
|
+
},
|
|
41
|
+
"react-native-builder-bob": {
|
|
42
|
+
"source": "src",
|
|
43
|
+
"output": "lib",
|
|
44
|
+
"targets": [
|
|
45
|
+
["module", { "esm": true }],
|
|
46
|
+
["typescript", { "project": "tsconfig.build.json" }]
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatProvider — 全局数据提供 Context
|
|
3
|
+
*
|
|
4
|
+
* 可选的全局 Provider 模式(也可直接使用 hooks)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, {createContext, useContext, useMemo} from 'react';
|
|
8
|
+
import {useXChat} from './useXChat';
|
|
9
|
+
import {useXConversations} from './useXConversations';
|
|
10
|
+
import {useXModel} from './useXModel';
|
|
11
|
+
import type {UseXChatConfig, MessageInfo} from './useXChat';
|
|
12
|
+
import type {UseXConversationsOptions, SessionSummary} from './useXConversations';
|
|
13
|
+
import type {ModelInfo} from './useXModel';
|
|
14
|
+
import type {ChatMessage} from '../types/message';
|
|
15
|
+
import type {RequestParams, SSEOutput} from '../types/provider';
|
|
16
|
+
import type {SuggestionItem} from '../types/sse';
|
|
17
|
+
import type {ChatStorage} from '../stores/storage';
|
|
18
|
+
import type {AbstractChatProvider} from '../providers/AbstractChatProvider';
|
|
19
|
+
|
|
20
|
+
export interface ChatProviderProps<
|
|
21
|
+
Message = ChatMessage,
|
|
22
|
+
Input = RequestParams<Message>,
|
|
23
|
+
Output = SSEOutput,
|
|
24
|
+
> {
|
|
25
|
+
provider: AbstractChatProvider<Message, Input, Output>;
|
|
26
|
+
storage?: ChatStorage;
|
|
27
|
+
models?: ModelInfo[];
|
|
28
|
+
defaultModel?: string;
|
|
29
|
+
generateId?: () => string;
|
|
30
|
+
requestPlaceholder?: (input: Input) => Message;
|
|
31
|
+
requestFallback?: (input: Input, info: { error: Error }) => Message;
|
|
32
|
+
children: React.ReactNode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ChatContextValue<Message = ChatMessage> {
|
|
36
|
+
// useXChat
|
|
37
|
+
messages: MessageInfo<Message>[];
|
|
38
|
+
requesting: boolean;
|
|
39
|
+
suggestions: SuggestionItem[];
|
|
40
|
+
onRequest: (input: unknown) => void;
|
|
41
|
+
abort: () => void;
|
|
42
|
+
// useXConversations
|
|
43
|
+
sessions: SessionSummary[];
|
|
44
|
+
activeId: string;
|
|
45
|
+
switchSession: (id: string) => Promise<Message[]>;
|
|
46
|
+
newSession: () => void;
|
|
47
|
+
deleteSession: (id: string) => Promise<void>;
|
|
48
|
+
archiveSession: (id: string, title: string, messages: Message[]) => Promise<void>;
|
|
49
|
+
// useXModel
|
|
50
|
+
selectedModel: string;
|
|
51
|
+
models: ModelInfo[];
|
|
52
|
+
setSelectedModel: (id: string) => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const ChatContext = createContext<ChatContextValue | null>(null);
|
|
56
|
+
|
|
57
|
+
// 内存存储 fallback(无 storage 时使用)
|
|
58
|
+
const memoryStorage: ChatStorage = {
|
|
59
|
+
_data: new Map<string, string>(),
|
|
60
|
+
async getItem(key: string) {
|
|
61
|
+
return (this._data as Map<string, string>).get(key) ?? null;
|
|
62
|
+
},
|
|
63
|
+
async setItem(key: string, value: string) {
|
|
64
|
+
(this._data as Map<string, string>).set(key, value);
|
|
65
|
+
},
|
|
66
|
+
async removeItem(key: string) {
|
|
67
|
+
(this._data as Map<string, string>).delete(key);
|
|
68
|
+
},
|
|
69
|
+
} as ChatStorage & { _data: Map<string, string> };
|
|
70
|
+
|
|
71
|
+
export function ChatProvider<
|
|
72
|
+
Message = ChatMessage,
|
|
73
|
+
Input = RequestParams<Message>,
|
|
74
|
+
Output = SSEOutput,
|
|
75
|
+
>(props: ChatProviderProps<Message, Input, Output>) {
|
|
76
|
+
const {
|
|
77
|
+
provider,
|
|
78
|
+
storage,
|
|
79
|
+
models = [],
|
|
80
|
+
defaultModel,
|
|
81
|
+
generateId,
|
|
82
|
+
requestPlaceholder,
|
|
83
|
+
requestFallback,
|
|
84
|
+
children,
|
|
85
|
+
} = props;
|
|
86
|
+
|
|
87
|
+
const chatConfig: UseXChatConfig<Message, Input, Output> = useMemo(
|
|
88
|
+
() => ({
|
|
89
|
+
provider,
|
|
90
|
+
generateId,
|
|
91
|
+
requestPlaceholder,
|
|
92
|
+
requestFallback,
|
|
93
|
+
}),
|
|
94
|
+
[provider, generateId, requestPlaceholder, requestFallback]
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const chat = useXChat(chatConfig);
|
|
98
|
+
|
|
99
|
+
const convOptions: UseXConversationsOptions = useMemo(
|
|
100
|
+
() => ({ storage: storage ?? memoryStorage }),
|
|
101
|
+
[storage]
|
|
102
|
+
);
|
|
103
|
+
const conversations = useXConversations(convOptions);
|
|
104
|
+
|
|
105
|
+
const model = useXModel({ models, defaultModel });
|
|
106
|
+
|
|
107
|
+
const value: ChatContextValue<Message> = useMemo(
|
|
108
|
+
() => ({
|
|
109
|
+
...chat,
|
|
110
|
+
...conversations,
|
|
111
|
+
...model,
|
|
112
|
+
}),
|
|
113
|
+
[chat, conversations, model]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<ChatContext.Provider value={value as unknown as ChatContextValue}>
|
|
118
|
+
{children}
|
|
119
|
+
</ChatContext.Provider>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function useChatContext<
|
|
124
|
+
Message = ChatMessage,
|
|
125
|
+
>(): ChatContextValue<Message> {
|
|
126
|
+
const ctx = useContext(ChatContext);
|
|
127
|
+
if (!ctx) {
|
|
128
|
+
throw new Error('useChatContext must be used within a ChatProvider');
|
|
129
|
+
}
|
|
130
|
+
return ctx as unknown as ChatContextValue<Message>;
|
|
131
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useXChat — 聊天操作 hook
|
|
3
|
+
*
|
|
4
|
+
* 消费 ChatProvider,输出可渲染的 messages 列表
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {useCallback, useEffect, useRef} from 'react';
|
|
8
|
+
import {createChatStore} from '../stores/chatStore';
|
|
9
|
+
import type {AbstractChatProvider} from '../providers/AbstractChatProvider';
|
|
10
|
+
import type {ChatMessage, MessageStatus} from '../types/message';
|
|
11
|
+
import type {RequestParams, SSEOutput} from '../types/provider';
|
|
12
|
+
import type {SuggestionItem} from '../types/sse';
|
|
13
|
+
|
|
14
|
+
export interface MessageInfo<Message = ChatMessage> {
|
|
15
|
+
id: string;
|
|
16
|
+
message: Message;
|
|
17
|
+
status: MessageStatus;
|
|
18
|
+
extra?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UseXChatConfig<
|
|
22
|
+
Message = ChatMessage,
|
|
23
|
+
Input = RequestParams<Message>,
|
|
24
|
+
Output = SSEOutput,
|
|
25
|
+
> {
|
|
26
|
+
provider: AbstractChatProvider<Message, Input, Output>;
|
|
27
|
+
requestPlaceholder?: (input: Input) => Message;
|
|
28
|
+
requestFallback?: (input: Input, info: { error: Error }) => Message;
|
|
29
|
+
generateId?: () => string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UseXChatReturn<
|
|
33
|
+
Message = ChatMessage,
|
|
34
|
+
Input = RequestParams<Message>,
|
|
35
|
+
> {
|
|
36
|
+
messages: MessageInfo<Message>[];
|
|
37
|
+
requesting: boolean;
|
|
38
|
+
suggestions: SuggestionItem[];
|
|
39
|
+
onRequest: (input: Input) => void;
|
|
40
|
+
abort: () => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let _idCounter = 0;
|
|
44
|
+
const defaultGenId = () => `xc-${Date.now()}-${++_idCounter}`;
|
|
45
|
+
|
|
46
|
+
export function useXChat<
|
|
47
|
+
Message = ChatMessage,
|
|
48
|
+
Input = RequestParams<Message>,
|
|
49
|
+
Output = SSEOutput,
|
|
50
|
+
>(
|
|
51
|
+
config: UseXChatConfig<Message, Input, Output>
|
|
52
|
+
): UseXChatReturn<Message, Input> {
|
|
53
|
+
const { provider, generateId = defaultGenId } = config;
|
|
54
|
+
|
|
55
|
+
const storeRef = useRef(createChatStore());
|
|
56
|
+
const store = storeRef.current;
|
|
57
|
+
|
|
58
|
+
// 订阅 store 变化
|
|
59
|
+
const state = store.getState();
|
|
60
|
+
|
|
61
|
+
// 同步最新值(组件级别使用 zustand subscribe 或 useSyncExternalStore)
|
|
62
|
+
// 简化实现:直接用 store.getState()
|
|
63
|
+
const getState = useCallback(() => store.getState(), [store]);
|
|
64
|
+
|
|
65
|
+
const onRequest = useCallback(
|
|
66
|
+
(input: Input) => {
|
|
67
|
+
const s = getState();
|
|
68
|
+
if (s.isRequesting) return; // 请求去重
|
|
69
|
+
|
|
70
|
+
store.getState().setRequesting(true);
|
|
71
|
+
store.getState().setError(null);
|
|
72
|
+
store.getState().setSuggestions([]);
|
|
73
|
+
|
|
74
|
+
// 添加用户本地消息
|
|
75
|
+
const localMessage = provider.transformLocalMessage(input);
|
|
76
|
+
store.getState().addMessage(localMessage as unknown as ChatMessage);
|
|
77
|
+
|
|
78
|
+
// 发起请求
|
|
79
|
+
provider.sendMessage(input, {
|
|
80
|
+
onUpdate: (message) => {
|
|
81
|
+
store
|
|
82
|
+
.getState()
|
|
83
|
+
.upsertMessage(message as unknown as ChatMessage);
|
|
84
|
+
},
|
|
85
|
+
onSuccess: (message) => {
|
|
86
|
+
const m = message as unknown as ChatMessage;
|
|
87
|
+
store.getState().upsertMessage({
|
|
88
|
+
...m,
|
|
89
|
+
status: 'success',
|
|
90
|
+
});
|
|
91
|
+
store.getState().setRequesting(false);
|
|
92
|
+
},
|
|
93
|
+
onError: (error) => {
|
|
94
|
+
store.getState().setError(error.message);
|
|
95
|
+
store.getState().setRequesting(false);
|
|
96
|
+
|
|
97
|
+
if (config.requestFallback) {
|
|
98
|
+
const fallbackMsg = config.requestFallback(input, { error });
|
|
99
|
+
store
|
|
100
|
+
.getState()
|
|
101
|
+
.addMessage(fallbackMsg as unknown as ChatMessage);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
[provider, getState, store, config]
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const abort = useCallback(() => {
|
|
110
|
+
provider.abort();
|
|
111
|
+
store.getState().setRequesting(false);
|
|
112
|
+
}, [provider, store]);
|
|
113
|
+
|
|
114
|
+
// 清理
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
return () => {
|
|
117
|
+
provider.abort();
|
|
118
|
+
};
|
|
119
|
+
}, [provider]);
|
|
120
|
+
|
|
121
|
+
// 转换为 MessageInfo 格式
|
|
122
|
+
const messages: MessageInfo<Message>[] = state.messages.map((m) => ({
|
|
123
|
+
id: m.id,
|
|
124
|
+
message: m as unknown as Message,
|
|
125
|
+
status: m.status,
|
|
126
|
+
extra: m.extra as Record<string, unknown> | undefined,
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
messages,
|
|
131
|
+
requesting: state.isRequesting,
|
|
132
|
+
suggestions: state.suggestions,
|
|
133
|
+
onRequest,
|
|
134
|
+
abort,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useXConversations — 会话管理 hook
|
|
3
|
+
*
|
|
4
|
+
* 组合 sessionStore + historyStore,管理会话生命周期
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {useState, useCallback, useEffect, useRef} from 'react';
|
|
8
|
+
import {createHistoryStore} from '../stores/historyStore';
|
|
9
|
+
import type {ChatStorage} from '../stores/storage';
|
|
10
|
+
import type {ChatMessage} from '../types/message';
|
|
11
|
+
|
|
12
|
+
export {type SessionSummary} from '../stores/historyStore';
|
|
13
|
+
|
|
14
|
+
export interface UseXConversationsOptions {
|
|
15
|
+
storage: ChatStorage;
|
|
16
|
+
maxSessions?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useXConversations(options: UseXConversationsOptions) {
|
|
20
|
+
const {storage} = options;
|
|
21
|
+
|
|
22
|
+
const historyRef = useRef(createHistoryStore(storage));
|
|
23
|
+
const history = historyRef.current;
|
|
24
|
+
|
|
25
|
+
const [sessions, setSessions] = useState(history.sessions);
|
|
26
|
+
const [activeId, setActiveId] = useState('');
|
|
27
|
+
|
|
28
|
+
// 初始化加载
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
// 触发 loadIndex
|
|
31
|
+
history.archiveSession('__init__', '', []).then(() => {
|
|
32
|
+
history.deleteSession('__init__').then(() => {
|
|
33
|
+
setSessions([...history.sessions]);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
const switchSession = useCallback(
|
|
40
|
+
async (id: string): Promise<ChatMessage[]> => {
|
|
41
|
+
setActiveId(id);
|
|
42
|
+
return await history.loadSessionMessages(id);
|
|
43
|
+
},
|
|
44
|
+
[history]
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const newSession = useCallback(() => {
|
|
48
|
+
setActiveId('');
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const deleteSession = useCallback(
|
|
52
|
+
async (id: string) => {
|
|
53
|
+
await history.deleteSession(id);
|
|
54
|
+
setSessions([...history.sessions]);
|
|
55
|
+
if (activeId === id) {
|
|
56
|
+
setActiveId('');
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
[history, activeId]
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const archiveSession = useCallback(
|
|
63
|
+
async (id: string, title: string, messages: ChatMessage[]) => {
|
|
64
|
+
await history.archiveSession(id, title, messages);
|
|
65
|
+
setSessions([...history.sessions]);
|
|
66
|
+
},
|
|
67
|
+
[history]
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const updateSession = useCallback(
|
|
71
|
+
(id: string, data: Partial<{ title: string }>) => {
|
|
72
|
+
const session = history.sessions.find((s) => s.id === id);
|
|
73
|
+
if (session && data.title) {
|
|
74
|
+
session.title = data.title;
|
|
75
|
+
setSessions([...history.sessions]);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
[history]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
sessions,
|
|
83
|
+
activeId,
|
|
84
|
+
switchSession,
|
|
85
|
+
newSession,
|
|
86
|
+
deleteSession,
|
|
87
|
+
archiveSession,
|
|
88
|
+
updateSession,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useXModel — 模型选择 hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {useState, useCallback} from 'react';
|
|
6
|
+
|
|
7
|
+
export interface ModelInfo {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
desc: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UseXModelOptions {
|
|
14
|
+
models: ModelInfo[];
|
|
15
|
+
defaultModel?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function useXModel(options: UseXModelOptions) {
|
|
19
|
+
const {models, defaultModel} = options;
|
|
20
|
+
const [selectedModel, setSelected] = useState(
|
|
21
|
+
defaultModel ?? models[0]?.id ?? ''
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const setSelectedModel = useCallback((id: string) => {
|
|
25
|
+
setSelected(id);
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
selectedModel,
|
|
30
|
+
models,
|
|
31
|
+
setSelectedModel,
|
|
32
|
+
};
|
|
33
|
+
}
|
package/src/index.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: SDK 概览
|
|
3
|
+
nav:
|
|
4
|
+
title: SDK
|
|
5
|
+
path: /sdk
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# @unif/react-native-chat-sdk
|
|
9
|
+
|
|
10
|
+
聊天 SDK 包,提供 Provider 抽象、Hooks、状态管理和流式处理工具。
|
|
11
|
+
|
|
12
|
+
## 架构
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
┌─────────────────────────────────────────┐
|
|
16
|
+
│ useXChat (Hook) │
|
|
17
|
+
│ 组合 Provider + store,输出 messages │
|
|
18
|
+
├─────────────────────────────────────────┤
|
|
19
|
+
│ ChatProvider 层 │
|
|
20
|
+
│ AbstractChatProvider → OpenAI/DeepSeek │
|
|
21
|
+
├─────────────────────────────────────────┤
|
|
22
|
+
│ 工具层 │
|
|
23
|
+
│ XRequest → XStream → SSEParser │
|
|
24
|
+
├─────────────────────────────────────────┤
|
|
25
|
+
│ 数据层 │
|
|
26
|
+
│ chatStore / sessionStore / modelStore │
|
|
27
|
+
└─────────────────────────────────────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 核心 Hooks
|
|
31
|
+
|
|
32
|
+
### useXChat — 聊天操作
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { useXChat, OpenAIChatProvider, XRequest } from '@unif/react-native-chat-sdk';
|
|
36
|
+
|
|
37
|
+
const request = new XRequest({
|
|
38
|
+
baseURL: 'https://api.openai.com',
|
|
39
|
+
getToken: async () => 'sk-...',
|
|
40
|
+
});
|
|
41
|
+
const provider = new OpenAIChatProvider({ request, model: 'gpt-4o' });
|
|
42
|
+
|
|
43
|
+
const { messages, requesting, onRequest, abort } = useXChat({ provider });
|
|
44
|
+
|
|
45
|
+
// 发起请求
|
|
46
|
+
onRequest({ message: { text: '你好' } });
|
|
47
|
+
|
|
48
|
+
// 中止请求
|
|
49
|
+
abort();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**返回值:**
|
|
53
|
+
|
|
54
|
+
| 属性 | 说明 | 类型 |
|
|
55
|
+
|------|------|------|
|
|
56
|
+
| messages | 可渲染消息列表 | `MessageInfo<Message>[]` |
|
|
57
|
+
| requesting | 是否请求中 | `boolean` |
|
|
58
|
+
| suggestions | 建议提示 | `SuggestionItem[]` |
|
|
59
|
+
| onRequest | 发起请求 | `(input: Input) => void` |
|
|
60
|
+
| abort | 中止请求 | `() => void` |
|
|
61
|
+
|
|
62
|
+
### useXConversations — 会话管理
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import { useXConversations } from '@unif/react-native-chat-sdk';
|
|
66
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
67
|
+
|
|
68
|
+
const {
|
|
69
|
+
sessions, activeId,
|
|
70
|
+
switchSession, newSession, deleteSession,
|
|
71
|
+
} = useXConversations({ storage: AsyncStorage });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### useXModel — 模型选择
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { useXModel } from '@unif/react-native-chat-sdk';
|
|
78
|
+
|
|
79
|
+
const { selectedModel, setSelectedModel } = useXModel({
|
|
80
|
+
models: [
|
|
81
|
+
{ id: 'gpt-4o', name: 'GPT-4o', desc: '最强模型' },
|
|
82
|
+
{ id: 'gpt-4o-mini', name: 'GPT-4o Mini', desc: '快速响应' },
|
|
83
|
+
],
|
|
84
|
+
defaultModel: 'gpt-4o',
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Provider 体系
|
|
89
|
+
|
|
90
|
+
### 内置 Provider
|
|
91
|
+
|
|
92
|
+
| Provider | 说明 |
|
|
93
|
+
|----------|------|
|
|
94
|
+
| `OpenAIChatProvider` | 兼容 OpenAI API 格式(含 Azure、Moonshot、通义千问等) |
|
|
95
|
+
| `DeepSeekChatProvider` | 继承 OpenAI,额外处理 `reasoning_content` 字段 |
|
|
96
|
+
|
|
97
|
+
### 自定义 Provider
|
|
98
|
+
|
|
99
|
+
继承 `AbstractChatProvider`,实现 3 个抽象方法:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { AbstractChatProvider } from '@unif/react-native-chat-sdk';
|
|
103
|
+
|
|
104
|
+
class MyProvider extends AbstractChatProvider {
|
|
105
|
+
transformParams(input) {
|
|
106
|
+
// 转换请求参数为你的 API 格式
|
|
107
|
+
return { query: input.message.text, model: input.model };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
transformLocalMessage(input) {
|
|
111
|
+
// 创建本地用户消息
|
|
112
|
+
return { id: generateId(), text: input.message.text, role: 'user', status: 'local', ... };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
transformMessage(output, current) {
|
|
116
|
+
// 解析流式响应,累积到 assistant 消息
|
|
117
|
+
const data = JSON.parse(output.data);
|
|
118
|
+
return { ...current, text: (current?.text || '') + data.content, status: 'updating' };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 工具类
|
|
124
|
+
|
|
125
|
+
### XRequest
|
|
126
|
+
|
|
127
|
+
面向 LLM 的 HTTP 请求工具,内置 token 注入和 SSE 解析。
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
const request = new XRequest({
|
|
131
|
+
baseURL: 'https://api.example.com',
|
|
132
|
+
endpoint: '/v1/chat/completions',
|
|
133
|
+
getToken: async () => await loadToken(),
|
|
134
|
+
timeout: 120000,
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### XStream
|
|
139
|
+
|
|
140
|
+
SSE 流适配器(RN 原生实现,基于 react-native-sse)。
|
|
141
|
+
|
|
142
|
+
### SSEParser
|
|
143
|
+
|
|
144
|
+
SSE 解析器,提供 `event_id` 幂等去重和 `seq` 严格排序。
|
|
145
|
+
|
|
146
|
+
## ChatProvider — 全局模式
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { ChatProvider, useChatContext } from '@unif/react-native-chat-sdk';
|
|
150
|
+
|
|
151
|
+
// 根组件
|
|
152
|
+
<ChatProvider provider={provider} storage={AsyncStorage} models={MODELS}>
|
|
153
|
+
<App />
|
|
154
|
+
</ChatProvider>
|
|
155
|
+
|
|
156
|
+
// 子组件
|
|
157
|
+
const { messages, onRequest, sessions, selectedModel } = useChatContext();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## 类型定义
|
|
161
|
+
|
|
162
|
+
### ChatMessage
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
interface ChatMessage {
|
|
166
|
+
id: string;
|
|
167
|
+
text: string;
|
|
168
|
+
role: 'user' | 'assistant' | 'system';
|
|
169
|
+
createdAt: Date;
|
|
170
|
+
messageType: 'text' | 'card' | 'system';
|
|
171
|
+
status: 'local' | 'loading' | 'updating' | 'success' | 'error' | 'abort';
|
|
172
|
+
cardType?: string;
|
|
173
|
+
cardData?: { data: Record<string, unknown>; actions: string[] };
|
|
174
|
+
turnId: string;
|
|
175
|
+
extra?: Record<string, unknown>;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### MessageInfo
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
interface MessageInfo<Message> {
|
|
183
|
+
id: string;
|
|
184
|
+
message: Message;
|
|
185
|
+
status: MessageStatus;
|
|
186
|
+
extra?: Record<string, unknown>;
|
|
187
|
+
}
|
|
188
|
+
```
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @unif/react-native-chat-sdk
|
|
3
|
+
* AI 聊天 SDK — 流式通信、Provider 模式、状态管理
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Types
|
|
7
|
+
export type {
|
|
8
|
+
ChatMessage,
|
|
9
|
+
MessageRole,
|
|
10
|
+
MessageType,
|
|
11
|
+
MessageStatus,
|
|
12
|
+
} from './types/message';
|
|
13
|
+
|
|
14
|
+
export type {
|
|
15
|
+
SSEEventType,
|
|
16
|
+
SSEEnvelopeBase,
|
|
17
|
+
SSETextEvent,
|
|
18
|
+
SSECardDataEvent,
|
|
19
|
+
SSESuggestionEvent,
|
|
20
|
+
SSEErrorEvent,
|
|
21
|
+
SSEDoneEvent,
|
|
22
|
+
SSEEnvelope,
|
|
23
|
+
} from './types/sse';
|
|
24
|
+
|
|
25
|
+
export type {
|
|
26
|
+
RequestParams,
|
|
27
|
+
SSEOutput,
|
|
28
|
+
ProviderCallbacks,
|
|
29
|
+
} from './types/provider';
|
|
30
|
+
|
|
31
|
+
// Tools
|
|
32
|
+
export {XStream} from './tools/XStream';
|
|
33
|
+
export type {XStreamConfig, XStreamCallbacks} from './tools/XStream';
|
|
34
|
+
|
|
35
|
+
export {XRequest} from './tools/XRequest';
|
|
36
|
+
export type {XRequestConfig, XRequestCallbacks} from './tools/XRequest';
|
|
37
|
+
|
|
38
|
+
export {SSEParser} from './tools/SSEParser';
|
|
39
|
+
|
|
40
|
+
// Providers
|
|
41
|
+
export {AbstractChatProvider} from './providers/AbstractChatProvider';
|
|
42
|
+
export {OpenAIChatProvider} from './providers/OpenAIChatProvider';
|
|
43
|
+
export type {OpenAIChatProviderConfig} from './providers/OpenAIChatProvider';
|
|
44
|
+
export {DeepSeekChatProvider} from './providers/DeepSeekChatProvider';
|
|
45
|
+
export type {DeepSeekChatProviderConfig} from './providers/DeepSeekChatProvider';
|
|
46
|
+
|
|
47
|
+
// Stores
|
|
48
|
+
export type {ChatStorage} from './stores/storage';
|
|
49
|
+
|
|
50
|
+
// Hooks
|
|
51
|
+
export {useXChat} from './hooks/useXChat';
|
|
52
|
+
export type {UseXChatConfig, MessageInfo} from './hooks/useXChat';
|
|
53
|
+
|
|
54
|
+
export {useXConversations} from './hooks/useXConversations';
|
|
55
|
+
export type {UseXConversationsOptions, SessionSummary} from './hooks/useXConversations';
|
|
56
|
+
|
|
57
|
+
export {useXModel} from './hooks/useXModel';
|
|
58
|
+
export type {UseXModelOptions, ModelInfo} from './hooks/useXModel';
|
|
59
|
+
|
|
60
|
+
export {ChatProvider, useChatContext} from './hooks/ChatProvider';
|
|
61
|
+
export type {ChatProviderProps} from './hooks/ChatProvider';
|