ipanel-rnapp-ajb-base 1.0.41 → 1.0.42

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.
Files changed (75) hide show
  1. package/app/.local_dev/localMode.js +3 -0
  2. package/app/App.js +15 -1
  3. package/app/api/ipanelCloudApi.js +58 -4
  4. package/app/assets/img/adjust_scene_link.jpg +0 -0
  5. package/app/assets/img/moji_weather/W0.png +0 -0
  6. package/app/assets/img/moji_weather/W1.png +0 -0
  7. package/app/assets/img/moji_weather/W10.png +0 -0
  8. package/app/assets/img/moji_weather/W13.png +0 -0
  9. package/app/assets/img/moji_weather/W14.png +0 -0
  10. package/app/assets/img/moji_weather/W15.png +0 -0
  11. package/app/assets/img/moji_weather/W16.png +0 -0
  12. package/app/assets/img/moji_weather/W17.png +0 -0
  13. package/app/assets/img/moji_weather/W18.png +0 -0
  14. package/app/assets/img/moji_weather/W19.png +0 -0
  15. package/app/assets/img/moji_weather/W2.png +0 -0
  16. package/app/assets/img/moji_weather/W20.png +0 -0
  17. package/app/assets/img/moji_weather/W29.png +0 -0
  18. package/app/assets/img/moji_weather/W3.png +0 -0
  19. package/app/assets/img/moji_weather/W30.png +0 -0
  20. package/app/assets/img/moji_weather/W31.png +0 -0
  21. package/app/assets/img/moji_weather/W32.png +0 -0
  22. package/app/assets/img/moji_weather/W33.png +0 -0
  23. package/app/assets/img/moji_weather/W34.png +0 -0
  24. package/app/assets/img/moji_weather/W35.png +0 -0
  25. package/app/assets/img/moji_weather/W36.png +0 -0
  26. package/app/assets/img/moji_weather/W4.png +0 -0
  27. package/app/assets/img/moji_weather/W44.png +0 -0
  28. package/app/assets/img/moji_weather/W45.png +0 -0
  29. package/app/assets/img/moji_weather/W46.png +0 -0
  30. package/app/assets/img/moji_weather/W5.png +0 -0
  31. package/app/assets/img/moji_weather/W6.png +0 -0
  32. package/app/assets/img/moji_weather/W7.png +0 -0
  33. package/app/assets/img/moji_weather/W8.png +0 -0
  34. package/app/assets/img/moji_weather/W9.png +0 -0
  35. package/app/assets/img/voice-white.png +0 -0
  36. package/app/assets/img/voice.png +0 -0
  37. package/app/assets/img/voice_wave1.png +0 -0
  38. package/app/assets/img/voice_wave2.png +0 -0
  39. package/app/assets/img/weather_bg.png +0 -0
  40. package/app/base.js +67 -9
  41. package/app/hooks/useStreamingText.js +222 -0
  42. package/app/screens/BaseScreen.js +95 -222
  43. package/app/screens/ChatModeScreen/ChatTemplates/ActionPrompt/PromptContents/alarm.js +33 -0
  44. package/app/screens/ChatModeScreen/ChatTemplates/ActionPrompt/PromptContents/index.js +11 -0
  45. package/app/screens/ChatModeScreen/ChatTemplates/ActionPrompt/PromptContents/itee_control_report.js +32 -0
  46. package/app/screens/ChatModeScreen/ChatTemplates/ActionPrompt/PromptContents/itee_state_change.js +41 -0
  47. package/app/screens/ChatModeScreen/ChatTemplates/ActionPrompt/PromptContents/itee_upgrade_status.js +31 -0
  48. package/app/screens/ChatModeScreen/ChatTemplates/ActionPrompt/index.js +240 -0
  49. package/app/screens/ChatModeScreen/ChatTemplates/AiAnswer/index.js +116 -0
  50. package/app/screens/ChatModeScreen/ChatTemplates/DeviceFaq/index.js +202 -0
  51. package/app/screens/ChatModeScreen/ChatTemplates/DeviceStatus/index.js +422 -0
  52. package/app/screens/ChatModeScreen/ChatTemplates/Link/index.js +62 -0
  53. package/app/screens/ChatModeScreen/ChatTemplates/SceneReport/index.js +526 -0
  54. package/app/screens/ChatModeScreen/ChatTemplates/SportHealthReport/index.js +298 -0
  55. package/app/screens/ChatModeScreen/ChatTemplates/Weather/index.js +263 -0
  56. package/app/screens/ChatModeScreen/ChatTemplates/commonStyle.js +63 -0
  57. package/app/screens/ChatModeScreen/ChatTemplates/index.js +116 -0
  58. package/app/screens/ChatModeScreen/chat/MyChat.js +423 -0
  59. package/app/screens/ChatModeScreen/components/ChatInputBar/README.md +102 -0
  60. package/app/screens/ChatModeScreen/components/ChatInputBar/TabBarInput.js +222 -0
  61. package/app/screens/ChatModeScreen/components/ChatInputBar/doc.md +920 -0
  62. package/app/screens/ChatModeScreen/components/ChatInputBar/index.js +1066 -0
  63. package/app/screens/ChatModeScreen/components/ChatMessage/index.js +321 -0
  64. package/app/screens/ChatModeScreen/components/EditableText/index.js +59 -0
  65. package/app/screens/ChatModeScreen/components/ErrorBoundary/index.js +108 -0
  66. package/app/screens/ChatModeScreen/components/IncrementalMarkdownText/index.js +566 -0
  67. package/app/screens/ChatModeScreen/components/SimpleProfileScreen.js +224 -0
  68. package/app/screens/ChatModeScreen/index.js +150 -0
  69. package/app/screens/CommonScreens/FamilyDetailScreen/index.js +1 -1
  70. package/app/screens/CommonScreens/HomedLoginScreen/index.js +2 -1
  71. package/app/screens/LoginScreen/index.js +4 -3
  72. package/app/utils/common.js +131 -0
  73. package/app/utils/navigationPackageUtils.js +275 -0
  74. package/bin/local-dev.js +8 -0
  75. package/package.json +2 -2
