@base-web-kits/base-tools-web 1.3.3 → 1.3.5

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/index.d.cts CHANGED
@@ -323,6 +323,34 @@ declare function hasCss(href: string): boolean;
323
323
  */
324
324
  declare function preloadImage(src: string): Promise<HTMLImageElement>;
325
325
 
326
+ /** 流式数据消息 */
327
+ type SSEMessage = {
328
+ /**
329
+ * SSE 消息类型
330
+ * - 'DONE' 流式数据接收完毕
331
+ * - 'thinking' 思考中
332
+ * - 'text' 文本内容
333
+ * - 其他业务类型
334
+ */
335
+ type?: string;
336
+ /** SSE 事件 ID (如 'id:123') */
337
+ id?: string;
338
+ /** SSE 建议重连时间 (毫秒, 如 'retry:5000') */
339
+ retry?: number;
340
+ /** SSE 事件类型 (如 'event:customEvent') */
341
+ event?: string;
342
+ /**
343
+ * 除type之外的其他data数据
344
+ * - 思考中 'data:{"type": "thinking", "content": "xx"}' -> {type: 'thinking', content: 'xx'}
345
+ * - JSON数据 'data:{"type": "text", "content": "xx"}' -> {type: 'text', content: 'xx'}
346
+ * - 非JSON数据 'data:xx' -> {raw: 'xx'}
347
+ * - 接收完毕 'data:[DONE]' -> {type: 'DONE'}
348
+ */
349
+ [key: string]: any;
350
+ };
351
+ /** 流式数据消息回调 */
352
+ type MessageCallback = (msg: SSEMessage) => void;
353
+
326
354
  /** 请求方法类型 */
327
355
  type RequestMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'HEAD' | 'OPTIONS' | 'TRACE' | 'PATCH';
328
356
  /**
@@ -380,23 +408,17 @@ type RequestConfigBase<D extends RequestData = RequestData> = {
380
408
  logExtra?: Record<string, unknown>;
381
409
  /** 响应数据的转换 */
382
410
  resMap?: (data: ResponseData) => ResponseData;
383
- /** 获取task对象, 用于取消请求或监听流式数据 */
411
+ /** 获取请求对象, 用于取消请求 */
384
412
  onTaskReady?: (task: RequestTask) => void;
413
+ /** 流式数据接收事件回调 (已完成基础流式解析,返回消息对象) */
414
+ onMessage?: MessageCallback;
385
415
  };
386
- /**
387
- * 请求任务对象 (用于取消请求或监听流式数据)
388
- */
389
- interface RequestTask {
416
+ /** 请求任务对象 (用于取消请求) */
417
+ type RequestTask = {
390
418
  /** 取消请求 */
391
419
  abort: () => void;
392
- /** 监听流式数据块接收事件 */
393
- onChunkReceived: (callback: ChunkCallback) => void;
394
- /** 取消监听流式数据块接收事件 */
395
- offChunkReceived: () => void;
396
- }
397
- /**
398
- * 流式数据块接收事件回调
399
- */
420
+ };
421
+ /** 流式数据块接收事件回调 */
400
422
  type ChunkCallback = (response: {
401
423
  data: ArrayBuffer;
402
424
  }) => void;
@@ -462,20 +484,23 @@ type ChunkCallback = (response: {
462
484
  * });
463
485
  * }
464
486
  *
465
- * // 流式监听
487
+ * // 初始化请求对象
488
+ * let chatTask: RequestTask;
466
489
  * const onTaskReady = (task: RequestTask) => {
467
- * task.onChunkReceived((res) => {
468
- * console.log('ArrayBuffer', res.data);
469
- * });
490
+ * chatTask = task;
491
+ * }
492
+ *
493
+ * // 流式监听
494
+ * const onMessage = (msg: SSEMessage) => {
495
+ * console.log(msg);
470
496
  * }
471
497
  *
472
498
  * // 流式发起
473
499
  * const data = { content: '你好', chatId: 123 };
