@ray-js/t-agent-ui-ray 0.0.8-beta-1 → 0.0.8-beta-2

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.
@@ -5,25 +5,20 @@ import "core-js/modules/web.dom-collections.iterator.js";
5
5
  import './index.less';
6
6
  import React, { useEffect, useMemo, useState } from 'react';
7
7
  import { View } from '@ray-js/components';
8
- import { getWebSocketStatusSync, SocketStatus } from '@ray-js/t-agent-plugin-assistant';
8
+ import { SocketStatus } from '@ray-js/t-agent-plugin-assistant';
9
+ import cx from 'clsx';
9
10
  import { ChatAgentContext, MessageContext, RenderContext } from '../contexts';
10
11
  import { defaultRenderOptions } from '../renderOption';
11
12
  import logger from '../logger';
12
13
  export default function ChatContainer(props) {
14
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
13
15
  const {
14
16
  createAgent,
15
17
  renderOptions = defaultRenderOptions,
16
18
  children,
17
- className = ''
19
+ className
18
20
  } = props;
19
21
  const [messages, setMessages] = useState([]);
20
- const [socketStatus, setSocketStatus] = useState(() => {
21
- try {
22
- return getWebSocketStatusSync().status;
23
- } catch (e) {
24
- return SocketStatus.WAITING;
25
- }
26
- });
27
22
  const [agent] = useState(() => {
28
23
  const agent = createAgent();
29
24
  if (!agent.plugins.ui) {
@@ -40,7 +35,9 @@ export default function ChatContainer(props) {
40
35
  emitEvent
41
36
  } = agent.plugins.ui;
42
37
  const offSocketStatusChange = agent.plugins.assistant.onSocketStatusChange(status => {
43
- setSocketStatus(status);
38
+ emitEvent('networkChange', {
39
+ online: status === SocketStatus.SUCCESS
40
+ });
44
41
  });
45
42
  const offMessageListInit = onEvent('messageListInit', _ref => {
46
43
  let {
@@ -71,7 +68,8 @@ export default function ChatContainer(props) {
71
68
  if (((_prev = prev[prev.length - 1]) === null || _prev === void 0 ? void 0 : _prev.id) === message.id) {
72
69
  // 是最后一条消息,滚动到最底部
73
70
  emitEvent('scrollToBottom', {
74
- animation: false
71
+ animation: false,
72
+ follow: true
75
73
  });
76
74
  }
77
75
  }
@@ -98,9 +96,9 @@ export default function ChatContainer(props) {
98
96
  const messageValue = useMemo(() => {
99
97
  return {
100
98
  messages,
101
- socketStatus
99
+ keyboardHeight
102
100
  };
103
- }, [messages, socketStatus]);
101
+ }, [messages, keyboardHeight]);
104
102
  useEffect(() => {
105
103
  logger.debug('ChatProvider agent.start');
106
104
  agent.start().then(() => {
@@ -112,8 +110,33 @@ export default function ChatContainer(props) {
112
110
  agent.dispose();
113
111
  };
114
112
  }, []);
113
+ useEffect(() => {
114
+ const show = _ref3 => {
115
+ let {
116
+ height
117
+ } = _ref3;
118
+ setKeyboardHeight(height);
119
+ };
120
+ const hide = () => {
121
+ setKeyboardHeight(0);
122
+ };
123
+ ty.onKeyboardWillShow(show);
124
+ ty.onKeyboardWillHide(hide);
125
+ ty.onKeyboardHeightChange(show);
126
+ return () => {
127
+ ty.offKeyboardWillShow(show);
128
+ ty.offKeyboardWillHide(hide);
129
+ ty.offKeyboardHeightChange(show);
130
+ };
131
+ }, []);
115
132
  return /*#__PURE__*/React.createElement(View, {
116
- className: "t-agent-chat-container ".concat(className || '')
133
+ style: {
134
+ // @ts-ignore
135
+ '--t-agent-chat-container-keyboard-height': "".concat(keyboardHeight, "px")
136
+ },
137
+ className: cx('t-agent-chat-container', className, {
138
+ 't-agent-chat-container-keyboard-show': keyboardHeight > 0
139
+ })
117
140
  }, /*#__PURE__*/React.createElement(ChatAgentContext.Provider, {
118
141
  value: agent
119
142
  }, /*#__PURE__*/React.createElement(RenderContext.Provider, {
@@ -28,11 +28,12 @@ export default function AsrInput(props) {
28
28
  }), /*#__PURE__*/React.createElement(View, {
29
29
  className: "t-agent-message-input-voice-bubble",
30
30
  "data-testid": "t-agent-message-input-asr-bubble"
31
- }, state === 'recording' ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, currentText), /*#__PURE__*/React.createElement(Text, {
31
+ }, state === 'recording' ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, currentText.endsWith(' ') ? /*#__PURE__*/React.createElement(React.Fragment, null, currentText, "\xA0") : currentText), /*#__PURE__*/React.createElement(Text, {
32
32
  className: "t-agent-message-input-voice-bubble-predicted"
33
33
  }, incomingText)) : /*#__PURE__*/React.createElement(Textarea, {
34
34
  "data-testid": "t-agent-message-input-asr-bubble-input",
35
35
  autoHeight: true,
36
+ maxLength: 2048,
36
37
  className: "t-agent-message-input-voice-bubble-input",
37
38
  value: text,
38
39
  onInput: event => setText(event.value)
@@ -56,8 +57,8 @@ export default function AsrInput(props) {
56
57
  className: "t-agent-message-input-button t-agent-message-input-button-voice-active",
57
58
  disabled: sendDisabled,
58
59
  onTouchStart: () => {
59
- press();
60
60
  closeMore();
61
+ press();
61
62
  },
62
63
  onClick: release,
63
64
  onTouchCancel: release,
@@ -6,7 +6,7 @@ import './index.less';
6
6
  import { Button, View } from '@ray-js/components';
7
7
  import React, { useState } from 'react';
8
8
  import { Image, Input, ScrollView } from '@ray-js/ray';
9
- import { Asr, SocketStatus } from '@ray-js/t-agent-plugin-assistant';
9
+ import { Asr } from '@ray-js/t-agent-plugin-assistant';
10
10
  import cx from 'clsx';
11
11
  import PrivateImage from '../PrivateImage';
12
12
  import imageSvg from './icons/image.svg';
@@ -31,8 +31,11 @@ export default function MessageInput(props) {
31
31
  const agent = useChatAgent();
32
32
  const emitEvent = useEmitEvent();
33
33
  const isUnmounted = useIsUnmounted();
34
- useOnEvent('updateSocketStatus', status => {
35
- if ((status === SocketStatus.FAILED || status === SocketStatus.CONNECTING) && responding) {
34
+ useOnEvent('networkChange', _ref => {
35
+ let {
36
+ online
37
+ } = _ref;
38
+ if (!online && responding) {
36
39
  setResponding(false);
37
40
  }
38
41
  });
@@ -53,10 +56,10 @@ export default function MessageInput(props) {
53
56
  }
54
57
  }
55
58
  };
56
- useOnEvent('sendMessage', async _ref => {
59
+ useOnEvent('sendMessage', async _ref2 => {
57
60
  let {
58
61
  blocks
59
- } = _ref;
62
+ } = _ref2;
60
63
  if (uploading || responding) {
61
64
  return;
62
65
  }
@@ -72,10 +75,10 @@ export default function MessageInput(props) {
72
75
  }
73
76
  }
74
77
  });
75
- useOnEvent('setInputBlocks', async _ref2 => {
78
+ useOnEvent('setInputBlocks', async _ref3 => {
76
79
  let {
77
80
  blocks
78
- } = _ref2;
81
+ } = _ref3;
79
82
  if (uploading || responding) {
80
83
  return;
81
84
  }
@@ -12,6 +12,10 @@
12
12
  padding-bottom: var(--t-agent-safe-bottom);
13
13
  }
14
14
 
15
+ .t-agent-chat-container-keyboard-show .t-agent-message-input {
16
+ padding-bottom: 0;
17
+ }
18
+
15
19
  .t-agent-message-input-container {
16
20
  border-top: 2rpx solid var(--t-agent-input-border-color);
17
21
  background: var(--app-B1);
@@ -261,7 +265,10 @@
261
265
  color: var(--app-M1-N1);
262
266
  font-size: 32rpx;
263
267
  transition: height 0.2s ease-in-out;
264
- white-space: pre-wrap;
268
+
269
+ text {
270
+ white-space: pre-wrap;
271
+ }
265
272
 
266
273
  &::after {
267
274
  content: '';
@@ -277,6 +284,10 @@
277
284
  }
278
285
  }
279
286
 
287
+ .t-agent-chat-container-keyboard-show .t-agent-message-input-voice-bubble {
288
+ bottom: 260rpx;
289
+ }
290
+
280
291
  .t-agent-message-input-voice-bubble-predicted {
281
292
  color: var(--app-M1-N3);
282
293
  }
@@ -4,7 +4,7 @@ import "core-js/modules/esnext.iterator.map.js";
4
4
  import "core-js/modules/web.dom-collections.iterator.js";
5
5
  import './index.less';
6
6
  import { View } from '@ray-js/components';
7
- import React, { useMemo, useState } from 'react';
7
+ import React, { useMemo, useRef, useState } from 'react';
8
8
  import { ScrollView } from '@ray-js/ray';
9
9
  import MessageRender from '../MessageRender';
10
10
  import { useAgentMessage, useOnEvent, useSleep } from '../hooks';
@@ -19,13 +19,19 @@ export default function MessageList(props) {
19
19
  const [scrollAnimation, setScrollAnimation] = useState(false);
20
20
  // 最大整数位数,用于强制滚动到底部,收到连续 scrollToBottom 时,每 10 毫秒更新一次
21
21
  const [scrollTop, setScrollTop] = useState(() => 1000000000000000 + Math.floor(Date.now() / 10));
22
+ const followNewMessageRef = useRef(true);
22
23
  const sleep = useSleep();
23
24
 
24
25
  // 强制滚动到底部
25
26
  useOnEvent('scrollToBottom', _ref => {
26
27
  let {
27
- animation
28
+ animation,
29
+ follow
28
30
  } = _ref;
31
+ // 如果发送的跟随新消息的滚动,判断当前是不是滚动到了底部,如果没有滚动到底部,不执行滚动
32
+ if (follow && !followNewMessageRef.current) {
33
+ return;
34
+ }
29
35
  sleep(100).then(() => {
30
36
  setScrollAnimation(!!animation);
31
37
  const top = 1000000000000000 + Math.floor(Date.now() / 10);
@@ -46,7 +52,11 @@ export default function MessageList(props) {
46
52
  scrollTop: scrollTop,
47
53
  refresherTriggered: false,
48
54
  enableFlex: true,
49
- scrollY: true
55
+ scrollY: true,
56
+ onScroll: event => {
57
+ // 使用了 flex-direction: column-reverse,所以滚动到顶部时,scrollTop = 0
58
+ followNewMessageRef.current = event.detail.scrollTop > -10;
59
+ }
50
60
  }, /*#__PURE__*/React.createElement(View, {
51
61
  className: "t-agent-message-list-padding"
52
62
  }), reversed.map((msg, index) => {
@@ -1,10 +1,10 @@
1
1
  /// <reference types="react" />
2
2
  import { ChatAgent, ChatMessageObject, UIPlugin } from '@ray-js/t-agent';
3
- import { AssistantPlugin, SocketStatus } from '@ray-js/t-agent-plugin-assistant';
3
+ import { AssistantPlugin } from '@ray-js/t-agent-plugin-assistant';
4
4
  import { RenderOptions, TileProps } from './types';
5
5
  export declare const MessageContext: import("react").Context<{
6
6
  messages: ChatMessageObject[];
7
- socketStatus: SocketStatus;
7
+ keyboardHeight: number;
8
8
  }>;
9
9
  export declare const RenderContext: import("react").Context<RenderOptions>;
10
10
  export declare const ChatAgentContext: import("react").Context<ChatAgent<UIPlugin & AssistantPlugin>>;
package/dist/contexts.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import { createContext } from 'react';
2
- import { SocketStatus } from '@ray-js/t-agent-plugin-assistant';
3
2
  export const MessageContext = /*#__PURE__*/createContext({
4
3
  messages: [],
5
- socketStatus: SocketStatus.WAITING
4
+ keyboardHeight: 0
6
5
  });
7
6
  export const RenderContext = /*#__PURE__*/createContext({
8
7
  renderTileAs: () => null,
@@ -2,7 +2,7 @@ import { TTTAction } from '@ray-js/t-agent-plugin-assistant';
2
2
  export declare const useChatAgent: () => import("@ray-js/t-agent").ChatAgent<import("@ray-js/t-agent").UIPlugin & import("@ray-js/t-agent-plugin-assistant").AssistantPlugin>;
3
3
  export declare const useAgentMessage: () => {
4
4
  messages: import("@ray-js/t-agent").ChatMessageObject[];
5
- socketStatus: import("@ray-js/t-agent-plugin-assistant").SocketStatus;
5
+ keyboardHeight: number;
6
6
  };
7
7
  export declare const useRenderOptions: () => import("..").RenderOptions;
8
8
  export declare const useOnEvent: (eventName: string, callback: (details: any) => void) => void;
@@ -1,6 +1,7 @@
1
1
  import "core-js/modules/web.dom-collections.iterator.js";
2
- import { useRef, useState } from 'react';
2
+ import { useEffect, useRef, useState } from 'react';
3
3
  import { Asr, AsrDetectResultState } from '@ray-js/t-agent-plugin-assistant';
4
+ import { useIsUnmounted } from './useIsUnmounted';
4
5
  export let AsrErrorCode = /*#__PURE__*/function (AsrErrorCode) {
5
6
  AsrErrorCode[AsrErrorCode["SHORT_TIME"] = 0] = "SHORT_TIME";
6
7
  return AsrErrorCode;
@@ -47,8 +48,18 @@ export function useAsrInput(options) {
47
48
  const asrRef = useRef(null);
48
49
  const [currentText, setCurrentText] = useState('');
49
50
  const [incomingText, setIncomingText] = useState('');
51
+ const isUnmounted = useIsUnmounted();
50
52
  const [state, setState] = useState('init');
51
53
  const startAt = useRef(0);
54
+ useEffect(() => {
55
+ return () => {
56
+ if (asrRef.current) {
57
+ asrRef.current.stop();
58
+ asrRef.current = null;
59
+ Asr.dispose();
60
+ }
61
+ };
62
+ }, []);
52
63
  return {
53
64
  currentText,
54
65
  incomingText,
@@ -61,21 +72,31 @@ export function useAsrInput(options) {
61
72
  },
62
73
  press: async () => {
63
74
  setState('recording');
64
- startAt.current = Date.now();
75
+
76
+ // 保存当前文本,用于恢复
65
77
  const initial = text;
66
- let last = initial;
78
+
79
+ // 上次识别的结果
80
+ let last = '';
67
81
  setCurrentText(initial);
68
82
  setIncomingText('');
69
83
 
70
84
  // 开始录音时
71
- const asr = Asr.detect(async res => {
72
- if (res.state === AsrDetectResultState.STARTING || res.state === AsrDetectResultState.END) {
73
- const full = initial + res.text;
74
- const incoming = full.slice(last.length);
75
- setIncomingText(incoming);
76
- setCurrentText(last);
77
- setText(full);
78
- last = full;
85
+ const asr = Asr.detect(res => {
86
+ if (isUnmounted()) {
87
+ return;
88
+ }
89
+ if (res.state === AsrDetectResultState.MID || res.state === AsrDetectResultState.END) {
90
+ if (res.text.startsWith(last)) {
91
+ const incoming = res.text.slice(last.length);
92
+ setIncomingText(incoming);
93
+ setCurrentText(initial + last);
94
+ last = res.text;
95
+ } else {
96
+ setIncomingText(res.text);
97
+ setCurrentText(initial);
98
+ }
99
+ setText(initial + res.text);
79
100
  }
80
101
  if (res.state === AsrDetectResultState.ERROR) {
81
102
  onError(new AsrError(res.errorCode));
@@ -83,6 +104,7 @@ export function useAsrInput(options) {
83
104
  });
84
105
  try {
85
106
  await asr.start();
107
+ startAt.current = Date.now();
86
108
  asrRef.current = asr;
87
109
  } catch (error) {
88
110
  onError(error);
@@ -92,15 +114,14 @@ export function useAsrInput(options) {
92
114
  if (state !== 'recording') {
93
115
  return;
94
116
  }
117
+ if (!text && startAt.current - Date.now() < 400) {
118
+ onError(new AsrError(AsrErrorCode.SHORT_TIME));
119
+ }
95
120
  if (text) {
96
121
  setState('pending');
97
122
  } else {
98
- onError(new AsrError(AsrErrorCode.SHORT_TIME));
99
123
  setState('init');
100
124
  }
101
- if (Date.now() - startAt.current < 500 && text.length === 0) {
102
- onError(new AsrError(AsrErrorCode.SHORT_TIME));
103
- }
104
125
  if (asrRef.current) {
105
126
  await asrRef.current.stop();
106
127
  asrRef.current = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ray-js/t-agent-ui-ray",
3
- "version": "0.0.8-beta-1",
3
+ "version": "0.0.8-beta-2",
4
4
  "author": "Tuya.inc",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -41,5 +41,5 @@
41
41
  "@types/echarts": "^4.9.22",
42
42
  "@types/markdown-it": "^14.1.1"
43
43
  },
44
- "gitHead": "16c52814d110e10b2e108915675d90e2b023df5e"
44
+ "gitHead": "0fe6e7c1daafbeffd1394a7ee85ab4ae86d8f93e"
45
45
  }