@ray-js/t-agent-plugin-aistream 0.2.0-beta-1
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/LICENSE.md +21 -0
- package/README-zh_CN.md +12 -0
- package/README.md +12 -0
- package/dist/AIStreamTypes.d.ts +1413 -0
- package/dist/AIStreamTypes.js +216 -0
- package/dist/ChatHistoryStore.d.ts +66 -0
- package/dist/ChatHistoryStore.js +160 -0
- package/dist/global.d.ts +4 -0
- package/dist/global.js +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/polyfill.d.ts +1 -0
- package/dist/polyfill.js +8 -0
- package/dist/utils/AIStream.d.ts +137 -0
- package/dist/utils/AIStream.js +486 -0
- package/dist/utils/abort.d.ts +38 -0
- package/dist/utils/abort.js +177 -0
- package/dist/utils/actions.d.ts +48 -0
- package/dist/utils/actions.js +76 -0
- package/dist/utils/apis.d.ts +15 -0
- package/dist/utils/apis.js +10 -0
- package/dist/utils/defaultMock.d.ts +1 -0
- package/dist/utils/defaultMock.js +429 -0
- package/dist/utils/index.d.ts +11 -0
- package/dist/utils/index.js +11 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.js +3 -0
- package/dist/utils/mock.d.ts +48 -0
- package/dist/utils/mock.js +72 -0
- package/dist/utils/observer.d.ts +61 -0
- package/dist/utils/observer.js +152 -0
- package/dist/utils/parsers.d.ts +10 -0
- package/dist/utils/parsers.js +13 -0
- package/dist/utils/promisify.d.ts +18 -0
- package/dist/utils/promisify.js +82 -0
- package/dist/utils/sendMessage.d.ts +21 -0
- package/dist/utils/sendMessage.js +241 -0
- package/dist/utils/ttt.d.ts +99 -0
- package/dist/utils/ttt.js +97 -0
- package/dist/utils/url.d.ts +11 -0
- package/dist/utils/url.js +31 -0
- package/dist/utils/version.d.ts +1 -0
- package/dist/utils/version.js +63 -0
- package/dist/withAIStream.d.ts +64 -0
- package/dist/withAIStream.js +420 -0
- package/package.json +39 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import "core-js/modules/es.array.flat.js";
|
|
2
|
+
import "core-js/modules/es.array.unscopables.flat.js";
|
|
3
|
+
import "core-js/modules/es.json.stringify.js";
|
|
4
|
+
import "core-js/modules/web.dom-collections.iterator.js";
|
|
5
|
+
import { createHooks, Emitter } from '@ray-js/t-agent';
|
|
6
|
+
const mock = {
|
|
7
|
+
loaded: false,
|
|
8
|
+
lastId: 1,
|
|
9
|
+
database: new Map(),
|
|
10
|
+
data: new Map(),
|
|
11
|
+
sleep: ms => new Promise(resolve => setTimeout(resolve, ms)),
|
|
12
|
+
generateInt: (min, max) => Math.floor(Math.random() * (max - min + 1)) + min,
|
|
13
|
+
emitter: new Emitter(),
|
|
14
|
+
hooks: createHooks(),
|
|
15
|
+
getRecords: () => {
|
|
16
|
+
var _rows;
|
|
17
|
+
if (mock.loaded) {
|
|
18
|
+
return Array.from(mock.database.values());
|
|
19
|
+
}
|
|
20
|
+
const ret = ty.getStorageSync({
|
|
21
|
+
key: 'AIStreamMockDatabase'
|
|
22
|
+
});
|
|
23
|
+
mock.loaded = true;
|
|
24
|
+
let rows = [];
|
|
25
|
+
if (typeof ret === 'string') {
|
|
26
|
+
rows = JSON.parse(ret);
|
|
27
|
+
} else if (typeof (ret === null || ret === void 0 ? void 0 : ret.data) === 'string') {
|
|
28
|
+
rows = JSON.parse(ret === null || ret === void 0 ? void 0 : ret.data);
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray((_rows = rows) === null || _rows === void 0 ? void 0 : _rows[0])) {
|
|
31
|
+
rows = rows.flat();
|
|
32
|
+
}
|
|
33
|
+
if (!rows) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
for (const row of rows) {
|
|
37
|
+
mock.database.set(row.id, row);
|
|
38
|
+
mock.lastId = Math.max(mock.lastId, row.id);
|
|
39
|
+
}
|
|
40
|
+
mock.lastId += 1;
|
|
41
|
+
return rows;
|
|
42
|
+
},
|
|
43
|
+
getRecord: id => {
|
|
44
|
+
mock.getRecords();
|
|
45
|
+
return mock.database.get(id);
|
|
46
|
+
},
|
|
47
|
+
setRecord: entry => {
|
|
48
|
+
mock.getRecords();
|
|
49
|
+
mock.database.set(entry.id, entry);
|
|
50
|
+
ty.setStorageSync({
|
|
51
|
+
key: 'AIStreamMockDatabase',
|
|
52
|
+
data: JSON.stringify(mock.getRecords())
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
deleteRecord: id => {
|
|
56
|
+
mock.getRecords();
|
|
57
|
+
mock.database.delete(id);
|
|
58
|
+
ty.setStorageSync({
|
|
59
|
+
key: 'AIStreamMockDatabase',
|
|
60
|
+
data: JSON.stringify(mock.getRecords())
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
clearRecords: () => {
|
|
64
|
+
mock.database.clear();
|
|
65
|
+
},
|
|
66
|
+
getId: () => {
|
|
67
|
+
const id = mock.lastId;
|
|
68
|
+
mock.lastId += 1;
|
|
69
|
+
return id;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
export { mock };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AudioBody, ConnectStateBody, EventBody, FileBody, ImageBody, RecordAmplitudesBody, SessionStateBody, TextBody, VideoBody } from '../AIStreamTypes';
|
|
2
|
+
export type AIStreamDataEntry = {
|
|
3
|
+
type: 'text';
|
|
4
|
+
body: TextBody;
|
|
5
|
+
} | {
|
|
6
|
+
type: 'image';
|
|
7
|
+
body: ImageBody;
|
|
8
|
+
} | {
|
|
9
|
+
type: 'video';
|
|
10
|
+
body: VideoBody;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'file';
|
|
13
|
+
body: FileBody;
|
|
14
|
+
} | {
|
|
15
|
+
type: 'audio';
|
|
16
|
+
body: AudioBody;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'event';
|
|
19
|
+
body: EventBody;
|
|
20
|
+
} | {
|
|
21
|
+
type: 'connectionState';
|
|
22
|
+
body: ConnectStateBody;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'sessionState';
|
|
25
|
+
body: SessionStateBody;
|
|
26
|
+
} | {
|
|
27
|
+
type: 'amplitudes';
|
|
28
|
+
body: RecordAmplitudesBody;
|
|
29
|
+
};
|
|
30
|
+
export interface AIStreamObserverOptions {
|
|
31
|
+
sessionState?: boolean;
|
|
32
|
+
connectionState?: boolean;
|
|
33
|
+
event?: boolean;
|
|
34
|
+
text?: boolean;
|
|
35
|
+
audio?: boolean;
|
|
36
|
+
video?: boolean;
|
|
37
|
+
file?: boolean;
|
|
38
|
+
image?: boolean;
|
|
39
|
+
dataChannels?: string[];
|
|
40
|
+
sessionId?: string;
|
|
41
|
+
amplitudes?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare class AIStreamObserverPool {
|
|
44
|
+
isStarted: boolean;
|
|
45
|
+
private observerMap;
|
|
46
|
+
constructor();
|
|
47
|
+
private cancels;
|
|
48
|
+
start(): void;
|
|
49
|
+
dispose(): void;
|
|
50
|
+
connect(observer: AIStreamObserver): void;
|
|
51
|
+
disconnect(observer: AIStreamObserver): void;
|
|
52
|
+
}
|
|
53
|
+
export declare class AIStreamObserver {
|
|
54
|
+
readonly callback: (entry: AIStreamDataEntry, observer: AIStreamObserver) => void;
|
|
55
|
+
private readonly pool;
|
|
56
|
+
get options(): AIStreamObserverOptions;
|
|
57
|
+
private _options;
|
|
58
|
+
constructor(callback: (entry: AIStreamDataEntry, observer: AIStreamObserver) => void, pool: AIStreamObserverPool);
|
|
59
|
+
observe(options: AIStreamObserverOptions): void;
|
|
60
|
+
disconnect(): void;
|
|
61
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
|
|
2
|
+
import "core-js/modules/esnext.iterator.constructor.js";
|
|
3
|
+
import "core-js/modules/esnext.iterator.for-each.js";
|
|
4
|
+
import "core-js/modules/web.dom-collections.iterator.js";
|
|
5
|
+
import { ConnectState, EventType, SessionState, StreamFlag } from '../AIStreamTypes';
|
|
6
|
+
import { listenAudioReceived, listenConnectStateChanged, listenEventReceived, listenImageReceived, listenRecordAmplitudes, listenSessionStateChanged, listenTextReceived } from './ttt';
|
|
7
|
+
import logger from './logger';
|
|
8
|
+
const types = ['connectionState', 'sessionState', 'event', 'text', 'audio', 'video', 'file', 'image', 'amplitudes'];
|
|
9
|
+
export class AIStreamObserverPool {
|
|
10
|
+
constructor() {
|
|
11
|
+
_defineProperty(this, "isStarted", false);
|
|
12
|
+
_defineProperty(this, "cancels", []);
|
|
13
|
+
this.observerMap = {
|
|
14
|
+
connectionState: new Set(),
|
|
15
|
+
sessionState: new Set(),
|
|
16
|
+
event: new Set(),
|
|
17
|
+
text: new Set(),
|
|
18
|
+
audio: new Set(),
|
|
19
|
+
video: new Set(),
|
|
20
|
+
file: new Set(),
|
|
21
|
+
image: new Set(),
|
|
22
|
+
amplitudes: new Set()
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
start() {
|
|
26
|
+
if (this.isStarted) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const handle = entry => {
|
|
30
|
+
const observers = this.observerMap[entry.type];
|
|
31
|
+
let state = '';
|
|
32
|
+
if (entry.type !== 'amplitudes') {
|
|
33
|
+
var _entry$body;
|
|
34
|
+
if (entry.type === 'connectionState') {
|
|
35
|
+
state = "".concat(ConnectState[entry.body.connectState], " ").concat(entry.body.code || '');
|
|
36
|
+
} else if (entry.type === 'sessionState') {
|
|
37
|
+
state = "".concat(SessionState[entry.body.sessionState], " ").concat(entry.body.code || '');
|
|
38
|
+
} else if (entry.type === 'event') {
|
|
39
|
+
state = "".concat(EventType[entry.body.eventType]);
|
|
40
|
+
} else if ((_entry$body = entry.body) !== null && _entry$body !== void 0 && _entry$body.streamFlag) {
|
|
41
|
+
state = "".concat(StreamFlag[entry.body.streamFlag]);
|
|
42
|
+
}
|
|
43
|
+
logger.debug("Pool received: %c".concat(observers.size ? '⊕' : '×').concat(entry.type, " ").concat(state), 'background: black; color: white', entry);
|
|
44
|
+
}
|
|
45
|
+
observers.forEach(observer => {
|
|
46
|
+
if (entry.type === 'text' || entry.type === 'audio' || entry.type === 'video' || entry.type === 'file' || entry.type === 'image') {
|
|
47
|
+
const {
|
|
48
|
+
dataChannels,
|
|
49
|
+
sessionId
|
|
50
|
+
} = observer.options;
|
|
51
|
+
let matched = true;
|
|
52
|
+
if (dataChannels !== null && dataChannels !== void 0 && dataChannels.length && !dataChannels.includes(entry.body.dataChannel)) {
|
|
53
|
+
matched = false;
|
|
54
|
+
}
|
|
55
|
+
if (sessionId && !entry.body.sessionIdList.includes(sessionId)) {
|
|
56
|
+
matched = false;
|
|
57
|
+
}
|
|
58
|
+
if (matched) {
|
|
59
|
+
observer.callback(entry, observer);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
observer.callback(entry, observer);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
this.cancels = [listenConnectStateChanged(body => handle({
|
|
67
|
+
type: 'connectionState',
|
|
68
|
+
body
|
|
69
|
+
})), listenSessionStateChanged(body => handle({
|
|
70
|
+
type: 'sessionState',
|
|
71
|
+
body
|
|
72
|
+
})), listenEventReceived(body => handle({
|
|
73
|
+
type: 'event',
|
|
74
|
+
body
|
|
75
|
+
})), listenTextReceived(body => handle({
|
|
76
|
+
type: 'text',
|
|
77
|
+
body
|
|
78
|
+
})), listenAudioReceived(body => handle({
|
|
79
|
+
type: 'audio',
|
|
80
|
+
body
|
|
81
|
+
})), listenImageReceived(body => handle({
|
|
82
|
+
type: 'image',
|
|
83
|
+
body
|
|
84
|
+
})), listenRecordAmplitudes(body => handle({
|
|
85
|
+
type: 'amplitudes',
|
|
86
|
+
body
|
|
87
|
+
}))
|
|
88
|
+
// listenVideoReceived(body => handle({ type: 'video', body })),
|
|
89
|
+
// listenFileReceived(body => handle({ type: 'file', body })),
|
|
90
|
+
];
|
|
91
|
+
this.isStarted = true;
|
|
92
|
+
}
|
|
93
|
+
dispose() {
|
|
94
|
+
this.isStarted = false;
|
|
95
|
+
this.cancels.forEach(cancel => cancel());
|
|
96
|
+
this.cancels = [];
|
|
97
|
+
this.observerMap = {
|
|
98
|
+
connectionState: new Set(),
|
|
99
|
+
sessionState: new Set(),
|
|
100
|
+
event: new Set(),
|
|
101
|
+
text: new Set(),
|
|
102
|
+
audio: new Set(),
|
|
103
|
+
video: new Set(),
|
|
104
|
+
file: new Set(),
|
|
105
|
+
image: new Set(),
|
|
106
|
+
amplitudes: new Set()
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
connect(observer) {
|
|
110
|
+
if (!this.isStarted) {
|
|
111
|
+
this.start();
|
|
112
|
+
}
|
|
113
|
+
types.forEach(key => {
|
|
114
|
+
const type = key;
|
|
115
|
+
if (observer.options[type]) {
|
|
116
|
+
this.observerMap[type].add(observer);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
disconnect(observer) {
|
|
121
|
+
types.forEach(key => {
|
|
122
|
+
this.observerMap[key].delete(observer);
|
|
123
|
+
});
|
|
124
|
+
let empty = true;
|
|
125
|
+
types.forEach(key => {
|
|
126
|
+
if (this.observerMap[key].size > 0) {
|
|
127
|
+
empty = false;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
if (empty) {
|
|
131
|
+
this.dispose();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export class AIStreamObserver {
|
|
136
|
+
get options() {
|
|
137
|
+
return this._options;
|
|
138
|
+
}
|
|
139
|
+
constructor(callback, pool) {
|
|
140
|
+
_defineProperty(this, "_options", {});
|
|
141
|
+
this.callback = callback;
|
|
142
|
+
this.pool = pool;
|
|
143
|
+
}
|
|
144
|
+
observe(options) {
|
|
145
|
+
this.pool.disconnect(this);
|
|
146
|
+
this._options = options;
|
|
147
|
+
this.pool.connect(this);
|
|
148
|
+
}
|
|
149
|
+
disconnect() {
|
|
150
|
+
this.pool.disconnect(this);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数字转化为整数
|
|
3
|
+
* @param v 带转化的数字
|
|
4
|
+
* @param defaultValue 兜底值,默认为0
|
|
5
|
+
* @returns number 转化后的整数
|
|
6
|
+
* @example
|
|
7
|
+
* parseInt(1.1) // 1
|
|
8
|
+
* parseInt(1.9) // 1
|
|
9
|
+
*/
|
|
10
|
+
export function safeParseInt(v) {
|
|
11
|
+
let defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
12
|
+
return v ? Number.parseInt(String(v), 10) || defaultValue : defaultValue;
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface AsyncTTTFnParams<P> {
|
|
2
|
+
complete?: () => void;
|
|
3
|
+
success?: (params: P) => void;
|
|
4
|
+
fail?: (params: {
|
|
5
|
+
errorMsg: string;
|
|
6
|
+
errorCode: string | number;
|
|
7
|
+
innerError: {
|
|
8
|
+
errorCode: string | number;
|
|
9
|
+
errorMsg: string;
|
|
10
|
+
};
|
|
11
|
+
}) => void;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
export declare const getEnableMock: () => boolean;
|
|
15
|
+
export declare const setEnableMock: (enable: boolean) => boolean;
|
|
16
|
+
export declare function promisify<T extends AsyncTTTFnParams<any>>(fn: (options: any) => void, enableMock?: boolean): (options?: Omit<T, 'success' | 'fail'>) => Promise<Parameters<NonNullable<T["success"]>>[0]>;
|
|
17
|
+
export declare function listening<T>(on: (listener: (params: T) => void) => void, off: (listener: (params: T) => void) => void, enableMock?: boolean): (listener: (params: T) => void) => () => void;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
2
|
+
import { isDevTools } from './ttt';
|
|
3
|
+
import logger from './logger';
|
|
4
|
+
import { mock } from './mock';
|
|
5
|
+
let callId = 100000;
|
|
6
|
+
export const getEnableMock = () => {
|
|
7
|
+
try {
|
|
8
|
+
const result = ty.getStorageSync({
|
|
9
|
+
key: 'AIAssistantEnableMock'
|
|
10
|
+
});
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
if (result && (result === 'true' || result.data === 'true')) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
} catch (e) {
|
|
16
|
+
console.error('获取AIAssistantEnableMock配置失败', e);
|
|
17
|
+
}
|
|
18
|
+
return isDevTools();
|
|
19
|
+
};
|
|
20
|
+
export const setEnableMock = enable => {
|
|
21
|
+
try {
|
|
22
|
+
ty.setStorageSync({
|
|
23
|
+
key: 'AIAssistantEnableMock',
|
|
24
|
+
data: String(enable)
|
|
25
|
+
});
|
|
26
|
+
return true;
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.error('设置AIAssistantEnableMock配置失败', e);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
export function promisify(fn) {
|
|
33
|
+
let enableMock = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
34
|
+
return options => {
|
|
35
|
+
return new Promise((_resolve, _reject) => {
|
|
36
|
+
if (!fn) {
|
|
37
|
+
_reject(new Error('fn is not a function'));
|
|
38
|
+
}
|
|
39
|
+
const id = callId++;
|
|
40
|
+
logger.debug("TTT call #".concat(id, " %c").concat(fn.name), 'background: blue; color: white', options);
|
|
41
|
+
const resolve = result => {
|
|
42
|
+
logger.debug("TTT resp #".concat(id, " %c").concat(fn.name), 'background: green; color: white', result);
|
|
43
|
+
_resolve(result);
|
|
44
|
+
};
|
|
45
|
+
const reject = error => {
|
|
46
|
+
logger.debug("TTT error #".concat(id, " %c").concat(fn.name), 'background: red; color: white', error);
|
|
47
|
+
_reject(error);
|
|
48
|
+
};
|
|
49
|
+
const context = {
|
|
50
|
+
options,
|
|
51
|
+
result: undefined
|
|
52
|
+
};
|
|
53
|
+
if (getEnableMock() && enableMock) {
|
|
54
|
+
mock.hooks.callHook(fn.name, context).then(() => resolve(context.result), reject);
|
|
55
|
+
} else {
|
|
56
|
+
fn.call(ty, _objectSpread(_objectSpread({}, options), {}, {
|
|
57
|
+
success: resolve,
|
|
58
|
+
fail: reject
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function listening(on, off) {
|
|
65
|
+
let enableMock = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
66
|
+
return listener => {
|
|
67
|
+
if (getEnableMock() && enableMock) {
|
|
68
|
+
const cb = event => {
|
|
69
|
+
logger.debug('listening on event', on.name, event.detail);
|
|
70
|
+
listener(event.detail);
|
|
71
|
+
};
|
|
72
|
+
mock.emitter.addEventListener(on.name, cb);
|
|
73
|
+
return () => {
|
|
74
|
+
mock.emitter.removeEventListener(on.name, cb);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
on(listener);
|
|
78
|
+
return () => {
|
|
79
|
+
off(listener);
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import '../polyfill';
|
|
2
|
+
import { AIStreamSession } from './AIStream';
|
|
3
|
+
import { AIStreamChatAttribute } from '../AIStreamTypes';
|
|
4
|
+
import { InputBlock, StreamResponse } from '@ray-js/t-agent';
|
|
5
|
+
export interface SendBlocksToAIStreamParams {
|
|
6
|
+
blocks: InputBlock[];
|
|
7
|
+
session: AIStreamSession;
|
|
8
|
+
attribute?: AIStreamChatAttribute;
|
|
9
|
+
}
|
|
10
|
+
export declare class AIStreamSessionError extends Error {
|
|
11
|
+
readonly code: number;
|
|
12
|
+
constructor(message: string, code: number);
|
|
13
|
+
}
|
|
14
|
+
export declare class AIStreamConnectionError extends Error {
|
|
15
|
+
readonly code: number;
|
|
16
|
+
constructor(message: string, code: number);
|
|
17
|
+
}
|
|
18
|
+
export declare function sendBlocksToAIStream(params: SendBlocksToAIStreamParams): {
|
|
19
|
+
response: StreamResponse;
|
|
20
|
+
metaPromise: Promise<Record<string, any>>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
2
|
+
import "core-js/modules/es.json.stringify.js";
|
|
3
|
+
import "core-js/modules/web.dom-collections.iterator.js";
|
|
4
|
+
import '../polyfill';
|
|
5
|
+
import { ReadableStream } from 'web-streams-polyfill';
|
|
6
|
+
import { AIStreamAttributePayloadType, AIStreamAttributeType, AIStreamChatSysWorkflow, ConnectState, FileFormat, ReceivedTextPacketEof, ReceivedTextPacketType, SessionState, StreamFlag } from '../AIStreamTypes';
|
|
7
|
+
import { EmitterEvent, generateId, safeParseJSON, StreamResponse } from '@ray-js/t-agent';
|
|
8
|
+
const mimeTypeToFormatMap = {
|
|
9
|
+
'video/mp4': FileFormat.MP4,
|
|
10
|
+
'text/json': FileFormat.JSON,
|
|
11
|
+
'application/json': FileFormat.JSON,
|
|
12
|
+
'application/pdf': FileFormat.PDF
|
|
13
|
+
};
|
|
14
|
+
export class AIStreamSessionError extends Error {
|
|
15
|
+
constructor(message, code) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'AIStreamSessionError';
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class AIStreamConnectionError extends Error {
|
|
22
|
+
constructor(message, code) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'AIStreamConnectionError';
|
|
25
|
+
this.code = code;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function sendBlocksToAIStream(params) {
|
|
29
|
+
const {
|
|
30
|
+
session,
|
|
31
|
+
blocks
|
|
32
|
+
} = params;
|
|
33
|
+
let audioEmitter = null;
|
|
34
|
+
let amplitudeCount = 0;
|
|
35
|
+
for (const block of blocks) {
|
|
36
|
+
if (block.type === 'audio') {
|
|
37
|
+
if (audioEmitter) {
|
|
38
|
+
throw new Error('only one audio emitter is allowed');
|
|
39
|
+
}
|
|
40
|
+
audioEmitter = block.audio_emitter;
|
|
41
|
+
amplitudeCount = block.amplitude_count || 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const attribute = _objectSpread({
|
|
45
|
+
'processing.interrupt': 'false',
|
|
46
|
+
'asr.enableVad': 'false',
|
|
47
|
+
'sys.workflow': audioEmitter ? AIStreamChatSysWorkflow.ASR_LLM : AIStreamChatSysWorkflow.LLM
|
|
48
|
+
}, params.attribute);
|
|
49
|
+
let canceled = false;
|
|
50
|
+
let event = null;
|
|
51
|
+
let metaResolve;
|
|
52
|
+
const metaPromise = new Promise(resolve => {
|
|
53
|
+
metaResolve = resolve;
|
|
54
|
+
});
|
|
55
|
+
const stream = new ReadableStream({
|
|
56
|
+
async start(controller) {
|
|
57
|
+
const enqueue = part => {
|
|
58
|
+
if (canceled) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
controller.enqueue(part);
|
|
62
|
+
};
|
|
63
|
+
event = await session.startEvent({
|
|
64
|
+
userData: [{
|
|
65
|
+
type: AIStreamAttributeType.AI_CHAT,
|
|
66
|
+
payloadType: AIStreamAttributePayloadType.STRING,
|
|
67
|
+
value: JSON.stringify(attribute)
|
|
68
|
+
}]
|
|
69
|
+
});
|
|
70
|
+
const meta = {
|
|
71
|
+
sessionId: event.sessionId,
|
|
72
|
+
eventId: event.eventId
|
|
73
|
+
};
|
|
74
|
+
metaResolve(meta);
|
|
75
|
+
let prevText = '';
|
|
76
|
+
let imageId = null;
|
|
77
|
+
let audioId = null;
|
|
78
|
+
event.on('data', data => {
|
|
79
|
+
if (data.type === 'text') {
|
|
80
|
+
if (!data.body.text) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const packet = safeParseJSON(data.body.text);
|
|
84
|
+
if (packet.bizType === ReceivedTextPacketType.NLG) {
|
|
85
|
+
const text = prevText + packet.data.content;
|
|
86
|
+
enqueue({
|
|
87
|
+
type: 'text',
|
|
88
|
+
delta: packet.data.content,
|
|
89
|
+
text,
|
|
90
|
+
meta
|
|
91
|
+
});
|
|
92
|
+
prevText = text;
|
|
93
|
+
} else if (packet.bizType === ReceivedTextPacketType.SKILL) {
|
|
94
|
+
enqueue({
|
|
95
|
+
id: generateId(),
|
|
96
|
+
type: 'attachment',
|
|
97
|
+
attachmentType: 'skill',
|
|
98
|
+
attachment: packet.data,
|
|
99
|
+
meta
|
|
100
|
+
});
|
|
101
|
+
} else if (packet.bizType === ReceivedTextPacketType.ASR && audioEmitter) {
|
|
102
|
+
if (packet.eof === ReceivedTextPacketEof.END) {
|
|
103
|
+
audioEmitter.dispatchEvent(new EmitterEvent('finish', {
|
|
104
|
+
detail: {
|
|
105
|
+
text: packet.data.text
|
|
106
|
+
}
|
|
107
|
+
}));
|
|
108
|
+
} else {
|
|
109
|
+
audioEmitter.dispatchEvent(new EmitterEvent('update', {
|
|
110
|
+
detail: {
|
|
111
|
+
text: packet.data.text
|
|
112
|
+
}
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} else if (data.type === 'image') {
|
|
117
|
+
if (!imageId) {
|
|
118
|
+
imageId = generateId();
|
|
119
|
+
}
|
|
120
|
+
enqueue({
|
|
121
|
+
id: imageId,
|
|
122
|
+
type: 'attachment',
|
|
123
|
+
attachmentType: 'image',
|
|
124
|
+
attachment: {
|
|
125
|
+
path: data.body.path || null,
|
|
126
|
+
width: data.body.width || null,
|
|
127
|
+
height: data.body.height || null
|
|
128
|
+
},
|
|
129
|
+
meta
|
|
130
|
+
});
|
|
131
|
+
if (data.body.streamFlag === StreamFlag.END) {
|
|
132
|
+
imageId = null;
|
|
133
|
+
}
|
|
134
|
+
} else if (data.type === 'audio') {
|
|
135
|
+
if (!audioId) {
|
|
136
|
+
audioId = generateId();
|
|
137
|
+
}
|
|
138
|
+
enqueue({
|
|
139
|
+
id: audioId,
|
|
140
|
+
type: 'attachment',
|
|
141
|
+
attachmentType: 'audio',
|
|
142
|
+
attachment: {
|
|
143
|
+
path: data.body.path || null
|
|
144
|
+
},
|
|
145
|
+
meta
|
|
146
|
+
});
|
|
147
|
+
if (data.body.streamFlag === StreamFlag.END) {
|
|
148
|
+
audioId = null;
|
|
149
|
+
}
|
|
150
|
+
} else if (data.type === 'sessionState') {
|
|
151
|
+
if (data.body.sessionState === SessionState.CLOSED) {
|
|
152
|
+
enqueue({
|
|
153
|
+
type: 'error',
|
|
154
|
+
error: new AIStreamSessionError('Session closed', data.body.code),
|
|
155
|
+
level: 'error',
|
|
156
|
+
meta
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
} else if (data.type === 'connectionState') {
|
|
160
|
+
if (data.body.connectState === ConnectState.DISCONNECTED) {
|
|
161
|
+
enqueue({
|
|
162
|
+
type: 'error',
|
|
163
|
+
error: new AIStreamConnectionError('Connection disconnected', data.body.code),
|
|
164
|
+
level: 'error',
|
|
165
|
+
meta
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
event.on('finish', () => {
|
|
171
|
+
if (!canceled) {
|
|
172
|
+
// 当取消后,不需要关闭控制器
|
|
173
|
+
controller.close();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
event.on('error', error => {
|
|
177
|
+
enqueue({
|
|
178
|
+
type: 'error',
|
|
179
|
+
level: 'error',
|
|
180
|
+
error: error instanceof Error ? error : new Error('Unknown error'),
|
|
181
|
+
meta: {}
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
for (const block of blocks) {
|
|
185
|
+
if (block.type === 'text') {
|
|
186
|
+
event.write({
|
|
187
|
+
type: 'text',
|
|
188
|
+
text: JSON.stringify({
|
|
189
|
+
type: 'text',
|
|
190
|
+
message: block.text
|
|
191
|
+
})
|
|
192
|
+
});
|
|
193
|
+
} else if (block.type === 'image_path') {
|
|
194
|
+
event.write({
|
|
195
|
+
type: 'image',
|
|
196
|
+
path: block.image_path.path
|
|
197
|
+
});
|
|
198
|
+
} else if (block.type === 'file_path') {
|
|
199
|
+
event.write({
|
|
200
|
+
type: 'file',
|
|
201
|
+
format: mimeTypeToFormatMap[block.file_url.mimeType] || 0,
|
|
202
|
+
path: block.file_path.path
|
|
203
|
+
});
|
|
204
|
+
} else if (block.type === 'video_path') {
|
|
205
|
+
event.write({
|
|
206
|
+
type: 'file',
|
|
207
|
+
format: FileFormat.MP4,
|
|
208
|
+
path: block.file_path.path
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (audioEmitter) {
|
|
213
|
+
await new Promise(resolve => {
|
|
214
|
+
const s = event.stream({
|
|
215
|
+
type: 'audio',
|
|
216
|
+
amplitudeCount
|
|
217
|
+
});
|
|
218
|
+
audioEmitter.addEventListener('confirm', () => {
|
|
219
|
+
s.stop().then(resolve);
|
|
220
|
+
});
|
|
221
|
+
audioEmitter.addEventListener('cancel', async () => {
|
|
222
|
+
await s.stop();
|
|
223
|
+
await event.abort();
|
|
224
|
+
resolve();
|
|
225
|
+
});
|
|
226
|
+
s.start();
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
await event.end();
|
|
230
|
+
},
|
|
231
|
+
async cancel() {
|
|
232
|
+
var _event;
|
|
233
|
+
canceled = true;
|
|
234
|
+
await ((_event = event) === null || _event === void 0 ? void 0 : _event.abort());
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
return {
|
|
238
|
+
response: new StreamResponse(stream),
|
|
239
|
+
metaPromise
|
|
240
|
+
};
|
|
241
|
+
}
|