pcm-agents 1.0.2 → 1.0.4

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 (165) hide show
  1. package/dist/cjs/{components-chat-message_33.cjs.entry.js → components-chat-message_34.cjs.entry.js} +5990 -4059
  2. package/dist/cjs/components-chat-message_34.cjs.entry.js.map +1 -0
  3. package/dist/cjs/{conversation-utils-B4jUfmtU.js → conversation-utils-DGL5uGhS.js} +4 -4
  4. package/dist/cjs/{conversation-utils-B4jUfmtU.js.map → conversation-utils-DGL5uGhS.js.map} +1 -1
  5. package/dist/cjs/{index-Dghp_NL6.js → index-CUDWXBQ4.js} +3 -3
  6. package/dist/cjs/{index-Dghp_NL6.js.map → index-CUDWXBQ4.js.map} +1 -1
  7. package/dist/cjs/index-C_qhED9Z.js +6 -6
  8. package/dist/cjs/index.cjs.js +1 -1
  9. package/dist/cjs/loader.cjs.js +1 -1
  10. package/dist/cjs/oem-htws-modal.cjs.entry.js +1 -1
  11. package/dist/cjs/oem-jd-modal.cjs.entry.js +2 -2
  12. package/dist/cjs/oem-jlpp-modal.cjs.entry.js +1 -1
  13. package/dist/cjs/oem-jlzz-modal.cjs.entry.js +1 -1
  14. package/dist/cjs/oem-mnct-modal.cjs.entry.js +1 -1
  15. package/dist/cjs/oem-qgqjl-modal.cjs.entry.js +3 -3
  16. package/dist/cjs/pcm-agents.cjs.js +1 -1
  17. package/dist/cjs/pcm-hr-chat-modal.cjs.entry.js +2 -2
  18. package/dist/cjs/pcm-jlsx-modal.cjs.entry.js +2 -2
  19. package/dist/cjs/{oem-mnms-modal.cjs.entry.js → pcm-mnms-modal.cjs.entry.js} +41 -156
  20. package/dist/cjs/pcm-mnms-modal.cjs.entry.js.map +1 -0
  21. package/dist/cjs/pcm-mnms-modal.entry.cjs.js.map +1 -0
  22. package/dist/cjs/{sentry-reporter-CgJowDyu.js → sentry-reporter-DsoYs8_e.js} +3 -3
  23. package/dist/cjs/{sentry-reporter-CgJowDyu.js.map → sentry-reporter-DsoYs8_e.js.map} +1 -1
  24. package/dist/collection/collection-manifest.json +1 -0
  25. package/dist/collection/components/components-oem-virtual-chat-modal/components-oem-virtual-chat-modal.css +1111 -0
  26. package/dist/collection/components/components-oem-virtual-chat-modal/components-oem-virtual-chat-modal.js +2334 -0
  27. package/dist/collection/components/components-oem-virtual-chat-modal/components-oem-virtual-chat-modal.js.map +1 -0
  28. package/dist/collection/components/oem-mnms-modal/oem-mnms-modal.js +2 -2
  29. package/dist/collection/components/oem-mnms-modal/oem-mnms-modal.js.map +1 -1
  30. package/dist/components/components-chat-message.js +1 -1
  31. package/dist/components/components-digital-human.js +1 -1
  32. package/dist/components/components-export-records-modal.js +1 -1
  33. package/dist/components/components-mobile-input-btn.js +1 -1
  34. package/dist/components/components-mobile-upload-btn.js +1 -1
  35. package/dist/components/components-oem-virtual-chat-modal.d.ts +11 -0
  36. package/dist/components/components-oem-virtual-chat-modal.js +9 -0
  37. package/dist/components/components-oem-virtual-chat-modal.js.map +1 -0
  38. package/dist/components/components-position-selector.js +1 -1
  39. package/dist/components/components-thousand-resume-wrapper.js +1 -1
  40. package/dist/components/components-upload.js +1 -1
  41. package/dist/components/components-voice-input-btn.js +1 -1
  42. package/dist/components/index.js +1 -1
  43. package/dist/components/oem-htws-modal.js +5 -5
  44. package/dist/components/oem-jd-modal.js +5 -5
  45. package/dist/components/oem-jlpp-modal.js +7 -7
  46. package/dist/components/oem-jlsx-modal.js +5 -5
  47. package/dist/components/oem-jlzz-modal.js +5 -5
  48. package/dist/components/oem-mnct-modal.js +7 -7
  49. package/dist/components/oem-mnms-modal.js +14 -14
  50. package/dist/components/oem-mnms-modal.js.map +1 -1
  51. package/dist/components/oem-qgqjl-modal.js +9 -9
  52. package/dist/components/oem-zygh-modal.js +5 -5
  53. package/dist/components/{p-BhdGTSLg.js → p-BCFZkcSd.js} +5 -5
  54. package/dist/components/{p-BhdGTSLg.js.map → p-BCFZkcSd.js.map} +1 -1
  55. package/dist/components/p-BG83hpwD.js +1910 -0
  56. package/dist/components/p-BG83hpwD.js.map +1 -0
  57. package/dist/components/{p-BDEiyYVl.js → p-BQcztM0Y.js} +3 -3
  58. package/dist/components/{p-BDEiyYVl.js.map → p-BQcztM0Y.js.map} +1 -1
  59. package/dist/components/{p-DJDJfWq9.js → p-BZPf0rl6.js} +3 -3
  60. package/dist/components/{p-DJDJfWq9.js.map → p-BZPf0rl6.js.map} +1 -1
  61. package/dist/components/{p-D4RnNrxx.js → p-BgwHiYHx.js} +3 -3
  62. package/dist/components/{p-D4RnNrxx.js.map → p-BgwHiYHx.js.map} +1 -1
  63. package/dist/components/{p-Bg0KKnpU.js → p-BqXnsrve.js} +3 -3
  64. package/dist/components/{p-Bg0KKnpU.js.map → p-BqXnsrve.js.map} +1 -1
  65. package/dist/components/{p-C7ubjB1x.js → p-CL_Guy01.js} +3 -3
  66. package/dist/components/{p-C7ubjB1x.js.map → p-CL_Guy01.js.map} +1 -1
  67. package/dist/components/{p-C-lvK9NX.js → p-CNFdfFMH.js} +3 -3
  68. package/dist/components/{p-C-lvK9NX.js.map → p-CNFdfFMH.js.map} +1 -1
  69. package/dist/components/{p-CzFKth7J.js → p-Cbaqx3Tt.js} +3 -3
  70. package/dist/components/{p-CzFKth7J.js.map → p-Cbaqx3Tt.js.map} +1 -1
  71. package/dist/components/{p-oVAdweom.js → p-DFDjQmKS.js} +3 -3
  72. package/dist/components/{p-oVAdweom.js.map → p-DFDjQmKS.js.map} +1 -1
  73. package/dist/components/{p-CB7myOe_.js → p-DNi0tT01.js} +3 -3
  74. package/dist/components/{p-CB7myOe_.js.map → p-DNi0tT01.js.map} +1 -1
  75. package/dist/components/{p-i4wY2JnB.js → p-Dc3NvH9p.js} +4 -4
  76. package/dist/components/{p-i4wY2JnB.js.map → p-Dc3NvH9p.js.map} +1 -1
  77. package/dist/components/{p-CPq5-i_-.js → p-DiLmXw24.js} +4 -4
  78. package/dist/components/{p-CPq5-i_-.js.map → p-DiLmXw24.js.map} +1 -1
  79. package/dist/components/{p-CEu-bFot.js → p-VwrhX6KG.js} +3 -3
  80. package/dist/components/{p-CEu-bFot.js.map → p-VwrhX6KG.js.map} +1 -1
  81. package/dist/components/pcm-1zhanshi-mnms-modal.js +5 -5
  82. package/dist/components/pcm-app-chat-modal.js +1 -1
  83. package/dist/components/pcm-ats-mnms-modal.js +5 -5
  84. package/dist/components/pcm-card.js +1 -1
  85. package/dist/components/pcm-hr-chat-modal.js +2 -2
  86. package/dist/components/pcm-htws-modal.js +8 -8
  87. package/dist/components/pcm-hyzj-modal.js +5 -5
  88. package/dist/components/pcm-jd-modal.js +6 -6
  89. package/dist/components/pcm-jlpp-modal.js +8 -8
  90. package/dist/components/pcm-jlsx-modal.js +5 -5
  91. package/dist/components/pcm-jlzz-modal.js +6 -6
  92. package/dist/components/pcm-mnct-modal.js +7 -7
  93. package/dist/components/pcm-mnms-modal.js +8 -8
  94. package/dist/components/pcm-mnms-zp-modal.js +8 -8
  95. package/dist/components/pcm-msbg-modal.js +5 -5
  96. package/dist/components/pcm-qgqjl-modal.js +9 -9
  97. package/dist/components/pcm-virtual-chat-modal.js +1 -1
  98. package/dist/components/pcm-zsk-chat-modal.js +2 -2
  99. package/dist/components/pcm-zygh-modal.js +7 -7
  100. package/dist/components/pcm-zygh-v2-modal.js +7 -7
  101. package/dist/esm/{components-chat-message_33.entry.js → components-chat-message_34.entry.js} +5989 -4059
  102. package/dist/esm/components-chat-message_34.entry.js.map +1 -0
  103. package/dist/esm/{conversation-utils-CLO35DHR.js → conversation-utils-DJr3w2Gu.js} +4 -4
  104. package/dist/esm/{conversation-utils-CLO35DHR.js.map → conversation-utils-DJr3w2Gu.js.map} +1 -1
  105. package/dist/esm/index-Bq0K-WqZ.js +6 -6
  106. package/dist/esm/{index-KVnu9w-h.js → index-DcQZ0v3S.js} +4 -4
  107. package/dist/esm/{index-KVnu9w-h.js.map → index-DcQZ0v3S.js.map} +1 -1
  108. package/dist/esm/index.js +1 -1
  109. package/dist/esm/loader.js +1 -1
  110. package/dist/esm/oem-htws-modal.entry.js +1 -1
  111. package/dist/esm/oem-jd-modal.entry.js +2 -2
  112. package/dist/esm/oem-jlpp-modal.entry.js +1 -1
  113. package/dist/esm/oem-jlzz-modal.entry.js +1 -1
  114. package/dist/esm/oem-mnct-modal.entry.js +1 -1
  115. package/dist/esm/oem-qgqjl-modal.entry.js +3 -3
  116. package/dist/esm/pcm-agents.js +1 -1
  117. package/dist/esm/pcm-hr-chat-modal.entry.js +2 -2
  118. package/dist/esm/pcm-jlsx-modal.entry.js +2 -2
  119. package/dist/esm/{oem-mnms-modal.entry.js → pcm-mnms-modal.entry.js} +41 -156
  120. package/dist/esm/pcm-mnms-modal.entry.js.map +1 -0
  121. package/dist/esm/{sentry-reporter-B4aHQq-A.js → sentry-reporter-N0y2nHh5.js} +3 -3
  122. package/dist/esm/{sentry-reporter-B4aHQq-A.js.map → sentry-reporter-N0y2nHh5.js.map} +1 -1
  123. package/dist/pcm-agents/index.esm.js +1 -1
  124. package/dist/pcm-agents/{p-eb9b355e.entry.js → p-317ec555.entry.js} +2 -2
  125. package/dist/pcm-agents/p-3f0d27cf.entry.js +8 -0
  126. package/dist/pcm-agents/p-3f0d27cf.entry.js.map +1 -0
  127. package/dist/pcm-agents/{p-aa606968.entry.js → p-57993edb.entry.js} +2 -2
  128. package/dist/pcm-agents/{p-e2b9a9f2.entry.js → p-7c1e7e5a.entry.js} +2 -2
  129. package/dist/pcm-agents/{p-Bktjt_YJ.js → p-C3qMuJRQ.js} +2 -2
  130. package/dist/pcm-agents/{p-Bktjt_YJ.js.map → p-C3qMuJRQ.js.map} +1 -1
  131. package/dist/pcm-agents/p-Ch9kExOi.js +2 -0
  132. package/dist/pcm-agents/{p-B4ClCdh9.js.map → p-Ch9kExOi.js.map} +1 -1
  133. package/dist/pcm-agents/{p-CMfzTNOk.js → p-b8oixHud.js} +2 -2
  134. package/dist/pcm-agents/{p-CMfzTNOk.js.map → p-b8oixHud.js.map} +1 -1
  135. package/dist/pcm-agents/{p-4d7511e0.entry.js → p-babf3d55.entry.js} +2 -2
  136. package/dist/pcm-agents/{p-efb4e819.entry.js → p-c2dc4dcd.entry.js} +2 -2
  137. package/dist/pcm-agents/p-ca1f514b.entry.js +2 -0
  138. package/dist/pcm-agents/p-ca1f514b.entry.js.map +1 -0
  139. package/dist/pcm-agents/{p-8256890d.entry.js → p-cdcdec03.entry.js} +2 -2
  140. package/dist/pcm-agents/{p-8144f0d4.entry.js → p-e13e3325.entry.js} +2 -2
  141. package/dist/pcm-agents/{p-dd6362f0.entry.js → p-f666e503.entry.js} +2 -2
  142. package/dist/pcm-agents/pcm-agents.esm.js +1 -1
  143. package/dist/pcm-agents/pcm-mnms-modal.entry.esm.js.map +1 -0
  144. package/dist/types/components/components-oem-virtual-chat-modal/components-oem-virtual-chat-modal.d.ts +290 -0
  145. package/dist/types/components.d.ts +186 -0
  146. package/package.json +1 -1
  147. package/dist/cjs/components-chat-message_33.cjs.entry.js.map +0 -1
  148. package/dist/cjs/oem-mnms-modal.cjs.entry.js.map +0 -1
  149. package/dist/cjs/oem-mnms-modal.entry.cjs.js.map +0 -1
  150. package/dist/esm/components-chat-message_33.entry.js.map +0 -1
  151. package/dist/esm/oem-mnms-modal.entry.js.map +0 -1
  152. package/dist/pcm-agents/oem-mnms-modal.entry.esm.js.map +0 -1
  153. package/dist/pcm-agents/p-7e8bdfa6.entry.js +0 -2
  154. package/dist/pcm-agents/p-7e8bdfa6.entry.js.map +0 -1
  155. package/dist/pcm-agents/p-B4ClCdh9.js +0 -2
  156. package/dist/pcm-agents/p-f6683f29.entry.js +0 -8
  157. package/dist/pcm-agents/p-f6683f29.entry.js.map +0 -1
  158. /package/dist/pcm-agents/{p-eb9b355e.entry.js.map → p-317ec555.entry.js.map} +0 -0
  159. /package/dist/pcm-agents/{p-aa606968.entry.js.map → p-57993edb.entry.js.map} +0 -0
  160. /package/dist/pcm-agents/{p-e2b9a9f2.entry.js.map → p-7c1e7e5a.entry.js.map} +0 -0
  161. /package/dist/pcm-agents/{p-4d7511e0.entry.js.map → p-babf3d55.entry.js.map} +0 -0
  162. /package/dist/pcm-agents/{p-efb4e819.entry.js.map → p-c2dc4dcd.entry.js.map} +0 -0
  163. /package/dist/pcm-agents/{p-8256890d.entry.js.map → p-cdcdec03.entry.js.map} +0 -0
  164. /package/dist/pcm-agents/{p-8144f0d4.entry.js.map → p-e13e3325.entry.js.map} +0 -0
  165. /package/dist/pcm-agents/{p-dd6362f0.entry.js.map → p-f666e503.entry.js.map} +0 -0
