@ray-js/t-agent-ui-ray 0.2.0-beta-10 → 0.2.0-beta-12
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/MessageInput/MessageInputAIStream/AsrInput.d.ts +1 -1
- package/dist/MessageInput/MessageInputAIStream/AsrInput.js +13 -7
- package/dist/MessageInput/MessageInputAIStream/index.js +263 -146
- package/dist/MessageInput/icons/camera.svg +1 -0
- package/dist/MessageInput/index.less +7 -0
- package/dist/MessageList/index.js +1 -2
- package/dist/hooks/useAttachmentInput.d.ts +1 -1
- package/dist/hooks/useAttachmentInput.js +68 -35
- package/dist/i18n/strings.d.ts +2 -0
- package/dist/i18n/strings.js +2 -0
- package/dist/tiles/FileTile/index.less +1 -0
- package/dist/tiles/ImageTile/index.js +0 -1
- package/package.json +2 -2
|
@@ -12,7 +12,7 @@ const AsrInput = props => {
|
|
|
12
12
|
const t = useTranslate();
|
|
13
13
|
const [active, setActive] = useState(false);
|
|
14
14
|
const [cancel, setCancel] = useState(false);
|
|
15
|
-
const onVoiceTouchEnd = event => {
|
|
15
|
+
const onVoiceTouchEnd = async event => {
|
|
16
16
|
const touchY = event.changedTouches[0].pageY;
|
|
17
17
|
setCancel(false);
|
|
18
18
|
setActive(false);
|
|
@@ -30,11 +30,13 @@ const AsrInput = props => {
|
|
|
30
30
|
startAt: 0,
|
|
31
31
|
y: 0
|
|
32
32
|
};
|
|
33
|
-
props.onCancel();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
const result = await props.onCancel();
|
|
34
|
+
if (result) {
|
|
35
|
+
ty.showToast({
|
|
36
|
+
icon: 'none',
|
|
37
|
+
title: t('t-agent.input.asr.error.too-short')
|
|
38
|
+
});
|
|
39
|
+
}
|
|
38
40
|
return;
|
|
39
41
|
}
|
|
40
42
|
props.onConfirm();
|
|
@@ -50,10 +52,14 @@ const AsrInput = props => {
|
|
|
50
52
|
}, /*#__PURE__*/React.createElement(View, {
|
|
51
53
|
className: cx('t-agent-message-input-ptt', {
|
|
52
54
|
't-agent-message-input-ptt-active': active,
|
|
53
|
-
't-agent-message-input-ptt-cancel': cancel
|
|
55
|
+
't-agent-message-input-ptt-cancel': cancel,
|
|
56
|
+
't-agent-message-input-ptt-disabled': props.disabled
|
|
54
57
|
}),
|
|
55
58
|
onTouchStart: async event => {
|
|
56
59
|
var _event$touches$;
|
|
60
|
+
if (props.disabled) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
57
63
|
touchRef.current = {
|
|
58
64
|
startAt: Date.now(),
|
|
59
65
|
y: (_event$touches$ = event.touches[0]) === null || _event$touches$ === void 0 ? void 0 : _event$touches$.pageY
|
|
@@ -5,39 +5,111 @@ import "core-js/modules/esnext.iterator.map.js";
|
|
|
5
5
|
import "core-js/modules/web.dom-collections.iterator.js";
|
|
6
6
|
import '../index.less';
|
|
7
7
|
import { Button, View } from '@ray-js/components';
|
|
8
|
-
import React, { useRef, useState } from 'react';
|
|
8
|
+
import React, { useRef, useState, useMemo } from 'react';
|
|
9
9
|
import { Image, Input, ScrollView } from '@ray-js/ray';
|
|
10
10
|
import { AbortController, authorize } from '@ray-js/t-agent-plugin-aistream';
|
|
11
|
-
import { Emitter, EmitterEvent } from '@ray-js/t-agent';
|
|
11
|
+
import { Emitter, EmitterEvent, isAbortError } from '@ray-js/t-agent';
|
|
12
12
|
import cx from 'clsx';
|
|
13
13
|
import imageSvg from '../icons/image.svg';
|
|
14
14
|
import videoSvg from '../icons/video.svg';
|
|
15
|
+
import cameraSvg from '../icons/camera.svg';
|
|
15
16
|
import { useAttachmentInput, useChatAgent, useEmitEvent, useIsUnmounted, useOnEvent, useTranslate } from '../../hooks';
|
|
16
17
|
import AsrInput from './AsrInput';
|
|
17
|
-
import
|
|
18
|
+
import logger from '../../logger';
|
|
18
19
|
const AMPLITUDE_COUNT = 60;
|
|
20
|
+
var InputState = /*#__PURE__*/function (InputState) {
|
|
21
|
+
InputState["PENDING"] = "pending";
|
|
22
|
+
InputState["RECORDING"] = "recording";
|
|
23
|
+
InputState["ASR"] = "asr";
|
|
24
|
+
InputState["RESPONDING"] = "responding";
|
|
25
|
+
InputState["ABORTING"] = "aborting";
|
|
26
|
+
return InputState;
|
|
27
|
+
}(InputState || {});
|
|
28
|
+
var InputAction = /*#__PURE__*/function (InputAction) {
|
|
29
|
+
InputAction["SEND"] = "send";
|
|
30
|
+
InputAction["RECORD"] = "record";
|
|
31
|
+
InputAction["RECORD_TIMEOUT"] = "record_timeout";
|
|
32
|
+
InputAction["RECORD_CANCEL"] = "record_cancel";
|
|
33
|
+
InputAction["RECORD_CONFIRM"] = "record_confirm";
|
|
34
|
+
InputAction["ASR_END"] = "asr_end";
|
|
35
|
+
InputAction["ASR_ERROR"] = "asr_error";
|
|
36
|
+
InputAction["ABORT"] = "abort";
|
|
37
|
+
InputAction["TEXT_END"] = "text_end";
|
|
38
|
+
InputAction["ABORT_DONE"] = "abort_done";
|
|
39
|
+
InputAction["NETWORK_CHANGE"] = "network_change";
|
|
40
|
+
return InputAction;
|
|
41
|
+
}(InputAction || {});
|
|
42
|
+
const transitions = {
|
|
43
|
+
[InputState.PENDING]: {
|
|
44
|
+
[InputAction.SEND]: InputState.RESPONDING,
|
|
45
|
+
[InputAction.RECORD]: InputState.RECORDING,
|
|
46
|
+
[InputAction.NETWORK_CHANGE]: InputState.PENDING,
|
|
47
|
+
// 这个变更是为了兼容 pushInputBlocks 结束的场景,没有实际意义
|
|
48
|
+
[InputAction.TEXT_END]: InputState.PENDING
|
|
49
|
+
},
|
|
50
|
+
[InputState.RECORDING]: {
|
|
51
|
+
[InputAction.RECORD_TIMEOUT]: InputState.ASR,
|
|
52
|
+
[InputAction.NETWORK_CHANGE]: InputState.PENDING,
|
|
53
|
+
[InputAction.RECORD_CANCEL]: InputState.PENDING,
|
|
54
|
+
[InputAction.RECORD_CONFIRM]: InputState.ASR,
|
|
55
|
+
[InputAction.ABORT]: InputState.ABORTING,
|
|
56
|
+
[InputAction.ASR_ERROR]: InputState.PENDING
|
|
57
|
+
},
|
|
58
|
+
[InputState.ASR]: {
|
|
59
|
+
[InputAction.ASR_END]: InputState.RESPONDING,
|
|
60
|
+
[InputAction.ASR_ERROR]: InputState.PENDING,
|
|
61
|
+
[InputAction.ABORT]: InputState.ABORTING,
|
|
62
|
+
[InputAction.NETWORK_CHANGE]: InputState.PENDING
|
|
63
|
+
},
|
|
64
|
+
[InputState.RESPONDING]: {
|
|
65
|
+
[InputAction.ABORT]: InputState.ABORTING,
|
|
66
|
+
[InputAction.TEXT_END]: InputState.PENDING,
|
|
67
|
+
[InputAction.NETWORK_CHANGE]: InputState.PENDING
|
|
68
|
+
},
|
|
69
|
+
[InputState.ABORTING]: {
|
|
70
|
+
[InputAction.ABORT_DONE]: InputState.PENDING,
|
|
71
|
+
[InputAction.NETWORK_CHANGE]: InputState.PENDING
|
|
72
|
+
}
|
|
73
|
+
};
|
|
19
74
|
export default function MessageInputAIStream(props) {
|
|
20
75
|
const [moreOpen, setMoreOpen] = useState(false);
|
|
21
76
|
const t = useTranslate();
|
|
22
77
|
const [text, setText] = useState('');
|
|
23
|
-
const [
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
confirm: null,
|
|
27
|
-
cancel: null
|
|
78
|
+
const [state, setState] = useState({
|
|
79
|
+
current: InputState.PENDING,
|
|
80
|
+
payload: {}
|
|
28
81
|
});
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
82
|
+
const dispatchRef = useRef(null);
|
|
83
|
+
dispatchRef.current = (event, payload) => {
|
|
84
|
+
var _transitions$state$cu;
|
|
85
|
+
const next = (_transitions$state$cu = transitions[state.current]) === null || _transitions$state$cu === void 0 ? void 0 : _transitions$state$cu[event];
|
|
86
|
+
if (next) {
|
|
87
|
+
logger.debug('MessageInputAIStream', "".concat(state.current, " ==").concat(event, "==> ").concat(next));
|
|
88
|
+
setState({
|
|
89
|
+
current: next,
|
|
90
|
+
payload
|
|
91
|
+
});
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
logger.error('MessageInputAIStream', "".concat(state.current, " ==").concat(event, "==> X not allowed"));
|
|
95
|
+
return false;
|
|
34
96
|
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
97
|
+
const dispatch = (event, payload) => dispatchRef.current(event, payload);
|
|
98
|
+
const attachmentOptions = useMemo(() => {
|
|
99
|
+
const options = {
|
|
100
|
+
image: true,
|
|
101
|
+
video: true,
|
|
102
|
+
imageCount: 1,
|
|
103
|
+
videoCount: 1
|
|
104
|
+
};
|
|
105
|
+
if (props.attachment === false) {
|
|
106
|
+
options.image = false;
|
|
107
|
+
options.video = false;
|
|
108
|
+
} else if (typeof props.attachment === 'object') {
|
|
109
|
+
Object.assign(options, props.attachment);
|
|
110
|
+
}
|
|
111
|
+
return options;
|
|
112
|
+
}, [props.attachment]);
|
|
41
113
|
const attachmentInput = useAttachmentInput({
|
|
42
114
|
local: true
|
|
43
115
|
});
|
|
@@ -47,8 +119,6 @@ export default function MessageInputAIStream(props) {
|
|
|
47
119
|
setUploaded,
|
|
48
120
|
upload
|
|
49
121
|
} = attachmentInput;
|
|
50
|
-
const abortRef = useRef(null);
|
|
51
|
-
const [responding, setResponding] = useState(false);
|
|
52
122
|
const [mode, setMode] = useState('text');
|
|
53
123
|
const agent = useChatAgent();
|
|
54
124
|
const emitEvent = useEmitEvent();
|
|
@@ -57,8 +127,8 @@ export default function MessageInputAIStream(props) {
|
|
|
57
127
|
let {
|
|
58
128
|
online
|
|
59
129
|
} = _ref;
|
|
60
|
-
if (!online
|
|
61
|
-
|
|
130
|
+
if (!online) {
|
|
131
|
+
dispatch(InputAction.NETWORK_CHANGE, {});
|
|
62
132
|
}
|
|
63
133
|
});
|
|
64
134
|
const hasMore = attachmentOptions.image || attachmentOptions.video;
|
|
@@ -67,32 +137,31 @@ export default function MessageInputAIStream(props) {
|
|
|
67
137
|
if (!(inputBlocks !== null && inputBlocks !== void 0 && inputBlocks.length)) {
|
|
68
138
|
return;
|
|
69
139
|
}
|
|
70
|
-
setUploaded([]);
|
|
71
|
-
setText('');
|
|
72
|
-
setResponding(true);
|
|
73
140
|
const controller = new AbortController();
|
|
74
141
|
let abortResolve;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
promise: new Promise(resolve => {
|
|
78
|
-
abortResolve = resolve;
|
|
79
|
-
})
|
|
80
|
-
};
|
|
81
|
-
controller.signal.addEventListener('abort', () => {
|
|
82
|
-
var _abortRef$current;
|
|
83
|
-
if (((_abortRef$current = abortRef.current) === null || _abortRef$current === void 0 ? void 0 : _abortRef$current.controller) === controller) {
|
|
84
|
-
abortRef.current = null;
|
|
85
|
-
setResponding(false);
|
|
86
|
-
}
|
|
142
|
+
const abortPromise = new Promise(resolve => {
|
|
143
|
+
abortResolve = resolve;
|
|
87
144
|
});
|
|
145
|
+
if (!dispatch(InputAction.SEND, {
|
|
146
|
+
abort: async () => {
|
|
147
|
+
if (dispatch(InputAction.ABORT, {})) {
|
|
148
|
+
controller.abort('User abort');
|
|
149
|
+
await abortPromise;
|
|
150
|
+
dispatch(InputAction.ABORT_DONE, {});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
})) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
setUploaded([]);
|
|
157
|
+
setMoreOpen(false);
|
|
158
|
+
setText('');
|
|
88
159
|
try {
|
|
89
160
|
await agent.pushInputBlocks(inputBlocks, controller.signal);
|
|
90
|
-
if (controller.signal.aborted) {
|
|
91
|
-
abortResolve();
|
|
92
|
-
}
|
|
93
161
|
} finally {
|
|
94
|
-
|
|
95
|
-
|
|
162
|
+
abortResolve();
|
|
163
|
+
if (!isUnmounted() && !controller.signal.aborted) {
|
|
164
|
+
dispatch(InputAction.TEXT_END, {});
|
|
96
165
|
}
|
|
97
166
|
}
|
|
98
167
|
};
|
|
@@ -100,43 +169,16 @@ export default function MessageInputAIStream(props) {
|
|
|
100
169
|
let {
|
|
101
170
|
blocks
|
|
102
171
|
} = _ref2;
|
|
103
|
-
if (uploading
|
|
172
|
+
if (uploading) {
|
|
104
173
|
return;
|
|
105
174
|
}
|
|
106
|
-
|
|
107
|
-
setMoreOpen(false);
|
|
108
|
-
setUploaded([]);
|
|
109
|
-
setResponding(true);
|
|
110
|
-
const controller = new AbortController();
|
|
111
|
-
let abortResolve;
|
|
112
|
-
abortRef.current = {
|
|
113
|
-
controller,
|
|
114
|
-
promise: new Promise(resolve => {
|
|
115
|
-
abortResolve = resolve;
|
|
116
|
-
})
|
|
117
|
-
};
|
|
118
|
-
controller.signal.addEventListener('abort', () => {
|
|
119
|
-
if (abortRef.current.controller === controller) {
|
|
120
|
-
abortRef.current = null;
|
|
121
|
-
}
|
|
122
|
-
setResponding(false);
|
|
123
|
-
});
|
|
124
|
-
try {
|
|
125
|
-
await agent.pushInputBlocks(blocks, controller.signal);
|
|
126
|
-
if (controller.signal.aborted) {
|
|
127
|
-
abortResolve();
|
|
128
|
-
}
|
|
129
|
-
} finally {
|
|
130
|
-
if (!isUnmounted()) {
|
|
131
|
-
setResponding(false);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
175
|
+
send(blocks);
|
|
134
176
|
});
|
|
135
177
|
useOnEvent('setInputBlocks', async _ref3 => {
|
|
136
178
|
let {
|
|
137
179
|
blocks
|
|
138
180
|
} = _ref3;
|
|
139
|
-
if (uploading ||
|
|
181
|
+
if (uploading || state.current !== InputState.PENDING) {
|
|
140
182
|
return;
|
|
141
183
|
}
|
|
142
184
|
if (mode !== 'text') {
|
|
@@ -162,9 +204,10 @@ export default function MessageInputAIStream(props) {
|
|
|
162
204
|
});
|
|
163
205
|
}
|
|
164
206
|
};
|
|
165
|
-
useSleep();
|
|
166
207
|
let container;
|
|
167
|
-
const canSend = text.trim().length &&
|
|
208
|
+
const canSend = text.trim().length && state.current === InputState.PENDING && !attachmentInput.uploading;
|
|
209
|
+
const canAbort = state.current === InputState.RESPONDING || state.current === InputState.ASR;
|
|
210
|
+
const recordingFlagRef = useRef(false);
|
|
168
211
|
if (mode === 'text') {
|
|
169
212
|
container = /*#__PURE__*/React.createElement(View, {
|
|
170
213
|
className: "t-agent-message-input-text-bar"
|
|
@@ -216,15 +259,14 @@ export default function MessageInputAIStream(props) {
|
|
|
216
259
|
className: cx('t-agent-message-input-button', {
|
|
217
260
|
't-agent-message-input-button-more': isMore,
|
|
218
261
|
't-agent-message-input-button-more-open': moreOpen && isMore,
|
|
219
|
-
't-agent-message-input-button-send': !isMore && !
|
|
220
|
-
't-agent-message-input-button-stop':
|
|
262
|
+
't-agent-message-input-button-send': !isMore && !canAbort,
|
|
263
|
+
't-agent-message-input-button-stop': canAbort
|
|
221
264
|
}),
|
|
222
265
|
"data-testid": "t-agent-message-input-button-main",
|
|
223
266
|
onClick: async () => {
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
267
|
+
if (canAbort) {
|
|
268
|
+
var _state$payload$abort, _state$payload;
|
|
269
|
+
await ((_state$payload$abort = (_state$payload = state.payload).abort) === null || _state$payload$abort === void 0 ? void 0 : _state$payload$abort.call(_state$payload));
|
|
228
270
|
} else if (isMore) {
|
|
229
271
|
openMoreClick();
|
|
230
272
|
} else if (canSend) {
|
|
@@ -237,92 +279,144 @@ export default function MessageInputAIStream(props) {
|
|
|
237
279
|
}));
|
|
238
280
|
} else {
|
|
239
281
|
container = /*#__PURE__*/React.createElement(AsrInput, {
|
|
240
|
-
responding:
|
|
241
|
-
disabled:
|
|
282
|
+
responding: canAbort,
|
|
283
|
+
disabled: state.current === InputState.ABORTING,
|
|
242
284
|
amplitudeCount: AMPLITUDE_COUNT,
|
|
243
|
-
recording:
|
|
285
|
+
recording: state.current === InputState.RECORDING,
|
|
244
286
|
onRecord: async () => {
|
|
287
|
+
logger.debug('MessageInputAIStream', 'AsrInput onRecord');
|
|
245
288
|
if (attachmentInput.uploading) {
|
|
246
289
|
return;
|
|
247
290
|
}
|
|
248
|
-
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
291
|
+
recordingFlagRef.current = true;
|
|
292
|
+
if (canAbort) {
|
|
293
|
+
logger.debug('MessageInputAIStream', 'AsrInput onRecord canAbort');
|
|
294
|
+
await state.payload.abort();
|
|
295
|
+
logger.debug('MessageInputAIStream', 'AsrInput onRecord canAbort done');
|
|
253
296
|
}
|
|
297
|
+
if (!recordingFlagRef.current) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
recordingFlagRef.current = false;
|
|
301
|
+
// 有可能在中断完成前就触发了录音,所以需要检查一下状态
|
|
302
|
+
// 中断完成后,再检查一次状态,如果取消了,那么不再继续录音
|
|
303
|
+
|
|
254
304
|
const emitter = new Emitter();
|
|
255
|
-
|
|
256
|
-
|
|
305
|
+
emitter.addEventListener('error', async error => {
|
|
306
|
+
logger.error('MessageInputAIStream', 'AsrInput error', error);
|
|
307
|
+
if (dispatch(InputAction.ASR_ERROR, {})) {
|
|
308
|
+
var _error$detail;
|
|
309
|
+
if (((_error$detail = error.detail) === null || _error$detail === void 0 ? void 0 : _error$detail.name) === 'AsrEmptyError') {
|
|
310
|
+
ty.showToast({
|
|
311
|
+
icon: 'error',
|
|
312
|
+
title: t('t-agent.input.asr.error.empty')
|
|
313
|
+
});
|
|
314
|
+
} else {
|
|
315
|
+
ty.showToast({
|
|
316
|
+
icon: 'error',
|
|
317
|
+
title: t('t-agent.input.asr.error.unknown')
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
emitter.addEventListener('finish', () => {
|
|
323
|
+
dispatch(InputAction.ASR_END, {
|
|
324
|
+
abort
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
const controller = new AbortController();
|
|
328
|
+
let abortResolve;
|
|
329
|
+
const abortPromise = new Promise(resolve => {
|
|
330
|
+
abortResolve = resolve;
|
|
331
|
+
});
|
|
332
|
+
const abort = async () => {
|
|
333
|
+
if (dispatch(InputAction.ABORT, {})) {
|
|
334
|
+
controller.abort('User abort');
|
|
335
|
+
await abortPromise;
|
|
336
|
+
dispatch(InputAction.ABORT_DONE, {});
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
const recordTimeOutId = setTimeout(async () => {
|
|
340
|
+
if (dispatch(InputAction.RECORD_TIMEOUT, {
|
|
341
|
+
abort
|
|
342
|
+
})) {
|
|
343
|
+
clearTimeout(recordTimeOutId);
|
|
344
|
+
emitter.dispatchEvent(new EmitterEvent('confirm'));
|
|
257
345
|
ty.showToast({
|
|
258
346
|
icon: 'none',
|
|
259
347
|
title: t('t-agent.input.asr.error.timeout')
|
|
260
348
|
});
|
|
261
|
-
r.confirm();
|
|
262
349
|
}
|
|
263
350
|
}, props.maxAudioMs || 30000);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
});
|
|
277
|
-
},
|
|
278
|
-
cancel: () => {
|
|
279
|
-
r.recording = false;
|
|
280
|
-
clearTimeout(id);
|
|
281
|
-
emitter.dispatchEvent(new EmitterEvent('cancel'));
|
|
282
|
-
setRecord({
|
|
283
|
-
startAt: 0,
|
|
284
|
-
recording: false,
|
|
285
|
-
confirm: null,
|
|
286
|
-
cancel: null
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
setRecord(r);
|
|
291
|
-
emitter.addEventListener('error', () => {
|
|
292
|
-
ty.showToast({
|
|
293
|
-
icon: 'error',
|
|
294
|
-
title: t('t-agent.input.asr.error.empty')
|
|
295
|
-
});
|
|
296
|
-
setRecord({
|
|
297
|
-
startAt: 0,
|
|
298
|
-
recording: false,
|
|
299
|
-
confirm: null,
|
|
300
|
-
cancel: null
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
await send([...attachmentInput.blocks, {
|
|
351
|
+
if (!dispatch(InputAction.RECORD, {
|
|
352
|
+
emitter,
|
|
353
|
+
recordTimeOutId,
|
|
354
|
+
recordStartAt: Date.now(),
|
|
355
|
+
abort
|
|
356
|
+
})) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
setUploaded([]);
|
|
360
|
+
setMoreOpen(false);
|
|
361
|
+
setText('');
|
|
362
|
+
const inputBlocks = [...attachmentInput.blocks, {
|
|
304
363
|
type: 'audio',
|
|
305
364
|
audio_emitter: emitter,
|
|
306
365
|
amplitude_count: AMPLITUDE_COUNT
|
|
307
|
-
}]
|
|
366
|
+
}];
|
|
367
|
+
try {
|
|
368
|
+
logger.debug('MessageInputAIStream', 'pushInputBlocks start');
|
|
369
|
+
await agent.pushInputBlocks(inputBlocks, controller.signal);
|
|
370
|
+
logger.debug('MessageInputAIStream', 'pushInputBlocks end');
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (!isAbortError(error)) {
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
} finally {
|
|
376
|
+
abortResolve();
|
|
377
|
+
if (!isUnmounted() && !controller.signal.aborted) {
|
|
378
|
+
dispatch(InputAction.TEXT_END, {});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
308
381
|
},
|
|
309
|
-
onConfirm: () => {
|
|
310
|
-
|
|
311
|
-
|
|
382
|
+
onConfirm: async () => {
|
|
383
|
+
logger.debug('MessageInputAIStream', 'AsrInput onConfirm');
|
|
384
|
+
recordingFlagRef.current = false;
|
|
385
|
+
if (state.current === InputState.ABORTING) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const payload = state.payload;
|
|
389
|
+
if (dispatch(InputAction.RECORD_CONFIRM, {
|
|
390
|
+
abort: payload.abort
|
|
391
|
+
})) {
|
|
392
|
+
clearTimeout(payload.recordTimeOutId);
|
|
393
|
+
payload.emitter.dispatchEvent(new EmitterEvent('confirm'));
|
|
394
|
+
}
|
|
312
395
|
},
|
|
313
|
-
onCancel: () => {
|
|
314
|
-
|
|
315
|
-
|
|
396
|
+
onCancel: async () => {
|
|
397
|
+
logger.debug('MessageInputAIStream', 'AsrInput onCancel');
|
|
398
|
+
recordingFlagRef.current = false;
|
|
399
|
+
if (state.current === InputState.ABORTING) {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
const payload = state.payload;
|
|
403
|
+
if (dispatch(InputAction.RECORD_CANCEL, {})) {
|
|
404
|
+
clearTimeout(payload.recordTimeOutId);
|
|
405
|
+
logger.debug('MessageInputAIStream', 'AsrInput onCancel send cancel');
|
|
406
|
+
payload.emitter.dispatchEvent(new EmitterEvent('cancel'));
|
|
407
|
+
await payload.abort();
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
return false;
|
|
316
411
|
},
|
|
317
412
|
onBack: () => {
|
|
318
413
|
setText('');
|
|
319
414
|
setMode('text');
|
|
320
415
|
setMoreOpen(false);
|
|
321
416
|
},
|
|
322
|
-
onAbort: () => {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
417
|
+
onAbort: async () => {
|
|
418
|
+
var _state$payload$abort2, _state$payload2;
|
|
419
|
+
await ((_state$payload$abort2 = (_state$payload2 = state.payload).abort) === null || _state$payload$abort2 === void 0 ? void 0 : _state$payload$abort2.call(_state$payload2));
|
|
326
420
|
}
|
|
327
421
|
});
|
|
328
422
|
}
|
|
@@ -378,7 +472,7 @@ export default function MessageInputAIStream(props) {
|
|
|
378
472
|
className: "t-agent-message-input-panel ".concat(moreOpen ? '' : 't-agent-message-input-panel-close')
|
|
379
473
|
}, /*#__PURE__*/React.createElement(View, {
|
|
380
474
|
className: "t-agent-message-input-panel-content"
|
|
381
|
-
}, attachmentOptions.image && /*#__PURE__*/React.createElement(Button, {
|
|
475
|
+
}, (attachmentOptions === null || attachmentOptions === void 0 ? void 0 : attachmentOptions.image) && /*#__PURE__*/React.createElement(Button, {
|
|
382
476
|
className: "t-agent-message-input-panel-button",
|
|
383
477
|
onClick: async () => {
|
|
384
478
|
try {
|
|
@@ -389,7 +483,7 @@ export default function MessageInputAIStream(props) {
|
|
|
389
483
|
});
|
|
390
484
|
return;
|
|
391
485
|
}
|
|
392
|
-
await upload('
|
|
486
|
+
await upload('album', 1);
|
|
393
487
|
} catch (e) {
|
|
394
488
|
ty.showToast({
|
|
395
489
|
icon: 'error',
|
|
@@ -401,7 +495,30 @@ export default function MessageInputAIStream(props) {
|
|
|
401
495
|
className: "t-agent-message-input-panel-button-icon"
|
|
402
496
|
}, /*#__PURE__*/React.createElement(Image, {
|
|
403
497
|
src: imageSvg
|
|
404
|
-
}))), attachmentOptions.
|
|
498
|
+
}))), (attachmentOptions === null || attachmentOptions === void 0 ? void 0 : attachmentOptions.image) && /*#__PURE__*/React.createElement(Button, {
|
|
499
|
+
className: "t-agent-message-input-panel-button",
|
|
500
|
+
onClick: async () => {
|
|
501
|
+
try {
|
|
502
|
+
if (uploaded.filter(f => f.type === 'image').length >= (attachmentOptions.imageCount || 1)) {
|
|
503
|
+
ty.showToast({
|
|
504
|
+
icon: 'none',
|
|
505
|
+
title: t('t-agent.input.upload.image.max-reached')
|
|
506
|
+
});
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
await upload('camera', 1);
|
|
510
|
+
} catch (e) {
|
|
511
|
+
ty.showToast({
|
|
512
|
+
icon: 'error',
|
|
513
|
+
title: t('t-agent.input.upload.failed')
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}, /*#__PURE__*/React.createElement(View, {
|
|
518
|
+
className: "t-agent-message-input-panel-button-icon"
|
|
519
|
+
}, /*#__PURE__*/React.createElement(Image, {
|
|
520
|
+
src: cameraSvg
|
|
521
|
+
}))), (attachmentOptions === null || attachmentOptions === void 0 ? void 0 : attachmentOptions.video) && /*#__PURE__*/React.createElement(Button, {
|
|
405
522
|
className: "t-agent-message-input-panel-button",
|
|
406
523
|
onClick: async () => {
|
|
407
524
|
try {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="20" height="18" viewBox="0 0 20 18"><g><path d="M7.82843,2L5.82843,4L2,4L2,16L18,16L18,4L14.1716,4L12.1716,2L7.82843,2ZM7,0L13,0L15,2L19,2C19.5523,2,20,2.44772,20,3L20,17C20,17.5523,19.5523,18,19,18L1,18C0.44772,18,0,17.5523,0,17L0,3C0,2.44772,0.44772,2,1,2L5,2L7,0ZM10,15C6.96243,15,4.5,12.5376,4.5,9.5C4.5,6.46243,6.96243,4,10,4C13.0376,4,15.5,6.46243,15.5,9.5C15.5,12.5376,13.0376,15,10,15ZM10,13C11.933,13,13.5,11.433,13.5,9.5C13.5,7.567,11.933,6,10,6C8.067,6,6.5,7.567,6.5,9.5C6.5,11.433,8.067,13,10,13Z" fill="#3D3D3D" fill-opacity="1"/></g></svg>
|
|
@@ -8,7 +8,6 @@ import { View } from '@ray-js/components';
|
|
|
8
8
|
import React, { useMemo, useRef, useState } from 'react';
|
|
9
9
|
import { ScrollView } from '@ray-js/ray';
|
|
10
10
|
import { generateId } from '@ray-js/t-agent';
|
|
11
|
-
import { isVersionMatch } from '@ray-js/t-agent-plugin-aistream';
|
|
12
11
|
import MessageRender from '../MessageRender';
|
|
13
12
|
import { useAgentMessage, useAgentSessionValue, useOnEvent } from '../hooks';
|
|
14
13
|
import { useDebouncedFn } from '../hooks/useDebouncedFn';
|
|
@@ -40,7 +39,7 @@ export default function MessageList(props) {
|
|
|
40
39
|
} = systemInfo;
|
|
41
40
|
return {
|
|
42
41
|
// 在 2.27.2 版本之前,pageScrollTo 在某些场景不生效
|
|
43
|
-
scroll:
|
|
42
|
+
scroll: false
|
|
44
43
|
};
|
|
45
44
|
}, []);
|
|
46
45
|
const scroll = useDebouncedFn(animation => {
|
|
@@ -18,5 +18,5 @@ export declare function useAttachmentInput({ local }?: {
|
|
|
18
18
|
setUploaded: import("react").Dispatch<import("react").SetStateAction<UploadFile[]>>;
|
|
19
19
|
uploading: boolean;
|
|
20
20
|
loadBlocks: (blocks: InputBlock[], append?: boolean) => void;
|
|
21
|
-
upload: (type: 'image' | 'video', count?: any) => Promise<void>;
|
|
21
|
+
upload: (type: 'image' | 'video' | 'camera' | 'album', count?: any) => Promise<void>;
|
|
22
22
|
};
|
|
@@ -109,47 +109,80 @@ export function useAttachmentInput() {
|
|
|
109
109
|
} = useRenderOptions();
|
|
110
110
|
const upload = useCallback(async function (type) {
|
|
111
111
|
let count = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
|
|
112
|
-
if (type === 'image') {
|
|
112
|
+
if (type === 'image' || type === 'camera' || type === 'album') {
|
|
113
113
|
let file = [];
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
114
|
+
if (type === 'image') {
|
|
115
|
+
try {
|
|
116
|
+
const sourceType = await new Promise(resolve => {
|
|
117
|
+
ty.showActionSheet({
|
|
118
|
+
itemList: [t('t-agent.input.upload.source-type.camera'), t('t-agent.input.upload.source-type.album')],
|
|
119
|
+
success: _ref => {
|
|
120
|
+
let {
|
|
121
|
+
tapIndex
|
|
122
|
+
} = _ref;
|
|
123
|
+
if (tapIndex === 0) {
|
|
124
|
+
ty.authorize({
|
|
125
|
+
scope: 'scope.camera',
|
|
126
|
+
success() {
|
|
127
|
+
resolve('camera');
|
|
128
|
+
},
|
|
129
|
+
fail() {
|
|
130
|
+
ty.showToast({
|
|
131
|
+
title: t('t-agent.input.upload.source-type.camera.require-permission'),
|
|
132
|
+
icon: 'none'
|
|
133
|
+
});
|
|
134
|
+
resolve('');
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
} else if (tapIndex === 1) {
|
|
138
|
+
resolve('album');
|
|
139
|
+
} else {
|
|
140
|
+
resolve('');
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
fail() {
|
|
139
144
|
resolve('');
|
|
140
145
|
}
|
|
141
|
-
}
|
|
142
|
-
fail() {
|
|
143
|
-
resolve('');
|
|
144
|
-
}
|
|
146
|
+
});
|
|
145
147
|
});
|
|
146
|
-
|
|
147
|
-
|
|
148
|
+
if (!sourceType) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
file = await chooseImage(count, sourceType);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (type === 'camera') {
|
|
157
|
+
try {
|
|
158
|
+
const allowUseCamera = await new Promise(resolve => {
|
|
159
|
+
ty.authorize({
|
|
160
|
+
scope: 'scope.camera',
|
|
161
|
+
success() {
|
|
162
|
+
resolve(true);
|
|
163
|
+
},
|
|
164
|
+
fail() {
|
|
165
|
+
ty.showToast({
|
|
166
|
+
title: t('t-agent.input.upload.source-type.camera.require-permission'),
|
|
167
|
+
icon: 'none'
|
|
168
|
+
});
|
|
169
|
+
resolve(false);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
if (allowUseCamera) {
|
|
174
|
+
file = await chooseImage(count, 'camera');
|
|
175
|
+
}
|
|
176
|
+
} catch (err) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (type === 'album') {
|
|
181
|
+
try {
|
|
182
|
+
file = await chooseImage(count, 'album');
|
|
183
|
+
} catch (err) {
|
|
148
184
|
return;
|
|
149
185
|
}
|
|
150
|
-
file = await chooseImage(count, sourceType);
|
|
151
|
-
} catch (err) {
|
|
152
|
-
return;
|
|
153
186
|
}
|
|
154
187
|
await Promise.all(file.map(async _ref2 => {
|
|
155
188
|
let {
|
package/dist/i18n/strings.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ declare const _default: {
|
|
|
22
22
|
't-agent.input.asr.ptt': string;
|
|
23
23
|
't-agent.input.asr.error.too-short': string;
|
|
24
24
|
't-agent.input.asr.error.empty': string;
|
|
25
|
+
't-agent.input.asr.error.unknown': string;
|
|
25
26
|
't-agent.input.asr.error.timeout': string;
|
|
26
27
|
't-agent.message.feedback.success': string;
|
|
27
28
|
't-agent.message.bubble.aborted': string;
|
|
@@ -165,6 +166,7 @@ declare const _default: {
|
|
|
165
166
|
't-agent.input.asr.ptt': string;
|
|
166
167
|
't-agent.input.asr.error.too-short': string;
|
|
167
168
|
't-agent.input.asr.error.empty': string;
|
|
169
|
+
't-agent.input.asr.error.unknown': string;
|
|
168
170
|
't-agent.input.asr.error.timeout': string;
|
|
169
171
|
't-agent.message.feedback.success': string;
|
|
170
172
|
't-agent.message.bubble.aborted': string;
|
package/dist/i18n/strings.js
CHANGED
|
@@ -22,6 +22,7 @@ export default {
|
|
|
22
22
|
't-agent.input.asr.ptt': '按住说话',
|
|
23
23
|
't-agent.input.asr.error.too-short': '说话时间太短',
|
|
24
24
|
't-agent.input.asr.error.empty': '未能从语音中识别到文字',
|
|
25
|
+
't-agent.input.asr.error.unknown': '语音识别失败',
|
|
25
26
|
't-agent.input.asr.error.timeout': '语音识别已达时长限制,将直接发送',
|
|
26
27
|
't-agent.message.feedback.success': '反馈成功',
|
|
27
28
|
't-agent.message.bubble.aborted': '用户中断',
|
|
@@ -165,6 +166,7 @@ export default {
|
|
|
165
166
|
't-agent.input.asr.ptt': 'Press & hold to talk',
|
|
166
167
|
't-agent.input.asr.error.too-short': 'Speaking time too short',
|
|
167
168
|
't-agent.input.asr.error.empty': 'No text recognized from voice',
|
|
169
|
+
't-agent.input.asr.error.unknown': 'Text recognized failed',
|
|
168
170
|
't-agent.input.asr.error.timeout': 'Voice recognition has reached time limit, sending directly',
|
|
169
171
|
't-agent.message.feedback.success': 'Feedback Successful',
|
|
170
172
|
't-agent.message.bubble.aborted': 'User Aborted',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ray-js/t-agent-ui-ray",
|
|
3
|
-
"version": "0.2.0-beta-
|
|
3
|
+
"version": "0.2.0-beta-12",
|
|
4
4
|
"author": "Tuya.inc",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"@types/echarts": "^4.9.22",
|
|
41
41
|
"@types/markdown-it": "^14.1.1"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "a4e65a8f099f9ffa6e215ae005589c63f40587fe"
|
|
44
44
|
}
|