@blueking/chat-helper 0.0.11 → 0.0.12-beta.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.
- package/dist/agent/use-agent.ts.js +6 -4
- package/dist/event/ag-ui.ts.js +3 -3
- package/dist/http/fetch/fetch.d.ts +20 -0
- package/dist/http/fetch/fetch.ts.js +65 -16
- package/dist/http/index.d.ts +2 -0
- package/dist/http/index.ts.js +2 -1
- package/dist/http/module/session.ts.js +29 -5
- package/dist/index.d.ts +3 -0
- package/dist/index.ts.js +2 -1
- package/dist/session/use-session.ts.js +10 -3
- package/package.json +1 -1
|
@@ -123,8 +123,7 @@ import { SessionStatus } from '../session/type.ts.js';
|
|
|
123
123
|
};
|
|
124
124
|
const streamRequest = function() {
|
|
125
125
|
var _ref = _async_to_generator(function*(sessionCode, url, config) {
|
|
126
|
-
var
|
|
127
|
-
_mediator_http;
|
|
126
|
+
var _mediator_http;
|
|
128
127
|
// ag-ui 协议需要注入消息模块
|
|
129
128
|
if (usedProtocol instanceof AGUIProtocol) {
|
|
130
129
|
usedProtocol.injectMessageModule(mediator.message);
|
|
@@ -151,7 +150,8 @@ import { SessionStatus } from '../session/type.ts.js';
|
|
|
151
150
|
};
|
|
152
151
|
// 创建 AbortController
|
|
153
152
|
abortController = new AbortController();
|
|
154
|
-
|
|
153
|
+
// 发起聊天
|
|
154
|
+
void ((_mediator_http = mediator.http) === null || _mediator_http === void 0 ? void 0 : _mediator_http.fetchClient.streamRequest(_object_spread({
|
|
155
155
|
url: url || 'chat_completion/',
|
|
156
156
|
method: 'POST',
|
|
157
157
|
data: {
|
|
@@ -165,7 +165,9 @@ import { SessionStatus } from '../session/type.ts.js';
|
|
|
165
165
|
onError,
|
|
166
166
|
onMessage,
|
|
167
167
|
onStart
|
|
168
|
-
}, config))
|
|
168
|
+
}, config)).catch(()=>{
|
|
169
|
+
// 非 abort 错误已通过 onError 回调处理;abort 为正常结束
|
|
170
|
+
}));
|
|
169
171
|
});
|
|
170
172
|
return function streamRequest(sessionCode, url, config) {
|
|
171
173
|
return _ref.apply(this, arguments);
|
package/dist/event/ag-ui.ts.js
CHANGED
|
@@ -397,10 +397,10 @@ import { CustomEventName, EventType, FlowTaskState } from './type.ts.js';
|
|
|
397
397
|
(_this_onDoneCallback = (_this = this).onDoneCallback) === null || _this_onDoneCallback === void 0 ? void 0 : _this_onDoneCallback.call(_this);
|
|
398
398
|
}
|
|
399
399
|
onError(error) {
|
|
400
|
-
var //
|
|
400
|
+
var // 在聊天区域显示错误消息
|
|
401
|
+
_this_messageModule, // 回调给上层
|
|
401
402
|
_this_onErrorCallback, _this;
|
|
402
|
-
|
|
403
|
-
this.messageModule.plusMessage({
|
|
403
|
+
(_this_messageModule = this.messageModule) === null || _this_messageModule === void 0 ? void 0 : _this_messageModule.plusMessage({
|
|
404
404
|
role: MessageRole.Assistant,
|
|
405
405
|
content: error.message,
|
|
406
406
|
status: MessageStatus.Error
|
|
@@ -45,6 +45,13 @@ export interface ISSEProtocol {
|
|
|
45
45
|
onMessage?: (event: unknown) => void;
|
|
46
46
|
onStart?: () => void;
|
|
47
47
|
}
|
|
48
|
+
/** 全局错误处理器类型 */
|
|
49
|
+
export type GlobalErrorHandler = (error: IRequestError) => void;
|
|
50
|
+
/** 错误处理器配置 */
|
|
51
|
+
export interface ErrorHandlerOptions {
|
|
52
|
+
/** 忽略的 URL 模式(字符串包含匹配或正则) */
|
|
53
|
+
ignoreErrors?: Array<string | RegExp>;
|
|
54
|
+
}
|
|
48
55
|
interface IInterceptor<T> {
|
|
49
56
|
fulfilled?: (value: T) => T;
|
|
50
57
|
rejected?: (error: unknown) => unknown;
|
|
@@ -62,7 +69,20 @@ export declare class FetchClient {
|
|
|
62
69
|
request: InterceptorManager<IRequestConfig>;
|
|
63
70
|
response: InterceptorManager<IResponse>;
|
|
64
71
|
};
|
|
72
|
+
/** 全局错误处理器 */
|
|
73
|
+
private globalErrorHandler?;
|
|
74
|
+
/** 忽略的错误 URL 模式 */
|
|
75
|
+
private ignoreErrorPatterns;
|
|
65
76
|
constructor(config?: IRequestConfig);
|
|
77
|
+
/**
|
|
78
|
+
* 注册全局错误处理器
|
|
79
|
+
* 所有 HTTP 错误(request 和 streamRequest)都会经过此处理器
|
|
80
|
+
*/
|
|
81
|
+
onError(handler: GlobalErrorHandler, options?: ErrorHandlerOptions): void;
|
|
82
|
+
/** 判断是否应忽略该错误 */
|
|
83
|
+
private shouldIgnoreError;
|
|
84
|
+
/** 统一错误分发 */
|
|
85
|
+
private dispatchError;
|
|
66
86
|
applyResponseErrorInterceptors(error: unknown): unknown;
|
|
67
87
|
create(config?: IRequestConfig): FetchClient;
|
|
68
88
|
delete<T = unknown>(url: string, params?: Record<string, unknown>, config?: IRequestConfig): Promise<T>;
|
|
@@ -132,6 +132,35 @@ class InterceptorManager {
|
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
export class FetchClient {
|
|
135
|
+
/**
|
|
136
|
+
* 注册全局错误处理器
|
|
137
|
+
* 所有 HTTP 错误(request 和 streamRequest)都会经过此处理器
|
|
138
|
+
*/ onError(handler, options) {
|
|
139
|
+
this.globalErrorHandler = handler;
|
|
140
|
+
if (options === null || options === void 0 ? void 0 : options.ignoreErrors) {
|
|
141
|
+
this.ignoreErrorPatterns = options.ignoreErrors;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** 判断是否应忽略该错误 */ shouldIgnoreError(error) {
|
|
145
|
+
var _error_config, _error_config1;
|
|
146
|
+
// 同时匹配相对路径和完整 URL
|
|
147
|
+
const url = ((_error_config = error.config) === null || _error_config === void 0 ? void 0 : _error_config.url) || '';
|
|
148
|
+
const baseURL = ((_error_config1 = error.config) === null || _error_config1 === void 0 ? void 0 : _error_config1.baseURL) || '';
|
|
149
|
+
const fullURL = baseURL && !url.startsWith('http') ? baseURL + url : url;
|
|
150
|
+
return this.ignoreErrorPatterns.some((pattern)=>{
|
|
151
|
+
if (typeof pattern === 'string') {
|
|
152
|
+
// 字符串模式:同时检查相对路径和完整 URL
|
|
153
|
+
return url.includes(pattern) || fullURL.includes(pattern);
|
|
154
|
+
}
|
|
155
|
+
// 正则模式:同时检查相对路径和完整 URL
|
|
156
|
+
return pattern.test(url) || pattern.test(fullURL);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/** 统一错误分发 */ dispatchError(error) {
|
|
160
|
+
if (this.globalErrorHandler && !this.shouldIgnoreError(error)) {
|
|
161
|
+
this.globalErrorHandler(error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
135
164
|
applyResponseErrorInterceptors(error) {
|
|
136
165
|
let rejectedError = error;
|
|
137
166
|
this.interceptors.response.forEach((interceptor)=>{
|
|
@@ -325,9 +354,11 @@ export class FetchClient {
|
|
|
325
354
|
}
|
|
326
355
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
327
356
|
const requestError = createError('Request timeout', requestConfig, 'ECONNABORTED', undefined);
|
|
357
|
+
_this.dispatchError(requestError);
|
|
328
358
|
throw _this.applyResponseErrorInterceptors(requestError);
|
|
329
359
|
}
|
|
330
360
|
const requestError = error.isAxiosError === true ? error : createError(error.message, requestConfig, error.code, undefined);
|
|
361
|
+
_this.dispatchError(requestError);
|
|
331
362
|
throw _this.applyResponseErrorInterceptors(requestError);
|
|
332
363
|
}
|
|
333
364
|
})();
|
|
@@ -340,13 +371,31 @@ export class FetchClient {
|
|
|
340
371
|
streamRequest(config) {
|
|
341
372
|
var _this = this;
|
|
342
373
|
return _async_to_generator(function*() {
|
|
343
|
-
|
|
374
|
+
// 错误去重:标记是否已调用过全局错误处理器
|
|
375
|
+
let errorDispatched = false;
|
|
376
|
+
// 包装 onError 回调,统一调用全局错误处理器
|
|
377
|
+
const originalOnError = config.onError;
|
|
378
|
+
const wrappedOnError = (error)=>{
|
|
379
|
+
if (!errorDispatched) {
|
|
380
|
+
// 将 requestConfig 附加到错误对象,以便 shouldIgnoreError 能匹配 URL
|
|
381
|
+
const errorWithConfig = error;
|
|
382
|
+
if (!errorWithConfig.config) {
|
|
383
|
+
errorWithConfig.config = requestConfig;
|
|
384
|
+
}
|
|
385
|
+
_this.dispatchError(errorWithConfig);
|
|
386
|
+
errorDispatched = true;
|
|
387
|
+
}
|
|
388
|
+
originalOnError === null || originalOnError === void 0 ? void 0 : originalOnError(error);
|
|
389
|
+
};
|
|
390
|
+
const wrappedConfig = _object_spread_props(_object_spread({}, config), {
|
|
391
|
+
onError: wrappedOnError
|
|
392
|
+
});
|
|
393
|
+
const { url, fetchConfig, requestConfig } = _this.prepareRequest(wrappedConfig, true);
|
|
344
394
|
try {
|
|
345
|
-
var
|
|
395
|
+
var _wrappedConfig_onStart, _fetchResponse_body;
|
|
346
396
|
const fetchResponse = yield fetch(url, fetchConfig);
|
|
347
397
|
const validateStatus = requestConfig.validateStatus || _this.defaults.validateStatus;
|
|
348
398
|
if (!validateStatus(fetchResponse.status)) {
|
|
349
|
-
var _config_onError;
|
|
350
399
|
let message = `Request failed with status code ${fetchResponse.status}`;
|
|
351
400
|
try {
|
|
352
401
|
var _errorData_error;
|
|
@@ -358,15 +407,14 @@ export class FetchClient {
|
|
|
358
407
|
message = `Request failed with status code ${fetchResponse.status}`;
|
|
359
408
|
}
|
|
360
409
|
const error = createError(message, requestConfig, `ERR_BAD_RESPONSE`, undefined);
|
|
361
|
-
(
|
|
410
|
+
wrappedOnError(error);
|
|
362
411
|
return;
|
|
363
412
|
}
|
|
364
|
-
(
|
|
413
|
+
(_wrappedConfig_onStart = wrappedConfig.onStart) === null || _wrappedConfig_onStart === void 0 ? void 0 : _wrappedConfig_onStart.call(wrappedConfig);
|
|
365
414
|
const reader = (_fetchResponse_body = fetchResponse.body) === null || _fetchResponse_body === void 0 ? void 0 : _fetchResponse_body.pipeThrough(new window.TextDecoderStream()).getReader();
|
|
366
415
|
if (!reader) {
|
|
367
|
-
var _config_onError1;
|
|
368
416
|
const error = new Error('IResponse body is not readable');
|
|
369
|
-
(
|
|
417
|
+
wrappedOnError(error);
|
|
370
418
|
return;
|
|
371
419
|
}
|
|
372
420
|
// 缓存跨行分片数据
|
|
@@ -383,17 +431,17 @@ export class FetchClient {
|
|
|
383
431
|
while(true){
|
|
384
432
|
const { value, done } = yield reader.read();
|
|
385
433
|
if (done) {
|
|
386
|
-
var
|
|
387
|
-
(
|
|
434
|
+
var _wrappedConfig_onDone;
|
|
435
|
+
(_wrappedConfig_onDone = wrappedConfig.onDone) === null || _wrappedConfig_onDone === void 0 ? void 0 : _wrappedConfig_onDone.call(wrappedConfig);
|
|
388
436
|
break;
|
|
389
437
|
}
|
|
390
438
|
const values = (temp + value.toString()).split('\n');
|
|
391
439
|
values.forEach((value)=>{
|
|
392
440
|
const item = value.replace('data:', '').trim();
|
|
393
441
|
if (isJson(item)) {
|
|
394
|
-
var
|
|
442
|
+
var _wrappedConfig_onMessage;
|
|
395
443
|
const json = JSON.parse(item);
|
|
396
|
-
(
|
|
444
|
+
(_wrappedConfig_onMessage = wrappedConfig.onMessage) === null || _wrappedConfig_onMessage === void 0 ? void 0 : _wrappedConfig_onMessage.call(wrappedConfig, json);
|
|
397
445
|
temp = '';
|
|
398
446
|
} else if (item) {
|
|
399
447
|
temp = item;
|
|
@@ -403,12 +451,11 @@ export class FetchClient {
|
|
|
403
451
|
} catch (error) {
|
|
404
452
|
if (error instanceof Error) {
|
|
405
453
|
if (error.name === 'AbortError') {
|
|
406
|
-
var
|
|
407
|
-
(
|
|
408
|
-
|
|
409
|
-
var _config_onError2;
|
|
410
|
-
(_config_onError2 = config.onError) === null || _config_onError2 === void 0 ? void 0 : _config_onError2.call(config, error);
|
|
454
|
+
var _wrappedConfig_onDone1;
|
|
455
|
+
(_wrappedConfig_onDone1 = wrappedConfig.onDone) === null || _wrappedConfig_onDone1 === void 0 ? void 0 : _wrappedConfig_onDone1.call(wrappedConfig);
|
|
456
|
+
return;
|
|
411
457
|
}
|
|
458
|
+
wrappedOnError(error);
|
|
412
459
|
}
|
|
413
460
|
throw error;
|
|
414
461
|
}
|
|
@@ -417,6 +464,8 @@ export class FetchClient {
|
|
|
417
464
|
constructor(config = {}){
|
|
418
465
|
_define_property(this, "defaults", void 0);
|
|
419
466
|
_define_property(this, "interceptors", void 0);
|
|
467
|
+
/** 全局错误处理器 */ _define_property(this, "globalErrorHandler", void 0);
|
|
468
|
+
/** 忽略的错误 URL 模式 */ _define_property(this, "ignoreErrorPatterns", []);
|
|
420
469
|
this.defaults = mergeConfig({
|
|
421
470
|
method: 'GET',
|
|
422
471
|
headers: {
|
package/dist/http/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export * from './transform';
|
|
|
8
8
|
*/
|
|
9
9
|
export declare const useHttp: (options: IUseChatHelperOptions) => {
|
|
10
10
|
reset: (newOptions: IUseChatHelperOptions) => void;
|
|
11
|
+
/** 注册全局错误处理器 */
|
|
12
|
+
onError: (handler: import("./fetch").GlobalErrorHandler, options?: import("./fetch").ErrorHandlerOptions) => void;
|
|
11
13
|
agent: {
|
|
12
14
|
getAgentInfo: (config?: import("./fetch").IRequestConfig) => Promise<import("..").IAgentInfo>;
|
|
13
15
|
};
|
package/dist/http/index.ts.js
CHANGED
|
@@ -75,6 +75,27 @@ function _object_spread_props(target, source) {
|
|
|
75
75
|
return target;
|
|
76
76
|
}
|
|
77
77
|
import { transferSession2SessionApi, transferSessionApi2Session, transferSessionFeedback2SessionFeedbackApi, transferSessionFeedbackApi2SessionFeedback } from '../transform/session.ts.js';
|
|
78
|
+
import { resolveRequestValue } from '../fetch/index.ts.js';
|
|
79
|
+
/** 上传接口传输用文件名前缀,与原始展示名解耦 */ const UPLOAD_FILE_NAME_PREFIX = 'upload_file';
|
|
80
|
+
/** 从原始文件名提取 ASCII 安全扩展名,用于保留文件类型 */ const getSafeFileExtension = (fileName)=>{
|
|
81
|
+
const extension = fileName.split('.').pop();
|
|
82
|
+
if (!extension || extension === fileName) {
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
const safeExtension = extension.replace(/[^A-Za-z0-9]/g, '').slice(0, 20).toLowerCase();
|
|
86
|
+
return safeExtension ? `.${safeExtension}` : '';
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* 生成上传接口使用的 ASCII 安全文件名。
|
|
90
|
+
* 中文等非 ASCII 原名不能用于 URL path / Content-Disposition(浏览器与后端均可能编码失败),
|
|
91
|
+
* 原始 file.name 仍保留在 File 对象上,由 chat-x 发送消息时作为展示名使用。
|
|
92
|
+
*/ const buildSafeUploadFileName = (file)=>{
|
|
93
|
+
const suffix = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
94
|
+
return `${UPLOAD_FILE_NAME_PREFIX}_${suffix}${getSafeFileExtension(file.name)}`;
|
|
95
|
+
};
|
|
96
|
+
/** Content-Disposition 仅允许 ASCII,须与 upload path 中的 file_name 一致 */ const buildUploadContentDisposition = (fileName)=>{
|
|
97
|
+
return `attachment; filename="${fileName}"`;
|
|
98
|
+
};
|
|
78
99
|
/**
|
|
79
100
|
* session 相关 http 接口
|
|
80
101
|
* @param fetchClient - 请求客户端
|
|
@@ -104,13 +125,16 @@ import { transferSession2SessionApi, transferSessionApi2Session, transferSession
|
|
|
104
125
|
}, config);
|
|
105
126
|
// 会话重命名
|
|
106
127
|
const renameSession = (sessionCode, config)=>fetchClient.post(`session/${sessionCode}/ai_rename/`, undefined, config).then((res)=>transferSessionApi2Session(res));
|
|
107
|
-
//
|
|
128
|
+
// 上传文件到会话(传输名与展示名分离,见 buildSafeUploadFileName)
|
|
108
129
|
const uploadFile = (sessionCode, file, config)=>{
|
|
109
|
-
const
|
|
130
|
+
const safeFileName = buildSafeUploadFileName(file);
|
|
131
|
+
const fileName = encodeURIComponent(safeFileName);
|
|
132
|
+
var _resolveRequestValue;
|
|
133
|
+
const headers = (_resolveRequestValue = resolveRequestValue(config === null || config === void 0 ? void 0 : config.headers)) !== null && _resolveRequestValue !== void 0 ? _resolveRequestValue : {};
|
|
110
134
|
return file.arrayBuffer().then((content)=>fetchClient.post(`session/${sessionCode}/upload/${fileName}/`, content, _object_spread_props(_object_spread({}, config), {
|
|
111
|
-
headers: {
|
|
112
|
-
'Content-Disposition':
|
|
113
|
-
}
|
|
135
|
+
headers: _object_spread_props(_object_spread({}, headers), {
|
|
136
|
+
'Content-Disposition': buildUploadContentDisposition(safeFileName)
|
|
137
|
+
})
|
|
114
138
|
})));
|
|
115
139
|
};
|
|
116
140
|
return {
|
package/dist/index.d.ts
CHANGED
|
@@ -3725,6 +3725,7 @@ export declare const useChatHelper: (options: IUseChatHelperOptions) => {
|
|
|
3725
3725
|
};
|
|
3726
3726
|
http: {
|
|
3727
3727
|
reset: (newOptions: IUseChatHelperOptions) => void;
|
|
3728
|
+
onError: (handler: import("./http").GlobalErrorHandler, options?: import("./http").ErrorHandlerOptions) => void;
|
|
3728
3729
|
agent: {
|
|
3729
3730
|
getAgentInfo: (config?: import("./http").IRequestConfig) => Promise<import("./agent").IAgentInfo>;
|
|
3730
3731
|
};
|
|
@@ -3763,4 +3764,6 @@ export declare const useChatHelper: (options: IUseChatHelperOptions) => {
|
|
|
3763
3764
|
fetchClient: import("./http").FetchClient;
|
|
3764
3765
|
};
|
|
3765
3766
|
reset: (options: IUseChatHelperOptions) => void;
|
|
3767
|
+
/** 注册全局错误处理器,简化调用链 */
|
|
3768
|
+
onError: (handler: import("./http").GlobalErrorHandler, options?: import("./http").ErrorHandlerOptions) => void;
|
|
3766
3769
|
};
|
package/dist/index.ts.js
CHANGED
|
@@ -126,10 +126,17 @@ import { ref } from 'vue';
|
|
|
126
126
|
var // 中止当前聊天
|
|
127
127
|
_mediator_agent;
|
|
128
128
|
(_mediator_agent = mediator.agent) === null || _mediator_agent === void 0 ? void 0 : _mediator_agent.abortChat();
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
const shouldLoadMessages = (options === null || options === void 0 ? void 0 : options.loadMessages) !== false;
|
|
130
|
+
const localSession = list.value.find((item)=>item.sessionCode === sessionCode);
|
|
131
|
+
// 新会话或明确空会话已在本地列表中,无需额外 GET 会话详情。
|
|
132
|
+
if (!shouldLoadMessages && localSession) {
|
|
133
|
+
current.value = localSession;
|
|
134
|
+
} else {
|
|
135
|
+
// 从接口更新当前会话信息
|
|
136
|
+
yield getSession(sessionCode);
|
|
137
|
+
}
|
|
131
138
|
// 默认加载消息,但允许跳过(新会话消息必定为空,无需加载)
|
|
132
|
-
if (
|
|
139
|
+
if (shouldLoadMessages) {
|
|
133
140
|
var _mediator_message, // 继续聊天
|
|
134
141
|
_mediator_agent1;
|
|
135
142
|
// 获取会话内容
|