@@ -0,0 +1,3 @@
1
+ // 表示是否开启本地首页聊天模式开发调试模式,默认为true,减少本地开发base的配置工作量。但是后续要在发布的base npm包的postinstall脚本中将该值改为false,从而使得避免影响其他开发同事的本地开发
2
+ // 该配置仅影响base在本地开发环境(不分包APP)下的表现,不影响在测试或生产环境(分包APP)下的表现
3
+ export const LOCAL_DEV_CHAT_MODE = true;
package/app/App.js CHANGED
@@ -40,7 +40,11 @@ import storage from "./storage";
40
40
 
41
41
  import { navigationRef } from "./utils/RootNavigation";
42
42
  import checkLogin from "./hooks/checkLogin";
43
- import { getModuleVersionInfo, getUserInfo, delThirdId } from "./api/";
43
+ import {
44
+ getModuleVersionInfo,
45
+ getUserInfo,
46
+ delThirdId,
47
+ } from "./api/ipanelCloudApi";
44
48
  import { appReport } from "./utils/common";
45
49
 
46
50
  // 基础导航页、登录页
@@ -103,6 +107,9 @@ import DeviceAnswerDetailScreen from "./screens/CommonScreens/DeviceAnswerDetail
103
107
  // Boss绑定页面
104
108
  import BindBossScreen from "./screens/CommonScreens/BossScreen";
105
109
 
110
+ // ChatModeScreen专用简化版"我的"页面
111
+ import SimpleProfileScreen from "./screens/ChatModeScreen/components/SimpleProfileScreen";
112
+
106
113
  const Stack = createStackNavigator();
107
114
 
108
115
  const envVersion = NativeModules?.RNToNativeEmitter?.serverCluster || "alpha";
