@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.
@@ -6,7 +6,7 @@ interface Props {
6
6
  disabled?: boolean;
7
7
  onConfirm: () => void;
8
8
  onRecord: () => void;
9
- onCancel: () => void;
9
+ onCancel: () => Promise<boolean>;
10
10
  onBack: () => void;
11
11
  onAbort: () => void;
12
12
  }
@@ -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
- ty.showToast({
35
- icon: 'none',
36
- title: t('t-agent.input.asr.error.too-short')
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 { useSleep } from '../../hooks/useSleep';
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 [record, setRecord] = useState({
24
- startAt: 0,
25
- recording: false,
26
- confirm: null,
27
- cancel: null
78
+ const [state, setState] = useState({
79
+ current: InputState.PENDING,
80
+ payload: {}
28
81
  });
29
- const attachmentOptions = {
30
- image: true,
31
- video: true,
32
- imageCount: 1,
33
- videoCount: 1
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
- if (props.attachment === false) {
36
- attachmentOptions.image = false;
37
- attachmentOptions.video = false;
38
- } else if (typeof props.attachment === 'object') {
39
- Object.assign(attachmentOptions, props.attachment);
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 && responding) {
61
- setResponding(false);
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
- abortRef.current = {
76
- controller,
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
- if (!isUnmounted()) {
95
- setResponding(false);
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 || responding) {
172
+ if (uploading) {
104
173
  return;
105
174
  }
106
- setText('');
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 || responding) {
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 && !responding && !attachmentInput.uploading;
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 && !responding,
220
- 't-agent-message-input-button-stop': responding
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 (responding) {
225
- if (abortRef.current) {
226
- abortRef.current.controller.abort('User abort');
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: responding,
241
- disabled: responding,
282
+ responding: canAbort,
283
+ disabled: state.current === InputState.ABORTING,
242
284
  amplitudeCount: AMPLITUDE_COUNT,
243
- recording: record.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
- const abort = abortRef.current;
249
- if (responding && abort) {
250
- abort.controller.abort('User abort');
251
- // 中断后等待上次结束再开始
252
- await abort.promise;
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
- const id = setTimeout(() => {
256
- if (r.recording) {
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
- const r = {
265
- startAt: Date.now(),
266
- recording: true,
267
- confirm: () => {
268
- r.recording = false;
269
- clearTimeout(id);
270
- emitter.dispatchEvent(new EmitterEvent('confirm'));
271
- setRecord({
272
- startAt: 0,
273
- recording: false,
274
- confirm: null,
275
- cancel: null
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
- var _record$confirm;
311
- (_record$confirm = record.confirm) === null || _record$confirm === void 0 || _record$confirm.call(record);
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
- var _record$cancel;
315
- (_record$cancel = record.cancel) === null || _record$cancel === void 0 || _record$cancel.call(record);
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
- if (responding && abortRef.current) {
324
- abortRef.current.controller.abort('User abort');
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('image', 1);
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.video && /*#__PURE__*/React.createElement(Button, {
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>
@@ -349,6 +349,13 @@
349
349
  text-align: center;
350
350
  }
351
351
 
352
+ .t-agent-message-input-ptt-disabled {
353
+ .t-agent-message-input-ptt-text {
354
+ color: var(--app-B1-N3);
355
+ }
356
+ }
357
+
358
+
352
359
  .t-agent-message-input-ptt-overlay-cancel {
353
360
 
354
361
  }
@@ -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: containerVersion && isVersionMatch('>=2.27.2', containerVersion)
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
- try {
115
- const sourceType = await new Promise(resolve => {
116
- ty.showActionSheet({
117
- itemList: [t('t-agent.input.upload.source-type.camera'), t('t-agent.input.upload.source-type.album')],
118
- success: _ref => {
119
- let {
120
- tapIndex
121
- } = _ref;
122
- if (tapIndex === 0) {
123
- ty.authorize({
124
- scope: 'scope.camera',
125
- success() {
126
- resolve('camera');
127
- },
128
- fail() {
129
- ty.showToast({
130
- title: t('t-agent.input.upload.source-type.camera.require-permission'),
131
- icon: 'none'
132
- });
133
- resolve('');
134
- }
135
- });
136
- } else if (tapIndex === 1) {
137
- resolve('album');
138
- } else {
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
- if (!sourceType) {
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 {
@@ -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;
@@ -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',
@@ -49,6 +49,7 @@
49
49
  display: -webkit-box;
50
50
  -webkit-line-clamp: 2; /* 控制显示行数 */
51
51
  -webkit-box-orient: vertical;
52
+ line-height: 33rpx;
52
53
  height: 66rpx;
53
54
 
54
55
 
@@ -8,7 +8,6 @@ export default function ImageTile(props) {
8
8
  const {
9
9
  src
10
10
  } = props.tile.data;
11
- console.log('props.tile.data', props.tile.data);
12
11
  const {
13
12
  getStaticResourceBizType
14
13
  } = useRenderOptions();
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-10",
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": "a17ba630a9ec71afe6a0fc46c427f664d46acf82"
43
+ "gitHead": "a4e65a8f099f9ffa6e215ae005589c63f40587fe"
44
44
  }