474
- * await apiChatStream({ data, onTaskReady });
500
+ * apiChatStream({ data, onTaskReady, onMessage });
475
501
  *
476
- * // 流式取消 (在组件销毁或页面关闭时调用)
477
- * task?.offChunkReceived(); // 取消监听,中断流式接收
478
- * task?.abort(); // 取消请求 (若流式已生成,此时abort无效,因为请求已成功)
502
+ * // 流式取消 (在组件销毁或页面关闭时调用, 若流式已生成, 此时abort无效, 因为请求已成功)
503
+ * chatTask?.abort();
479
504
  */
480
505
  declare function request<T, D extends RequestData = RequestData>(config: RequestConfigBase<D>): Promise<T>;
481
506
  /**
package/dist/index.mjs CHANGED
@@ -17,6 +17,7 @@ var __spreadValues = (a, b) => {
17
17
  return a;
18
18
  };
19
19
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
20
21
  var __async = (__this, __arguments, generator) => {
21
22
  return new Promise((resolve, reject) => {
22
23
  var fulfilled = (value) => {
@@ -774,12 +775,12 @@ function isUnsafeProperty(key) {
774
775
 
775
776
  // src/ts/day/index.ts
776
777
  import dayjs from "dayjs";
777
- import customParseFormat from "dayjs/plugin/customParseFormat";
778
- import utc from "dayjs/plugin/utc";
779
- import timezone from "dayjs/plugin/timezone";
780
- import relativeTime from "dayjs/plugin/relativeTime";
781
- import advancedFormat from "dayjs/plugin/advancedFormat";
782
- import "dayjs/locale/zh-cn";
778
+ import customParseFormat from "dayjs/plugin/customParseFormat.js";
779
+ import utc from "dayjs/plugin/utc.js";
780
+ import timezone from "dayjs/plugin/timezone.js";
781
+ import relativeTime from "dayjs/plugin/relativeTime.js";
782
+ import advancedFormat from "dayjs/plugin/advancedFormat.js";
783
+ import "dayjs/locale/zh-cn.js";
783
784
  dayjs.extend(customParseFormat);
784
785
  dayjs.extend(utc);
785
786
  dayjs.extend(timezone);
@@ -1010,6 +1011,262 @@ function appendUrlParam(url, param) {
1010
1011
  return base + (qs ? `?${qs}` : "") + hash;
1011
1012
  }
1012
1013
 
1014
+ // src/ts/buffer/PolyfillTextDecoder.ts
1015
+ var PolyfillTextDecoder = class {
1016
+ constructor() {
1017
+ __publicField(this, "leftOver", new Uint8Array(0));
1018
+ }
1019
+ decode(input, options) {
1020
+ var _a;
1021
+ const stream = (_a = options == null ? void 0 : options.stream) != null ? _a : false;
1022
+ let bytes;
1023
+ if (!input) {
1024
+ bytes = new Uint8Array(0);
1025
+ } else if (input instanceof ArrayBuffer) {
1026
+ bytes = new Uint8Array(input);
1027
+ } else {
1028
+ bytes = input;
1029
+ }
1030
+ if (this.leftOver.length > 0) {
1031
+ const merged = new Uint8Array(this.leftOver.length + bytes.length);
1032
+ merged.set(this.leftOver);
1033
+ merged.set(bytes, this.leftOver.length);
1034
+ bytes = merged;
1035
+ this.leftOver = new Uint8Array(0);
1036
+ }
1037
+ const len = bytes.length;
1038
+ if (len === 0) return "";
1039
+ const parts = [];
1040
+ let i = 0;
1041
+ const replacement = "\uFFFD";
1042
+ const isContinuationByte = (b) => (b & 192) === 128;
1043
+ while (i < len) {
1044
+ const byte1 = bytes[i];
1045
+ if (byte1 < 128) {
1046
+ parts.push(String.fromCharCode(byte1));
1047
+ i += 1;
1048
+ } else if (byte1 >= 194 && byte1 < 224) {
1049
+ if (i + 1 >= len) {
1050
+ if (stream) {
1051
+ this.leftOver = bytes.slice(i);
1052
+ break;
1053
+ }
1054
+ parts.push(replacement);
1055
+ break;
1056
+ }
1057
+ const byte2 = bytes[i + 1];
1058
+ if (!isContinuationByte(byte2)) {
1059
+ parts.push(replacement);
1060
+ i += 1;
1061
+ continue;
1062
+ }
1063
+ parts.push(String.fromCharCode((byte1 & 31) << 6 | byte2 & 63));
1064
+ i += 2;
1065
+ } else if (byte1 >= 224 && byte1 < 240) {
1066
+ if (i + 2 >= len) {
1067
+ if (stream) {
1068
+ this.leftOver = bytes.slice(i);
1069
+ break;
1070
+ }
1071
+ parts.push(replacement);
1072
+ break;
1073
+ }
1074
+ const byte2 = bytes[i + 1];
1075
+ const byte3 = bytes[i + 2];
1076
+ if (!isContinuationByte(byte2) || !isContinuationByte(byte3)) {
1077
+ parts.push(replacement);
1078
+ i += 1;
1079
+ continue;
1080
+ }
1081
+ if (byte1 === 224 && byte2 < 160) {
1082
+ parts.push(replacement);
1083
+ i += 3;
1084
+ continue;
1085
+ }
1086
+ if (byte1 === 237 && byte2 >= 160) {
1087
+ parts.push(replacement);
1088
+ i += 3;
1089
+ continue;
1090
+ }
1091
+ const codeUnit = (byte1 & 15) << 12 | (byte2 & 63) << 6 | byte3 & 63;
1092
+ parts.push(String.fromCharCode(codeUnit));
1093
+ i += 3;
1094
+ } else if (byte1 >= 240 && byte1 <= 244) {
1095
+ if (i + 3 >= len) {
1096
+ if (stream) {
1097
+ this.leftOver = bytes.slice(i);
1098
+ break;
1099
+ }
1100
+ parts.push(replacement);
1101
+ break;
1102
+ }
1103
+ const byte2 = bytes[i + 1];
1104
+ const byte3 = bytes[i + 2];
1105
+ const byte4 = bytes[i + 3];
1106
+ if (!isContinuationByte(byte2) || !isContinuationByte(byte3) || !isContinuationByte(byte4)) {
1107
+ parts.push(replacement);
1108
+ i += 1;
1109
+ continue;
1110
+ }
1111
+ if (byte1 === 240 && byte2 < 144) {
1112
+ parts.push(replacement);
1113
+ i += 4;
1114
+ continue;
1115
+ }
1116
+ if (byte1 === 244 && byte2 >= 144) {
1117
+ parts.push(replacement);
1118
+ i += 4;
1119
+ continue;
1120
+ }
1121
+ const codepoint = (byte1 & 7) << 18 | (byte2 & 63) << 12 | (byte3 & 63) << 6 | byte4 & 63;
1122
+ const offset = codepoint - 65536;
1123
+ parts.push(String.fromCharCode(55296 + (offset >> 10), 56320 + (offset & 1023)));
1124
+ i += 4;
1125
+ } else {
1126
+ parts.push(replacement);
1127
+ i += 1;
1128
+ }
1129
+ }
1130
+ return parts.join("");
1131
+ }
1132
+ };
1133
+ var PolyfillTextDecoder_default = PolyfillTextDecoder;
1134
+
1135
+ // src/ts/buffer/SSEParser.ts
1136
+ var SSEParser = class {
1137
+ constructor(onMessage) {
1138
+ __publicField(this, "buffer", "");
1139
+ __publicField(this, "onMessage");
1140
+ __publicField(this, "decoder");
1141
+ __publicField(this, "eventDataLines", []);
1142
+ __publicField(this, "eventType");
1143
+ __publicField(this, "eventId");
1144
+ __publicField(this, "eventRetry");
1145
+ this.onMessage = onMessage;
1146
+ this.decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf-8") : new PolyfillTextDecoder_default();
1147
+ }
1148
+ /**
1149
+ * 接收流式数据
1150
+ * @param buffer ArrayBuffer
1151
+ */
1152
+ receive(buffer) {
1153
+ const text = this.decoder.decode(new Uint8Array(buffer), { stream: true });
1154
+ this.appendText(text);
1155
+ }
1156
+ /**
1157
+ * 刷新解码器残留数据并处理尾部未换行的内容
1158
+ */
1159
+ flush() {
1160
+ const tail = this.decoder.decode(void 0, { stream: false });
1161
+ if (tail) this.appendText(tail);
1162
+ this.flushRemainder();
1163
+ }
1164
+ /**
1165
+ * 追加文本并按行拆分处理,保留不完整尾行
1166
+ * @param text 新到达的文本片段
1167
+ */
1168
+ appendText(text) {
1169
+ this.buffer += text;
1170
+ const lines = this.buffer.split(/\r?\n/);
1171
+ this.buffer = lines.pop() || "";
1172
+ this.processLines(lines);
1173
+ }
1174
+ /**
1175
+ * 处理缓冲区中剩余的尾行内容
1176
+ */
1177
+ flushRemainder() {
1178
+ if (!this.buffer.trim()) {
1179
+ this.buffer = "";
1180
+ return;
1181
+ }
1182
+ const rest = this.buffer;
1183
+ this.buffer = "";
1184
+ this.processLines([rest, ""]);
1185
+ }
1186
+ /**
1187
+ * 解析每行 SSE 数据并触发回调
1188
+ * @param lines 以换行切分后的行内容
1189
+ */
1190
+ processLines(lines) {
1191
+ for (const line of lines) {
1192
+ if (!line.trim()) {
1193
+ this.dispatchEvent();
1194
+ continue;
1195
+ }
1196
+ if (line.startsWith(":")) continue;
1197
+ const colonIndex = line.indexOf(":");
1198
+ const field = colonIndex === -1 ? line : line.slice(0, colonIndex);
1199
+ let value = colonIndex === -1 ? "" : line.slice(colonIndex + 1);
1200
+ if (value.startsWith(" ")) value = value.slice(1);
1201
+ if (field === "data") {
1202
+ this.eventDataLines.push(value);
1203
+ continue;
1204
+ }
1205
+ if (field === "event") {
1206
+ this.eventType = value || void 0;
1207
+ continue;
1208
+ }
1209
+ if (field === "id") {
1210
+ this.eventId = value || void 0;
1211
+ continue;
1212
+ }
1213
+ if (field === "retry") {
1214
+ const retry = Number(value);
1215
+ this.eventRetry = Number.isFinite(retry) ? retry : void 0;
1216
+ continue;
1217
+ }
1218
+ }
1219
+ }
1220
+ /**
1221
+ * 将当前缓存的一次 SSE 事件分发给回调
1222
+ * @description 以空行作为事件边界,将多行 data 合并后再解析;处理 "[DONE]" 结束标记;分发完成后会重置事件缓存
1223
+ */
1224
+ dispatchEvent() {
1225
+ if (!this.eventDataLines.length) {
1226
+ this.resetEvent();
1227
+ return;
1228
+ }
1229
+ const data = this.eventDataLines.join("\n");
1230
+ if (!data) {
1231
+ this.resetEvent();
1232
+ return;
1233
+ }
1234
+ if (data.trim() === "[DONE]") {
1235
+ this.safeOnMessage({ type: "DONE" });
1236
+ this.resetEvent();
1237
+ return;
1238
+ }
1239
+ let msg;
1240
+ try {
1241
+ const json = JSON.parse(data);
1242
+ msg = isPlainObject(json) ? json : { data: json };
1243
+ } catch (e) {
1244
+ msg = { raw: data };
1245
+ }
1246
+ if (this.eventType && msg.event === void 0) msg.event = this.eventType;
1247
+ if (this.eventType && msg.type === void 0) msg.type = this.eventType;
1248
+ if (this.eventId && msg.id === void 0) msg.id = this.eventId;
1249
+ if (this.eventRetry !== void 0 && msg.retry === void 0) msg.retry = this.eventRetry;
1250
+ this.safeOnMessage(msg);
1251
+ this.resetEvent();
1252
+ }
1253
+ /** 安全调用 onMessage 回调,捕获并打印错误 */
1254
+ safeOnMessage(msg) {
1255
+ try {
1256
+ this.onMessage(msg);
1257
+ } catch (onMessageError) {
1258
+ console.error("SSEParser onMessage error:", onMessageError);
1259
+ }
1260
+ }
1261
+ /** 重置当前 SSE 事件的临时缓存 */
1262
+ resetEvent() {
1263
+ this.eventDataLines = [];
1264
+ this.eventType = void 0;
1265
+ this.eventId = void 0;
1266
+ this.eventRetry = void 0;
1267
+ }
1268
+ };
1269
+
1013
1270
  // src/web/network/request.ts