@@ -229,6 +236,13 @@ const CommonScreens = {
229
236
  headerShown: false,
230
237
  },
231
238
  },
239
+ SimpleProfileScreen: {
240
+ component: SimpleProfileScreen,
241
+ screenOptions: {
242
+ title: "我的",
243
+ headerShown: false,
244
+ },
245
+ },
232
246
  };
233
247
 
234
248
  // screen分组
@@ -18,8 +18,63 @@ export const ADG_SERVICE = `${config.IPANEL_CLOUD_BASE_ADG_URI}/s/install_ea`;
18
18
 
19
19
  export const APARTMENT_SERVICE = `${config.IPANEL_CLOUD_BASE_URI}/s/smart_home_manage/admin-api`;
20
20
 
21
- // todo 云端获取token服务:https://ai.baidu.com/ai-doc/SPEECH/Mk38lxpgn
22
- export const baiduAsr = (cuid, len, speechBase64) => {
21
+ const CLOUDHOMECENTER_URL = `${config.IPANEL_CLOUD_BASE_URI}/s/smart_home/cloudhomecenter`;
22
+
23
+ // 获取百度ASR token,支持缓存和自动刷新
24
+ export const getBaiduAsrToken = async () => {
25
+ const BAIDU_ASR_TOKEN_KEY = 'baiduasrtoken';
26
+ const {storage} = global.base;
27
+
28
+ try {
29
+ const cachedTokenInfo = await storage.get(BAIDU_ASR_TOKEN_KEY);
30
+ // 减去一个缓冲时间,比如5分钟,提前刷新
31
+ if (
32
+ cachedTokenInfo &&
33
+ cachedTokenInfo.accessToken &&
34
+ cachedTokenInfo.expiresAt > Date.now() - 5 * 60 * 1000
35
+ ) {
36
+ return cachedTokenInfo.accessToken;
37
+ }
38
+ } catch (error) {
39
+ console.error('Get baidu asr token from storage error', error);
40
+ }
41
+
42
+ const result = await request({
43
+ url: CLOUDHOMECENTER_URL,
44
+ method: 'POST',
45
+ data: {
46
+ path: 'asrproxy/token',
47
+ },
48
+ }).catch(err => {
49
+ console.error('Failed to fetch Baidu ASR token', err);
50
+ });
51
+
52
+ if (result && result.code === 0) {
53
+ const tokenData = result.result;
54
+
55
+ if (tokenData && tokenData.access_token && tokenData.expires_in) {
56
+ const expiresAt = Date.now() + tokenData.expires_in * 1000; // expires_in是秒,转换为毫秒
57
+ try {
58
+ await storage.set(BAIDU_ASR_TOKEN_KEY, {
59
+ accessToken: tokenData.access_token,
60
+ expiresAt: expiresAt,
61
+ });
62
+ } catch (error) {
63
+ console.error('Set baidu asr token to storage error', error);
64
+ }
65
+ return tokenData.access_token;
66
+ }
67
+ }
68
+ return '';
69
+ };
70
+
71
+ // 百度ASR语音识别服务,支持动态token获取
72
+ export const baiduAsr = async (cuid, len, speechBase64) => {
73
+ const token = await getBaiduAsrToken();
74
+ if (!token) {
75
+ throw new Error('Failed to obtain Baidu ASR token');
76
+ }
77
+
23
78
  return request({
24
79
  url: "https://vop.baidu.com/pro_api", // 语音识别极速版调用地址
25
80
  method: "POST",
@@ -28,8 +83,7 @@ export const baiduAsr = (cuid, len, speechBase64) => {
28
83
  rate: 16000,
29
84
  dev_pid: 80001,
30
85
  channel: 1,
31
- token:
32
- "24.5298600e65d054e08a1290fcc75f174f.2592000.1691141832.282335-17005893",
86
+ token: token,
33
87
  cuid: cuid,
34
88
  len,
35
89
  speech: speechBase64, // xxx为 base64(FILE_CONTENT)
Binary file
Binary file
Binary file
Binary file
Binary file
package/app/base.js CHANGED
@@ -385,6 +385,65 @@ global.base.getBundleInfo = (moduleName) => {
385
385
  });
386
386
  };
387
387
 
388
+ /**
389
+ * 加载包并注册组件的公共函数
390
+ * @param {Array} packages - 要加载的包数组
391
+ * @param {Object} options - 配置选项
392
+ * @param {boolean} options.registerStackScreens - 是否注册StackScreens,默认true
393
+ * @param {boolean} options.showErrorToast - 是否显示错误Toast,默认true
394
+ * @param {string} options.errorPrefix - 错误日志前缀,默认为空
395
+ * @returns {Promise<{success: boolean, components: Array}>} 加载结果
396
+ */
397
+ global.base.loadAndRegisterPackages = async (packages, options = {}) => {
398
+ const {
399
+ registerStackScreens = true,
400
+ showErrorToast = true,
401
+ errorPrefix = '',
402
+ } = options;
403
+
404
+ try {
405
+ // 1. 加载包
406
+ const loadFlag = await global.base
407
+ .loadPackages({ packages })
408
+ .catch((errors) => {
409
+ console.log(`${errorPrefix}包加载失败:`, JSON.stringify(errors));
410
+ return false;
411
+ });
412
+
413
+ if (!loadFlag) {
414
+ if (showErrorToast) {
415
+ global.toast?.showToast("网络繁忙");
416
+ }
417
+ console.log(`${errorPrefix}包加载失败`);
418
+ return { success: false, components: [] };
419
+ }
420
+
421
+ // 2. 获取远程组件
422
+ const componentPromises = packages.map((pack) =>
423
+ global.base.getRemoteComponentByName(pack.moduleName)
424
+ );
425
+
426
+ const components = await Promise.all(componentPromises);
427
+
428
+ // 3. 注册组件(如果需要)
429
+ if (registerStackScreens) {
430
+ components.forEach((component) => {
431
+ typeof component?.registerStackScreens === "function" &&
432
+ component.registerStackScreens();
433
+ });
434
+ }
435
+
436
+ console.log(`${errorPrefix}包加载和组件注册成功`);
437
+ return { success: true, components };
438
+ } catch (error) {
439
+ console.log(`${errorPrefix}loadAndRegisterPackages失败:`, error);
440
+ if (showErrorToast) {
441
+ global.toast?.showToast("加载失败");
442
+ }
443
+ return { success: false, components: [] };
444
+ }
445
+ };
446
+
388
447
  // 动态加载指定模块并注册为独立页面,然后跳转
389
448
  global.base.navigateModule = async ({
390
449
  operatorId,
@@ -425,20 +484,19 @@ global.base.navigateModule = async ({
425
484
 
426
485
  const packages = result.data.packages;
427
486
 
428
- const flag = await global.base
429
- .loadPackages({
430
- packages,
431
- })
432
- .catch((errors) => {
433
- console.log(JSON.stringify(errors));
434
- });
487
+ // 使用公共函数加载包,但不注册StackScreens(使用特殊的GlobalStackScreen注册)
488
+ const loadResult = await global.base.loadAndRegisterPackages(packages, {
489
+ registerStackScreens: false, // navigateModule使用特殊的注册方式
490
+ showErrorToast: true,
491
+ errorPrefix: `动态加载${moduleName}`,
492
+ });
435
493
 
436
- if (!flag) {
437
- global.toast.showToast("网络繁忙");
494
+ if (!loadResult.success) {
438
495
  console.log("网络繁忙");
439
496
  return false;
440
497
  }
441
498
 
499
+ // 使用navigateModule专用的注册方式
442
500
  const registerFlag =
443
501
  await global.base.registerRemoteComponentGlobalStackScreen(moduleName, {
444
502
  title: packages[0].moduleChineseName,
@@ -0,0 +1,222 @@
1
+ import {useState, useRef, useEffect, useCallback} from 'react';
2
+
3
+ /**
4
+ * 流式文本状态管理Hook
5
+ * 专门用于处理智能客服的实时文本流
6
+ */
7
+ export default function useStreamingText() {
8
+ const [streamingMessages, setStreamingMessages] = useState(new Map());
9
+ const streamBufferRef = useRef(new Map());
10
+ const streamTimerRef = useRef(new Map());
11
+
12
+ /**
13
+ * 更新流式消息内容
14
+ * @param {string} messageId - 消息ID
15
+ * @param {string} content - 新的内容
16
+ * @param {boolean} isComplete - 是否完成
17
+ */
18
+ const updateStreamingMessage = useCallback(
19
+ (messageId, content, isComplete = false) => {
20
+ setStreamingMessages(prev => {
21
+ const newMap = new Map(prev);
22
+ newMap.set(messageId, {
23
+ content,
24
+ isComplete,
25
+ lastUpdate: Date.now(),
26
+ });
27
+ return newMap;
28
+ });
29
+
30
+ // 如果消息完成,清理相关的缓冲区和定时器
31
+ if (isComplete) {
32
+ streamBufferRef.current.delete(messageId);
33
+ if (streamTimerRef.current.has(messageId)) {
34
+ clearTimeout(streamTimerRef.current.get(messageId));
35
+ streamTimerRef.current.delete(messageId);
36
+ }
37
+ }
38
+ },
39
+ [],
40
+ );
41
+
42
+ /**
43
+ * 处理增量文本数据
44
+ * @param {string} messageId - 消息ID
45
+ * @param {string} newChunk - 新的文本块
46
+ * @param {number} debounceMs - 防抖时间(毫秒)
47
+ */
48
+ const appendStreamingText = useCallback(
49
+ (messageId, newChunk, debounceMs = 100) => {
50
+ // 更新缓冲区
51
+ const currentBuffer = streamBufferRef.current.get(messageId) || '';
52
+ const updatedContent = currentBuffer + newChunk;
53
+ streamBufferRef.current.set(messageId, updatedContent);
54
+
55
+ // 清除之前的定时器
56
+ if (streamTimerRef.current.has(messageId)) {
57
+ clearTimeout(streamTimerRef.current.get(messageId));
58
+ }
59
+
60
+ // 设置新的防抖定时器
61
+ const timer = setTimeout(() => {
62
+ updateStreamingMessage(messageId, updatedContent, false);
63
+ }, debounceMs);
64
+
65
+ streamTimerRef.current.set(messageId, timer);
66
+ },
67
+ [updateStreamingMessage],
68
+ );
69
+
70
+ /**
71
+ * 完成流式消息
72
+ * @param {string} messageId - 消息ID
73
+ * @param {string} finalContent - 最终内容(可选,如果不提供则使用缓冲区内容)
74
+ */
75
+ const completeStreamingMessage = useCallback(
76
+ (messageId, finalContent = null) => {
77
+ const content =
78
+ finalContent || streamBufferRef.current.get(messageId) || '';
79
+ updateStreamingMessage(messageId, content, true);
80
+ },
81
+ [updateStreamingMessage],
82
+ );
83
+
84
+ /**
85
+ * 获取流式消息状态
86
+ * @param {string} messageId - 消息ID
87
+ * @returns {Object|null} 消息状态对象
88
+ */
89
+ const getStreamingMessage = useCallback(
90
+ messageId => {
91
+ return streamingMessages.get(messageId) || null;
92
+ },
93
+ [streamingMessages],
94
+ );
95
+
96
+ /**
97
+ * 清除特定的流式消息
98
+ * @param {string} messageId - 消息ID
99
+ */
100
+ const clearStreamingMessage = useCallback(messageId => {
101
+ setStreamingMessages(prev => {
102
+ const newMap = new Map(prev);
103
+ newMap.delete(messageId);
104
+ return newMap;
105
+ });
106
+
107
+ streamBufferRef.current.delete(messageId);
108
+ if (streamTimerRef.current.has(messageId)) {
109
+ clearTimeout(streamTimerRef.current.get(messageId));
110
+ streamTimerRef.current.delete(messageId);
111
+ }
112
+ }, []);
113
+
114
+ /**
115
+ * 清除所有流式消息
116
+ */
117
+ const clearAllStreamingMessages = useCallback(() => {
118
+ setStreamingMessages(new Map());
119
+ streamBufferRef.current.clear();
120
+
121
+ // 清理所有定时器
122
+ streamTimerRef.current.forEach(timer => clearTimeout(timer));
123
+ streamTimerRef.current.clear();
124
+ }, []);
125
+
126
+ /**
127
+ * 处理chat类型消息的流式更新
128
+ * @param {Object} chatMessage - chat类型的消息对象
129
+ */
130
+ const handleChatMessage = useCallback(
131
+ chatMessage => {
132
+ const messageId = chatMessage.MsgId;
133
+
134
+ switch (chatMessage.Chat.type) {
135
+ case 'data':
136
+ // 累积式流数据
137
+ const totalData = chatMessage.Chat.payload.total_data || '';
138
+ updateStreamingMessage(messageId, totalData, false);
139
+ break;
140
+
141
+ case 'end':
142
+ // 流结束
143
+ let finalContent = '';
144
+ if (chatMessage.Chat.payload.message.tool_calls) {
145
+ finalContent = `${
146
+ chatMessage.Chat.payload.message.content || ''
147
+ }<正在调用 ${
148
+ chatMessage.Chat.payload.message.tool_calls[0].function.name
149
+ } 工具>`;
150
+ } else {
151
+ finalContent = chatMessage.Chat.payload.message.content || '';
152
+ }
153
+ completeStreamingMessage(messageId, finalContent);
154
+ break;
155
+
156
+ case 'tool_call_begin':
157
+ completeStreamingMessage(
158
+ messageId,
159
+ `<正在调用 ${chatMessage.Chat.payload.tool_calls[0].function.name} 工具>`,
160
+ );
161
+ break;
162
+
163
+ case 'tool_call_end':
164
+ completeStreamingMessage(messageId, '<工具调用结束>');
165
+ break;
166
+
167
+ case 'info':
168
+ let infoContent = '';
169
+ switch (chatMessage.Chat.payload.message) {
170
+ case 'processing':
171
+ infoContent = 'AI正在思考...';
172
+ break;
173
+ case 'stoped':
174
+ infoContent = '<用户主动停止>';
175
+ break;
176
+ default:
177
+ infoContent = chatMessage.Chat.payload.message;
178
+ }
179
+ completeStreamingMessage(messageId, infoContent);
180
+ break;
181
+
182
+ case 'system_error':
183
+ completeStreamingMessage(
184
+ messageId,
185
+ chatMessage.Chat.payload.message.content,
186
+ );
187
+ break;
188
+
189
+ case 'error':
190
+ completeStreamingMessage(
191
+ messageId,
192
+ chatMessage.Chat.payload.message.error,
193
+ );
194
+ break;
195
+
196
+ default:
197
+ completeStreamingMessage(messageId, JSON.stringify(chatMessage.Chat));
198
+ }
199
+ },
200
+ [updateStreamingMessage, completeStreamingMessage],
201
+ );
202
+
203
+ // 清理效果
204
+ useEffect(() => {
205
+ const currentTimers = streamTimerRef.current;
206
+ return () => {
207
+ // 组件卸载时清理所有定时器
208
+ currentTimers.forEach(timer => clearTimeout(timer));
209
+ };
210
+ }, []);
211
+
212
+ return {
213
+ streamingMessages,
214
+ updateStreamingMessage,
215
+ appendStreamingText,
216
+ completeStreamingMessage,
217
+ getStreamingMessage,
218
+ clearStreamingMessage,
219
+ clearAllStreamingMessages,
220
+ handleChatMessage,
221
+ };
222
+ }