@maiyunnet/kebab 3.2.27 → 3.2.29
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/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/core.d.ts +7 -0
- package/lib/core.js +19 -0
- package/lib/text.js +21 -7
- package/package.json +1 -1
- package/sys/child.js +2 -0
- package/sys/ctr.d.ts +1 -1
- package/sys/ctr.js +1 -1
- package/sys/route.js +1 -1
- package/www/example/ctr/test.d.ts +2 -0
- package/www/example/ctr/test.js +103 -2
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* --- 本文件用来定义每个目录实体地址的常量 ---
|
|
7
7
|
*/
|
|
8
8
|
/** --- 当前系统版本号 --- */
|
|
9
|
-
export const VER = '3.2.
|
|
9
|
+
export const VER = '3.2.29';
|
|
10
10
|
// --- 服务端用的路径 ---
|
|
11
11
|
const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
|
|
12
12
|
/** --- /xxx/xxx --- */
|
package/lib/core.d.ts
CHANGED
|
@@ -249,3 +249,10 @@ export declare function display(message?: any, ...optionalParams: any[]): void;
|
|
|
249
249
|
* @param headers 头部
|
|
250
250
|
*/
|
|
251
251
|
export declare function writeHead(res: http2.Http2ServerResponse | http.ServerResponse, statusCode: number, headers?: http.OutgoingHttpHeaders): void;
|
|
252
|
+
export declare function writeEventStreamHead(res: http2.Http2ServerResponse | http.ServerResponse): void;
|
|
253
|
+
/**
|
|
254
|
+
* --- 向 res 发送数据 ---
|
|
255
|
+
* @param res 响应对象
|
|
256
|
+
* @param data 数据
|
|
257
|
+
*/
|
|
258
|
+
export declare function write(res: http2.Http2ServerResponse | http.ServerResponse, data: string | Buffer): void;
|
package/lib/core.js
CHANGED
|
@@ -806,3 +806,22 @@ export function writeHead(res, statusCode, headers) {
|
|
|
806
806
|
res.writeHead(statusCode, headers);
|
|
807
807
|
}
|
|
808
808
|
}
|
|
809
|
+
export function writeEventStreamHead(res) {
|
|
810
|
+
writeHead(res, 200, {
|
|
811
|
+
'content-type': 'text/event-stream; charset=utf-8',
|
|
812
|
+
'cache-control': 'no-store',
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* --- 向 res 发送数据 ---
|
|
817
|
+
* @param res 响应对象
|
|
818
|
+
* @param data 数据
|
|
819
|
+
*/
|
|
820
|
+
export function write(res, data) {
|
|
821
|
+
if (res instanceof http2.Http2ServerResponse) {
|
|
822
|
+
res.write(data);
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
res.write(data);
|
|
826
|
+
}
|
|
827
|
+
}
|
package/lib/text.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as kebab from '#kebab/index.js';
|
|
7
7
|
import * as lFs from './fs.js';
|
|
8
|
+
import * as lCore from './core.js';
|
|
8
9
|
/**
|
|
9
10
|
* --- 将文件大小格式化为带单位的字符串 ---
|
|
10
11
|
* @param size 文件大小
|
|
@@ -521,18 +522,31 @@ export function stringifyBuffer(buf) {
|
|
|
521
522
|
* --- 递归删除 json 中的字符串首尾空格,会返回一个新的对象 ---
|
|
522
523
|
*/
|
|
523
524
|
export function trimJson(json) {
|
|
524
|
-
json =
|
|
525
|
+
json = lCore.clone(json);
|
|
525
526
|
trimJsonRecursion(json);
|
|
526
527
|
return json;
|
|
527
528
|
}
|
|
528
529
|
function trimJsonRecursion(json) {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
530
|
+
if (Array.isArray(json)) {
|
|
531
|
+
for (let i = 0; i < json.length; ++i) {
|
|
532
|
+
const val = json[i];
|
|
533
|
+
if (typeof val === 'string') {
|
|
534
|
+
json[i] = val.trim();
|
|
535
|
+
}
|
|
536
|
+
else if (typeof val === 'object') {
|
|
537
|
+
trimJsonRecursion(val);
|
|
538
|
+
}
|
|
533
539
|
}
|
|
534
|
-
|
|
535
|
-
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
for (const key in json) {
|
|
543
|
+
const val = json[key];
|
|
544
|
+
if (typeof val === 'string') {
|
|
545
|
+
json[key] = val.trim();
|
|
546
|
+
}
|
|
547
|
+
else if (typeof val === 'object') {
|
|
548
|
+
trimJsonRecursion(val);
|
|
549
|
+
}
|
|
536
550
|
}
|
|
537
551
|
}
|
|
538
552
|
}
|
package/package.json
CHANGED
package/sys/child.js
CHANGED
package/sys/ctr.d.ts
CHANGED
|
@@ -58,7 +58,7 @@ export declare class Ctr {
|
|
|
58
58
|
protected _localeData: Record<string, Record<string, string>>;
|
|
59
59
|
constructor(config: kebab.IConfig, req: http2.Http2ServerRequest | http.IncomingMessage, res?: http2.Http2ServerResponse | http.ServerResponse);
|
|
60
60
|
/** --- 当前用户连接是否还在连接中 --- */
|
|
61
|
-
get
|
|
61
|
+
protected get _isAvail(): boolean;
|
|
62
62
|
/** --- timeout 的 timer --- */
|
|
63
63
|
protected _timer?: {
|
|
64
64
|
'timer': NodeJS.Timeout;
|
package/sys/ctr.js
CHANGED
package/sys/route.js
CHANGED
|
@@ -505,7 +505,7 @@ export async function run(data) {
|
|
|
505
505
|
pathRight = pathRight.replace(/-([a-zA-Z0-9])/g, function (t, t1) {
|
|
506
506
|
return t1.toUpperCase();
|
|
507
507
|
});
|
|
508
|
-
if (cctr[pathRight] === undefined) {
|
|
508
|
+
if ((cctr[pathRight] === undefined) || (typeof cctr[pathRight] !== 'function')) {
|
|
509
509
|
if (config.route['#404']) {
|
|
510
510
|
data.res.setHeader('location', lText.urlResolve(config.const.urlBase, config.route['#404']));
|
|
511
511
|
lCore.writeHead(data.res, 302);
|
package/www/example/ctr/test.js
CHANGED
|
@@ -128,6 +128,7 @@ export default class extends sCtr.Ctr {
|
|
|
128
128
|
'<br><br><b>Library test:</b>',
|
|
129
129
|
`<br><br><b>Ai:</b>`,
|
|
130
130
|
`<br><br><a href="${this._config.const.urlBase}test/ai">View "test/ai"</a>`,
|
|
131
|
+
`<br><a href="${this._config.const.urlBase}test/ai-stream">View "test/ai-stream"</a>`,
|
|
131
132
|
'<br><br><b>Core:</b>',
|
|
132
133
|
`<br><br><a href="${this._config.const.urlBase}test/core-random">View "test/core-random"</a>`,
|
|
133
134
|
`<br><a href="${this._config.const.urlBase}test/core-rand">View "test/core-rand"</a>`,
|
|
@@ -3128,18 +3129,118 @@ rtn.push(reader.readBCDString());</pre>${JSON.stringify(rtn)}`);
|
|
|
3128
3129
|
'model': 'qwen-plus',
|
|
3129
3130
|
'messages': [
|
|
3130
3131
|
{ 'role': 'system', 'content': 'You are Kebab, a friendly and knowledgeable assistant based on an open-source Node framework. You do not mention any model names or AI identity. You can chat casually, answer questions, and provide guidance naturally. Respond in a human-like, approachable manner, as if you are a helpful companion rather than a traditional AI assistant.' },
|
|
3131
|
-
{ 'role': 'user', 'content': '你是谁?' }
|
|
3132
|
+
{ 'role': 'user', 'content': '你是谁?' },
|
|
3132
3133
|
],
|
|
3133
3134
|
});
|
|
3134
3135
|
echo.push(`<pre>await ai.link.chat.completions.create({
|
|
3135
3136
|
'model': 'qwen-plus',
|
|
3136
3137
|
'messages': [
|
|
3137
3138
|
{ 'role': 'system', 'content': 'You are Kebab, a friendly and knowledgeable assistant based on an open-source Node framework. You do not mention any model names or AI identity. You can chat casually, answer questions, and provide guidance naturally. Respond in a human-like, approachable manner, as if you are a helpful companion rather than a traditional AI assistant.' },
|
|
3138
|
-
{ 'role': 'user', 'content': '你是谁?' }
|
|
3139
|
+
{ 'role': 'user', 'content': '你是谁?' },
|
|
3139
3140
|
],
|
|
3140
3141
|
});</pre>` + JSON.stringify(completion.choices[0].message.content));
|
|
3141
3142
|
return echo.join('') + '<br><br>' + this._getEnd();
|
|
3142
3143
|
}
|
|
3144
|
+
aiStream() {
|
|
3145
|
+
const echo = `<input id="text" type="text"><button id="send">Send</button>
|
|
3146
|
+
<hr>
|
|
3147
|
+
<div id="content"></div>
|
|
3148
|
+
<script>
|
|
3149
|
+
let controller;
|
|
3150
|
+
const text = document.getElementById('text');
|
|
3151
|
+
const send = document.getElementById('send');
|
|
3152
|
+
const content = document.getElementById('content');
|
|
3153
|
+
send.addEventListener('click', async () => {
|
|
3154
|
+
if (send.innerHTML === 'Stop') {
|
|
3155
|
+
controller.abort();
|
|
3156
|
+
send.innerHTML = 'Send';
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
if (!text.value) {
|
|
3160
|
+
alert('Please input content');
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3163
|
+
send.innerHTML = 'Stop';
|
|
3164
|
+
send.disabled = true;
|
|
3165
|
+
controller = new AbortController();
|
|
3166
|
+
const res = await fetch('http${this._config.const.https ? 's' : ''}://${this._config.const.host}/test/ai-stream1', {
|
|
3167
|
+
'method': 'POST',
|
|
3168
|
+
'headers': { 'content-type': 'application/json' },
|
|
3169
|
+
'body': JSON.stringify({ 'content': text.value, }),
|
|
3170
|
+
'signal': controller.signal,
|
|
3171
|
+
});
|
|
3172
|
+
send.disabled = false;
|
|
3173
|
+
text.value = '';
|
|
3174
|
+
content.textContent = '';
|
|
3175
|
+
const reader = res.body.getReader();
|
|
3176
|
+
const decoder = new TextDecoder('utf8');
|
|
3177
|
+
let buf = '';
|
|
3178
|
+
while (true) {
|
|
3179
|
+
const { value, done } = await reader.read();
|
|
3180
|
+
if (done) {
|
|
3181
|
+
break;
|
|
3182
|
+
}
|
|
3183
|
+
buf += decoder.decode(value, { 'stream': true, });
|
|
3184
|
+
if (!buf.includes('\\n\\n')) {
|
|
3185
|
+
// --- 还没接收完 ---
|
|
3186
|
+
continue;
|
|
3187
|
+
}
|
|
3188
|
+
const events = buf.split('\\n\\n');
|
|
3189
|
+
buf = events.pop(); // --- 最后一个可能不完整 ---
|
|
3190
|
+
for (const ev of events) {
|
|
3191
|
+
content.textContent += JSON.parse(ev.slice(5).trim());
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
send.innerHTML = 'Send';
|
|
3195
|
+
});
|
|
3196
|
+
</script>`;
|
|
3197
|
+
return echo + '<br>' + this._getEnd();
|
|
3198
|
+
}
|
|
3199
|
+
async aiStream1() {
|
|
3200
|
+
const ai = lAi.get(this, {
|
|
3201
|
+
'service': lAi.ESERVICE.ALICN,
|
|
3202
|
+
});
|
|
3203
|
+
const stream = await ai.link.chat.completions.create({
|
|
3204
|
+
'model': 'qwen-plus',
|
|
3205
|
+
'stream': true,
|
|
3206
|
+
'messages': [
|
|
3207
|
+
{ 'role': 'system', 'content': 'You are Kebab, a friendly and knowledgeable assistant based on an open-source Node framework. You do not mention any model names or AI identity. You can chat casually, answer questions, and provide guidance naturally. Respond in a human-like, approachable manner, as if you are a helpful companion rather than a traditional AI assistant.' },
|
|
3208
|
+
{ 'role': 'user', 'content': this._post['content'] },
|
|
3209
|
+
],
|
|
3210
|
+
'stream_options': {
|
|
3211
|
+
'include_usage': true,
|
|
3212
|
+
},
|
|
3213
|
+
});
|
|
3214
|
+
lCore.writeEventStreamHead(this._res);
|
|
3215
|
+
for await (const chunk of stream) {
|
|
3216
|
+
if (!this._isAvail) {
|
|
3217
|
+
lCore.debug('Client disconnect');
|
|
3218
|
+
stream.controller.abort();
|
|
3219
|
+
break;
|
|
3220
|
+
}
|
|
3221
|
+
if (chunk.choices.length) {
|
|
3222
|
+
const content = chunk.choices[0].delta.content;
|
|
3223
|
+
if (!content) {
|
|
3224
|
+
continue;
|
|
3225
|
+
}
|
|
3226
|
+
if (!this._isAvail) {
|
|
3227
|
+
// --- 测试上面 abort 后还会执行到这里吗 ---
|
|
3228
|
+
// --- 测试结果:确实不会 ---
|
|
3229
|
+
lCore.debug('Client has been closed');
|
|
3230
|
+
continue;
|
|
3231
|
+
}
|
|
3232
|
+
lCore.write(this._res, 'data: ' + JSON.stringify(content) + '\n\n');
|
|
3233
|
+
continue;
|
|
3234
|
+
}
|
|
3235
|
+
if (!chunk.usage) {
|
|
3236
|
+
continue;
|
|
3237
|
+
}
|
|
3238
|
+
lCore.debug('--- All over ---');
|
|
3239
|
+
lCore.debug(`Input Tokens: ${chunk.usage.prompt_tokens}`);
|
|
3240
|
+
lCore.debug(`Output Tokens: ${chunk.usage.completion_tokens}`);
|
|
3241
|
+
lCore.debug(`Total Tokens: ${chunk.usage.total_tokens}`);
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3143
3244
|
/**
|
|
3144
3245
|
* --- END ---
|
|
3145
3246
|
*/
|