identity-verify.js 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307
  1. "use strict";
  2. const common_vendor = require("../../common/vendor.js");
  3. const _sfc_main = {
  4. name: "IdentityVerify",
  5. data() {
  6. return {
  7. loading: false,
  8. responses: [],
  9. processedResponses: [],
  10. assistantResponse: "",
  11. audioTranscript: "",
  12. videoPlaying: false,
  13. showDebugInfo: false,
  14. // 设置为true可以显示调试信息
  15. videoUrl: "http://121.36.251.245:9000/minlong/9c139c99-a613-49f2-986b-8a3ac20a9b1b.mp4",
  16. // 用于存储AI数字人视频URL
  17. showReplayButton: false,
  18. cameraStream: null,
  19. // 存储摄像头流
  20. cameraError: null,
  21. // 存储摄像头错误信息
  22. useMiniProgramCameraComponent: false,
  23. // 添加小程序相机组件标志
  24. cameraContext: null,
  25. // 添加相机上下文
  26. currentSubtitle: "",
  27. subtitles: [
  28. {
  29. startTime: 0,
  30. // 开始时间(秒)
  31. endTime: 5,
  32. // 结束时间(秒)
  33. text: "你好,我是本次面试的面试官,欢迎参加本公司的线上面试!"
  34. },
  35. {
  36. startTime: 5,
  37. endTime: 13,
  38. text: "面试预计需要15分钟,请你提前安排在网络良好、光线亮度合适、且相对安静的环境参加这次面试"
  39. },
  40. {
  41. startTime: 13,
  42. endTime: 20,
  43. text: "以免影响本次面试的结果。如果你在面试过程中遇到问题,请与我们的招聘人员联系。"
  44. }
  45. ],
  46. secondVideoSubtitles: [
  47. {
  48. startTime: 0,
  49. endTime: 10,
  50. text: "请结合您的基本信息与过往履历进行简单的自我介绍,并讲一讲您有哪些优势胜任本岗位:"
  51. }
  52. ],
  53. thirdVideoSubtitles: [
  54. {
  55. startTime: 0,
  56. endTime: 3,
  57. text: "在工作中,你如何确保个人防护装备的正确使用?"
  58. }
  59. ],
  60. fourthVideoSubtitles: [
  61. {
  62. startTime: 0,
  63. endTime: 3,
  64. text: "描述一次你与团队合作改善生产流程的经历。"
  65. }
  66. ],
  67. fifthVideoSubtitles: [
  68. {
  69. startTime: 0,
  70. endTime: 6,
  71. text: "你在团队合作中曾遇到过哪些挑战?如何解决团队内部的分歧?"
  72. }
  73. ],
  74. sixthVideoSubtitles: [
  75. {
  76. startTime: 0,
  77. endTime: 5,
  78. text: "您已完成本次面试全部题目,请问您对于这个岗位还有什么想要了解的吗?"
  79. }
  80. ],
  81. showAnswerButton: false,
  82. // 控制答题按钮显示
  83. currentVideoIndex: 0,
  84. // 当前播放的视频索引
  85. videoList: [
  86. "http://121.36.251.245:9000/minlong/9c139c99-a613-49f2-986b-8a3ac20a9b1b.mp4",
  87. // 第一段视频
  88. "http://121.36.251.245:9000/minlong/15467aca-2c1e-4d40-bebc-11fc984f0a24.mp4",
  89. // 第二段视频
  90. "http://121.36.251.245:9000/minlong/a96f295c-ecba-4b08-b627-0ed0dc72d727.mp4",
  91. // 第三段视频
  92. "http://121.36.251.245:9000/minlong/e152eb38-7e03-4bb0-8b8d-73d51342d2aa.mp4",
  93. "http://121.36.251.245:9000/minlong/6381eec3-1373-400f-aa8c-e229b60522ea.mp4",
  94. "http://121.36.251.245:9000/minlong/2a11fe78-38d5-4af3-91c8-a080feb122e0.mp4"
  95. //结束
  96. ],
  97. isRecording: false,
  98. recordingTimer: null,
  99. showStopRecordingButton: false,
  100. mediaRecorder: null,
  101. recordedChunks: [],
  102. recorder: null,
  103. lastUploadedVideoUrl: "",
  104. showStartRecordingButton: false,
  105. showRetryButton: false,
  106. // 控制重试按钮显示
  107. lastVideoToRetry: null
  108. // 存储上次失败的视频URL,用于重试
  109. };
  110. },
  111. mounted() {
  112. this.playDigitalHumanVideo();
  113. this.checkAudioPermission();
  114. this.initCamera();
  115. setTimeout(() => {
  116. if (this.cameraStream && !this.useMiniProgramCameraComponent) {
  117. this.testAudioInput();
  118. }
  119. }, 3e3);
  120. },
  121. beforeDestroy() {
  122. this.stopUserCamera();
  123. },
  124. methods: {
  125. // 初始化相机
  126. async initCamera() {
  127. const systemInfo = common_vendor.index.getSystemInfoSync();
  128. const isMiniProgram = systemInfo.uniPlatform === "mp-weixin" || systemInfo.uniPlatform === "mp-alipay" || systemInfo.uniPlatform === "mp-baidu";
  129. if (isMiniProgram) {
  130. this.useMiniProgramCameraComponent = true;
  131. this.cameraContext = common_vendor.index.createCameraContext();
  132. common_vendor.index.getSetting({
  133. success: (res) => {
  134. if (!res.authSetting["scope.record"]) {
  135. common_vendor.index.authorize({
  136. scope: "scope.record",
  137. success: () => {
  138. console.log("录音权限已获取");
  139. },
  140. fail: (err) => {
  141. console.error("录音权限获取失败:", err);
  142. this.showPermissionDialog("录音");
  143. }
  144. });
  145. }
  146. if (!res.authSetting["scope.camera"]) {
  147. common_vendor.index.authorize({
  148. scope: "scope.camera",
  149. success: () => {
  150. console.log("相机权限已获取");
  151. },
  152. fail: (err) => {
  153. console.error("相机权限获取失败:", err);
  154. this.showPermissionDialog("相机");
  155. }
  156. });
  157. }
  158. }
  159. });
  160. } else {
  161. try {
  162. const constraints = {
  163. audio: {
  164. echoCancellation: true,
  165. noiseSuppression: true,
  166. autoGainControl: true
  167. },
  168. video: {
  169. width: { ideal: 640 },
  170. height: { ideal: 480 },
  171. facingMode: "user"
  172. }
  173. };
  174. const stream = await navigator.mediaDevices.getUserMedia(constraints);
  175. this.cameraStream = stream;
  176. const audioTracks = stream.getAudioTracks();
  177. console.log("音频轨道数量:", audioTracks.length);
  178. if (audioTracks.length > 0) {
  179. console.log("音频轨道已获取:", audioTracks[0].label);
  180. audioTracks[0].enabled = true;
  181. } else {
  182. console.warn("未检测到音频轨道,尝试单独获取音频");
  183. this.tryGetAudioOnly();
  184. }
  185. const videoElement = this.$refs.userCameraVideo;
  186. if (videoElement) {
  187. videoElement.srcObject = stream;
  188. videoElement.muted = true;
  189. }
  190. } catch (error) {
  191. console.error("获取摄像头失败:", error);
  192. this.cameraError = error.message || "无法访问摄像头";
  193. common_vendor.index.showToast({
  194. title: "无法访问摄像头,请检查权限设置",
  195. icon: "none"
  196. });
  197. }
  198. }
  199. },
  200. // 停止用户摄像头
  201. stopUserCamera() {
  202. if (this.cameraStream) {
  203. this.cameraStream.getTracks().forEach((track) => {
  204. track.stop();
  205. });
  206. this.cameraStream = null;
  207. }
  208. },
  209. async fetchData() {
  210. this.loading = true;
  211. this.assistantResponse = "";
  212. this.audioTranscript = "";
  213. this.processedResponses = [];
  214. try {
  215. const requestTask = common_vendor.index.request({
  216. url: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
  217. method: "POST",
  218. header: {
  219. "Content-Type": "application/json",
  220. "Authorization": "Bearer sk-9e1ec73a7d97493b8613c63f06b6110c"
  221. },
  222. data: {
  223. "model": "qwen-omni-turbo",
  224. "messages": [
  225. {
  226. "role": "user",
  227. "content": [
  228. {
  229. "type": "input_audio",
  230. "input_audio": {
  231. "data": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250211/tixcef/cherry.wav",
  232. "format": "wav"
  233. }
  234. },
  235. {
  236. "type": "text",
  237. "text": "这段音频在说什么"
  238. }
  239. ]
  240. }
  241. ],
  242. "stream": true,
  243. "stream_options": {
  244. "include_usage": true
  245. },
  246. "modalities": ["text", "audio"],
  247. "audio": { "voice": "Cherry", "format": "wav" }
  248. },
  249. success: (res) => {
  250. console.log("请求成功,响应数据:", res.data);
  251. if (typeof res.data === "string" && res.data.includes("data: {")) {
  252. const chunks = res.data.split("data: ").filter((chunk) => chunk.trim() !== "");
  253. chunks.forEach((chunk) => {
  254. this.handleStreamResponse(chunk);
  255. });
  256. } else {
  257. this.handleStreamResponse(res.data);
  258. }
  259. this.playDigitalHumanVideo();
  260. },
  261. fail: (err) => {
  262. console.error("请求失败:", err);
  263. },
  264. complete: () => {
  265. this.loading = false;
  266. }
  267. });
  268. } catch (error) {
  269. console.error("获取数据失败:", error);
  270. this.loading = false;
  271. }
  272. },
  273. handleStreamResponse(data) {
  274. if (typeof data === "string") {
  275. if (data === "[DONE]")
  276. return;
  277. try {
  278. const cleanData = data.trim();
  279. if (cleanData.startsWith("{") && cleanData.endsWith("}")) {
  280. const jsonData = JSON.parse(cleanData);
  281. this.processStreamChunk(jsonData);
  282. }
  283. } catch (e) {
  284. console.error("解析JSON失败:", e, "原始数据:", data);
  285. }
  286. } else {
  287. this.processStreamChunk(data);
  288. }
  289. },
  290. processStreamChunk(chunk) {
  291. if (chunk.choices && chunk.choices.length > 0) {
  292. const choice = chunk.choices[0];
  293. if (choice.delta && choice.delta.content) {
  294. this.assistantResponse += choice.delta.content;
  295. }
  296. if (choice.delta && choice.delta.audio && choice.delta.audio.transcript) {
  297. this.audioTranscript += choice.delta.audio.transcript;
  298. }
  299. if (choice.delta) {
  300. const result = {};
  301. if (choice.delta.role) {
  302. result.role = choice.delta.role;
  303. }
  304. if (choice.delta.audio && choice.delta.audio.transcript) {
  305. result.transcript = choice.delta.audio.transcript;
  306. }
  307. if (Object.keys(result).length > 0) {
  308. this.processedResponses.push(result);
  309. }
  310. }
  311. }
  312. },
  313. processResponseData() {
  314. this.processedResponses = this.responses.map((item) => {
  315. const result = {};
  316. if (item.delta && item.delta.role) {
  317. result.role = item.delta.role;
  318. }
  319. if (item.delta && item.delta.audio && item.delta.audio.transcript) {
  320. result.transcript = item.delta.audio.transcript;
  321. }
  322. return result;
  323. }).filter((item) => Object.keys(item).length > 0);
  324. },
  325. // 播放数字人视频
  326. playDigitalHumanVideo() {
  327. this.videoUrl = this.videoList[this.currentVideoIndex];
  328. this.videoPlaying = true;
  329. this.$nextTick(() => {
  330. const videoContext = common_vendor.index.createVideoContext("myVideo", this);
  331. if (videoContext) {
  332. videoContext.play();
  333. setTimeout(() => {
  334. if (this.videoPlaying && this.$refs.videoPlayer) {
  335. console.log("视频应该正在播放");
  336. } else {
  337. console.log("视频可能未成功播放,尝试替代方案");
  338. this.tryAlternativeVideoPath();
  339. }
  340. }, 1e3);
  341. } else {
  342. console.error("无法创建视频上下文");
  343. this.tryAlternativeVideoPath();
  344. }
  345. });
  346. },
  347. // 修改 tryAlternativeVideoPath 方法
  348. tryAlternativeVideoPath() {
  349. console.log("尝试使用替代路径");
  350. const alternativePaths = [
  351. "./static/demo.mp4",
  352. "../static/demo.mp4",
  353. "static/demo.mp4",
  354. "/static/demo.mp4",
  355. // 添加绝对路径
  356. `${window.location.origin}/static/demo.mp4`
  357. ];
  358. const currentPathIndex = alternativePaths.indexOf(this.videoUrl);
  359. const nextPathIndex = (currentPathIndex + 1) % alternativePaths.length;
  360. this.videoUrl = alternativePaths[nextPathIndex];
  361. console.log("尝试新路径:", this.videoUrl);
  362. this.$nextTick(() => {
  363. const videoContext = common_vendor.index.createVideoContext("myVideo", this);
  364. if (videoContext) {
  365. videoContext.stop();
  366. videoContext.play();
  367. setTimeout(() => {
  368. if (nextPathIndex === alternativePaths.length - 1 && !this.videoPlaying) {
  369. console.log("所有路径均失败,尝试使用uni.getVideoInfo检查视频");
  370. this.checkVideoWithAPI();
  371. }
  372. }, 1e3);
  373. }
  374. });
  375. },
  376. // 添加新方法:使用uni API检查视频
  377. checkVideoWithAPI() {
  378. common_vendor.index.getVideoInfo({
  379. src: "/static/demo.mp4",
  380. success: (res) => {
  381. console.log("视频信息获取成功:", res);
  382. this.videoUrl = "/static/demo.mp4";
  383. this.$nextTick(() => {
  384. const videoContext = common_vendor.index.createVideoContext("myVideo", this);
  385. if (videoContext) {
  386. videoContext.play();
  387. }
  388. });
  389. },
  390. fail: (err) => {
  391. console.error("视频信息获取失败:", err);
  392. this.fallbackToLocalVideo();
  393. }
  394. });
  395. },
  396. // 添加新方法:回退到本地视频
  397. fallbackToLocalVideo() {
  398. console.log("尝试使用本地视频资源");
  399. const platform = common_vendor.index.getSystemInfoSync().platform;
  400. if (platform === "android" || platform === "ios") {
  401. this.videoUrl = platform === "android" ? "android.resource://package_name/raw/demo" : "file:///assets/demo.mp4";
  402. this.$nextTick(() => {
  403. const videoContext = common_vendor.index.createVideoContext("myVideo", this);
  404. if (videoContext) {
  405. videoContext.play();
  406. }
  407. });
  408. } else {
  409. this.videoPlaying = false;
  410. common_vendor.index.showToast({
  411. title: "视频加载失败,显示静态图片",
  412. icon: "none"
  413. });
  414. }
  415. },
  416. // 修改 handleVideoError 方法
  417. handleVideoError(e) {
  418. console.error("视频加载错误:", e);
  419. if (e && e.detail) {
  420. console.error("详细错误信息:", e.detail);
  421. }
  422. common_vendor.index.getFileInfo({
  423. filePath: this.videoUrl.startsWith("/") ? this.videoUrl.substring(1) : this.videoUrl,
  424. success: (res) => {
  425. console.log("文件存在,大小:", res.size);
  426. this.tryDifferentFormat();
  427. },
  428. fail: (err) => {
  429. console.error("文件不存在或无法访问:", err);
  430. this.tryAlternativeVideoPath();
  431. }
  432. });
  433. common_vendor.index.showToast({
  434. title: "视频加载失败,请检查文件是否存在",
  435. icon: "none",
  436. duration: 2e3
  437. });
  438. },
  439. // 添加新方法:尝试不同格式
  440. tryDifferentFormat() {
  441. console.log("尝试不同的视频格式");
  442. const formats = [
  443. { ext: "mp4", mime: "video/mp4" },
  444. { ext: "webm", mime: "video/webm" },
  445. { ext: "ogg", mime: "video/ogg" },
  446. { ext: "mov", mime: "video/quicktime" }
  447. ];
  448. const currentPath = this.videoUrl;
  449. const basePath = currentPath.substring(0, currentPath.lastIndexOf(".")) || "/static/demo";
  450. let nextFormat = formats.find((f) => !currentPath.endsWith(f.ext));
  451. if (nextFormat) {
  452. this.videoUrl = `${basePath}.${nextFormat.ext}`;
  453. console.log("尝试新格式:", this.videoUrl);
  454. this.$nextTick(() => {
  455. const videoContext = common_vendor.index.createVideoContext("myVideo", this);
  456. if (videoContext) {
  457. videoContext.stop();
  458. videoContext.play();
  459. }
  460. });
  461. } else {
  462. this.useBuiltInResource();
  463. }
  464. },
  465. // 添加新方法:使用内置资源
  466. useBuiltInResource() {
  467. console.log("尝试使用内置资源");
  468. const platform = common_vendor.index.getSystemInfoSync().platform;
  469. if (platform === "windows") {
  470. process.env.UNI_INPUT_DIR || "";
  471. this.videoUrl = `./static/demo.mp4`;
  472. console.log("Windows平台尝试路径:", this.videoUrl);
  473. } else if (platform === "android" || platform === "ios") {
  474. this.useNativeVideo();
  475. } else {
  476. const baseUrl = window.location.origin;
  477. this.videoUrl = `${baseUrl}/static/demo.mp4`;
  478. console.log("Web平台尝试URL:", this.videoUrl);
  479. }
  480. this.$nextTick(() => {
  481. const videoContext = common_vendor.index.createVideoContext("myVideo", this);
  482. if (videoContext) {
  483. videoContext.play();
  484. }
  485. });
  486. },
  487. // 添加新方法:使用原生视频能力
  488. useNativeVideo() {
  489. console.log("尝试使用原生视频能力");
  490. common_vendor.index.chooseVideo({
  491. sourceType: ["album"],
  492. success: (res) => {
  493. this.videoUrl = res.tempFilePath;
  494. this.$nextTick(() => {
  495. const videoContext = common_vendor.index.createVideoContext("myVideo", this);
  496. if (videoContext) {
  497. videoContext.play();
  498. }
  499. });
  500. },
  501. fail: () => {
  502. this.videoPlaying = false;
  503. common_vendor.index.showToast({
  504. title: "无法加载视频,显示静态图片",
  505. icon: "none"
  506. });
  507. }
  508. });
  509. },
  510. // 处理视频结束事件
  511. handleVideoEnded() {
  512. console.log("视频播放结束");
  513. this.videoPlaying = false;
  514. if (this.currentVideoIndex >= 1) {
  515. this.showStartRecordingButton = true;
  516. } else {
  517. this.showAnswerButton = true;
  518. }
  519. },
  520. // 添加新方法:开始录制用户回答
  521. startRecordingAnswer() {
  522. console.log("开始录制用户回答");
  523. this.isRecording = true;
  524. const systemInfo = common_vendor.index.getSystemInfoSync();
  525. const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith("mp-");
  526. if (isMiniProgram) {
  527. this.startMiniProgramRecording();
  528. } else {
  529. this.startBrowserRecording();
  530. }
  531. this.showStopRecordingButton = true;
  532. },
  533. // 修改小程序环境下的录制方法
  534. startMiniProgramRecording() {
  535. if (!this.cameraContext) {
  536. this.cameraContext = common_vendor.index.createCameraContext();
  537. }
  538. common_vendor.index.getSetting({
  539. success: (res) => {
  540. const hasRecordAuth = res.authSetting["scope.record"];
  541. const hasCameraAuth = res.authSetting["scope.camera"];
  542. if (!hasRecordAuth || !hasCameraAuth) {
  543. console.warn("缺少必要权限,请求权限");
  544. this.requestMiniProgramPermissions();
  545. return;
  546. }
  547. const options = {
  548. timeout: 6e4,
  549. // 最长录制60秒
  550. success: () => {
  551. console.log("小程序录像开始成功");
  552. },
  553. fail: (err) => {
  554. console.error("小程序录像开始失败:", err);
  555. common_vendor.index.showToast({
  556. title: "录制失败,请检查相机权限",
  557. icon: "none"
  558. });
  559. this.proceedToNextQuestion();
  560. },
  561. timeoutCallback: () => {
  562. console.log("录制超时自动停止");
  563. this.stopRecordingAnswer();
  564. }
  565. };
  566. this.recorder = this.cameraContext.startRecord(options);
  567. }
  568. });
  569. },
  570. // 添加新方法:请求小程序权限
  571. requestMiniProgramPermissions() {
  572. common_vendor.index.authorize({
  573. scope: "scope.record",
  574. success: () => {
  575. console.log("录音权限已获取");
  576. common_vendor.index.authorize({
  577. scope: "scope.camera",
  578. success: () => {
  579. console.log("相机权限已获取");
  580. this.startMiniProgramRecording();
  581. },
  582. fail: (err) => {
  583. console.error("相机权限获取失败:", err);
  584. this.showPermissionDialog("相机");
  585. }
  586. });
  587. },
  588. fail: (err) => {
  589. console.error("录音权限获取失败:", err);
  590. this.showPermissionDialog("录音");
  591. }
  592. });
  593. },
  594. // 修改浏览器环境下的录制方法
  595. startBrowserRecording() {
  596. if (!this.cameraStream) {
  597. console.error("没有可用的摄像头流");
  598. common_vendor.index.showToast({
  599. title: "录制失败,摄像头未就绪",
  600. icon: "none"
  601. });
  602. this.proceedToNextQuestion();
  603. return;
  604. }
  605. try {
  606. const hasAudio = this.cameraStream.getAudioTracks().length > 0;
  607. if (!hasAudio) {
  608. console.warn("警告:媒体流中没有音频轨道,尝试重新获取带音频的媒体流");
  609. navigator.mediaDevices.getUserMedia({
  610. audio: {
  611. echoCancellation: true,
  612. noiseSuppression: true,
  613. autoGainControl: true
  614. },
  615. video: true
  616. }).then((newStream) => {
  617. const audioTracks = newStream.getAudioTracks();
  618. if (audioTracks.length > 0) {
  619. console.log("成功获取音频轨道:", audioTracks[0].label);
  620. const videoTrack = this.cameraStream.getVideoTracks()[0];
  621. const audioTrack = newStream.getAudioTracks()[0];
  622. const combinedStream = new MediaStream();
  623. if (videoTrack)
  624. combinedStream.addTrack(videoTrack);
  625. if (audioTrack)
  626. combinedStream.addTrack(audioTrack);
  627. this.cameraStream = combinedStream;
  628. const videoElement = this.$refs.userCameraVideo;
  629. if (videoElement) {
  630. videoElement.srcObject = combinedStream;
  631. videoElement.muted = true;
  632. }
  633. this.setupMediaRecorder(combinedStream);
  634. } else {
  635. console.warn("仍然无法获取音频轨道");
  636. this.setupMediaRecorder(this.cameraStream);
  637. }
  638. }).catch((err) => {
  639. console.error("获取音频失败:", err);
  640. this.setupMediaRecorder(this.cameraStream);
  641. });
  642. } else {
  643. console.log("检测到音频轨道,直接使用");
  644. this.setupMediaRecorder(this.cameraStream);
  645. }
  646. } catch (error) {
  647. console.error("浏览器录制失败:", error);
  648. common_vendor.index.showToast({
  649. title: "录制失败,浏览器可能不支持此功能",
  650. icon: "none"
  651. });
  652. this.proceedToNextQuestion();
  653. }
  654. },
  655. // 修改 setupMediaRecorder 方法
  656. setupMediaRecorder(stream) {
  657. const videoTracks = stream.getVideoTracks();
  658. const audioTracks = stream.getAudioTracks();
  659. console.log("设置MediaRecorder - 视频轨道:", videoTracks.length, "音频轨道:", audioTracks.length);
  660. let mimeType = "";
  661. const supportedTypes = [
  662. "video/webm;codecs=vp9,opus",
  663. "video/webm;codecs=vp8,opus",
  664. "video/webm;codecs=h264,opus",
  665. "video/mp4;codecs=h264,aac",
  666. "video/webm",
  667. "video/mp4"
  668. ];
  669. for (const type of supportedTypes) {
  670. if (MediaRecorder.isTypeSupported(type)) {
  671. mimeType = type;
  672. console.log("使用支持的MIME类型:", mimeType);
  673. break;
  674. }
  675. }
  676. const options = {
  677. mimeType: mimeType || "",
  678. audioBitsPerSecond: 128e3,
  679. videoBitsPerSecond: 25e5
  680. };
  681. try {
  682. this.mediaRecorder = new MediaRecorder(stream, options);
  683. console.log("MediaRecorder创建成功,使用选项:", options);
  684. } catch (e) {
  685. console.warn("使用指定选项创建MediaRecorder失败,尝试使用默认选项");
  686. this.mediaRecorder = new MediaRecorder(stream);
  687. }
  688. this.recordedChunks = [];
  689. this.mediaRecorder.ondataavailable = (event) => {
  690. if (event.data && event.data.size > 0) {
  691. this.recordedChunks.push(event.data);
  692. console.log(`收到数据块: ${event.data.size} 字节`);
  693. }
  694. };
  695. this.mediaRecorder.onstop = () => {
  696. console.log("MediaRecorder停止,数据块数量:", this.recordedChunks.length);
  697. if (this.recordedChunks.length === 0) {
  698. console.error("没有录制到数据");
  699. common_vendor.index.showToast({
  700. title: "录制失败,未捕获到数据",
  701. icon: "none"
  702. });
  703. this.proceedToNextQuestion();
  704. return;
  705. }
  706. const mimeType2 = this.mediaRecorder.mimeType || "video/webm";
  707. const blob = new Blob(this.recordedChunks, { type: mimeType2 });
  708. console.log("创建Blob,大小:", blob.size, "类型:", mimeType2);
  709. const fileName = `answer_${this.currentVideoIndex}_${Date.now()}.webm`;
  710. const file = new File([blob], fileName, { type: mimeType2 });
  711. this.uploadRecordedVideo(file);
  712. };
  713. this.mediaRecorder.onerror = (event) => {
  714. console.error("MediaRecorder错误:", event.error);
  715. };
  716. try {
  717. this.mediaRecorder.start(1e3);
  718. console.log("MediaRecorder开始录制");
  719. } catch (e) {
  720. console.error("开始录制失败:", e);
  721. }
  722. },
  723. // 添加新方法:停止录制用户回答
  724. stopRecordingAnswer() {
  725. console.log("停止录制用户回答");
  726. this.isRecording = false;
  727. if (this.recordingTimer) {
  728. clearTimeout(this.recordingTimer);
  729. this.recordingTimer = null;
  730. }
  731. common_vendor.index.hideLoading();
  732. this.showStopRecordingButton = false;
  733. const systemInfo = common_vendor.index.getSystemInfoSync();
  734. const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith("mp-");
  735. if (isMiniProgram) {
  736. this.stopMiniProgramRecording();
  737. } else {
  738. this.stopBrowserRecording();
  739. }
  740. },
  741. // 添加新方法:停止小程序录制
  742. stopMiniProgramRecording() {
  743. if (!this.cameraContext) {
  744. console.error("相机上下文不存在");
  745. this.proceedToNextQuestion();
  746. return;
  747. }
  748. this.cameraContext.stopRecord({
  749. success: (res) => {
  750. console.log("小程序录像停止成功:", res);
  751. const tempFilePath = res.tempVideoPath;
  752. this.uploadRecordedVideo(tempFilePath);
  753. },
  754. fail: (err) => {
  755. console.error("小程序录像停止失败:", err);
  756. common_vendor.index.showToast({
  757. title: "录制失败",
  758. icon: "none"
  759. });
  760. this.proceedToNextQuestion();
  761. }
  762. });
  763. },
  764. // 添加新方法:停止浏览器录制
  765. stopBrowserRecording() {
  766. if (this.mediaRecorder && this.mediaRecorder.state !== "inactive") {
  767. this.mediaRecorder.stop();
  768. console.log("浏览器录制停止成功");
  769. } else {
  770. console.error("MediaRecorder不存在或已经停止");
  771. this.proceedToNextQuestion();
  772. }
  773. },
  774. // 修改上传录制的视频方法
  775. uploadRecordedVideo(fileOrPath) {
  776. console.log("准备上传视频:", typeof fileOrPath === "string" ? fileOrPath : fileOrPath.name);
  777. common_vendor.index.showLoading({
  778. title: "正在上传...",
  779. mask: true
  780. });
  781. const userInfo = common_vendor.index.getStorageSync("userInfo");
  782. const openid = userInfo ? JSON.parse(userInfo).openid || "" : "";
  783. const tenant_id = common_vendor.index.getStorageSync("tenant_id") || "1";
  784. let questionId;
  785. switch (this.currentVideoIndex) {
  786. case 1:
  787. questionId = 10;
  788. break;
  789. case 2:
  790. questionId = 11;
  791. break;
  792. case 3:
  793. questionId = 12;
  794. break;
  795. case 4:
  796. questionId = 13;
  797. break;
  798. default:
  799. questionId = 10;
  800. }
  801. if (typeof fileOrPath !== "string") {
  802. const formData = new FormData();
  803. formData.append("file", fileOrPath);
  804. formData.append("openid", openid);
  805. formData.append("tenant_id", tenant_id);
  806. formData.append("application_id", 1);
  807. formData.append("question_id", questionId);
  808. formData.append("video_duration", 0);
  809. formData.append("has_audio", "true");
  810. const xhr = new XMLHttpRequest();
  811. xhr.open("POST", "https://minlong.raycos.com.cn/api/system/upload/", true);
  812. xhr.onload = () => {
  813. common_vendor.index.hideLoading();
  814. if (xhr.status === 200) {
  815. try {
  816. const res = JSON.parse(xhr.responseText);
  817. console.log("上传响应:", res);
  818. if (res.code === 2e3) {
  819. const videoUrl = res.data.url || res.data.photoUrl || "";
  820. if (videoUrl) {
  821. this.submitVideoToInterview(videoUrl);
  822. } else {
  823. common_vendor.index.showToast({
  824. title: "视频URL获取失败",
  825. icon: "none"
  826. });
  827. }
  828. } else {
  829. common_vendor.index.showToast({
  830. title: res.msg || "上传失败,请重试",
  831. icon: "none"
  832. });
  833. }
  834. } catch (e) {
  835. console.error("解析响应失败:", e);
  836. common_vendor.index.showToast({
  837. title: "解析响应失败",
  838. icon: "none"
  839. });
  840. }
  841. } else {
  842. common_vendor.index.showToast({
  843. title: "上传失败,HTTP状态: " + xhr.status,
  844. icon: "none"
  845. });
  846. }
  847. };
  848. xhr.onerror = () => {
  849. common_vendor.index.hideLoading();
  850. console.error("上传网络错误");
  851. common_vendor.index.showToast({
  852. title: "网络错误,请重试",
  853. icon: "none"
  854. });
  855. };
  856. xhr.upload.onprogress = (event) => {
  857. if (event.lengthComputable) {
  858. const percentComplete = Math.round(event.loaded / event.total * 100);
  859. console.log("上传进度:", percentComplete + "%");
  860. }
  861. };
  862. xhr.send(formData);
  863. return;
  864. }
  865. common_vendor.index.uploadFile({
  866. url: "https://minlong.raycos.com.cn/api/system/upload/",
  867. filePath: fileOrPath,
  868. name: "file",
  869. formData: {
  870. openid,
  871. tenant_id,
  872. application_id: 1,
  873. question_id: questionId,
  874. video_duration: 0,
  875. has_audio: "true"
  876. // 明确标记视频包含音频
  877. },
  878. success: (uploadRes) => {
  879. try {
  880. const res = JSON.parse(uploadRes.data);
  881. console.log("上传响应:", res);
  882. if (res.code === 2e3) {
  883. const videoUrl = res.data.permanent_link || res.data.url || "";
  884. if (videoUrl) {
  885. this.submitVideoToInterview(videoUrl);
  886. } else {
  887. common_vendor.index.showToast({
  888. title: "视频URL获取失败",
  889. icon: "none"
  890. });
  891. }
  892. } else {
  893. common_vendor.index.showToast({
  894. title: res.msg || "上传失败,请重试",
  895. icon: "none"
  896. });
  897. }
  898. } catch (e) {
  899. console.error("解析响应失败:", e);
  900. common_vendor.index.showToast({
  901. title: "解析响应失败",
  902. icon: "none"
  903. });
  904. }
  905. },
  906. fail: (err) => {
  907. console.error("上传失败:", err);
  908. common_vendor.index.showToast({
  909. title: "上传失败,请重试",
  910. icon: "none"
  911. });
  912. },
  913. complete: () => {
  914. common_vendor.index.hideLoading();
  915. }
  916. });
  917. },
  918. // 修改 submitVideoToInterview 方法
  919. submitVideoToInterview(videoUrl) {
  920. console.log("提交视频URL到面试接口:", videoUrl);
  921. common_vendor.index.showLoading({
  922. title: "正在提交...",
  923. mask: true
  924. });
  925. let questionId;
  926. switch (this.currentVideoIndex) {
  927. case 1:
  928. questionId = 10;
  929. break;
  930. case 2:
  931. questionId = 11;
  932. break;
  933. case 3:
  934. questionId = 12;
  935. break;
  936. case 4:
  937. questionId = 13;
  938. break;
  939. default:
  940. questionId = 10;
  941. }
  942. const requestData = {
  943. application_id: 1,
  944. question_id: questionId,
  945. video_url: videoUrl,
  946. tenant_id: common_vendor.index.getStorageSync("tenant_id") || "1"
  947. };
  948. common_vendor.index.request({
  949. url: "https://minlong.raycos.com.cn/api/job/upload_question_video",
  950. method: "POST",
  951. data: requestData,
  952. // 使用data而不是formData
  953. header: {
  954. "content-type": "application/x-www-form-urlencoded"
  955. // 修改为form-data格式的content-type
  956. },
  957. success: (res) => {
  958. console.log("面试接口提交成功:", res);
  959. common_vendor.index.hideLoading();
  960. if (res.data.code === 0 || res.data.code === 2e3) {
  961. common_vendor.index.showToast({
  962. title: "回答已提交",
  963. icon: "success"
  964. });
  965. this.lastUploadedVideoUrl = videoUrl;
  966. this.showRetryButton = false;
  967. this.lastVideoToRetry = null;
  968. setTimeout(() => {
  969. if (this.currentVideoIndex === 4) {
  970. common_vendor.index.navigateTo({
  971. url: "/pages/camera/camera"
  972. });
  973. } else {
  974. this.proceedToNextQuestion();
  975. }
  976. }, 1500);
  977. } else {
  978. common_vendor.index.showToast({
  979. title: res.data.msg || "提交失败,请重试",
  980. icon: "none"
  981. });
  982. this.lastVideoToRetry = videoUrl;
  983. this.showRetryButton = true;
  984. }
  985. },
  986. fail: (err) => {
  987. console.error("面试接口提交失败:", err);
  988. common_vendor.index.hideLoading();
  989. common_vendor.index.showToast({
  990. title: "网络错误,请重试",
  991. icon: "none"
  992. });
  993. this.lastVideoToRetry = videoUrl;
  994. this.showRetryButton = true;
  995. }
  996. });
  997. },
  998. // 修改 proceedToNextQuestion 方法
  999. proceedToNextQuestion() {
  1000. if (this.currentVideoIndex === 4) {
  1001. common_vendor.index.navigateTo({
  1002. url: "/pages/camera/camera"
  1003. });
  1004. return;
  1005. }
  1006. this.currentVideoIndex++;
  1007. if (this.currentVideoIndex < this.videoList.length) {
  1008. this.videoUrl = this.videoList[this.currentVideoIndex];
  1009. this.videoPlaying = true;
  1010. this.currentSubtitle = "";
  1011. this.$nextTick(() => {
  1012. const videoContext = common_vendor.index.createVideoContext("myVideo", this);
  1013. if (videoContext) {
  1014. videoContext.play();
  1015. }
  1016. });
  1017. } else {
  1018. common_vendor.index.showToast({
  1019. title: "面试完成",
  1020. icon: "success",
  1021. duration: 2e3
  1022. });
  1023. setTimeout(() => {
  1024. common_vendor.index.navigateTo({
  1025. url: "/pages/interview-result/interview-result"
  1026. });
  1027. }, 2e3);
  1028. }
  1029. },
  1030. // 修改 handleAnswerButtonClick 方法
  1031. handleAnswerButtonClick() {
  1032. this.showAnswerButton = false;
  1033. this.proceedToNextQuestion();
  1034. },
  1035. // 处理相机错误
  1036. handleCameraError(e) {
  1037. console.error("相机错误:", e);
  1038. common_vendor.index.showToast({
  1039. title: "相机初始化失败,请检查权限设置",
  1040. icon: "none"
  1041. });
  1042. this.tryFallbackOptions();
  1043. },
  1044. // 添加新方法:尝试备用选项
  1045. tryFallbackOptions() {
  1046. const systemInfo = common_vendor.index.getSystemInfoSync();
  1047. if (systemInfo.uniPlatform === "mp-weixin" || systemInfo.uniPlatform === "mp-alipay") {
  1048. this.useMiniProgramCamera();
  1049. } else {
  1050. this.showStaticCameraPlaceholder();
  1051. }
  1052. },
  1053. // 添加新方法:使用小程序相机API
  1054. useMiniProgramCamera() {
  1055. console.log("尝试使用小程序相机组件");
  1056. this.useMiniProgramCameraComponent = true;
  1057. },
  1058. // 添加新方法:显示静态图像
  1059. showStaticCameraPlaceholder() {
  1060. console.log("显示静态摄像头占位图");
  1061. const img = document.createElement("img");
  1062. img.src = "/static/images/camera-placeholder.png";
  1063. img.className = "static-camera-image";
  1064. img.style.width = "100%";
  1065. img.style.height = "100%";
  1066. img.style.objectFit = "cover";
  1067. const container = this.$refs.userCameraVideo.parentNode;
  1068. container.appendChild(img);
  1069. },
  1070. // 处理视频时间更新事件
  1071. handleTimeUpdate(e) {
  1072. const currentTime = e.target.currentTime;
  1073. let currentSubtitles;
  1074. if (this.currentVideoIndex === 0) {
  1075. currentSubtitles = this.subtitles;
  1076. } else if (this.currentVideoIndex === 1) {
  1077. currentSubtitles = this.secondVideoSubtitles;
  1078. } else if (this.currentVideoIndex === 2) {
  1079. currentSubtitles = this.thirdVideoSubtitles;
  1080. } else if (this.currentVideoIndex === 3) {
  1081. currentSubtitles = this.fourthVideoSubtitles;
  1082. } else if (this.currentVideoIndex === 4) {
  1083. currentSubtitles = this.fifthVideoSubtitles;
  1084. } else if (this.currentVideoIndex === 5) {
  1085. currentSubtitles = this.sixthVideoSubtitles;
  1086. } else {
  1087. currentSubtitles = [];
  1088. }
  1089. const subtitle = currentSubtitles.find(
  1090. (sub) => currentTime >= sub.startTime && currentTime < sub.endTime
  1091. );
  1092. this.currentSubtitle = subtitle ? subtitle.text : "";
  1093. },
  1094. // Add a new method to handle the "Start Recording" button click
  1095. handleStartRecordingClick() {
  1096. this.showStartRecordingButton = false;
  1097. this.startRecordingAnswer();
  1098. },
  1099. // 修改 checkAudioPermission 方法,确保在录制前获取音频权限
  1100. checkAudioPermission() {
  1101. const systemInfo = common_vendor.index.getSystemInfoSync();
  1102. const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith("mp-");
  1103. if (isMiniProgram) {
  1104. common_vendor.index.getSetting({
  1105. success: (res) => {
  1106. if (!res.authSetting["scope.record"]) {
  1107. common_vendor.index.authorize({
  1108. scope: "scope.record",
  1109. success: () => {
  1110. console.log("录音权限已获取");
  1111. },
  1112. fail: (err) => {
  1113. console.error("录音权限获取失败:", err);
  1114. this.showPermissionDialog("录音");
  1115. }
  1116. });
  1117. }
  1118. if (!res.authSetting["scope.camera"]) {
  1119. common_vendor.index.authorize({
  1120. scope: "scope.camera",
  1121. success: () => {
  1122. console.log("相机权限已获取");
  1123. },
  1124. fail: (err) => {
  1125. console.error("相机权限获取失败:", err);
  1126. this.showPermissionDialog("相机");
  1127. }
  1128. });
  1129. }
  1130. }
  1131. });
  1132. }
  1133. },
  1134. // 添加新方法:测试音频输入
  1135. testAudioInput() {
  1136. if (!this.cameraStream) {
  1137. console.warn("没有可用的媒体流,无法测试音频");
  1138. return;
  1139. }
  1140. const audioTracks = this.cameraStream.getAudioTracks();
  1141. if (audioTracks.length === 0) {
  1142. console.warn("没有检测到音频轨道,尝试重新获取");
  1143. this.tryGetAudioOnly();
  1144. return;
  1145. }
  1146. console.log("音频轨道信息:", audioTracks[0].getSettings());
  1147. try {
  1148. const AudioContext = window.AudioContext || window.webkitAudioContext;
  1149. if (!AudioContext) {
  1150. console.warn("浏览器不支持AudioContext");
  1151. return;
  1152. }
  1153. const audioContext = new AudioContext();
  1154. const analyser = audioContext.createAnalyser();
  1155. const microphone = audioContext.createMediaStreamSource(this.cameraStream);
  1156. microphone.connect(analyser);
  1157. analyser.fftSize = 256;
  1158. const bufferLength = analyser.frequencyBinCount;
  1159. const dataArray = new Uint8Array(bufferLength);
  1160. let silenceCounter = 0;
  1161. const checkAudio = () => {
  1162. if (this.isRecording)
  1163. return;
  1164. analyser.getByteFrequencyData(dataArray);
  1165. let sum = 0;
  1166. for (let i = 0; i < bufferLength; i++) {
  1167. sum += dataArray[i];
  1168. }
  1169. const average = sum / bufferLength;
  1170. if (average > 10) {
  1171. console.log("检测到音频输入,音量:", average);
  1172. silenceCounter = 0;
  1173. } else {
  1174. silenceCounter++;
  1175. if (silenceCounter > 10) {
  1176. console.warn("持续检测不到音频输入,可能麦克风未正常工作");
  1177. silenceCounter = 0;
  1178. }
  1179. }
  1180. requestAnimationFrame(checkAudio);
  1181. };
  1182. checkAudio();
  1183. } catch (e) {
  1184. console.error("音频测试失败:", e);
  1185. }
  1186. },
  1187. // 添加新方法:尝试单独获取音频
  1188. tryGetAudioOnly() {
  1189. navigator.mediaDevices.getUserMedia({ audio: true }).then((audioStream) => {
  1190. if (this.cameraStream) {
  1191. const videoTrack = this.cameraStream.getVideoTracks()[0];
  1192. const audioTrack = audioStream.getAudioTracks()[0];
  1193. const combinedStream = new MediaStream();
  1194. if (videoTrack)
  1195. combinedStream.addTrack(videoTrack);
  1196. if (audioTrack)
  1197. combinedStream.addTrack(audioTrack);
  1198. this.cameraStream = combinedStream;
  1199. const videoElement = this.$refs.userCameraVideo;
  1200. if (videoElement) {
  1201. videoElement.srcObject = combinedStream;
  1202. videoElement.muted = true;
  1203. }
  1204. console.log("成功合并音频和视频轨道");
  1205. } else {
  1206. console.warn("没有视频流可合并");
  1207. }
  1208. }).catch((err) => {
  1209. console.error("单独获取音频失败:", err);
  1210. });
  1211. },
  1212. // 添加新方法:显示权限对话框
  1213. showPermissionDialog(permissionType) {
  1214. common_vendor.index.showModal({
  1215. title: "需要权限",
  1216. content: `请允许使用${permissionType}权限,否则可能影响面试功能`,
  1217. confirmText: "去设置",
  1218. success: (res) => {
  1219. if (res.confirm) {
  1220. common_vendor.index.openSetting({
  1221. success: (settingRes) => {
  1222. console.log("设置页面打开成功", settingRes);
  1223. }
  1224. });
  1225. }
  1226. }
  1227. });
  1228. },
  1229. // 添加重试上传方法
  1230. retryVideoUpload() {
  1231. if (this.lastVideoToRetry) {
  1232. this.showRetryButton = false;
  1233. common_vendor.index.showLoading({
  1234. title: "正在重新提交...",
  1235. mask: true
  1236. });
  1237. this.submitVideoToInterview(this.lastVideoToRetry);
  1238. } else {
  1239. common_vendor.index.showToast({
  1240. title: "没有可重试的视频",
  1241. icon: "none"
  1242. });
  1243. }
  1244. }
  1245. }
  1246. };
  1247. function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  1248. return common_vendor.e({
  1249. a: $data.videoUrl,
  1250. b: common_vendor.o((...args) => $options.handleVideoError && $options.handleVideoError(...args)),
  1251. c: common_vendor.o((...args) => $options.handleVideoEnded && $options.handleVideoEnded(...args)),
  1252. d: common_vendor.o((...args) => $options.handleTimeUpdate && $options.handleTimeUpdate(...args)),
  1253. e: $data.currentSubtitle
  1254. }, $data.currentSubtitle ? {
  1255. f: common_vendor.t($data.currentSubtitle)
  1256. } : {}, {
  1257. g: $data.showAnswerButton
  1258. }, $data.showAnswerButton ? {
  1259. h: common_vendor.o((...args) => $options.handleAnswerButtonClick && $options.handleAnswerButtonClick(...args))
  1260. } : {}, {
  1261. i: $data.useMiniProgramCameraComponent
  1262. }, $data.useMiniProgramCameraComponent ? {
  1263. j: common_vendor.o((...args) => $options.handleCameraError && $options.handleCameraError(...args))
  1264. } : {}, {
  1265. k: $data.loading
  1266. }, $data.loading ? {} : {}, {
  1267. l: $data.showDebugInfo
  1268. }, $data.showDebugInfo ? common_vendor.e({
  1269. m: $data.assistantResponse
  1270. }, $data.assistantResponse ? {
  1271. n: common_vendor.t($data.assistantResponse)
  1272. } : {}, {
  1273. o: $data.audioTranscript
  1274. }, $data.audioTranscript ? {
  1275. p: common_vendor.t($data.audioTranscript)
  1276. } : {}, {
  1277. q: common_vendor.f($data.processedResponses, (item, index, i0) => {
  1278. return common_vendor.e({
  1279. a: item.role
  1280. }, item.role ? {
  1281. b: common_vendor.t(item.role)
  1282. } : {}, {
  1283. c: item.transcript
  1284. }, item.transcript ? {
  1285. d: common_vendor.t(item.transcript)
  1286. } : {}, {
  1287. e: index
  1288. });
  1289. })
  1290. }) : {}, {
  1291. r: $data.showStopRecordingButton
  1292. }, $data.showStopRecordingButton ? {
  1293. s: common_vendor.o((...args) => $options.stopRecordingAnswer && $options.stopRecordingAnswer(...args))
  1294. } : {}, {
  1295. t: $data.isRecording
  1296. }, $data.isRecording ? {} : {}, {
  1297. v: $data.showStartRecordingButton
  1298. }, $data.showStartRecordingButton ? {
  1299. w: common_vendor.o((...args) => $options.handleStartRecordingClick && $options.handleStartRecordingClick(...args))
  1300. } : {}, {
  1301. x: $data.showRetryButton
  1302. }, $data.showRetryButton ? {
  1303. y: common_vendor.o((...args) => $options.retryVideoUpload && $options.retryVideoUpload(...args))
  1304. } : {});
  1305. }
  1306. const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-464e78c6"]]);
  1307. wx.createPage(MiniProgramPage);