1014
1271
  var requestCache = /* @__PURE__ */ new Map();
1015
1272
  function request(config) {
@@ -1031,18 +1288,16 @@ function request(config) {
1031
1288
  resMap,
1032
1289
  responseType = "json",
1033
1290
  timeout = 6e4,
1034
- onTaskReady
1291
+ onTaskReady,
1292
+ onMessage
1035
1293
  } = config;
1036
1294
  const controller = new AbortController();
1037
1295
  const signal = controller.signal;
1038
- let chunkCallback = null;
1296
+ const sseTask = { parser: onMessage ? new SSEParser(onMessage) : void 0 };
1039
1297
  const task = {
1040
- abort: () => controller.abort(),
1041
- onChunkReceived: (cb) => {
1042
- chunkCallback = cb;
1043
- },
1044
- offChunkReceived: () => {
1045
- chunkCallback = null;
1298
+ abort: () => {
1299
+ sseTask.parser = null;
1300
+ controller.abort();
1046
1301
  }
1047
1302
  };
1048
1303
  onTaskReady == null ? void 0 : onTaskReady(task);
@@ -1114,7 +1369,7 @@ function request(config) {
1114
1369
  }
1115
1370
  if (enableChunked) {
1116
1371
  if (showLoading) (_c = appConfig2.hideLoading) == null ? void 0 : _c.call(appConfig2);
1117
- const res2 = yield handleStreamResponse(response, chunkCallback);
1372
+ const res2 = yield handleStreamResponse(response, sseTask);
1118
1373
  logRequestInfo({ status: "success", config: logConfig, startTime, res: res2 });
1119
1374
  resolve(res2);
1120
1375
  return;
@@ -1215,17 +1470,23 @@ function checkCache(cacheKey) {
1215
1470
  }
1216
1471
  return cached.res;
1217
1472
  }
1218
- function handleStreamResponse(response, chunkCallback) {
1473
+ function handleStreamResponse(response, sseTask) {
1219
1474
  return __async(this, null, function* () {
1220
- if (!response.body) throw new Error("Response body is null");
1475
+ if (!response.body) return "Stream is empty";
1476
+ if (sseTask.parser === void 0) {
1477
+ return "onMessage is undefined \u2192 Stream parser skipped";
1478
+ }
1221
1479
  const reader = response.body.getReader();
1222
1480
  while (true) {
1223
1481
  const { done, value } = yield reader.read();
1224
- if (done) break;
1225
- if (chunkCallback && value) {
1226
- chunkCallback({ data: value.buffer });
1482
+ if (sseTask.parser === null) {
1483
+ yield reader.cancel();
1484
+ throw new DOMException("BodyStreamBuffer was aborted", "AbortError");
1227
1485
  }
1486
+ if (done) break;
1487
+ if (sseTask.parser && value) sseTask.parser.receive(value.buffer);
1228
1488
  }
1489
+ if (sseTask.parser) sseTask.parser.flush();
1229
1490
  return "Stream Finished";
1230
1491
  });
1231
1492
  }