@@ -0,0 +1,2334 @@
1
+ import { h } from "@stencil/core";
2
+ import { sendSSERequest, sendHttpRequest, uploadFileToBackend, fetchAgentInfo, getSupportedMimeType, convertAudioToText } from "../../utils/utils";
3
+ import { showMessage } from "../../utils/message-utils";
4
+ import { ErrorEventBus } from "../../utils/error-event";
5
+ import { authStore } from "../../../store/auth.store"; // 导入 authStore
6
+ import { configStore } from "../../../store/config.store";
7
+ import { SentryReporter } from "../../utils/sentry-reporter";
8
+ export class ComponentsOemVirtualChatModal {
9
+ /**
10
+ * SDK鉴权密钥
11
+ */
12
+ token;
13
+ /**
14
+ * 是否显示聊天模态框
15
+ */
16
+ isOpen = false;
17
+ /**
18
+ * 聊天消息历史
19
+ */
20
+ messages = [];
21
+ /**
22
+ * 聊天框的页面层级
23
+ */
24
+ zIndex;
25
+ /**
26
+ * 会话ID,传入继续对话,否则创建新会话
27
+ */
28
+ conversationId;
29
+ /**
30
+ * 是否正在加载回复
31
+ */
32
+ isLoading = false;
33
+ /**
34
+ * 当前正在流式输出的消息
35
+ */
36
+ currentStreamingMessage = null;
37
+ isLoadingHistory = false;
38
+ // 使用 @Element 装饰器获取组件的 host 元素
39
+ hostElement;
40
+ /**
41
+ * 一轮对话结束时的回调
42
+ */
43
+ streamComplete;
44
+ /**
45
+ * 新会话开始的回调,只会在一轮对话开始时触发一次
46
+ */
47
+ conversationStart;
48
+ /**
49
+ * 默认发送文本
50
+ */
51
+ defaultQuery = '你好!聘才猫';
52
+ /**
53
+ * 视频录制最大时长(秒)
54
+ */
55
+ maxRecordingTime = 120;
56
+ /**
57
+ * 录制倒计时提醒时间(秒)
58
+ * 当剩余时间小于此值时,显示倒计时警告
59
+ */
60
+ countdownWarningTime = 30;
61
+ // 添加视频录制相关状态
62
+ isRecording = false;
63
+ recordingStream = null;
64
+ recordedBlob = null;
65
+ mediaRecorder = null;
66
+ recordingTimeLeft = 0;
67
+ showRecordingUI = false;
68
+ recordingTimer = null;
69
+ recordingStartTime = 0;
70
+ waitingToRecord = false;
71
+ waitingTimer = null;
72
+ _visibilityChangeHandler = null;
73
+ waitingTimeLeft = 10; // 等待时间(秒)
74
+ // 添加一个新的私有属性来存储视频元素的引用
75
+ videoRef = null;
76
+ /**
77
+ * 当前轮数
78
+ */
79
+ currentQuestionNumber = 0;
80
+ /**
81
+ * 当聊天完成时触发
82
+ */
83
+ interviewComplete;
84
+ showCountdownWarning = false;
85
+ /**
86
+ * 是否以全屏模式打开,移动端建议设置为true
87
+ */
88
+ fullscreen = false;
89
+ // 添加新的状态来跟踪视频上传
90
+ isUploadingVideo = false;
91
+ /**
92
+ * 数字人视频相关状态
93
+ */
94
+ digitalHumanVideoUrl = '';
95
+ digitalHumanDefaultVideoUrl = '';
96
+ virtualmanKey = '';
97
+ isPlayingDigitalHumanVideo = false;
98
+ digitalHumanVideoReady = false;
99
+ digitalHumanOpeningContents = [];
100
+ isPlayingWelcomeVideo = false;
101
+ isGeneratingDigitalHumanVideo = false;
102
+ /**
103
+ * 待播放的生成视频URL(当开场白视频播放时,暂存生成的视频URL)
104
+ */
105
+ pendingGeneratedVideoUrl = '';
106
+ // 视频预加载缓存管理
107
+ preloadedVideos = new Set();
108
+ preloadingVideos = new Map();
109
+ /**
110
+ * 是否正在等待数字人视频播放完成
111
+ */
112
+ waitingForDigitalHuman = false;
113
+ /**
114
+ * 虚拟数字人ID,指定则开启虚拟数字人功能
115
+ */
116
+ digitalId;
117
+ /**
118
+ * 数字人开场白索引,用于选择开场白和开场视频(可选:0, 1, 2)
119
+ * 0、您好,我是聘才猫 AI 面试助手。很高兴为你主持这场面试!在开始前,请确保:身处安静、光线充足的环境。网络顺畅,摄像头和麦克风工作正常。现在我正在查看本次面试的相关信息,为您生成专属面试题,马上就好,请稍等片刻。</br>
120
+ * 1、您好,我是您的 AI 面试助手。欢迎参加本次AI面试!为了获得最佳效果,请确认:您在安静、明亮的环境中。您的网络稳定,摄像头和麦克风已开启。我们正在后台为您准备本次专属面试内容,很快开始,请稍候。<br>
121
+ * 2、您好,我是您的 AI 面试助手。面试马上开始。趁此片刻,请快速确认:周围安静吗?光线足够吗?网络没问题?摄像头和麦克风准备好了吗?我们正在为您加载个性化的面试环节,稍等就好!
122
+ */
123
+ openingIndex = 0;
124
+ /**
125
+ * 等待录制时间(秒)默认10
126
+ */
127
+ waitingTime = 10;
128
+ /**
129
+ * 数字人视频播放速率(0.5-2.0之间),默认1.0
130
+ */
131
+ playbackRate = 1.0;
132
+ /**
133
+ * 是否显示结束面试按钮
134
+ */
135
+ showEndInterviewButton = false;
136
+ /**
137
+ * 录制错误事件
138
+ */
139
+ recordingError;
140
+ /**
141
+ * 录制状态变化事件
142
+ */
143
+ recordingStatusChange;
144
+ /**
145
+ * SDK密钥验证失败事件
146
+ */
147
+ tokenInvalid;
148
+ /**
149
+ * 当点击模态框关闭时触发
150
+ */
151
+ modalClosed;
152
+ /**
153
+ * 自定义智能体inputs输入参数
154
+ */
155
+ customInputs = {};
156
+ /**
157
+ * 机器人ID
158
+ */
159
+ botId;
160
+ /**
161
+ * 智能体头像URL(从后端获取)
162
+ */
163
+ agentLogo = '';
164
+ // 添加新的状态属性来跟踪任务是否已完成
165
+ isTaskCompleted = false;
166
+ tokenInvalidListener;
167
+ deviceError = null;
168
+ /**
169
+ * 面试流程状态管理说明:
170
+ *
171
+ * 核心状态:
172
+ * - isTaskCompleted: 任务是否已完成(面试正常结束)
173
+ * - isEndingInterview: 是否正在执行结束面试流程(用户主动结束)
174
+ * - isRecording: 是否正在录制视频
175
+ * - waitingToRecord: 是否正在等待开始录制
176
+ * - waitingForDigitalHuman: 是否正在等待数字人视频播放完成
177
+ * - isLoading: 是否正在等待AI回复
178
+ * - isUploadingVideo: 是否正在上传视频
179
+ * - isGeneratingDigitalHumanVideo: 是否正在生成数字人视频
180
+ *
181
+ * 流程控制原则:
182
+ * 1. 一旦 isTaskCompleted 或 isEndingInterview 为 true,所有录制和等待流程都应停止
183
+ * 2. isEndingInterview 设置为 true 后,只允许发送结束面试消息
184
+ * 3. 结束面试按钮在 isTaskCompleted 或 isEndingInterview 为 true 时置灰禁用
185
+ * 4. 所有可能触发新录制的地方都需要检查这两个状态
186
+ */
187
+ // 添加二次确认相关状态
188
+ showConfirmModal = false;
189
+ skipConfirmThisInterview = false;
190
+ // 添加关闭确认相关状态
191
+ showCloseConfirmModal = false;
192
+ // 添加结束面试确认相关状态
193
+ showEndInterviewConfirmModal = false;
194
+ /**
195
+ * 标记是否正在执行结束面试流程
196
+ */
197
+ isEndingInterview = false;
198
+ // 添加内部状态标志,用于控制异步操作的执行
199
+ isComponentActive = true;
200
+ handleTokenChange(newToken) {
201
+ // 当传入的 token 变化时,更新 authStore 中的 token
202
+ if (newToken && newToken !== authStore.getToken()) {
203
+ authStore.setToken(newToken);
204
+ }
205
+ }
206
+ /**
207
+ * 公共方法:设置组件活动状态
208
+ * 可被外部调用来控制组件是否继续执行异步操作
209
+ */
210
+ async setComponentActive(active) {
211
+ console.log('设置组件活动状态:', active);
212
+ this.isComponentActive = active;
213
+ }
214
+ componentWillLoad() {
215
+ // 将 zIndex 存入配置缓存
216
+ if (this.zIndex) {
217
+ configStore.setItem('modal-zIndex', this.zIndex);
218
+ }
219
+ else {
220
+ // 如果没有提供 zIndex,尝试从缓存中读取
221
+ const cachedZIndex = configStore.getItem('modal-zIndex');
222
+ if (cachedZIndex) {
223
+ this.zIndex = cachedZIndex;
224
+ }
225
+ else {
226
+ this.zIndex = 1000;
227
+ configStore.setItem('modal-zIndex', 1000);
228
+ }
229
+ }
230
+ if (this.token) {
231
+ authStore.setToken(this.token);
232
+ }
233
+ // 添加全局token无效事件监听器
234
+ this.tokenInvalidListener = () => {
235
+ this.tokenInvalid.emit();
236
+ };
237
+ document.addEventListener('pcm-token-invalid', this.tokenInvalidListener);
238
+ // 监听页面可见性变化,处理关屏/切后台回来后流程卡住的问题
239
+ this._visibilityChangeHandler = () => {
240
+ if (document.visibilityState === 'visible') {
241
+ this.handlePageResume();
242
+ }
243
+ };
244
+ document.addEventListener('visibilitychange', this._visibilityChangeHandler);
245
+ // 确保 customInputs 是一个对象
246
+ if (!this.customInputs) {
247
+ this.customInputs = {};
248
+ }
249
+ // 如果没有设置助手头像,尝试获取智能体头像
250
+ if (this.botId) {
251
+ this.fetchAgentLogo();
252
+ }
253
+ // 添加数字人初始化日志
254
+ console.log('componentWillLoad - 数字人相关状态:', {
255
+ digitalId: this.digitalId,
256
+ isOpen: this.isOpen,
257
+ conversationId: this.conversationId,
258
+ virtualmanKey: this.virtualmanKey,
259
+ });
260
+ // 如果组件加载时已经是打开状态,则直接开始对话
261
+ if (this.isOpen) {
262
+ if (this.conversationId) {
263
+ // 在下一个事件循环中加载历史消息,避免在componentWillLoad中进行异步操作
264
+ setTimeout(() => this.loadHistoryMessages(), 0);
265
+ }
266
+ else {
267
+ // 初始化数字人功能
268
+ if (this.digitalId) {
269
+ console.log('准备初始化数字人功能...');
270
+ this.initializeDigitalHuman();
271
+ }
272
+ // 在下一个事件循环中发送初始消息,避免在componentWillLoad中进行异步操作
273
+ setTimeout(() => {
274
+ this.sendMessageToAPI(this.defaultQuery);
275
+ }, 0);
276
+ }
277
+ }
278
+ }
279
+ // 添加获取智能体信息的方法
280
+ async fetchAgentLogo() {
281
+ if (!this.botId)
282
+ return;
283
+ try {
284
+ const agentInfo = await fetchAgentInfo(this.botId);
285
+ if (agentInfo && agentInfo.logo) {
286
+ this.agentLogo = agentInfo.logo;
287
+ }
288
+ }
289
+ catch (error) {
290
+ SentryReporter.captureError(error, {
291
+ action: 'fetchAgentLogo',
292
+ component: 'components-oem-virtual-chat-modal',
293
+ title: '获取智能体信息失败',
294
+ });
295
+ ErrorEventBus.emitError({
296
+ error: error,
297
+ message: '获取智能体信息失败',
298
+ });
299
+ }
300
+ }
301
+ async sendMessageToAPI(message, videoUrl) {
302
+ // 如果任务已完成或正在结束面试,不允许发送消息
303
+ if (this.isTaskCompleted && !this.isEndingInterview) {
304
+ console.log('任务已完成,不允许发送消息');
305
+ return;
306
+ }
307
+ // 发送新消息时重置状态
308
+ this.waitingForDigitalHuman = false;
309
+ this.digitalHumanVideoReady = false;
310
+ this.isLoading = true;
311
+ let answer = '';
312
+ let llmText = '';
313
+ const now = new Date();
314
+ const time = `${now.getHours()}:${now.getMinutes().toString().padStart(2, '0')}`;
315
+ // 修改消息处理逻辑,移除文件上传相关代码
316
+ const queryText = message.trim() || '请开始';
317
+ // 如果有视频URL,添加到customInputs中
318
+ if (videoUrl) {
319
+ this.customInputs = {
320
+ ...this.customInputs,
321
+ video_url: videoUrl,
322
+ };
323
+ }
324
+ // 如果没有设置url_callback,则使用默认值
325
+ if (!this.customInputs.url_callback) {
326
+ this.customInputs = {
327
+ ...this.customInputs,
328
+ url_callback: 'https://tagents.ylzhaopin.com/agents/api/test/callback',
329
+ };
330
+ }
331
+ // 创建新的消息对象
332
+ const newMessage = {
333
+ id: `temp-${Date.now()}`, // 临时ID,将被服务器返回的ID替换
334
+ conversation_id: this.conversationId, // 会话ID
335
+ inputs: this.customInputs || {}, // 输入参数
336
+ query: queryText, // 用户输入的消息内容
337
+ answer: '', // 初始为空
338
+ message_files: [], // 消息附件
339
+ feedback: {}, // 反馈
340
+ retriever_resources: [], // 检索资源
341
+ created_at: Math.floor(Date.now() / 1000).toString(), // 创建时间
342
+ agent_thoughts: [], // 代理思考过程
343
+ status: 'normal', // 消息状态
344
+ error: null, // 错误信息
345
+ // 添加组件内部使用的字段
346
+ time: time, // 消息时间(显示用)
347
+ isStreaming: true, // 是否正在流式输出
348
+ };
349
+ // 设置当前流式消息
350
+ this.currentStreamingMessage = newMessage;
351
+ // 准备请求数据
352
+ const requestData = {
353
+ response_mode: 'streaming',
354
+ conversation_id: this.conversationId,
355
+ query: queryText,
356
+ bot_id: this.botId, // 添加 botId 到请求数据中
357
+ };
358
+ // 合并基本输入参数和自定义输入参数
359
+ requestData.inputs = {
360
+ // 合并自定义输入参数
361
+ ...this.customInputs,
362
+ };
363
+ await sendSSERequest({
364
+ url: '/sdk/v1/chat/chat-messages',
365
+ method: 'POST',
366
+ data: requestData,
367
+ onMessage: data => {
368
+ console.log(new Date().toLocaleString());
369
+ console.log('收到Stream数据:', data);
370
+ if (data.conversation_id && !this.conversationId) {
371
+ this.conversationId = data.conversation_id;
372
+ this.conversationStart.emit({
373
+ conversation_id: data.conversation_id,
374
+ event: data.event,
375
+ message_id: data.message_id,
376
+ id: data.id,
377
+ });
378
+ }
379
+ // 检查是否有 node_finished 事件和 LLMText
380
+ if (data.event === 'node_finished' && data.data.inputs && data.data.inputs.LLMText) {
381
+ llmText = data.data.inputs.LLMText;
382
+ }
383
+ // 添加对任务结束的判断
384
+ if (data.event === 'node_finished' && data.data.title && data.data.title.includes('聘才猫任务结束')) {
385
+ console.log('检测到任务结束事件:', data);
386
+ // 设置标志,表示任务已结束
387
+ this.isTaskCompleted = true;
388
+ // 获取当前AI回复内容,优先使用LLMText,否则使用answer
389
+ const aiResponse = llmText || answer || this.currentStreamingMessage?.answer || '';
390
+ // 触发面试完成事件
391
+ this.interviewComplete.emit({
392
+ conversation_id: this.conversationId,
393
+ current_question_number: this.currentQuestionNumber,
394
+ ai_response: aiResponse, // 添加AI回复内容
395
+ });
396
+ }
397
+ if (data.event === 'node_finished' && data.data.title && data.data.title.includes('聘才猫数字人题目')) {
398
+ const digital_human_list = JSON.parse(data.data.process_data.digital_human_list);
399
+ console.log('数字人题目:', digital_human_list);
400
+ this.precreateDigitalHumanVideos(digital_human_list);
401
+ }
402
+ if (data.event === 'error') {
403
+ const errorMessage = data.data?.message || data.message || '请求失败';
404
+ this.messages = [
405
+ ...this.messages,
406
+ {
407
+ ...newMessage,
408
+ answer: errorMessage,
409
+ error: data.data,
410
+ isStreaming: false,
411
+ },
412
+ ];
413
+ this.currentStreamingMessage = null;
414
+ this.isLoading = false;
415
+ ErrorEventBus.emitError({
416
+ error: data.data,
417
+ message: errorMessage,
418
+ });
419
+ return;
420
+ }
421
+ if (data.event === 'message') {
422
+ if (data.event === 'agent_message' || data.event === 'message') {
423
+ if (data.answer) {
424
+ answer += data.answer;
425
+ // 更新消息时保持完整的消息结构
426
+ const updatedMessage = {
427
+ ...this.currentStreamingMessage,
428
+ answer,
429
+ id: data.message_id || this.currentStreamingMessage.id,
430
+ isStreaming: true,
431
+ // 如果服务器返回了其他字段,也更新它们
432
+ retriever_resources: data.retriever_resources || this.currentStreamingMessage.retriever_resources,
433
+ agent_thoughts: data.agent_thoughts || this.currentStreamingMessage.agent_thoughts,
434
+ };
435
+ this.currentStreamingMessage = updatedMessage;
436
+ }
437
+ }
438
+ }
439
+ if (data.event === 'message_end') {
440
+ this.streamComplete.emit({
441
+ conversation_id: data.conversation_id || '',
442
+ event: data.event,
443
+ message_id: data.message_id,
444
+ id: data.id,
445
+ });
446
+ }
447
+ },
448
+ onError: error => {
449
+ console.error('发生错误:', error);
450
+ ErrorEventBus.emitError({
451
+ error: error,
452
+ message: '消息发送失败,请稍后再试',
453
+ });
454
+ SentryReporter.captureError(error, {
455
+ action: 'sendMessageToAPI',
456
+ component: 'components-oem-virtual-chat-modal',
457
+ title: '消息发送失败',
458
+ });
459
+ this.messages = [
460
+ ...this.messages,
461
+ {
462
+ ...newMessage,
463
+ answer: '消息发送失败,请检查网络。',
464
+ error: error,
465
+ isStreaming: false,
466
+ },
467
+ ];
468
+ this.currentStreamingMessage = null;
469
+ this.isLoading = false;
470
+ },
471
+ onComplete: async () => {
472
+ this.isLoading = false;
473
+ // 根据currentQuestionNumber判断是否清空customInputs
474
+ if (this.currentQuestionNumber === 0) {
475
+ // 发送第一条消息后,清空指定的 customInputs 字段
476
+ setTimeout(() => {
477
+ if (this.customInputs) {
478
+ // 只清除指定的字段,其他字段保留
479
+ delete this.customInputs.job_info;
480
+ delete this.customInputs.file_url;
481
+ delete this.customInputs.file_name;
482
+ }
483
+ }, 1000); // 给一些时间让第一条消息处理完成
484
+ }
485
+ // 获取最新的AI回复内容
486
+ const latestAIMessage = this.currentStreamingMessage;
487
+ latestAIMessage.isStreaming = false;
488
+ if (this.digitalId && latestAIMessage && latestAIMessage.answer) {
489
+ // 如果开启了数字人功能,生成数字人视频
490
+ console.log('生成数字人视频:', latestAIMessage.answer);
491
+ this.generateDigitalHumanVideo(latestAIMessage.answer);
492
+ }
493
+ this.currentStreamingMessage = null;
494
+ // 更新消息列表
495
+ this.messages = [...this.messages, latestAIMessage];
496
+ // 增加计数
497
+ this.currentQuestionNumber++;
498
+ // 如果任务已完成或正在结束面试,不执行后续流程
499
+ if (this.isTaskCompleted || this.isEndingInterview) {
500
+ console.log('任务已完成或正在结束面试,停止后续流程');
501
+ return;
502
+ }
503
+ // 如果开启了数字人,等待数字人视频播放完成
504
+ if (this.digitalId) {
505
+ this.waitingForDigitalHuman = true;
506
+ console.log('等待数字人视频播放完成...');
507
+ }
508
+ },
509
+ });
510
+ }
511
+ // 修改加载历史消息的方法
512
+ async loadHistoryMessages() {
513
+ if (!this.conversationId)
514
+ return;
515
+ this.isLoadingHistory = true;
516
+ console.log('加载历史消息...');
517
+ let conversationStatus = false;
518
+ this.digitalHumanVideoReady = true;
519
+ try {
520
+ // 首先获取会话状态
521
+ const conversationStatusResponse = await sendHttpRequest({
522
+ url: `/sdk/v1/chat/conversation`,
523
+ method: 'GET',
524
+ data: {
525
+ conversation_id: this.conversationId,
526
+ },
527
+ });
528
+ // 处理会话状态
529
+ if (conversationStatusResponse.success && conversationStatusResponse.data && conversationStatusResponse.data.run_status == '结束') {
530
+ conversationStatus = true;
531
+ }
532
+ // 获取历史消息
533
+ const result = await sendHttpRequest({
534
+ url: '/sdk/v1/chat/messages',
535
+ method: 'GET',
536
+ data: {
537
+ conversation_id: this.conversationId,
538
+ bot_id: this.botId,
539
+ limit: 20,
540
+ },
541
+ });
542
+ if (result.success && result.data) {
543
+ const historyData = result.data.data || [];
544
+ const formattedMessages = historyData.map(msg => {
545
+ const time = new Date(parseInt(msg.created_at) * 1000);
546
+ const hours = time.getHours().toString().padStart(2, '0');
547
+ const minutes = time.getMinutes().toString().padStart(2, '0');
548
+ const timeStr = `${hours}:${minutes}`;
549
+ // 创建完整的消息对象,保持统一结构
550
+ return {
551
+ id: msg.id,
552
+ conversation_id: msg.conversation_id,
553
+ inputs: msg.inputs || {},
554
+ query: msg.query || '',
555
+ answer: msg.answer || '',
556
+ message_files: msg.message_files || [],
557
+ feedback: msg.feedback || {},
558
+ retriever_resources: msg.retriever_resources || [],
559
+ created_at: msg.created_at,
560
+ agent_thoughts: msg.agent_thoughts || [],
561
+ status: msg.status || 'normal',
562
+ error: msg.error,
563
+ // 组件内部使用的字段
564
+ time: timeStr,
565
+ isStreaming: false,
566
+ };
567
+ });
568
+ this.messages = formattedMessages;
569
+ // 根据历史消息数量设置currentQuestionNumber
570
+ this.currentQuestionNumber = formattedMessages.length || 0;
571
+ }
572
+ }
573
+ catch (error) {
574
+ console.error('加载历史消息或会话状态失败:', error);
575
+ SentryReporter.captureError(error, {
576
+ action: 'loadHistoryMessages',
577
+ component: 'components-oem-virtual-chat-modal',
578
+ title: '加载历史消息失败',
579
+ });
580
+ ErrorEventBus.emitError({
581
+ error: error,
582
+ message: '加载历史消息失败,请刷新重试',
583
+ });
584
+ }
585
+ finally {
586
+ this.isLoadingHistory = false;
587
+ if (this.conversationId && this.messages.length > 0 && !conversationStatus && !this.isEndingInterview) {
588
+ // 不播放欢迎视频
589
+ this.isPlayingDigitalHumanVideo = false;
590
+ this.isPlayingWelcomeVideo = false;
591
+ this.digitalHumanVideoReady = true;
592
+ // 先初始化数字人,等待获取到默认视频URL后再设置
593
+ this.initializeDigitalHuman().then(() => {
594
+ // 确保在初始化完成后设置视频URL
595
+ if (this.digitalHumanDefaultVideoUrl) {
596
+ this.digitalHumanVideoUrl = this.digitalHumanDefaultVideoUrl;
597
+ }
598
+ });
599
+ setTimeout(() => {
600
+ // 开始等待录制
601
+ this.startWaitingToRecord();
602
+ }, 200);
603
+ }
604
+ else if (conversationStatus) {
605
+ // 如果会话已结束,设置任务完成状态
606
+ this.isTaskCompleted = true;
607
+ // 确保数字人视频状态正确设置
608
+ if (this.digitalId) {
609
+ this.isPlayingDigitalHumanVideo = false;
610
+ this.isPlayingWelcomeVideo = false;
611
+ this.digitalHumanVideoReady = true;
612
+ // 先初始化数字人,等待获取到默认视频URL后再设置
613
+ this.initializeDigitalHuman().then(() => {
614
+ // 确保在初始化完成后设置视频URL
615
+ if (this.digitalHumanDefaultVideoUrl) {
616
+ this.digitalHumanVideoUrl = this.digitalHumanDefaultVideoUrl;
617
+ }
618
+ });
619
+ }
620
+ }
621
+ }
622
+ }
623
+ // 开始等待录制
624
+ startWaitingToRecord() {
625
+ // 如果任务已完成或正在结束面试,不开始录制流程
626
+ if (this.isTaskCompleted || this.isEndingInterview) {
627
+ console.log('任务已完成或正在结束面试,不开始录制流程');
628
+ return;
629
+ }
630
+ // 清除可能存在的计时器
631
+ if (this.waitingTimer) {
632
+ clearInterval(this.waitingTimer);
633
+ }
634
+ if (this.recordingTimer) {
635
+ clearInterval(this.recordingTimer);
636
+ }
637
+ this.waitingToRecord = true;
638
+ this.waitingTimeLeft = this.waitingTime;
639
+ this.waitingTimer = setInterval(() => {
640
+ this.waitingTimeLeft--;
641
+ if (this.waitingTimeLeft <= 0) {
642
+ clearInterval(this.waitingTimer);
643
+ this.waitingTimer = null;
644
+ this.waitingToRecord = false;
645
+ this.startRecording();
646
+ }
647
+ }, 1000);
648
+ }
649
+ // 开始录制视频
650
+ async startRecording() {
651
+ // 如果任务已完成或正在结束面试,不开始录制
652
+ if (this.isTaskCompleted || this.isEndingInterview) {
653
+ console.log('任务已完成或正在结束面试,不开始录制');
654
+ return;
655
+ }
656
+ try {
657
+ const stream = await navigator.mediaDevices.getUserMedia({
658
+ audio: true,
659
+ video: {
660
+ width: { ideal: 1280 },
661
+ height: { ideal: 720 },
662
+ frameRate: { ideal: 30 },
663
+ },
664
+ });
665
+ // 如果成功获取到媒体流,清除之前的设备错误
666
+ this.deviceError = null;
667
+ this.recordingStream = stream;
668
+ this.showRecordingUI = true;
669
+ this.showCountdownWarning = false;
670
+ // 重置视频引用
671
+ this.videoRef = null;
672
+ // 确保视频元素获取到流
673
+ this.setupVideoPreview(stream);
674
+ // 检测浏览器支持的MIME类型
675
+ const mimeType = getSupportedMimeType();
676
+ // 创建MediaRecorder实例
677
+ let mediaRecorder;
678
+ try {
679
+ mediaRecorder = new MediaRecorder(stream, {
680
+ mimeType: mimeType,
681
+ videoBitsPerSecond: 800000, // 800kbps 视频比特率限制
682
+ audioBitsPerSecond: 64000, // 64kbps 音频比特率限制
683
+ });
684
+ }
685
+ catch (e) {
686
+ // 如果指定MIME类型失败,尝试使用默认设置
687
+ console.warn('指定的MIME类型不受支持,使用默认设置:', e);
688
+ try {
689
+ mediaRecorder = new MediaRecorder(stream, {
690
+ videoBitsPerSecond: 800000, // 即使降级也保持比特率限制
691
+ audioBitsPerSecond: 64000,
692
+ });
693
+ }
694
+ catch (recorderError) {
695
+ // 最后的降级选项,不设置任何参数
696
+ try {
697
+ mediaRecorder = new MediaRecorder(stream);
698
+ }
699
+ catch (finalError) {
700
+ // 通知父组件录制器创建失败
701
+ this.recordingError.emit({
702
+ type: 'recorder_creation_failed',
703
+ message: '无法创建媒体录制器,您的浏览器可能不支持此功能',
704
+ details: finalError,
705
+ });
706
+ this.showRecordingUI = false;
707
+ return;
708
+ }
709
+ }
710
+ }
711
+ this.mediaRecorder = mediaRecorder;
712
+ const chunks = [];
713
+ mediaRecorder.ondataavailable = event => {
714
+ if (event.data.size > 0) {
715
+ chunks.push(event.data);
716
+ }
717
+ };
718
+ mediaRecorder.onerror = event => {
719
+ // 通知父组件录制过程中发生错误
720
+ this.recordingError.emit({
721
+ type: 'recording_error',
722
+ message: '录制过程中发生错误',
723
+ details: event,
724
+ });
725
+ this.stopRecording();
726
+ };
727
+ mediaRecorder.onstop = () => {
728
+ try {
729
+ // 根据实际使用的MIME类型创建Blob
730
+ const blobType = mimeType || 'video/mp4';
731
+ const blob = new Blob(chunks, { type: blobType });
732
+ if (blob.size === 0) {
733
+ // 通知父组件录制的视频为空
734
+ this.recordingError.emit({
735
+ type: 'empty_recording',
736
+ message: '录制的视频为空',
737
+ });
738
+ this.showRecordingUI = false;
739
+ return;
740
+ }
741
+ this.recordedBlob = blob;
742
+ // 通知父组件录制已完成
743
+ this.recordingStatusChange.emit({
744
+ status: 'stopped',
745
+ details: {
746
+ duration: Math.floor((Date.now() - this.recordingStartTime) / 1000),
747
+ size: blob.size,
748
+ type: blob.type,
749
+ },
750
+ });
751
+ this.uploadRecordedVideo();
752
+ }
753
+ catch (error) {
754
+ // 通知父组件处理录制视频时出错
755
+ this.recordingError.emit({
756
+ type: 'processing_error',
757
+ message: '处理录制视频时出错',
758
+ details: error,
759
+ });
760
+ this.showRecordingUI = false;
761
+ SentryReporter.captureError(error, {
762
+ action: 'uploadRecordedVideo',
763
+ component: 'pcm-hr-chat-modal',
764
+ title: '处理录制视频时出错',
765
+ });
766
+ }
767
+ };
768
+ // 开始录制
769
+ try {
770
+ mediaRecorder.start();
771
+ this.isRecording = true;
772
+ this.recordingStartTime = Date.now();
773
+ this.recordingTimeLeft = this.maxRecordingTime;
774
+ // 通知父组件录制已开始
775
+ this.recordingStatusChange.emit({
776
+ status: 'started',
777
+ details: {
778
+ maxDuration: this.maxRecordingTime,
779
+ mimeType: mediaRecorder.mimeType,
780
+ },
781
+ });
782
+ }
783
+ catch (startError) {
784
+ // 通知父组件开始录制失败
785
+ this.recordingError.emit({
786
+ type: 'start_failed',
787
+ message: '开始录制失败,请检查您的设备权限',
788
+ details: startError,
789
+ });
790
+ this.showRecordingUI = false;
791
+ SentryReporter.captureError(startError, {
792
+ action: 'startRecording',
793
+ component: 'pcm-hr-chat-modal',
794
+ title: '开始录制失败',
795
+ });
796
+ return;
797
+ }
798
+ // 设置录制计时器
799
+ this.recordingTimer = setInterval(() => {
800
+ const elapsedTime = Math.floor((Date.now() - this.recordingStartTime) / 1000);
801
+ this.recordingTimeLeft = Math.max(0, this.maxRecordingTime - elapsedTime);
802
+ // 检查是否需要显示倒计时警告
803
+ if (this.recordingTimeLeft <= this.countdownWarningTime && !this.showCountdownWarning) {
804
+ this.showCountdownWarning = true;
805
+ }
806
+ // 时间到自动停止录制
807
+ if (this.recordingTimeLeft <= 0) {
808
+ this.stopRecording();
809
+ }
810
+ }, 1000);
811
+ }
812
+ catch (error) {
813
+ console.error('无法访问摄像头或麦克风:', error);
814
+ // 设置设备错误状态
815
+ let errorMessage = '设备访问失败';
816
+ if (error.name === 'NotAllowedError') {
817
+ errorMessage = '请允许访问摄像头和麦克风权限';
818
+ }
819
+ else if (error.name === 'NotFoundError') {
820
+ errorMessage = '未检测到摄像头或麦克风设备';
821
+ }
822
+ else if (error.name === 'NotReadableError') {
823
+ errorMessage = '您的浏览器不支持视频录制,请切换到微信或Chrome、Edge使用';
824
+ }
825
+ else if (error.name === 'OverconstrainedError') {
826
+ errorMessage = '摄像头不支持指定的配置';
827
+ }
828
+ else {
829
+ errorMessage = '无法访问摄像头或麦克风,请检查设备连接';
830
+ }
831
+ this.deviceError = errorMessage;
832
+ this.showRecordingUI = false;
833
+ ErrorEventBus.emitError({
834
+ error: error,
835
+ message: errorMessage,
836
+ });
837
+ SentryReporter.captureError(error, {
838
+ action: 'startRecording',
839
+ component: 'components-oem-virtual-chat-modal',
840
+ title: errorMessage,
841
+ });
842
+ }
843
+ }
844
+ // 添加新方法来设置视频预览
845
+ setupVideoPreview(stream) {
846
+ // 延迟执行以确保DOM已更新
847
+ setTimeout(() => {
848
+ const videoElement = this.hostElement.shadowRoot?.querySelector('.user-video-preview');
849
+ if (videoElement && stream) {
850
+ // 先尝试使用标准方法
851
+ try {
852
+ videoElement.srcObject = stream;
853
+ videoElement.play().catch(err => {
854
+ console.error('视频播放失败:', err);
855
+ SentryReporter.captureError(err, {
856
+ action: 'setupVideoPreview',
857
+ component: 'components-oem-virtual-chat-modal',
858
+ title: '视频播放失败',
859
+ });
860
+ ErrorEventBus.emitError({
861
+ error: err,
862
+ message: '视频播放失败',
863
+ });
864
+ });
865
+ }
866
+ catch (e) {
867
+ console.warn('设置srcObject失败,尝试替代方法:', e);
868
+ // 对于不支持srcObject的旧浏览器,使用URL.createObjectURL
869
+ try {
870
+ // 使用类型断言解决TypeScript错误
871
+ const objectUrl = URL.createObjectURL(stream);
872
+ videoElement.src = objectUrl;
873
+ // 确保在视频元素不再使用时释放URL
874
+ videoElement.onended = () => {
875
+ URL.revokeObjectURL(objectUrl);
876
+ };
877
+ }
878
+ catch (urlError) {
879
+ console.error('创建对象URL失败:', urlError);
880
+ SentryReporter.captureError(urlError, {
881
+ action: 'setupVideoPreview',
882
+ component: 'components-oem-virtual-chat-modal',
883
+ title: '创建对象URL失败',
884
+ });
885
+ ErrorEventBus.emitError({
886
+ error: urlError,
887
+ message: '创建对象URL失败',
888
+ });
889
+ }
890
+ }
891
+ }
892
+ else {
893
+ console.warn('未找到视频元素或媒体流无效');
894
+ }
895
+ }, 100);
896
+ }
897
+ // 停止录制
898
+ stopRecording() {
899
+ if (this.mediaRecorder && this.isRecording) {
900
+ this.mediaRecorder.stop();
901
+ this.isRecording = false;
902
+ // 清理计时器
903
+ if (this.recordingTimer) {
904
+ clearInterval(this.recordingTimer);
905
+ this.recordingTimer = null;
906
+ }
907
+ // 停止并释放媒体流
908
+ if (this.recordingStream) {
909
+ this.recordingStream.getTracks().forEach(track => track.stop());
910
+ this.recordingStream = null;
911
+ }
912
+ // 清理视频引用
913
+ this.videoRef = null;
914
+ }
915
+ // 清理待播放的视频URL(即使没有正在录制,也需要清理待播放状态)
916
+ this.pendingGeneratedVideoUrl = '';
917
+ }
918
+ // 处理停止录制按钮点击
919
+ handleStopRecording = () => {
920
+ // 如果已经设置了跳过确认,直接停止录制
921
+ if (this.skipConfirmThisInterview) {
922
+ this.stopRecording();
923
+ return;
924
+ }
925
+ // 否则显示确认模态框
926
+ this.showConfirmModal = true;
927
+ };
928
+ // 处理"本次面试不再提醒"复选框变化
929
+ handleSkipConfirmChange = (event) => {
930
+ const checkbox = event.target;
931
+ this.skipConfirmThisInterview = checkbox.checked;
932
+ };
933
+ // 处理确认模态框的取消事件
934
+ handleConfirmModalCancel = () => {
935
+ this.showConfirmModal = false;
936
+ };
937
+ // 处理确认模态框的OK事件
938
+ handleConfirmModalOk = () => {
939
+ this.showConfirmModal = false;
940
+ this.stopRecording();
941
+ };
942
+ // 处理确认模态框的Cancel事件
943
+ handleConfirmModalCancelEvent = () => {
944
+ this.showConfirmModal = false;
945
+ };
946
+ // 上传录制的视频
947
+ async uploadRecordedVideo() {
948
+ if (!this.recordedBlob)
949
+ return;
950
+ try {
951
+ this.isUploadingVideo = true; // 开始上传时设置状态
952
+ this.showRecordingUI = false; // 隐藏视频预览
953
+ // 根据Blob类型确定文件扩展名
954
+ const fileExtension = this.recordedBlob.type.includes('webm') ? 'webm' : 'mp4';
955
+ const fileName = `answer.${fileExtension}`;
956
+ // 创建File对象
957
+ const file = new File([this.recordedBlob], fileName, { type: this.recordedBlob.type });
958
+ // 使用uploadFileToBackend上传文件
959
+ const fileInfo = await uploadFileToBackend(file, {}, {
960
+ tags: ['other'],
961
+ });
962
+ // 使用 cos_key 作为视频标识符
963
+ // 调用音频转文字API
964
+ const transcriptionText = await convertAudioToText(fileInfo.cos_key);
965
+ // 检查是否是结束面试流程
966
+ if (this.isEndingInterview) {
967
+ console.log('视频上传完成,现在发送结束面试消息');
968
+ // 如果是结束面试流程,发送结束面试消息
969
+ await this.sendEndInterviewMessage();
970
+ }
971
+ else {
972
+ // 正常流程:发送"下一题"请求,可以附带转录文本
973
+ this.sendMessageToAPI(transcriptionText || '下一题', fileInfo.cos_key);
974
+ }
975
+ }
976
+ catch (error) {
977
+ console.error('视频上传或处理错误:', error);
978
+ this.isEndingInterview = false; // 出错时重置标志
979
+ SentryReporter.captureError(error, {
980
+ action: 'uploadRecordedVideo',
981
+ component: 'components-oem-virtual-chat-modal',
982
+ title: '视频上传或处理失败',
983
+ });
984
+ ErrorEventBus.emitError({
985
+ error: error,
986
+ message: '视频上传或处理失败',
987
+ });
988
+ }
989
+ finally {
990
+ this.isUploadingVideo = false; // 上传完成后重置状态
991
+ this.showRecordingUI = false;
992
+ this.recordedBlob = null;
993
+ }
994
+ }
995
+ // 处理关闭按钮点击
996
+ handleCloseClick = () => {
997
+ // 显示关闭确认对话框
998
+ this.showCloseConfirmModal = true;
999
+ };
1000
+ // 处理关闭确认对话框的确认事件
1001
+ handleCloseConfirmOk = () => {
1002
+ this.showCloseConfirmModal = false;
1003
+ this.stopRecording();
1004
+ this.modalClosed.emit();
1005
+ };
1006
+ // 处理关闭确认对话框的取消事件
1007
+ handleCloseConfirmCancel = () => {
1008
+ this.showCloseConfirmModal = false;
1009
+ };
1010
+ // 确保组件卸载时释放资源
1011
+ disconnectedCallback() {
1012
+ document.removeEventListener('pcm-token-invalid', this.tokenInvalidListener);
1013
+ // 清理页面可见性监听器
1014
+ if (this._visibilityChangeHandler) {
1015
+ document.removeEventListener('visibilitychange', this._visibilityChangeHandler);
1016
+ this._visibilityChangeHandler = null;
1017
+ }
1018
+ // 清理其他计时器
1019
+ if (this.waitingTimer) {
1020
+ clearInterval(this.waitingTimer);
1021
+ this.waitingTimer = null;
1022
+ }
1023
+ if (this.recordingTimer) {
1024
+ clearInterval(this.recordingTimer);
1025
+ this.recordingTimer = null;
1026
+ }
1027
+ // 停止录制
1028
+ this.stopRecording();
1029
+ // 清理视频预加载缓存
1030
+ this.preloadedVideos.clear();
1031
+ this.preloadingVideos.clear();
1032
+ // 清理待播放的视频URL
1033
+ this.pendingGeneratedVideoUrl = '';
1034
+ }
1035
+ /**
1036
+ * 预创建数字人视频
1037
+ */
1038
+ async precreateDigitalHumanVideos(digital_human_list) {
1039
+ if (!this.virtualmanKey) {
1040
+ console.warn('VirtualmanKey尚未加载,无法预创建视频。');
1041
+ // 可以在此处添加逻辑,等待virtualmanKey加载后再执行
1042
+ return;
1043
+ }
1044
+ for (const text of digital_human_list) {
1045
+ if (!text || !text.trim())
1046
+ continue;
1047
+ try {
1048
+ // 注意:这里只创建任务,不等待视频生成完成
1049
+ await sendHttpRequest({
1050
+ url: '/sdk/v1/virtual-human/create-video',
1051
+ method: 'POST',
1052
+ data: {
1053
+ VirtualmanKey: this.virtualmanKey,
1054
+ InputSsml: text,
1055
+ SpeechParam: {
1056
+ Speed: 1,
1057
+ },
1058
+ VideoParam: {
1059
+ Format: 'TransparentWebm',
1060
+ },
1061
+ DriverType: 'Text',
1062
+ },
1063
+ });
1064
+ }
1065
+ catch (error) {
1066
+ console.error(`为文本 "${text}" 创建视频任务失败:`, error);
1067
+ // 可以选择性地报告错误
1068
+ SentryReporter.captureError(error, {
1069
+ action: 'precreateDigitalHumanVideos',
1070
+ component: 'components-oem-virtual-chat-modal',
1071
+ title: `为文本 "${text}" 创建视频任务失败`,
1072
+ });
1073
+ }
1074
+ }
1075
+ console.log('数字人视频预创建任务已全部发送。');
1076
+ }
1077
+ /**
1078
+ * 初始化数字人功能
1079
+ */
1080
+ async initializeDigitalHuman() {
1081
+ if (!this.digitalId)
1082
+ return;
1083
+ try {
1084
+ console.log('初始化数字人功能,digitalId:', this.digitalId);
1085
+ const response = await sendHttpRequest({
1086
+ url: '/sdk/v1/virtual-human/avatar-detail',
1087
+ method: 'POST',
1088
+ data: {
1089
+ avatar_id: this.digitalId
1090
+ }
1091
+ });
1092
+ if (response.success) {
1093
+ const { placeholder_video_url, virtualman_key, opening_contents } = response.data;
1094
+ if (placeholder_video_url) {
1095
+ this.digitalHumanDefaultVideoUrl = placeholder_video_url;
1096
+ this.digitalHumanVideoUrl = placeholder_video_url;
1097
+ }
1098
+ if (virtualman_key) {
1099
+ this.virtualmanKey = virtualman_key;
1100
+ }
1101
+ // 处理开场白内容(JSON格式)
1102
+ if (opening_contents && Array.isArray(opening_contents) && opening_contents.length > 0) {
1103
+ this.digitalHumanOpeningContents = opening_contents;
1104
+ // 验证并调整开场白索引
1105
+ const validIndex = Math.max(0, Math.min(2, this.openingIndex || 0)); // 限制索引范围0-2
1106
+ const selectedOpeningContent = this.digitalHumanOpeningContents[validIndex] || this.digitalHumanOpeningContents[0];
1107
+ // 按顺序准备预加载列表:1.选择的欢迎视频 2.默认占位视频 3.其他视频
1108
+ const orderedVideosToPreload = [];
1109
+ // 1. 首先加载选择的欢迎视频
1110
+ if (selectedOpeningContent.video_url) {
1111
+ orderedVideosToPreload.push(selectedOpeningContent.video_url);
1112
+ }
1113
+ // 2. 然后加载默认占位视频(如果不同于选择的欢迎视频)
1114
+ if (placeholder_video_url && placeholder_video_url !== selectedOpeningContent.video_url) {
1115
+ orderedVideosToPreload.push(placeholder_video_url);
1116
+ }
1117
+ // 使用顺序预加载
1118
+ this.handleVideoUrlReceivedSequential(orderedVideosToPreload, '数字人初始化(顺序)');
1119
+ // 播放选择的欢迎视频 - 检查是否已经在继续对话模式下设置了状态
1120
+ if (selectedOpeningContent.video_url && !this.digitalHumanVideoReady) {
1121
+ console.log('播放数字人欢迎视频:', selectedOpeningContent.video_url, '索引:', validIndex);
1122
+ this.playDigitalHumanVideo(selectedOpeningContent.video_url, true);
1123
+ }
1124
+ }
1125
+ else {
1126
+ // 没有欢迎视频,只预加载默认占位视频
1127
+ if (placeholder_video_url) {
1128
+ this.handleVideoUrlReceivedSequential([placeholder_video_url], '数字人初始化(仅默认视频)');
1129
+ }
1130
+ }
1131
+ }
1132
+ }
1133
+ catch (error) {
1134
+ console.error('初始化数字人失败:', error);
1135
+ SentryReporter.captureError(error, {
1136
+ action: 'initializeDigitalHuman',
1137
+ component: 'components-oem-virtual-chat-modal',
1138
+ title: '数字人初始化失败',
1139
+ });
1140
+ }
1141
+ }
1142
+ /**
1143
+ * 生成数字人视频
1144
+ */
1145
+ async generateDigitalHumanVideo(text) {
1146
+ // 如果组件已停用,直接跳过生成视频
1147
+ if (!this.isComponentActive) {
1148
+ console.warn('组件已停用,跳过数字人视频生成');
1149
+ return;
1150
+ }
1151
+ if (!text.trim() || !this.virtualmanKey) {
1152
+ // 条件不满足时,取消等待状态并直接开始录制流程
1153
+ if (this.waitingForDigitalHuman && !this.isTaskCompleted) {
1154
+ console.log('数字人视频生成条件不满足,直接开始录制流程');
1155
+ this.waitingForDigitalHuman = false;
1156
+ this.startWaitingToRecord();
1157
+ }
1158
+ return;
1159
+ }
1160
+ console.log('开始生成数字人视频,文本内容:', text);
1161
+ // 设置正在生成视频的状态
1162
+ this.isGeneratingDigitalHumanVideo = true;
1163
+ try {
1164
+ // 创建视频任务
1165
+ const createResponse = await sendHttpRequest({
1166
+ url: '/sdk/v1/virtual-human/create-video',
1167
+ method: 'POST',
1168
+ data: {
1169
+ VirtualmanKey: this.virtualmanKey,
1170
+ InputSsml: text,
1171
+ SpeechParam: {
1172
+ Speed: 1
1173
+ },
1174
+ VideoParam: {
1175
+ Format: 'TransparentWebm',
1176
+ },
1177
+ DriverType: "Text",
1178
+ }
1179
+ });
1180
+ console.log('数字人视频创建响应:', createResponse);
1181
+ if (!createResponse.success) {
1182
+ showMessage(createResponse.message, 'error');
1183
+ throw new Error(`创建视频任务失败: ${createResponse.message}`);
1184
+ }
1185
+ const taskId = createResponse.data.Payload.TaskId;
1186
+ console.log('视频任务创建成功,TaskId:', taskId);
1187
+ // 轮询查询进度
1188
+ const videoUrl = await this.pollVideoProgress(taskId);
1189
+ // 轮询完成后,再次检查组件是否仍处于活动状态
1190
+ if (!this.isComponentActive) {
1191
+ console.warn('组件已停用,跳过视频播放');
1192
+ return;
1193
+ }
1194
+ if (videoUrl) {
1195
+ // 立即预加载新生成的视频(单个视频,不需要顺序处理)
1196
+ await this.preloadVideo(videoUrl);
1197
+ // 预加载后再次检查组件状态
1198
+ if (!this.isComponentActive) {
1199
+ console.warn('组件已停用,跳过视频播放');
1200
+ return;
1201
+ }
1202
+ // 检查是否正在播放开场白视频
1203
+ if (this.isPlayingWelcomeVideo) {
1204
+ // 如果正在播放开场白视频,保存生成的视频URL,等待开场白播放完成
1205
+ console.log('正在播放开场白视频,暂存生成的视频URL,等待开场白播放完成');
1206
+ this.pendingGeneratedVideoUrl = videoUrl;
1207
+ }
1208
+ else {
1209
+ // 否则立即播放生成的视频
1210
+ await this.playDigitalHumanVideo(videoUrl);
1211
+ console.log('数字人视频生成完成,视频URL:', videoUrl);
1212
+ }
1213
+ }
1214
+ else {
1215
+ // 如果没有获取到视频URL,抛出错误
1216
+ throw new Error('未能获取到数字人视频URL');
1217
+ }
1218
+ }
1219
+ catch (error) {
1220
+ console.error('数字人视频生成失败:', error);
1221
+ SentryReporter.captureError(error, {
1222
+ action: 'generateDigitalHumanVideo',
1223
+ component: 'components-oem-virtual-chat-modal',
1224
+ title: '数字人视频生成失败',
1225
+ });
1226
+ // 数字人视频生成失败时,取消等待状态并直接开始录制流程
1227
+ if (this.waitingForDigitalHuman && !this.isTaskCompleted) {
1228
+ console.log('数字人视频生成失败,直接开始录制流程');
1229
+ this.waitingForDigitalHuman = false;
1230
+ this.startWaitingToRecord();
1231
+ }
1232
+ }
1233
+ finally {
1234
+ // 无论成功还是失败,都重置生成状态
1235
+ this.isGeneratingDigitalHumanVideo = false;
1236
+ }
1237
+ }
1238
+ /**
1239
+ * 轮询查询视频生成进度
1240
+ */
1241
+ async pollVideoProgress(taskId) {
1242
+ const maxAttempts = 45;
1243
+ const pollInterval = 2000;
1244
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
1245
+ try {
1246
+ const response = await sendHttpRequest({
1247
+ url: '/sdk/v1/virtual-human/query-progress',
1248
+ method: 'POST',
1249
+ data: {
1250
+ TaskId: taskId
1251
+ }
1252
+ });
1253
+ if (!response.success) {
1254
+ throw new Error(`查询进度失败: ${response.message}`);
1255
+ }
1256
+ const payload = response.data.Payload;
1257
+ if (payload.Status === 'SUCCESS' && payload.Progress === 100) {
1258
+ return payload.MediaUrl;
1259
+ }
1260
+ else if (payload.Status === 'FAILED') {
1261
+ throw new Error(`视频生成失败: ${payload.FailMessage}`);
1262
+ }
1263
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
1264
+ }
1265
+ catch (error) {
1266
+ console.error('轮询视频进度失败:', error);
1267
+ break;
1268
+ }
1269
+ }
1270
+ throw new Error('视频生成超时');
1271
+ }
1272
+ /**
1273
+ * 播放数字人视频
1274
+ */
1275
+ async playDigitalHumanVideo(videoUrl, isWelcomeVideo = false) {
1276
+ console.log('开始播放数字人视频:', videoUrl, '是否为欢迎视频:', isWelcomeVideo);
1277
+ try {
1278
+ // 确保视频已预加载(如果未预加载则立即预加载)
1279
+ await this.preloadVideo(videoUrl);
1280
+ // 只通过状态来控制video元素
1281
+ this.digitalHumanVideoUrl = videoUrl;
1282
+ this.isPlayingDigitalHumanVideo = true;
1283
+ this.digitalHumanVideoReady = true;
1284
+ this.isPlayingWelcomeVideo = isWelcomeVideo;
1285
+ }
1286
+ catch (error) {
1287
+ console.error('播放数字人视频失败:', error);
1288
+ // 数字人视频播放失败时,取消等待状态并直接开始录制流程
1289
+ if (this.waitingForDigitalHuman && !this.isTaskCompleted) {
1290
+ console.log('数字人视频播放失败,直接开始录制流程');
1291
+ this.waitingForDigitalHuman = false;
1292
+ this.startWaitingToRecord();
1293
+ }
1294
+ }
1295
+ }
1296
+ /**
1297
+ * 智能视频预加载管理器
1298
+ * 确保每个视频只预加载一次,避免重复请求
1299
+ */
1300
+ async preloadVideo(videoUrl) {
1301
+ if (!videoUrl || !videoUrl.trim()) {
1302
+ return Promise.resolve();
1303
+ }
1304
+ const normalizedUrl = videoUrl.trim();
1305
+ const fileName = normalizedUrl.split('/').pop() || normalizedUrl;
1306
+ // 如果已经预加载过,直接返回
1307
+ if (this.preloadedVideos.has(normalizedUrl)) {
1308
+ console.log(`💾 视频已预加载,跳过: ${fileName.substring(0, 30)}${fileName.length > 30 ? '...' : ''}`);
1309
+ return Promise.resolve();
1310
+ }
1311
+ // 如果正在预加载中,返回现有的Promise
1312
+ if (this.preloadingVideos.has(normalizedUrl)) {
1313
+ console.log(`🔄 视频正在预加载中,等待完成: ${fileName.substring(0, 30)}${fileName.length > 30 ? '...' : ''}`);
1314
+ return this.preloadingVideos.get(normalizedUrl);
1315
+ }
1316
+ // 创建新的预加载Promise
1317
+ const preloadPromise = this.executeVideoPreload(normalizedUrl);
1318
+ this.preloadingVideos.set(normalizedUrl, preloadPromise);
1319
+ try {
1320
+ await preloadPromise;
1321
+ // 预加载成功,添加到已预加载集合
1322
+ this.preloadedVideos.add(normalizedUrl);
1323
+ console.log(`🎯 视频预加载成功: ${fileName.substring(0, 30)}${fileName.length > 30 ? '...' : ''}`);
1324
+ }
1325
+ catch (error) {
1326
+ console.error(`💥 视频预加载失败: ${fileName.substring(0, 30)}${fileName.length > 30 ? '...' : ''}`, error);
1327
+ // 预加载失败时不抛出错误,避免阻塞后续流程
1328
+ }
1329
+ finally {
1330
+ // 清理正在预加载的记录
1331
+ this.preloadingVideos.delete(normalizedUrl);
1332
+ }
1333
+ }
1334
+ /**
1335
+ * 执行实际的视频预加载操作
1336
+ */
1337
+ async executeVideoPreload(videoUrl) {
1338
+ return new Promise((resolve, reject) => {
1339
+ const preloadVideo = document.createElement('video');
1340
+ preloadVideo.preload = 'auto';
1341
+ preloadVideo.src = videoUrl;
1342
+ preloadVideo.muted = true; // 确保可以自动播放
1343
+ preloadVideo.crossOrigin = 'anonymous'; // 处理跨域问题
1344
+ let isResolved = false;
1345
+ const handleSuccess = () => {
1346
+ if (isResolved)
1347
+ return;
1348
+ isResolved = true;
1349
+ cleanup();
1350
+ console.log('视频预加载完成:', videoUrl);
1351
+ resolve();
1352
+ };
1353
+ const handleError = (event) => {
1354
+ if (isResolved)
1355
+ return;
1356
+ isResolved = true;
1357
+ cleanup();
1358
+ console.error('视频预加载失败:', videoUrl, event);
1359
+ reject(new Error('视频预加载失败'));
1360
+ };
1361
+ const cleanup = () => {
1362
+ preloadVideo.removeEventListener('canplaythrough', handleSuccess);
1363
+ preloadVideo.removeEventListener('loadeddata', handleSuccess);
1364
+ preloadVideo.removeEventListener('error', handleError);
1365
+ preloadVideo.removeEventListener('abort', handleError);
1366
+ preloadVideo.src = '';
1367
+ };
1368
+ // 监听多种成功事件,提高兼容性
1369
+ preloadVideo.addEventListener('canplaythrough', handleSuccess);
1370
+ preloadVideo.addEventListener('loadeddata', handleSuccess);
1371
+ // 监听错误事件
1372
+ preloadVideo.addEventListener('error', handleError);
1373
+ preloadVideo.addEventListener('abort', handleError);
1374
+ // 设置超时机制,避免长时间等待
1375
+ setTimeout(() => {
1376
+ if (!isResolved) {
1377
+ console.warn('视频预加载超时:', videoUrl);
1378
+ handleError('timeout');
1379
+ }
1380
+ }, 15000); // 15秒超时
1381
+ // 开始加载
1382
+ preloadVideo.load();
1383
+ });
1384
+ }
1385
+ /**
1386
+ * 顺序预加载多个视频
1387
+ * 上一个视频加载完毕后立刻加载下一个视频
1388
+ */
1389
+ async sequentialPreloadVideos(videoUrls) {
1390
+ if (!videoUrls || videoUrls.length === 0) {
1391
+ return;
1392
+ }
1393
+ const validUrls = videoUrls.filter(url => url && url.trim());
1394
+ if (validUrls.length === 0) {
1395
+ return;
1396
+ }
1397
+ console.log('🎬 开始顺序预加载视频:', validUrls);
1398
+ console.log('📋 预加载计划:');
1399
+ validUrls.forEach((url, index) => {
1400
+ const fileName = url.split('/').pop() || url;
1401
+ console.log(` ${index + 1}. ${fileName.substring(0, 50)}${fileName.length > 50 ? '...' : ''}`);
1402
+ });
1403
+ for (let i = 0; i < validUrls.length; i++) {
1404
+ const url = validUrls[i];
1405
+ const fileName = url.split('/').pop() || url;
1406
+ try {
1407
+ console.log(`⏳ [${i + 1}/${validUrls.length}] 正在预加载: ${fileName.substring(0, 50)}${fileName.length > 50 ? '...' : ''}`);
1408
+ const startTime = Date.now();
1409
+ await this.preloadVideo(url);
1410
+ const duration = Date.now() - startTime;
1411
+ console.log(`✅ [${i + 1}/${validUrls.length}] 预加载完成 (${duration}ms): ${fileName.substring(0, 50)}${fileName.length > 50 ? '...' : ''}`);
1412
+ }
1413
+ catch (error) {
1414
+ console.warn(`❌ [${i + 1}/${validUrls.length}] 预加载失败: ${fileName.substring(0, 50)}${fileName.length > 50 ? '...' : ''}`, error);
1415
+ // 即使单个视频失败,也继续加载下一个
1416
+ }
1417
+ }
1418
+ console.log('🎉 所有视频顺序预加载处理完成');
1419
+ }
1420
+ /**
1421
+ * 顺序视频URL处理器
1422
+ * 一旦获得视频URL,按顺序预加载
1423
+ */
1424
+ handleVideoUrlReceivedSequential(videoUrls, context = '') {
1425
+ if (!videoUrls || videoUrls.length === 0)
1426
+ return;
1427
+ const validUrls = videoUrls.filter(url => url && url.trim());
1428
+ if (validUrls.length === 0)
1429
+ return;
1430
+ console.log(`📥 收到${validUrls.length}个视频URL,将按顺序预加载 (${context})`);
1431
+ // 异步顺序预加载,不阻塞主流程
1432
+ this.sequentialPreloadVideos(validUrls).catch(error => {
1433
+ console.warn(`⚠️ 顺序预加载视频失败 (${context}):`, error);
1434
+ });
1435
+ }
1436
+ /**
1437
+ * 处理页面从后台恢复(关屏/切后台回来)
1438
+ * 检测并恢复可能卡住的流程
1439
+ */
1440
+ handlePageResume() {
1441
+ // 如果任务已完成或未打开,不需要处理
1442
+ if (this.isTaskCompleted || !this.isOpen || this.isEndingInterview) {
1443
+ return;
1444
+ }
1445
+ console.log('页面恢复可见,检查流程状态:', {
1446
+ waitingForDigitalHuman: this.waitingForDigitalHuman,
1447
+ isPlayingDigitalHumanVideo: this.isPlayingDigitalHumanVideo,
1448
+ waitingToRecord: this.waitingToRecord,
1449
+ isRecording: this.isRecording,
1450
+ showRecordingUI: this.showRecordingUI,
1451
+ });
1452
+ // 场景1: 数字人视频正在播放但可能被浏览器暂停/中断
1453
+ // 尝试恢复视频播放,如果3秒后仍未结束则跳过
1454
+ if (this.isPlayingDigitalHumanVideo) {
1455
+ const videoEl = this.hostElement.shadowRoot?.querySelector('.digital-human-background-video');
1456
+ if (videoEl) {
1457
+ // 尝试恢复播放
1458
+ const playPromise = videoEl.play();
1459
+ if (playPromise) {
1460
+ playPromise.catch(() => {
1461
+ console.warn('恢复数字人视频播放失败,跳过视频');
1462
+ this.skipDigitalHumanVideo();
1463
+ });
1464
+ }
1465
+ // 设置安全超时:如果3秒后视频还没结束,直接跳过
1466
+ setTimeout(() => {
1467
+ if (this.isPlayingDigitalHumanVideo) {
1468
+ console.warn('数字人视频恢复后3秒仍未结束,强制跳过');
1469
+ this.skipDigitalHumanVideo();
1470
+ }
1471
+ }, 3000);
1472
+ }
1473
+ else {
1474
+ // 视频元素不存在,直接跳过
1475
+ this.skipDigitalHumanVideo();
1476
+ }
1477
+ return;
1478
+ }
1479
+ // 场景2: 等待数字人但视频已经不在播放状态(ended事件丢失)
1480
+ if (this.waitingForDigitalHuman && !this.isPlayingDigitalHumanVideo) {
1481
+ console.warn('检测到waitingForDigitalHuman卡住(视频未在播放),直接开始录制流程');
1482
+ this.waitingForDigitalHuman = false;
1483
+ this.startWaitingToRecord();
1484
+ return;
1485
+ }
1486
+ // 场景3: 正在录制中,检查MediaStream是否仍然有效
1487
+ if (this.isRecording && this.recordingStream) {
1488
+ const tracks = this.recordingStream.getTracks();
1489
+ const allEnded = tracks.length > 0 && tracks.every(track => track.readyState === 'ended');
1490
+ if (allEnded) {
1491
+ console.warn('检测到录制的MediaStream已被系统回收,停止录制');
1492
+ this.deviceError = '设备被系统中断,请重新录制';
1493
+ this.stopRecording();
1494
+ }
1495
+ }
1496
+ }
1497
+ /**
1498
+ * 跳过当前数字人视频播放,直接进入录制流程
1499
+ */
1500
+ skipDigitalHumanVideo() {
1501
+ this.digitalHumanVideoUrl = this.digitalHumanDefaultVideoUrl;
1502
+ this.isPlayingDigitalHumanVideo = false;
1503
+ this.isPlayingWelcomeVideo = false;
1504
+ if (this.waitingForDigitalHuman && !this.isTaskCompleted && !this.isEndingInterview) {
1505
+ this.waitingForDigitalHuman = false;
1506
+ this.startWaitingToRecord();
1507
+ }
1508
+ }
1509
+ /**
1510
+ * 处理数字人视频播放结束
1511
+ */
1512
+ handleVideoElementEnded = async () => {
1513
+ if (this.isPlayingDigitalHumanVideo) {
1514
+ console.log('数字人视频播放完成,是否为欢迎视频:', this.isPlayingWelcomeVideo);
1515
+ // 只通过状态来控制video元素,恢复默认视频
1516
+ this.digitalHumanVideoUrl = this.digitalHumanDefaultVideoUrl;
1517
+ this.isPlayingDigitalHumanVideo = false;
1518
+ const wasWelcomeVideo = this.isPlayingWelcomeVideo;
1519
+ this.isPlayingWelcomeVideo = false;
1520
+ // 如果是开场白视频播放完成,检查是否有待播放的生成视频
1521
+ if (wasWelcomeVideo && this.pendingGeneratedVideoUrl) {
1522
+ console.log('开场白视频播放完成,开始播放生成的视频:', this.pendingGeneratedVideoUrl);
1523
+ const videoUrl = this.pendingGeneratedVideoUrl;
1524
+ this.pendingGeneratedVideoUrl = ''; // 清空待播放视频URL
1525
+ await this.playDigitalHumanVideo(videoUrl);
1526
+ return; // 播放生成的视频,不执行后续的录制流程
1527
+ }
1528
+ // 只有非欢迎视频播放完成后,才开始录制流程
1529
+ // 如果任务已完成或正在结束面试,不开始录制流程
1530
+ if (this.waitingForDigitalHuman && !this.isTaskCompleted && !this.isEndingInterview && !wasWelcomeVideo) {
1531
+ this.waitingForDigitalHuman = false;
1532
+ this.startWaitingToRecord();
1533
+ }
1534
+ }
1535
+ };
1536
+ /**
1537
+ * 处理面试结束按钮点击 - 显示二次确认模态框
1538
+ */
1539
+ handleInterviewEnd = () => {
1540
+ // 如果已经在结束流程中或任务已完成,不允许再次点击
1541
+ if (this.isEndingInterview || this.isTaskCompleted) {
1542
+ console.log('已在结束流程中或任务已完成,忽略点击');
1543
+ return;
1544
+ }
1545
+ console.log('点击结束面试按钮,显示确认模态框');
1546
+ this.showEndInterviewConfirmModal = true;
1547
+ };
1548
+ /**
1549
+ * 确认结束面试
1550
+ */
1551
+ handleConfirmEndInterview = async () => {
1552
+ try {
1553
+ console.log('用户确认结束面试');
1554
+ this.showEndInterviewConfirmModal = false;
1555
+ // 设置结束面试标志
1556
+ this.isEndingInterview = true;
1557
+ // 停止所有可能正在进行的流程
1558
+ // 1. 清除等待录制计时器
1559
+ if (this.waitingTimer) {
1560
+ clearInterval(this.waitingTimer);
1561
+ this.waitingTimer = null;
1562
+ }
1563
+ this.waitingToRecord = false;
1564
+ // 2. 清除等待数字人标志
1565
+ this.waitingForDigitalHuman = false;
1566
+ // 3. 如果正在录制,直接停止录制(不显示确认框)
1567
+ if (this.isRecording) {
1568
+ console.log('正在录制,直接停止录制并上传视频');
1569
+ this.stopRecording(); // 直接调用 stopRecording,不通过 handleStopRecording
1570
+ // uploadRecordedVideo 会在上传完成后检查 isEndingInterview 标志
1571
+ // 并发送结束面试消息
1572
+ }
1573
+ else {
1574
+ // 如果没有录制,直接发送结束面试消息
1575
+ await this.sendEndInterviewMessage();
1576
+ }
1577
+ }
1578
+ catch (error) {
1579
+ console.error('点击结束发送消息失败:', error);
1580
+ this.isEndingInterview = false;
1581
+ SentryReporter.captureError(error, {
1582
+ action: 'handleConfirmEndInterview',
1583
+ component: 'components-oem-virtual-chat-modal',
1584
+ title: '点击结束发送消息失败',
1585
+ });
1586
+ ErrorEventBus.emitError({
1587
+ error: error,
1588
+ message: '点击结束发送消息失败',
1589
+ });
1590
+ }
1591
+ };
1592
+ /**
1593
+ * 发送结束面试消息
1594
+ */
1595
+ sendEndInterviewMessage = async () => {
1596
+ try {
1597
+ const endMessage = '请结束面试';
1598
+ console.log('面试结束消息已发送:', {
1599
+ conversation_id: this.conversationId,
1600
+ message: endMessage,
1601
+ });
1602
+ // 发送消息到API
1603
+ await this.sendMessageToAPI(endMessage);
1604
+ // 重置标志
1605
+ this.isEndingInterview = false;
1606
+ }
1607
+ catch (error) {
1608
+ console.error('发送结束面试消息失败:', error);
1609
+ this.isEndingInterview = false;
1610
+ throw error;
1611
+ }
1612
+ };
1613
+ /**
1614
+ * 取消结束面试
1615
+ */
1616
+ handleCancelEndInterview = () => {
1617
+ console.log('用户取消结束面试');
1618
+ this.showEndInterviewConfirmModal = false;
1619
+ };
1620
+ /**
1621
+ * 计算已回答的题数
1622
+ */
1623
+ getAnsweredQuestionCount = () => {
1624
+ // 统计用户已经回答的问题数量(有answer的消息)
1625
+ return this.messages.filter(msg => msg.answer && msg.answer.trim()).length;
1626
+ };
1627
+ renderChatHistory() {
1628
+ // 如果正在播放欢迎视频,优先显示欢迎语
1629
+ if (this.isPlayingWelcomeVideo) {
1630
+ // 验证并调整开场白索引
1631
+ const validIndex = Math.max(0, Math.min(2, this.openingIndex || 0)); // 限制索引范围0-2
1632
+ const selectedOpeningContent = this.digitalHumanOpeningContents[validIndex] || this.digitalHumanOpeningContents[0];
1633
+ const welcomeText = selectedOpeningContent.text;
1634
+ return (h("div", { class: "ai-message-item" }, h("div", { class: "ai-message-content" }, welcomeText)));
1635
+ }
1636
+ // 如果正在生成数字人视频,显示查看面试信息的提示
1637
+ if (this.isGeneratingDigitalHumanVideo || this.isLoading) {
1638
+ return (h("div", { class: "ai-message-item" }, h("div", { class: "ai-message-content" }, "\u8BF7\u7A0D\u7B49\uFF0C\u6211\u67E5\u770B\u4E00\u4E0B\u672C\u6B21\u9762\u8BD5\u7684\u4FE1\u606F...")));
1639
+ }
1640
+ // 优先显示正在流式输出的消息
1641
+ if (this.currentStreamingMessage && this.currentStreamingMessage.answer) {
1642
+ return (h("div", { class: "ai-message-item streaming" }, h("div", { class: "ai-message-content", innerHTML: this.currentStreamingMessage.answer }), this.currentStreamingMessage.isStreaming && (h("div", { class: "typing-indicator" }, h("span", null), h("span", null), h("span", null)))));
1643
+ }
1644
+ // 找到最后一条有AI回复的消息
1645
+ const lastMessageWithAnswer = [...this.messages].reverse().find(msg => msg.answer && msg.answer.trim());
1646
+ if (lastMessageWithAnswer) {
1647
+ return (h("div", { class: "ai-message-item" }, h("div", { class: "ai-message-content", innerHTML: lastMessageWithAnswer.answer })));
1648
+ }
1649
+ return (h("div", { class: "ai-message-item" }, h("div", { class: "ai-message-content" }, "\u8BF7\u7A0D\u7B49\uFF0C\u6211\u67E5\u770B\u4E00\u4E0B\u672C\u6B21\u9762\u8BD5\u7684\u4FE1\u606F...")));
1650
+ }
1651
+ render() {
1652
+ if (!this.isOpen)
1653
+ return null;
1654
+ const modalStyle = {
1655
+ zIndex: String(this.zIndex),
1656
+ };
1657
+ const containerClass = {
1658
+ 'modal-container': true,
1659
+ 'fullscreen': this.fullscreen,
1660
+ 'digital-human-mode': !!this.digitalId,
1661
+ };
1662
+ const overlayClass = {
1663
+ 'modal-overlay': true,
1664
+ 'fullscreen-overlay': this.fullscreen,
1665
+ };
1666
+ return (h("div", { class: overlayClass, style: modalStyle }, h("div", { class: containerClass }, h("button", { class: "close-button", onClick: this.handleCloseClick }, h("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "currentColor" }, h("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }))), this.digitalId && this.digitalHumanVideoUrl && (h("div", { class: "digital-human-background" }, h("video", { autoplay: true, playsinline: true, loop: !this.isPlayingDigitalHumanVideo, muted: !this.isPlayingDigitalHumanVideo, src: this.digitalHumanVideoUrl, class: "digital-human-background-video", onEnded: this.handleVideoElementEnded, onPlay: e => {
1667
+ // 浏览器切换 src 后可能重置 playbackRate,在 play 事件里补设一次
1668
+ const el = e.target;
1669
+ const isDefaultVideo = this.digitalHumanVideoUrl === this.digitalHumanDefaultVideoUrl;
1670
+ el.playbackRate = isDefaultVideo ? 1.0 : this.playbackRate;
1671
+ }, ref: el => {
1672
+ if (el) {
1673
+ // 只有非默认占位视频才应用语速控制
1674
+ const isDefaultVideo = this.digitalHumanVideoUrl === this.digitalHumanDefaultVideoUrl;
1675
+ el.playbackRate = isDefaultVideo ? 1.0 : this.playbackRate;
1676
+ // 微信环境下,确保视频URL变化时重新加载
1677
+ // 如果src已经设置但视频未加载,手动触发load
1678
+ if (el.src && el.readyState === 0) {
1679
+ el.load();
1680
+ }
1681
+ }
1682
+ } }))), this.renderVideoPreview(), h("div", { class: "modal-content-layer" }, h("div", { class: "main-content" }, h("div", { class: "chat-history-section" }, this.renderChatHistory()), this.showEndInterviewButton && !this.isTaskCompleted && this.conversationId && (h("div", { class: "end-interview-button-container" }, h("button", { class: "end-interview-button", onClick: this.handleInterviewEnd, disabled: this.isEndingInterview || !this.isRecording, title: this.isEndingInterview ? '正在结束面试...' : !this.isRecording ? '请在录制时点击' : '结束面试' }, this.isEndingInterview ? '正在结束...' : '结束面试'))), this.renderRecordingStatusBar())), h("div", { style: { position: 'relative', zIndex: '100' } }, h("components-confirm-modal", { isOpen: this.showConfirmModal, modalTitle: "\u786E\u8BA4\u5B8C\u6210\u56DE\u7B54\uFF1F", okText: "\u786E\u8BA4\u5B8C\u6210", cancelText: "\u7EE7\u7EED\u5F55\u5236", okType: "danger", onOk: this.handleConfirmModalOk, onCancel: this.handleConfirmModalCancelEvent, onClosed: this.handleConfirmModalCancel }, h("div", { style: { marginBottom: '16px' } }, h("div", { style: {
1683
+ fontSize: '14px',
1684
+ color: '#1890ff',
1685
+ fontWeight: '500',
1686
+ marginBottom: '16px',
1687
+ textAlign: 'center',
1688
+ } }, "\u5F53\u524D\u5F55\u5236\u65F6\u957F\uFF1A", (() => {
1689
+ const elapsedSeconds = this.recordingStartTime > 0 ? Math.floor((Date.now() - this.recordingStartTime) / 1000) : 0;
1690
+ const minutes = Math.floor(elapsedSeconds / 60);
1691
+ const seconds = elapsedSeconds % 60;
1692
+ return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
1693
+ })()), h("div", { style: {
1694
+ display: 'flex',
1695
+ alignItems: 'center',
1696
+ gap: '8px',
1697
+ padding: '12px 16px',
1698
+ background: '#fff7e6',
1699
+ border: '1px solid #ffec99',
1700
+ borderRadius: '6px',
1701
+ color: '#d46b08',
1702
+ fontSize: '14px',
1703
+ marginBottom: '16px',
1704
+ } }, h("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "currentColor" }, h("path", { d: "M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" })), h("span", null, "\u6CE8\u610F\uFF1A\u5F55\u5236\u4ECD\u5728\u8FDB\u884C\u4E2D")), h("div", { style: {
1705
+ fontSize: '16px',
1706
+ color: '#595959',
1707
+ lineHeight: '1.5',
1708
+ marginBottom: '16px',
1709
+ } }, "\u70B9\u51FB\"\u786E\u8BA4\u5B8C\u6210\"\u5C06\u7ED3\u675F\u672C\u9898\u56DE\u7B54"), h("label", { style: {
1710
+ display: 'flex',
1711
+ alignItems: 'center',
1712
+ gap: '8px',
1713
+ cursor: 'pointer',
1714
+ padding: '8px 0',
1715
+ userSelect: 'none',
1716
+ } }, h("input", { type: "checkbox", style: {
1717
+ width: '16px',
1718
+ height: '16px',
1719
+ accentColor: '#1890ff',
1720
+ cursor: 'pointer',
1721
+ margin: '0',
1722
+ }, checked: this.skipConfirmThisInterview, onChange: this.handleSkipConfirmChange }), h("span", { style: {
1723
+ fontSize: '14px',
1724
+ color: '#595959',
1725
+ cursor: 'pointer',
1726
+ lineHeight: '1.4',
1727
+ } }, "\u672C\u6B21\u9762\u8BD5\u4E0D\u518D\u63D0\u9192"))))), h("div", { style: { position: 'relative', zIndex: '100' } }, h("components-confirm-modal", { isOpen: this.showCloseConfirmModal, modalTitle: "\u786E\u8BA4\u5173\u95ED\uFF1F", okText: "\u786E\u8BA4\u5173\u95ED", cancelText: "\u53D6\u6D88", okType: "danger", onOk: this.handleCloseConfirmOk, onCancel: this.handleCloseConfirmCancel, onClosed: this.handleCloseConfirmCancel }, h("div", { style: { marginBottom: '16px' } }, h("div", { style: {
1728
+ fontSize: '16px',
1729
+ color: '#595959',
1730
+ lineHeight: '1.5',
1731
+ marginBottom: '16px',
1732
+ } }, this.isTaskCompleted ? '面试已结束,确认要关闭吗?' : '您的面试未完成,确认要关闭吗?')))), h("div", { style: { position: 'relative', zIndex: '100' } }, h("components-confirm-modal", { isOpen: this.showEndInterviewConfirmModal, modalTitle: "\u786E\u8BA4\u7ED3\u675F\u9762\u8BD5\uFF1F", okText: "\u786E\u8BA4\u7ED3\u675F", cancelText: "\u53D6\u6D88", okType: "danger", onOk: this.handleConfirmEndInterview, onCancel: this.handleCancelEndInterview, onClosed: this.handleCancelEndInterview }, h("div", { style: { marginBottom: '16px' } }, (() => {
1733
+ const answeredCount = this.getAnsweredQuestionCount();
1734
+ return (h("div", null, h("div", { style: {
1735
+ fontSize: '14px',
1736
+ color: '#1890ff',
1737
+ fontWeight: '500',
1738
+ marginBottom: '16px',
1739
+ textAlign: 'center',
1740
+ } }, "\u5F53\u524D\u7B2C ", answeredCount, " \u9898"), answeredCount < 3 && (h("div", { style: {
1741
+ display: 'flex',
1742
+ alignItems: 'flex-start',
1743
+ gap: '8px',
1744
+ padding: '12px 16px',
1745
+ background: '#fff2e8',
1746
+ border: '1px solid #ffbb96',
1747
+ borderRadius: '6px',
1748
+ color: '#d4380d',
1749
+ fontSize: '14px',
1750
+ marginBottom: '16px',
1751
+ } }, h("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "currentColor", style: { flexShrink: '0', marginTop: '2px' } }, h("path", { d: "M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" })), h("span", null, h("strong", null, "\u8B66\u544A\uFF1A"), "\u5F53\u524D\u5DF2\u56DE\u7B54\u9898\u6570\u5C0F\u4E8E 3 \u9898\uFF0C\u7ED3\u675F\u9762\u8BD5\u53EF\u80FD\u65E0\u6CD5\u6B63\u5E38\u5B8C\u6210\u9762\u8BD5\u6D41\u7A0B\uFF0C\u5EFA\u8BAE\u7EE7\u7EED\u5B8C\u6210\u66F4\u591A\u9898\u76EE\u540E\u518D\u7ED3\u675F\u9762\u8BD5\u3002"))), h("div", { style: {
1752
+ fontSize: '14px',
1753
+ color: '#595959',
1754
+ lineHeight: '1.6',
1755
+ textAlign: 'center',
1756
+ } }, "\u786E\u8BA4\u8981\u7ED3\u675F\u9762\u8BD5\u5417\uFF1F\u7ED3\u675F\u540E\u5C06\u751F\u6210\u9762\u8BD5\u62A5\u544A\u3002")));
1757
+ })()))))));
1758
+ }
1759
+ renderVideoPreview() {
1760
+ if (!this.showRecordingUI) {
1761
+ return null;
1762
+ }
1763
+ return (h("div", { class: "recording-preview-top-right" }, h("video", { class: "user-video-preview", autoPlay: true, playsInline: true, muted: true, style: { transform: 'scaleX(-1)' }, ref: el => {
1764
+ if (el && this.recordingStream && !this.videoRef) {
1765
+ this.videoRef = el;
1766
+ }
1767
+ } })));
1768
+ }
1769
+ renderRecordingStatusBar() {
1770
+ if (this.showRecordingUI) {
1771
+ const minutes = Math.floor(this.recordingTimeLeft / 60)
1772
+ .toString()
1773
+ .padStart(2, '0');
1774
+ const seconds = (this.recordingTimeLeft % 60).toString().padStart(2, '0');
1775
+ return (h("div", { class: "recording-status-bar" }, h("div", { class: "audio-waveform" }, h("div", { class: "waveform-bar" }), h("div", { class: "waveform-bar" }), h("div", { class: "waveform-bar" }), h("div", { class: "waveform-bar" }), h("div", { class: "waveform-bar" }), h("div", { class: "waveform-bar" }), h("div", { class: "waveform-bar" }), h("div", { class: "waveform-bar" })), h("span", { class: "recording-timer" }, minutes, ":", seconds), h("button", { class: "finish-recording-btn", onClick: () => this.handleStopRecording() }, h("img", { src: "https://pcm-resource-1312611446.cos.ap-guangzhou.myqcloud.com/sdk/icon/gou.png", width: "16", height: "16", alt: "\u5B8C\u6210", style: { filter: 'brightness(0) invert(1)' } }))));
1776
+ }
1777
+ return h("div", { class: "recording-status-bar" }, this.renderStatusIndicator());
1778
+ }
1779
+ renderStatusIndicator() {
1780
+ // 优先显示设备错误
1781
+ if (this.deviceError) {
1782
+ return (h("div", { class: "status-indicator-text error" }, h("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "currentColor" }, h("path", { d: "M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" })), h("span", null, this.deviceError)));
1783
+ }
1784
+ // 任务完成
1785
+ if (this.isTaskCompleted) {
1786
+ return (h("div", { class: "status-indicator-text" }, h("span", null, "\u9762\u8BD5\u5DF2\u5B8C\u6210")));
1787
+ }
1788
+ // 正在生成数字人视频
1789
+ if (this.waitingForDigitalHuman && this.digitalId) {
1790
+ return (h("div", { class: "status-indicator-text loading" }, h("div", { class: "loading-spinner-small" }), h("span", null, "\u9762\u8BD5\u5B98\u601D\u8003\u4E2D...")));
1791
+ }
1792
+ // 上传视频中
1793
+ if (this.isUploadingVideo) {
1794
+ return (h("div", { class: "status-indicator-text loading" }, h("div", { class: "loading-spinner-small" }), h("span", null, "\u6B63\u5728\u63D0\u4EA4\u56DE\u7B54...")));
1795
+ }
1796
+ // 等待题目
1797
+ if (this.isLoading || this.currentStreamingMessage) {
1798
+ return (h("div", { class: "status-indicator-text loading" }, h("div", { class: "loading-spinner-small" }), h("span", null, "\u9898\u76EE\u751F\u6210\u4E2D...")));
1799
+ }
1800
+ // 等待录制
1801
+ if (this.waitingToRecord) {
1802
+ return (h("div", { class: "status-indicator-text" }, h("span", null, this.waitingTimeLeft, " \u79D2\u540E\u5F00\u59CB\u60A8\u7684\u56DE\u7B54")));
1803
+ }
1804
+ // 准备就绪
1805
+ return (h("div", { class: "status-indicator-text ready" }, h("img", { src: "https://pcm-resource-1312611446.cos.ap-guangzhou.myqcloud.com/sdk/icon/gou.png", width: "16", height: "16", alt: "\u51C6\u5907\u5C31\u7EEA" }), h("span", null, "\u51C6\u5907\u5C31\u7EEA")));
1806
+ }
1807
+ static get is() { return "components-oem-virtual-chat-modal"; }
1808
+ static get encapsulation() { return "shadow"; }
1809
+ static get originalStyleUrls() {
1810
+ return {
1811
+ "$": ["components-oem-virtual-chat-modal.css", "../../global/host.css"]
1812
+ };
1813
+ }
1814
+ static get styleUrls() {
1815
+ return {
1816
+ "$": ["components-oem-virtual-chat-modal.css", "../../global/host.css"]
1817
+ };
1818
+ }
1819
+ static get properties() {
1820
+ return {
1821
+ "token": {
1822
+ "type": "string",
1823
+ "attribute": "token",
1824
+ "mutable": false,
1825
+ "complexType": {
1826
+ "original": "string",
1827
+ "resolved": "string",
1828
+ "references": {}
1829
+ },
1830
+ "required": false,
1831
+ "optional": true,
1832
+ "docs": {
1833
+ "tags": [],
1834
+ "text": "SDK\u9274\u6743\u5BC6\u94A5"
1835
+ },
1836
+ "getter": false,
1837
+ "setter": false,
1838
+ "reflect": false
1839
+ },
1840
+ "isOpen": {
1841
+ "type": "boolean",
1842
+ "attribute": "is-open",
1843
+ "mutable": true,
1844
+ "complexType": {
1845
+ "original": "boolean",
1846
+ "resolved": "boolean",
1847
+ "references": {}
1848
+ },
1849
+ "required": false,
1850
+ "optional": false,
1851
+ "docs": {
1852
+ "tags": [],
1853
+ "text": "\u662F\u5426\u663E\u793A\u804A\u5929\u6A21\u6001\u6846"
1854
+ },
1855
+ "getter": false,
1856
+ "setter": false,
1857
+ "reflect": false,
1858
+ "defaultValue": "false"
1859
+ },
1860
+ "zIndex": {
1861
+ "type": "number",
1862
+ "attribute": "z-index",
1863
+ "mutable": true,
1864
+ "complexType": {
1865
+ "original": "number",
1866
+ "resolved": "number",
1867
+ "references": {}
1868
+ },
1869
+ "required": false,
1870
+ "optional": true,
1871
+ "docs": {
1872
+ "tags": [],
1873
+ "text": "\u804A\u5929\u6846\u7684\u9875\u9762\u5C42\u7EA7"
1874
+ },
1875
+ "getter": false,
1876
+ "setter": false,
1877
+ "reflect": false
1878
+ },
1879
+ "conversationId": {
1880
+ "type": "string",
1881
+ "attribute": "conversation-id",
1882
+ "mutable": true,
1883
+ "complexType": {
1884
+ "original": "string",
1885
+ "resolved": "string",
1886
+ "references": {}
1887
+ },
1888
+ "required": false,
1889
+ "optional": true,
1890
+ "docs": {
1891
+ "tags": [],
1892
+ "text": "\u4F1A\u8BDDID\uFF0C\u4F20\u5165\u7EE7\u7EED\u5BF9\u8BDD\uFF0C\u5426\u5219\u521B\u5EFA\u65B0\u4F1A\u8BDD"
1893
+ },
1894
+ "getter": false,
1895
+ "setter": false,
1896
+ "reflect": false
1897
+ },
1898
+ "defaultQuery": {
1899
+ "type": "string",
1900
+ "attribute": "default-query",
1901
+ "mutable": false,
1902
+ "complexType": {
1903
+ "original": "string",
1904
+ "resolved": "string",
1905
+ "references": {}
1906
+ },
1907
+ "required": false,
1908
+ "optional": false,
1909
+ "docs": {
1910
+ "tags": [],
1911
+ "text": "\u9ED8\u8BA4\u53D1\u9001\u6587\u672C"
1912
+ },
1913
+ "getter": false,
1914
+ "setter": false,
1915
+ "reflect": false,
1916
+ "defaultValue": "'\u4F60\u597D\uFF01\u8058\u624D\u732B'"
1917
+ },
1918
+ "maxRecordingTime": {
1919
+ "type": "number",
1920
+ "attribute": "max-recording-time",
1921
+ "mutable": false,
1922
+ "complexType": {
1923
+ "original": "number",
1924
+ "resolved": "number",
1925
+ "references": {}
1926
+ },
1927
+ "required": false,
1928
+ "optional": false,
1929
+ "docs": {
1930
+ "tags": [],
1931
+ "text": "\u89C6\u9891\u5F55\u5236\u6700\u5927\u65F6\u957F\uFF08\u79D2\uFF09"
1932
+ },
1933
+ "getter": false,
1934
+ "setter": false,
1935
+ "reflect": false,
1936
+ "defaultValue": "120"
1937
+ },
1938
+ "countdownWarningTime": {
1939
+ "type": "number",
1940
+ "attribute": "countdown-warning-time",
1941
+ "mutable": false,
1942
+ "complexType": {
1943
+ "original": "number",
1944
+ "resolved": "number",
1945
+ "references": {}
1946
+ },
1947
+ "required": false,
1948
+ "optional": false,
1949
+ "docs": {
1950
+ "tags": [],
1951
+ "text": "\u5F55\u5236\u5012\u8BA1\u65F6\u63D0\u9192\u65F6\u95F4\uFF08\u79D2\uFF09\r\n\u5F53\u5269\u4F59\u65F6\u95F4\u5C0F\u4E8E\u6B64\u503C\u65F6\uFF0C\u663E\u793A\u5012\u8BA1\u65F6\u8B66\u544A"
1952
+ },
1953
+ "getter": false,
1954
+ "setter": false,
1955
+ "reflect": false,
1956
+ "defaultValue": "30"
1957
+ },
1958
+ "fullscreen": {
1959
+ "type": "boolean",
1960
+ "attribute": "fullscreen",
1961
+ "mutable": false,
1962
+ "complexType": {
1963
+ "original": "boolean",
1964
+ "resolved": "boolean",
1965
+ "references": {}
1966
+ },
1967
+ "required": false,
1968
+ "optional": false,
1969
+ "docs": {
1970
+ "tags": [],
1971
+ "text": "\u662F\u5426\u4EE5\u5168\u5C4F\u6A21\u5F0F\u6253\u5F00\uFF0C\u79FB\u52A8\u7AEF\u5EFA\u8BAE\u8BBE\u7F6E\u4E3Atrue"
1972
+ },
1973
+ "getter": false,
1974
+ "setter": false,
1975
+ "reflect": false,
1976
+ "defaultValue": "false"
1977
+ },
1978
+ "digitalId": {
1979
+ "type": "string",
1980
+ "attribute": "digital-id",
1981
+ "mutable": false,
1982
+ "complexType": {
1983
+ "original": "string",
1984
+ "resolved": "string",
1985
+ "references": {}
1986
+ },
1987
+ "required": false,
1988
+ "optional": true,
1989
+ "docs": {
1990
+ "tags": [],
1991
+ "text": "\u865A\u62DF\u6570\u5B57\u4EBAID\uFF0C\u6307\u5B9A\u5219\u5F00\u542F\u865A\u62DF\u6570\u5B57\u4EBA\u529F\u80FD"
1992
+ },
1993
+ "getter": false,
1994
+ "setter": false,
1995
+ "reflect": false
1996
+ },
1997
+ "openingIndex": {
1998
+ "type": "number",
1999
+ "attribute": "opening-index",
2000
+ "mutable": false,
2001
+ "complexType": {
2002
+ "original": "number",
2003
+ "resolved": "number",
2004
+ "references": {}
2005
+ },
2006
+ "required": false,
2007
+ "optional": false,
2008
+ "docs": {
2009
+ "tags": [],
2010
+ "text": "\u6570\u5B57\u4EBA\u5F00\u573A\u767D\u7D22\u5F15\uFF0C\u7528\u4E8E\u9009\u62E9\u5F00\u573A\u767D\u548C\u5F00\u573A\u89C6\u9891\uFF08\u53EF\u9009\uFF1A0, 1, 2\uFF09\r\n0\u3001\u60A8\u597D\uFF0C\u6211\u662F\u8058\u624D\u732B AI \u9762\u8BD5\u52A9\u624B\u3002\u5F88\u9AD8\u5174\u4E3A\u4F60\u4E3B\u6301\u8FD9\u573A\u9762\u8BD5\uFF01\u5728\u5F00\u59CB\u524D\uFF0C\u8BF7\u786E\u4FDD\uFF1A\u8EAB\u5904\u5B89\u9759\u3001\u5149\u7EBF\u5145\u8DB3\u7684\u73AF\u5883\u3002\u7F51\u7EDC\u987A\u7545\uFF0C\u6444\u50CF\u5934\u548C\u9EA6\u514B\u98CE\u5DE5\u4F5C\u6B63\u5E38\u3002\u73B0\u5728\u6211\u6B63\u5728\u67E5\u770B\u672C\u6B21\u9762\u8BD5\u7684\u76F8\u5173\u4FE1\u606F\uFF0C\u4E3A\u60A8\u751F\u6210\u4E13\u5C5E\u9762\u8BD5\u9898\uFF0C\u9A6C\u4E0A\u5C31\u597D\uFF0C\u8BF7\u7A0D\u7B49\u7247\u523B\u3002</br>\r\n1\u3001\u60A8\u597D\uFF0C\u6211\u662F\u60A8\u7684 AI \u9762\u8BD5\u52A9\u624B\u3002\u6B22\u8FCE\u53C2\u52A0\u672C\u6B21AI\u9762\u8BD5\uFF01\u4E3A\u4E86\u83B7\u5F97\u6700\u4F73\u6548\u679C\uFF0C\u8BF7\u786E\u8BA4\uFF1A\u60A8\u5728\u5B89\u9759\u3001\u660E\u4EAE\u7684\u73AF\u5883\u4E2D\u3002\u60A8\u7684\u7F51\u7EDC\u7A33\u5B9A\uFF0C\u6444\u50CF\u5934\u548C\u9EA6\u514B\u98CE\u5DF2\u5F00\u542F\u3002\u6211\u4EEC\u6B63\u5728\u540E\u53F0\u4E3A\u60A8\u51C6\u5907\u672C\u6B21\u4E13\u5C5E\u9762\u8BD5\u5185\u5BB9\uFF0C\u5F88\u5FEB\u5F00\u59CB\uFF0C\u8BF7\u7A0D\u5019\u3002<br>\r\n2\u3001\u60A8\u597D\uFF0C\u6211\u662F\u60A8\u7684 AI \u9762\u8BD5\u52A9\u624B\u3002\u9762\u8BD5\u9A6C\u4E0A\u5F00\u59CB\u3002\u8D81\u6B64\u7247\u523B\uFF0C\u8BF7\u5FEB\u901F\u786E\u8BA4\uFF1A\u5468\u56F4\u5B89\u9759\u5417\uFF1F\u5149\u7EBF\u8DB3\u591F\u5417\uFF1F\u7F51\u7EDC\u6CA1\u95EE\u9898\uFF1F\u6444\u50CF\u5934\u548C\u9EA6\u514B\u98CE\u51C6\u5907\u597D\u4E86\u5417\uFF1F\u6211\u4EEC\u6B63\u5728\u4E3A\u60A8\u52A0\u8F7D\u4E2A\u6027\u5316\u7684\u9762\u8BD5\u73AF\u8282\uFF0C\u7A0D\u7B49\u5C31\u597D\uFF01"
2011
+ },
2012
+ "getter": false,
2013
+ "setter": false,
2014
+ "reflect": false,
2015
+ "defaultValue": "0"
2016
+ },
2017
+ "waitingTime": {
2018
+ "type": "number",
2019
+ "attribute": "waiting-time",
2020
+ "mutable": false,
2021
+ "complexType": {
2022
+ "original": "number",
2023
+ "resolved": "number",
2024
+ "references": {}
2025
+ },
2026
+ "required": false,
2027
+ "optional": false,
2028
+ "docs": {
2029
+ "tags": [],
2030
+ "text": "\u7B49\u5F85\u5F55\u5236\u65F6\u95F4\uFF08\u79D2\uFF09\u9ED8\u8BA410"
2031
+ },
2032
+ "getter": false,
2033
+ "setter": false,
2034
+ "reflect": false,
2035
+ "defaultValue": "10"
2036
+ },
2037
+ "playbackRate": {
2038
+ "type": "number",
2039
+ "attribute": "playback-rate",
2040
+ "mutable": false,
2041
+ "complexType": {
2042
+ "original": "number",
2043
+ "resolved": "number",
2044
+ "references": {}
2045
+ },
2046
+ "required": false,
2047
+ "optional": false,
2048
+ "docs": {
2049
+ "tags": [],
2050
+ "text": "\u6570\u5B57\u4EBA\u89C6\u9891\u64AD\u653E\u901F\u7387\uFF080.5-2.0\u4E4B\u95F4\uFF09\uFF0C\u9ED8\u8BA41.0"
2051
+ },
2052
+ "getter": false,
2053
+ "setter": false,
2054
+ "reflect": false,
2055
+ "defaultValue": "1.0"
2056
+ },
2057
+ "showEndInterviewButton": {
2058
+ "type": "boolean",
2059
+ "attribute": "show-end-interview-button",
2060
+ "mutable": false,
2061
+ "complexType": {
2062
+ "original": "boolean",
2063
+ "resolved": "boolean",
2064
+ "references": {}
2065
+ },
2066
+ "required": false,
2067
+ "optional": false,
2068
+ "docs": {
2069
+ "tags": [],
2070
+ "text": "\u662F\u5426\u663E\u793A\u7ED3\u675F\u9762\u8BD5\u6309\u94AE"
2071
+ },
2072
+ "getter": false,
2073
+ "setter": false,
2074
+ "reflect": false,
2075
+ "defaultValue": "false"
2076
+ },
2077
+ "customInputs": {
2078
+ "type": "unknown",
2079
+ "attribute": "custom-inputs",
2080
+ "mutable": true,
2081
+ "complexType": {
2082
+ "original": "Record<string, any>",
2083
+ "resolved": "{ [x: string]: any; }",
2084
+ "references": {
2085
+ "Record": {
2086
+ "location": "global",
2087
+ "id": "global::Record"
2088
+ }
2089
+ }
2090
+ },
2091
+ "required": false,
2092
+ "optional": false,
2093
+ "docs": {
2094
+ "tags": [],
2095
+ "text": "\u81EA\u5B9A\u4E49\u667A\u80FD\u4F53inputs\u8F93\u5165\u53C2\u6570"
2096
+ },
2097
+ "getter": false,
2098
+ "setter": false,
2099
+ "defaultValue": "{}"
2100
+ },
2101
+ "botId": {
2102
+ "type": "string",
2103
+ "attribute": "bot-id",
2104
+ "mutable": false,
2105
+ "complexType": {
2106
+ "original": "string",
2107
+ "resolved": "string",
2108
+ "references": {}
2109
+ },
2110
+ "required": false,
2111
+ "optional": true,
2112
+ "docs": {
2113
+ "tags": [],
2114
+ "text": "\u673A\u5668\u4EBAID"
2115
+ },
2116
+ "getter": false,
2117
+ "setter": false,
2118
+ "reflect": false
2119
+ }
2120
+ };
2121
+ }
2122
+ static get states() {
2123
+ return {
2124
+ "messages": {},
2125
+ "isLoading": {},
2126
+ "currentStreamingMessage": {},
2127
+ "isLoadingHistory": {},
2128
+ "isRecording": {},
2129
+ "recordingStream": {},
2130
+ "recordedBlob": {},
2131
+ "mediaRecorder": {},
2132
+ "recordingTimeLeft": {},
2133
+ "showRecordingUI": {},
2134
+ "recordingTimer": {},
2135
+ "recordingStartTime": {},
2136
+ "waitingToRecord": {},
2137
+ "waitingTimer": {},
2138
+ "waitingTimeLeft": {},
2139
+ "currentQuestionNumber": {},
2140
+ "showCountdownWarning": {},
2141
+ "isUploadingVideo": {},
2142
+ "digitalHumanVideoUrl": {},
2143
+ "digitalHumanDefaultVideoUrl": {},
2144
+ "virtualmanKey": {},
2145
+ "isPlayingDigitalHumanVideo": {},
2146
+ "digitalHumanVideoReady": {},
2147
+ "digitalHumanOpeningContents": {},
2148
+ "isPlayingWelcomeVideo": {},
2149
+ "isGeneratingDigitalHumanVideo": {},
2150
+ "pendingGeneratedVideoUrl": {},
2151
+ "waitingForDigitalHuman": {},
2152
+ "agentLogo": {},
2153
+ "isTaskCompleted": {},
2154
+ "deviceError": {},
2155
+ "showConfirmModal": {},
2156
+ "skipConfirmThisInterview": {},
2157
+ "showCloseConfirmModal": {},
2158
+ "showEndInterviewConfirmModal": {},
2159
+ "isEndingInterview": {},
2160
+ "isComponentActive": {}
2161
+ };
2162
+ }
2163
+ static get events() {
2164
+ return [{
2165
+ "method": "streamComplete",
2166
+ "name": "streamComplete",
2167
+ "bubbles": true,
2168
+ "cancelable": true,
2169
+ "composed": true,
2170
+ "docs": {
2171
+ "tags": [],
2172
+ "text": "\u4E00\u8F6E\u5BF9\u8BDD\u7ED3\u675F\u65F6\u7684\u56DE\u8C03"
2173
+ },
2174
+ "complexType": {
2175
+ "original": "StreamCompleteEventData",
2176
+ "resolved": "StreamCompleteEventData",
2177
+ "references": {
2178
+ "StreamCompleteEventData": {
2179
+ "location": "import",
2180
+ "path": "../../interfaces/events",
2181
+ "id": "src/interfaces/events.ts::StreamCompleteEventData"
2182
+ }
2183
+ }
2184
+ }
2185
+ }, {
2186
+ "method": "conversationStart",
2187
+ "name": "conversationStart",
2188
+ "bubbles": true,
2189
+ "cancelable": true,
2190
+ "composed": true,
2191
+ "docs": {
2192
+ "tags": [],
2193
+ "text": "\u65B0\u4F1A\u8BDD\u5F00\u59CB\u7684\u56DE\u8C03\uFF0C\u53EA\u4F1A\u5728\u4E00\u8F6E\u5BF9\u8BDD\u5F00\u59CB\u65F6\u89E6\u53D1\u4E00\u6B21"
2194
+ },
2195
+ "complexType": {
2196
+ "original": "ConversationStartEventData",
2197
+ "resolved": "ConversationStartEventData",
2198
+ "references": {
2199
+ "ConversationStartEventData": {
2200
+ "location": "import",
2201
+ "path": "../../interfaces/events",
2202
+ "id": "src/interfaces/events.ts::ConversationStartEventData"
2203
+ }
2204
+ }
2205
+ }
2206
+ }, {
2207
+ "method": "interviewComplete",
2208
+ "name": "interviewComplete",
2209
+ "bubbles": true,
2210
+ "cancelable": true,
2211
+ "composed": true,
2212
+ "docs": {
2213
+ "tags": [],
2214
+ "text": "\u5F53\u804A\u5929\u5B8C\u6210\u65F6\u89E6\u53D1"
2215
+ },
2216
+ "complexType": {
2217
+ "original": "InterviewCompleteEventData",
2218
+ "resolved": "InterviewCompleteEventData",
2219
+ "references": {
2220
+ "InterviewCompleteEventData": {
2221
+ "location": "import",
2222
+ "path": "../../interfaces/events",
2223
+ "id": "src/interfaces/events.ts::InterviewCompleteEventData"
2224
+ }
2225
+ }
2226
+ }
2227
+ }, {
2228
+ "method": "recordingError",
2229
+ "name": "recordingError",
2230
+ "bubbles": true,
2231
+ "cancelable": true,
2232
+ "composed": true,
2233
+ "docs": {
2234
+ "tags": [],
2235
+ "text": "\u5F55\u5236\u9519\u8BEF\u4E8B\u4EF6"
2236
+ },
2237
+ "complexType": {
2238
+ "original": "RecordingErrorEventData",
2239
+ "resolved": "RecordingErrorEventData",
2240
+ "references": {
2241
+ "RecordingErrorEventData": {
2242
+ "location": "import",
2243
+ "path": "../../interfaces/events",
2244
+ "id": "src/interfaces/events.ts::RecordingErrorEventData"
2245
+ }
2246
+ }
2247
+ }
2248
+ }, {
2249
+ "method": "recordingStatusChange",
2250
+ "name": "recordingStatusChange",
2251
+ "bubbles": true,
2252
+ "cancelable": true,
2253
+ "composed": true,
2254
+ "docs": {
2255
+ "tags": [],
2256
+ "text": "\u5F55\u5236\u72B6\u6001\u53D8\u5316\u4E8B\u4EF6"
2257
+ },
2258
+ "complexType": {
2259
+ "original": "RecordingStatusChangeEventData",
2260
+ "resolved": "RecordingStatusChangeEventData",
2261
+ "references": {
2262
+ "RecordingStatusChangeEventData": {
2263
+ "location": "import",
2264
+ "path": "../../interfaces/events",
2265
+ "id": "src/interfaces/events.ts::RecordingStatusChangeEventData"
2266
+ }
2267
+ }
2268
+ }
2269
+ }, {
2270
+ "method": "tokenInvalid",
2271
+ "name": "tokenInvalid",
2272
+ "bubbles": true,
2273
+ "cancelable": true,
2274
+ "composed": true,
2275
+ "docs": {
2276
+ "tags": [],
2277
+ "text": "SDK\u5BC6\u94A5\u9A8C\u8BC1\u5931\u8D25\u4E8B\u4EF6"
2278
+ },
2279
+ "complexType": {
2280
+ "original": "void",
2281
+ "resolved": "void",
2282
+ "references": {}
2283
+ }
2284
+ }, {
2285
+ "method": "modalClosed",
2286
+ "name": "modalClosed",
2287
+ "bubbles": true,
2288
+ "cancelable": true,
2289
+ "composed": true,
2290
+ "docs": {
2291
+ "tags": [],
2292
+ "text": "\u5F53\u70B9\u51FB\u6A21\u6001\u6846\u5173\u95ED\u65F6\u89E6\u53D1"
2293
+ },
2294
+ "complexType": {
2295
+ "original": "void",
2296
+ "resolved": "void",
2297
+ "references": {}
2298
+ }
2299
+ }];
2300
+ }
2301
+ static get methods() {
2302
+ return {
2303
+ "setComponentActive": {
2304
+ "complexType": {
2305
+ "signature": "(active: boolean) => Promise<void>",
2306
+ "parameters": [{
2307
+ "name": "active",
2308
+ "type": "boolean",
2309
+ "docs": ""
2310
+ }],
2311
+ "references": {
2312
+ "Promise": {
2313
+ "location": "global",
2314
+ "id": "global::Promise"
2315
+ }
2316
+ },
2317
+ "return": "Promise<void>"
2318
+ },
2319
+ "docs": {
2320
+ "text": "\u516C\u5171\u65B9\u6CD5\uFF1A\u8BBE\u7F6E\u7EC4\u4EF6\u6D3B\u52A8\u72B6\u6001\r\n\u53EF\u88AB\u5916\u90E8\u8C03\u7528\u6765\u63A7\u5236\u7EC4\u4EF6\u662F\u5426\u7EE7\u7EED\u6267\u884C\u5F02\u6B65\u64CD\u4F5C",
2321
+ "tags": []
2322
+ }
2323
+ }
2324
+ };
2325
+ }
2326
+ static get elementRef() { return "hostElement"; }
2327
+ static get watchers() {
2328
+ return [{
2329
+ "propName": "token",
2330
+ "methodName": "handleTokenChange"
2331
+ }];
2332
+ }
2333
+ }
2334
+ //# sourceMappingURL=components-oem-virtual-chat-modal.js.map