my-ai-chat-framework 1.0.2

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.
@@ -0,0 +1,288 @@
1
+ import axios from 'axios';
2
+ import EventEmitter from './EventEmitter.js';
3
+
4
+ /**
5
+ * API客户端(使用axios)
6
+ * 支持浏览器和Node.js环境
7
+ */
8
+ class ApiClient extends EventEmitter {
9
+ constructor(apiKey, apiUrl = 'https://api.deepseek.com/v1/chat/completions') {
10
+ super();
11
+ this.apiKey = apiKey;
12
+ this.url = apiUrl;
13
+ this.isGenerating = false;
14
+ this.cancelTokenSource = null;
15
+
16
+ // 创建axios实例
17
+ this.axiosInstance = axios.create({
18
+ baseURL: apiUrl,
19
+ headers: {
20
+ 'Authorization': `Bearer ${apiKey}`,
21
+ 'Content-Type': 'application/json'
22
+ },
23
+ timeout: 30000 // 30秒超时
24
+ });
25
+ }
26
+
27
+ setApiKey(apiKey) {
28
+ if (!apiKey || typeof apiKey !== 'string') {
29
+ throw new Error('无效的API Key');
30
+ }
31
+ this.apiKey = apiKey;
32
+ this.axiosInstance.defaults.headers['Authorization'] = `Bearer ${apiKey}`;
33
+ }
34
+
35
+ setApiUrl(apiUrl) {
36
+ if (!apiUrl || typeof apiUrl !== 'string') {
37
+ throw new Error('无效的API URL');
38
+ }
39
+ this.url = apiUrl;
40
+ this.axiosInstance.defaults.baseURL = apiUrl;
41
+ }
42
+
43
+ async send(body) {
44
+ this.isGenerating = true;
45
+ this.cancelTokenSource = axios.CancelToken.source();
46
+
47
+ try {
48
+ // 触发事件:开始发送
49
+ this.emit('request-start', { body });
50
+
51
+ const requestBody = this._buildRequestBody(body);
52
+ const response = await this.axiosInstance.post('', requestBody, {
53
+ cancelToken: this.cancelTokenSource.token
54
+ });
55
+
56
+ // 触发事件:请求成功
57
+ this.emit('request-success', { response: response.data });
58
+
59
+ return response.data;
60
+ } catch (error) {
61
+ if (axios.isCancel(error)) {
62
+ const cancelError = new Error('请求被中断');
63
+ cancelError.name = 'CancelError';
64
+ // 触发事件:请求被取消
65
+ this.emit('request-cancel', { error: cancelError });
66
+ throw cancelError;
67
+ }
68
+
69
+ // 触发事件:请求失败
70
+ this.emit('request-error', { error });
71
+ throw this._handleAxiosError(error);
72
+ } finally {
73
+ this.isGenerating = false;
74
+ this.cancelTokenSource = null;
75
+ }
76
+ }
77
+
78
+ async strSend(body, onProgress = () => {}, onDone = () => {}) {
79
+ this.isGenerating = true;
80
+ this.cancelTokenSource = axios.CancelToken.source();
81
+
82
+ try {
83
+ // 触发事件:开始流式请求
84
+ this.emit('stream-start', { body });
85
+
86
+ const requestBody = this._buildRequestBody(body);
87
+ const response = await fetch(this.url, {
88
+ method: 'POST',
89
+ headers: {
90
+ 'Content-Type': 'application/json',
91
+ 'Authorization': `Bearer ${this.apiKey}`,
92
+ 'Accept': 'text/event-stream'
93
+ },
94
+ body: requestBody,
95
+ signal: this.cancelTokenSource.token ?
96
+ new AbortController().signal : undefined
97
+ });
98
+
99
+ if (!response.ok) {
100
+ throw new Error(`HTTP错误: ${response.status}`);
101
+ }
102
+
103
+ const reader = response.body.getReader();
104
+ const decoder = new TextDecoder();
105
+ let buffer = '';
106
+ const message = {
107
+ content: '',
108
+ reasoning_content: '',
109
+ tool_calls: [] // 新增:收集工具调用
110
+ };
111
+
112
+ while (true) {
113
+ const { done, value } = await reader.read();
114
+ if (done) break;
115
+
116
+ buffer += decoder.decode(value, { stream: true });
117
+ let lines = buffer.split(/(\r?\n){2,}/);
118
+ buffer = lines.pop() || '';
119
+
120
+ for (let rawChunk of lines) {
121
+ const cleanChunk = rawChunk.trim().replace(/^data: /, '');
122
+ if (!cleanChunk) continue;
123
+ if (cleanChunk === '[DONE]') {
124
+ // 触发事件:流式传输完成
125
+ this.emit('stream-done', { message });
126
+ onDone({ ...message });
127
+ return;
128
+ }
129
+
130
+ try {
131
+ const obj = JSON.parse(cleanChunk);
132
+ console.log('🔍 收到流式chunk:', obj);
133
+
134
+ // 收集内容
135
+ if (obj.choices?.[0]?.delta?.content) {
136
+ message.content += obj.choices[0].delta.content;
137
+ }
138
+ if (obj.choices?.[0]?.delta?.reasoning_content) {
139
+ message.reasoning_content += obj.choices[0].delta.reasoning_content;
140
+ }
141
+
142
+ // 收集工具调用
143
+ if (obj.choices?.[0]?.delta?.tool_calls) {
144
+ const deltaToolCalls = obj.choices[0].delta.tool_calls;
145
+ deltaToolCalls.forEach(toolCall => {
146
+ const index = toolCall.index || 0;
147
+
148
+ // 确保数组存在
149
+ if (!message.tool_calls) message.tool_calls = [];
150
+
151
+ // 初始化或更新
152
+ if (!message.tool_calls[index]) {
153
+ message.tool_calls[index] = {
154
+ id: '',
155
+ type: 'function',
156
+ function: { name: '', arguments: '' }
157
+ };
158
+ }
159
+
160
+ // 只更新非空字段
161
+ if (toolCall.id) message.tool_calls[index].id = toolCall.id;
162
+ if (toolCall.type) message.tool_calls[index].type = toolCall.type;
163
+ if (toolCall.function?.name) {
164
+ message.tool_calls[index].function.name += toolCall.function.name;
165
+ }
166
+ if (toolCall.function?.arguments) {
167
+ message.tool_calls[index].function.arguments += toolCall.function.arguments;
168
+ }
169
+ });
170
+ }
171
+
172
+ // 触发事件:收到流式数据块
173
+ this.emit('stream-chunk', { chunk: obj, message });
174
+ onProgress({ ...message });
175
+ } catch (e) {
176
+ console.error('🔍 解析chunk错误:', e, '原始数据:', cleanChunk);
177
+ }
178
+ }
179
+
180
+ if (!this.isGenerating) {
181
+ break;
182
+ }
183
+ }
184
+
185
+ // 触发事件:流式传输完成
186
+ this.emit('stream-done', { message });
187
+ onDone({ ...message });
188
+
189
+ } catch (error) {
190
+ if (error.name === 'AbortError' || axios.isCancel(error)) {
191
+ const cancelError = new Error('请求被中断');
192
+ cancelError.name = 'CancelError';
193
+ // 触发事件:流式请求被取消
194
+ this.emit('stream-cancel', { error: cancelError });
195
+ throw cancelError;
196
+ }
197
+
198
+ // 触发事件:流式请求失败
199
+ this.emit('stream-error', { error });
200
+ throw error;
201
+ } finally {
202
+ this.isGenerating = false;
203
+ this.cancelTokenSource = null;
204
+ }
205
+ }
206
+
207
+ interrupt() {
208
+ if (this.isGenerating && this.cancelTokenSource) {
209
+ this.cancelTokenSource.cancel('用户中断请求');
210
+ this.isGenerating = false;
211
+ }
212
+ }
213
+
214
+ _buildRequestBody(body) {
215
+ if (typeof body?.toJSON === 'function') {
216
+ return body.toJSON();
217
+ } else if (typeof body === 'object' && body !== null) {
218
+ return JSON.stringify(body);
219
+ } else if (typeof body === 'string') {
220
+ try {
221
+ JSON.parse(body);
222
+ return body;
223
+ } catch {
224
+ throw new Error('字符串不是有效的JSON格式');
225
+ }
226
+ } else {
227
+ throw new Error('无效的请求体类型,应传入对象或JSON字符串');
228
+ }
229
+ }
230
+
231
+ _handleAxiosError(error) {
232
+ if (error.response) {
233
+ // 服务器返回错误状态码
234
+ const status = error.response.status;
235
+ const data = error.response.data;
236
+
237
+ let message = `API错误 ${status}`;
238
+ if (data?.error?.message) {
239
+ message += `: ${data.error.message}`;
240
+ }
241
+
242
+ return new Error(message);
243
+ } else if (error.request) {
244
+ // 请求发送但无响应
245
+ return new Error('网络错误:无法连接到API服务器');
246
+ } else {
247
+ // 其他错误
248
+ return error;
249
+ }
250
+ }
251
+
252
+ static parseCompatible(obj) {
253
+ // 保持原有方法不变
254
+ if (obj?.__format_version === 1 && obj.payload) {
255
+ return {
256
+ messages: obj.payload.messages || [],
257
+ systems: obj.payload.systems || [],
258
+ hintData: obj.payload.hintData || {
259
+ name: '',
260
+ toolState: {
261
+ currentTime: '同步失败',
262
+ currentTime_TS: 0
263
+ }
264
+ },
265
+ tools: obj.payload.tools || []
266
+ };
267
+ }
268
+
269
+ if (obj?.messages && Array.isArray(obj.messages)) {
270
+ return {
271
+ messages: obj.messages || [],
272
+ systems: obj.systems || [],
273
+ hintData: obj.hintData || {
274
+ name: '',
275
+ toolState: {
276
+ currentTime: '同步失败',
277
+ currentTime_TS: 0
278
+ }
279
+ },
280
+ tools: obj.tools || []
281
+ };
282
+ }
283
+
284
+ throw new Error('无法识别的聊天数据格式');
285
+ }
286
+ }
287
+
288
+ export default ApiClient;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * 简单的事件发射器类
3
+ * 实现观察者模式,允许对象监听和触发事件
4
+ */
5
+ class EventEmitter {
6
+ constructor() {
7
+ //存储事件和对应的回调数组
8
+ //结构:
9
+ // { 'event_name': [ callback1, callback2 ] }
10
+ this.events = {}
11
+ }
12
+
13
+ /**
14
+ * 监听事件
15
+ * @param {string} eventName - 要注册的事件名称
16
+ * @param {Function} callback - 事件触发时执行的回调函数
17
+ */
18
+ //实现一个监听事件的方法
19
+ on(eventName, callback) {
20
+ //1.如果没有事件数组,先创建
21
+ if (!this.events[eventName]) {
22
+ this.events[eventName] = []
23
+ }
24
+
25
+ //2.给数组添加(push)事件
26
+ this.events[eventName].push(callback)
27
+
28
+ //返回一个取消键听得函数(方便实用)
29
+ return () => this.off(eventName, callback)
30
+ }
31
+
32
+ /**
33
+ * 触发事件
34
+ * @param {string} eventName --要触发的事件名称
35
+ * @param {any} data --传递给回调函数的数据
36
+ */
37
+ emit(eventName, data) {
38
+ //获取这个事件的所有回调函数
39
+ const callbacks = this.events[eventName]
40
+
41
+ //仅在有回调函数时调用他们,否则提前退出(可以这么写吧?少一层嵌套)
42
+ if (!callbacks) return
43
+
44
+ //使用slice()创建副本,放置在循环中修改数组导致问题
45
+ const callbakesCopy = callbacks.slice()
46
+
47
+ for (let i = 0; i < callbakesCopy.length; i++) {
48
+ try {
49
+ //执行回调函数,并传入数据
50
+ callbakesCopy[i](data)
51
+ } catch (error) {
52
+ // 如果某个回调出错,不影响其他回调
53
+ console.error(`事件 ${eventName} 的回调函数执行出错:`, error)
54
+ }
55
+ }
56
+ }
57
+
58
+ /**
59
+ * 取消监听事件
60
+ * @param {string} eventName --要取消的事件名称
61
+ * @param {Function} callback --要移除的回调函数
62
+ */
63
+ off(eventName, callback) {
64
+ const callbacks = this.events[eventName]
65
+
66
+ //判断一下防止报错
67
+ if (!callbacks) return
68
+
69
+ //过滤掉要移除的回调函数
70
+ this.events[eventName] = callbacks.filter(item => item !== callback)
71
+
72
+ // 如果这个事件没有监听器了,删除这个事件
73
+ if (this.events[eventName].length === 0) {
74
+ delete this.events[eventName]
75
+ }
76
+ }
77
+
78
+ /**
79
+ * 只监听一次事件(触发后自动取消)
80
+ * @param {string} eventName - 事件名称
81
+ * @param {Function} callback - 回调函数
82
+ */
83
+ once(evenName, callback) {
84
+ // 创建一个包装函数,执行完成后自动取消
85
+ const onceCallback = (data) => {
86
+ //先取消监听
87
+ this.off(evenName, onceCallback)
88
+ //在执行原回调
89
+ callback(data)
90
+ }
91
+
92
+ //监听包装后的函数
93
+ this.on(evenName, onceCallback)
94
+
95
+ //返回取消函数
96
+ return () => this.off(evenName, onceCallback)
97
+ }
98
+
99
+ /**
100
+ * 移除某个事件的所有监听器
101
+ * @param {string} eventName - 事件名称
102
+ */
103
+ removeAllListeners(eventName) {
104
+ if (eventName) {
105
+ // 移除指定事件的所有监听器
106
+ delete this.events[eventName]
107
+ } else {
108
+ // 如果没有指定事件名,移除所有事件的监听器
109
+ this.events = {}
110
+ }
111
+ }
112
+
113
+ /**
114
+ * 获取某个事件的所有监听器数量
115
+ * @param {string} eventName - 事件名称
116
+ * @returns {number} 监听器数量
117
+ */
118
+ listenerCount(eventName) {
119
+ const callbacks = this.events[eventName]
120
+ return callbacks ? callbacks.length : 0
121
+ }
122
+
123
+ }
124
+
125
+ //导出EventEmiter类
126
+ export default EventEmitter
@@ -0,0 +1,126 @@
1
+ /**
2
+ * 消息格式转换器
3
+ * 负责将Messages的状态转换为不同格式
4
+ */
5
+ class MessageFormatter {
6
+ /**
7
+ * 将消息转换为标准格式(过滤掉自定义元数据)
8
+ */
9
+ static toStandardFormat(message) {
10
+ const { role, content } = message;
11
+ const standardMessage = { role, content };
12
+
13
+ // 保留重要的特殊字段
14
+ if (message.reasoning_content) {
15
+ standardMessage.reasoning_content = message.reasoning_content;
16
+ }
17
+ if (message.tool_calls) {
18
+ standardMessage.tool_calls = message.tool_calls;
19
+ }
20
+ if (message.tool_call_id) {
21
+ standardMessage.tool_call_id = message.tool_call_id; // ✅ 修复:保留tool_call_id
22
+ }
23
+
24
+ return standardMessage;
25
+ }
26
+
27
+ /**
28
+ * 批量转换消息为标准格式
29
+ */
30
+ static batchToStandardFormat(messages) {
31
+ return messages.map(msg => this.toStandardFormat(msg));
32
+ }
33
+
34
+ /**
35
+ * 从Messages实例获取格式化后的消息数组
36
+ */
37
+ static formatMessages(messagesInstance, baseRounds, cycleRounds) {
38
+ // 使用新的方法名
39
+ const systems = messagesInstance.getEnabledSystemPrompts();
40
+
41
+ // 添加提示系统
42
+ const hintSystem = {
43
+ role: 'system',
44
+ content: `你的名字||标题是:${messagesInstance.getName() || '未知'}`
45
+ };
46
+ systems.unshift(hintSystem);
47
+
48
+ // 使用新的方法名
49
+ const recentMessages = messagesInstance.getRecentMessages(baseRounds, cycleRounds);
50
+ return [...systems, ...recentMessages];
51
+ }
52
+
53
+ /**
54
+ * 构造工具参数
55
+ */
56
+ static constructParameters(array) {
57
+ let paramsArray = [];
58
+
59
+ if (Array.isArray(array)) {
60
+ paramsArray = array;
61
+ } else if (typeof array === 'object' && array !== null) {
62
+ paramsArray = [array];
63
+ } else {
64
+ throw new Error('[系统提示异常] 检测到无效的参数格式');
65
+ }
66
+
67
+ const parameters = {};
68
+
69
+ for (let i = 0; i < paramsArray.length; i++) {
70
+ const param = paramsArray[i];
71
+
72
+ if (!param?.name?.trim()) {
73
+ throw new Error(`[系统提示异常] 检测到空参数名称 位置: 第 ${i + 1} 个参数`);
74
+ }
75
+
76
+ if (!param?.type?.trim()) {
77
+ throw new Error(`[系统提示异常] 检测到空参数类型 位置: 参数 "${param.name}"`);
78
+ }
79
+
80
+ if (!param?.description?.trim()) {
81
+ throw new Error(`[系统提示异常] 检测到空参数描述 位置: 参数 "${param.name}"`);
82
+ }
83
+
84
+ parameters[param.name] = {
85
+ type: param.type,
86
+ description: param.description,
87
+ };
88
+ }
89
+
90
+ return {
91
+ type: 'object',
92
+ properties: parameters,
93
+ required: Object.keys(parameters),
94
+ };
95
+ }
96
+
97
+ /**
98
+ * 构造工具定义
99
+ */
100
+ static constructTool({ name, description, parameters }) {
101
+ if (!name?.trim()) {
102
+ throw new Error('[系统提示异常] 检测到空工具名称');
103
+ }
104
+ if (!description?.trim()) {
105
+ throw new Error('[系统提示异常] 检测到空工具描述');
106
+ }
107
+
108
+ let processedParameters;
109
+ try {
110
+ processedParameters = this.constructParameters(parameters);
111
+ } catch (error) {
112
+ throw new Error(`[系统提示异常] 工具参数验证失败 工具名称: ${name} 错误详情: ${error.message}`);
113
+ }
114
+
115
+ return {
116
+ type: 'function',
117
+ function: {
118
+ name,
119
+ description,
120
+ parameters: processedParameters,
121
+ },
122
+ };
123
+ }
124
+ }
125
+
126
+ export default MessageFormatter;