pcm-agents 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/LICENSE +21 -21
  2. package/dist/cjs/index-BFPEnLbS.js +195 -0
  3. package/dist/cjs/index-BFPEnLbS.js.map +1 -0
  4. package/dist/cjs/index.cjs.js +1 -1
  5. package/dist/cjs/loader.cjs.js +1 -1
  6. package/dist/cjs/my-component.cjs.entry.js +2 -2
  7. package/dist/cjs/my-component.cjs.entry.js.map +1 -1
  8. package/dist/cjs/my-component.entry.cjs.js.map +1 -1
  9. package/dist/cjs/pcm-agents.cjs.js +1 -1
  10. package/dist/cjs/pcm-app-chat-modal.pcm-chat-message.pcm-hr-chat-modal.pcm-jlpx-modal.pcm-mnms-modal.pcm-video-chat-modal.pcm-zygh-modal.entry.cjs.js.map +1 -0
  11. package/dist/cjs/pcm-app-chat-modal_7.cjs.entry.js +6560 -0
  12. package/dist/cjs/pcm-app-chat-modal_7.cjs.entry.js.map +1 -0
  13. package/dist/cjs/pcm-chat-modal.cjs.entry.js +17 -32
  14. package/dist/cjs/pcm-chat-modal.cjs.entry.js.map +1 -1
  15. package/dist/cjs/pcm-chat-modal.entry.cjs.js.map +1 -1
  16. package/dist/collection/collection-manifest.json +2 -0
  17. package/dist/collection/components/my-component/my-component.css +3 -3
  18. package/dist/collection/components/my-component/my-component.js +1 -1
  19. package/dist/collection/components/my-component/my-component.js.map +1 -1
  20. package/dist/collection/components/pcm-app-chat-modal/pcm-app-chat-modal.css +2 -1
  21. package/dist/collection/components/pcm-app-chat-modal/pcm-app-chat-modal.js +137 -159
  22. package/dist/collection/components/pcm-app-chat-modal/pcm-app-chat-modal.js.map +1 -1
  23. package/dist/collection/components/pcm-chat-message/pcm-chat-message.css +66 -12
  24. package/dist/collection/components/pcm-chat-message/pcm-chat-message.js +106 -13
  25. package/dist/collection/components/pcm-chat-message/pcm-chat-message.js.map +1 -1
  26. package/dist/collection/components/pcm-chat-modal/pcm-chat-modal.js +39 -34
  27. package/dist/collection/components/pcm-chat-modal/pcm-chat-modal.js.map +1 -1
  28. package/dist/collection/components/pcm-hr-chat-modal/pcm-hr-chat-modal.js +76 -105
  29. package/dist/collection/components/pcm-hr-chat-modal/pcm-hr-chat-modal.js.map +1 -1
  30. package/dist/collection/components/pcm-jlpx-modal/pcm-jlpx-modal.css +162 -0
  31. package/dist/collection/components/pcm-jlpx-modal/pcm-jlpx-modal.js +616 -0
  32. package/dist/collection/components/pcm-jlpx-modal/pcm-jlpx-modal.js.map +1 -0
  33. package/dist/collection/components/pcm-mnms-modal/pcm-mnms-modal.css +0 -79
  34. package/dist/collection/components/pcm-mnms-modal/pcm-mnms-modal.js +133 -81
  35. package/dist/collection/components/pcm-mnms-modal/pcm-mnms-modal.js.map +1 -1
  36. package/dist/collection/components/pcm-video-chat-modal/pcm-video-chat-modal.js +77 -101
  37. package/dist/collection/components/pcm-video-chat-modal/pcm-video-chat-modal.js.map +1 -1
  38. package/dist/collection/components/pcm-zygh-modal/pcm-zygh-modal.css +273 -0
  39. package/dist/collection/components/pcm-zygh-modal/pcm-zygh-modal.js +613 -0
  40. package/dist/collection/components/pcm-zygh-modal/pcm-zygh-modal.js.map +1 -0
  41. package/dist/collection/global/global.css +324 -0
  42. package/dist/collection/index.js.map +1 -1
  43. package/dist/collection/interfaces/chat.js.map +1 -1
  44. package/dist/collection/utils/utils.js +54 -113
  45. package/dist/collection/utils/utils.js.map +1 -1
  46. package/dist/components/index.js +1298 -11280
  47. package/dist/components/index.js.map +1 -1
  48. package/dist/components/my-component.js +2 -3
  49. package/dist/components/my-component.js.map +1 -1
  50. package/dist/components/{p-C4l_DOnx.js → p-BctfuDvG.js} +106 -147
  51. package/dist/components/p-BctfuDvG.js.map +1 -0
  52. package/dist/components/{p-D0s1Q-3O.js → p-LkDC0SN2.js} +343 -16
  53. package/dist/components/p-LkDC0SN2.js.map +1 -0
  54. package/dist/components/pcm-app-chat-modal.js +1 -1
  55. package/dist/components/pcm-chat-message.js +1 -1
  56. package/dist/components/pcm-chat-modal.js +19 -34
  57. package/dist/components/pcm-chat-modal.js.map +1 -1
  58. package/dist/components/pcm-hr-chat-modal.js +70 -100
  59. package/dist/components/pcm-hr-chat-modal.js.map +1 -1
  60. package/dist/components/pcm-jlpx-modal.d.ts +11 -0
  61. package/dist/components/pcm-jlpx-modal.js +339 -0
  62. package/dist/components/pcm-jlpx-modal.js.map +1 -0
  63. package/dist/components/pcm-mnms-modal.js +109 -57
  64. package/dist/components/pcm-mnms-modal.js.map +1 -1
  65. package/dist/components/pcm-video-chat-modal.js +74 -99
  66. package/dist/components/pcm-video-chat-modal.js.map +1 -1
  67. package/dist/components/pcm-zygh-modal.d.ts +11 -0
  68. package/dist/components/pcm-zygh-modal.js +330 -0
  69. package/dist/components/pcm-zygh-modal.js.map +1 -0
  70. package/dist/esm/index-nVjZGfA8.js +189 -0
  71. package/dist/esm/index-nVjZGfA8.js.map +1 -0
  72. package/dist/esm/index.js +1 -1
  73. package/dist/esm/loader.js +1 -1
  74. package/dist/esm/my-component.entry.js +2 -2
  75. package/dist/esm/my-component.entry.js.map +1 -1
  76. package/dist/esm/pcm-agents.js +1 -1
  77. package/dist/esm/pcm-app-chat-modal.pcm-chat-message.pcm-hr-chat-modal.pcm-jlpx-modal.pcm-mnms-modal.pcm-video-chat-modal.pcm-zygh-modal.entry.js.map +1 -0
  78. package/dist/esm/pcm-app-chat-modal_7.entry.js +6552 -0
  79. package/dist/esm/pcm-app-chat-modal_7.entry.js.map +1 -0
  80. package/dist/esm/pcm-chat-modal.entry.js +17 -32
  81. package/dist/esm/pcm-chat-modal.entry.js.map +1 -1
  82. package/dist/pcm-agents/index.esm.js +1 -1
  83. package/dist/pcm-agents/my-component.entry.esm.js.map +1 -1
  84. package/dist/pcm-agents/p-55417392.entry.js +2 -0
  85. package/dist/pcm-agents/p-55417392.entry.js.map +1 -0
  86. package/dist/pcm-agents/p-a698b59f.entry.js +2 -0
  87. package/dist/pcm-agents/p-a698b59f.entry.js.map +1 -0
  88. package/dist/pcm-agents/p-f3ca99b4.entry.js +2 -0
  89. package/dist/pcm-agents/p-f3ca99b4.entry.js.map +1 -0
  90. package/dist/pcm-agents/p-nVjZGfA8.js +2 -0
  91. package/dist/pcm-agents/p-nVjZGfA8.js.map +1 -0
  92. package/dist/pcm-agents/pcm-agents.esm.js +1 -1
  93. package/dist/pcm-agents/pcm-app-chat-modal.pcm-chat-message.pcm-hr-chat-modal.pcm-jlpx-modal.pcm-mnms-modal.pcm-video-chat-modal.pcm-zygh-modal.entry.esm.js.map +1 -0
  94. package/dist/pcm-agents/pcm-chat-modal.entry.esm.js.map +1 -1
  95. package/dist/types/components/pcm-app-chat-modal/pcm-app-chat-modal.d.ts +13 -8
  96. package/dist/types/components/pcm-chat-message/pcm-chat-message.d.ts +5 -0
  97. package/dist/types/components/pcm-chat-modal/pcm-chat-modal.d.ts +8 -8
  98. package/dist/types/components/pcm-hr-chat-modal/pcm-hr-chat-modal.d.ts +6 -12
  99. package/dist/types/components/pcm-jlpx-modal/pcm-jlpx-modal.d.ts +113 -0
  100. package/dist/types/components/pcm-mnms-modal/pcm-mnms-modal.d.ts +19 -20
  101. package/dist/types/components/pcm-video-chat-modal/pcm-video-chat-modal.d.ts +4 -4
  102. package/dist/types/components/pcm-zygh-modal/pcm-zygh-modal.d.ts +117 -0
  103. package/dist/types/components.d.ts +429 -80
  104. package/dist/types/interfaces/chat.d.ts +0 -4
  105. package/dist/types/utils/utils.d.ts +29 -83
  106. package/package.json +61 -60
  107. package/readme.md +307 -307
  108. package/dist/cjs/index-DfIUl99H.js +0 -11413
  109. package/dist/cjs/index-DfIUl99H.js.map +0 -1
  110. package/dist/cjs/pcm-app-chat-modal.pcm-chat-message.pcm-mnms-modal.entry.cjs.js.map +0 -1
  111. package/dist/cjs/pcm-app-chat-modal_3.cjs.entry.js +0 -3734
  112. package/dist/cjs/pcm-app-chat-modal_3.cjs.entry.js.map +0 -1
  113. package/dist/cjs/pcm-hr-chat-modal.cjs.entry.js +0 -1078
  114. package/dist/cjs/pcm-hr-chat-modal.cjs.entry.js.map +0 -1
  115. package/dist/cjs/pcm-hr-chat-modal.entry.cjs.js.map +0 -1
  116. package/dist/cjs/pcm-video-chat-modal.cjs.entry.js +0 -927
  117. package/dist/cjs/pcm-video-chat-modal.cjs.entry.js.map +0 -1
  118. package/dist/cjs/pcm-video-chat-modal.entry.cjs.js.map +0 -1
  119. package/dist/components/p-C4l_DOnx.js.map +0 -1
  120. package/dist/components/p-CgDy4pJp.js +0 -1244
  121. package/dist/components/p-CgDy4pJp.js.map +0 -1
  122. package/dist/components/p-D0s1Q-3O.js.map +0 -1
  123. package/dist/esm/index-B2EtEi7v.js +0 -11409
  124. package/dist/esm/index-B2EtEi7v.js.map +0 -1
  125. package/dist/esm/pcm-app-chat-modal.pcm-chat-message.pcm-mnms-modal.entry.js.map +0 -1
  126. package/dist/esm/pcm-app-chat-modal_3.entry.js +0 -3730
  127. package/dist/esm/pcm-app-chat-modal_3.entry.js.map +0 -1
  128. package/dist/esm/pcm-hr-chat-modal.entry.js +0 -1076
  129. package/dist/esm/pcm-hr-chat-modal.entry.js.map +0 -1
  130. package/dist/esm/pcm-video-chat-modal.entry.js +0 -925
  131. package/dist/esm/pcm-video-chat-modal.entry.js.map +0 -1
  132. package/dist/pcm-agents/p-0ddd5c47.entry.js +0 -2
  133. package/dist/pcm-agents/p-0ddd5c47.entry.js.map +0 -1
  134. package/dist/pcm-agents/p-5f624943.entry.js +0 -2
  135. package/dist/pcm-agents/p-5f624943.entry.js.map +0 -1
  136. package/dist/pcm-agents/p-6c07f155.entry.js +0 -2
  137. package/dist/pcm-agents/p-6c07f155.entry.js.map +0 -1
  138. package/dist/pcm-agents/p-9a1fb6ca.entry.js +0 -2
  139. package/dist/pcm-agents/p-9a1fb6ca.entry.js.map +0 -1
  140. package/dist/pcm-agents/p-B2EtEi7v.js +0 -146
  141. package/dist/pcm-agents/p-B2EtEi7v.js.map +0 -1
  142. package/dist/pcm-agents/p-e21bc169.entry.js +0 -2
  143. package/dist/pcm-agents/p-e21bc169.entry.js.map +0 -1
  144. package/dist/pcm-agents/pcm-app-chat-modal.pcm-chat-message.pcm-mnms-modal.entry.esm.js.map +0 -1
  145. package/dist/pcm-agents/pcm-hr-chat-modal.entry.esm.js.map +0 -1
  146. package/dist/pcm-agents/pcm-video-chat-modal.entry.esm.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"names":["pcmChatModalCss","ChatModal","modalTitle","isOpen","messages","currentMessage","messageSent","modalClosed","icon","zIndex","isShowHeader","isNeedClose","botId","conversationId","currentAssistantMessage","isLoading","currentStreamingMessage","shouldAutoScroll","SCROLL_THRESHOLD","isLoadingHistory","streamComplete","suggestedQuestions","suggestedQuestionsLoading","stopSuggestedQuestionsRef","current","selectedFile","isUploading","uploadedFileInfo","defaultQuery","fullscreen","handleClose","this","emit","handleInputChange","event","input","target","value","getSuggestedQuestions","messageId","response","sendHttpRequest","url","params","bot_id","isOk","data","result","error","console","handleStopSuggestedQuestions","handleFileChange","async","files","length","uploadFile","formData","FormData","append","fetch","method","body","json","log","push","cos_key","filename","ext","presigned_url","clearSelectedFile","alert","handleUploadClick","fileInput","hostElement","shadowRoot","querySelector","click","sendMessageToAPI","message","answer","now","Date","time","getHours","getMinutes","toString","padStart","queryText","trim","newMessage","id","query","isStreaming","conversation_id","inputs","status","scrollToBottom","requestData","response_mode","user","fileUrls","map","fileInfo","join","sendSSERequest","onMessage","updateUrlWithConversationId","updatedMessage","message_id","onError","onComplete","handleScroll","chatHistory","scrollTop","scrollHeight","clientHeight","distanceFromBottom","componentDidRender","urlParams","URLSearchParams","window","location","search","get","newUrl","URL","href","searchParams","set","history","replaceState","handleSendMessage","inputElement","focus","handleKeyDown","key","shiftKey","preventDefault","loadHistoryMessages","limit","Error","historyData","formattedMessages","msg","created_at","timeStr","requestAnimationFrame","handleIsOpenChange","newValue","handleDefaultQueryChange","componentWillLoad","render","modalStyle","String","containerClass","overlayClass","h","class","style","src","alt","onClick","onScroll","Fragment","onMessageChange","updatedMessages","detail","question","index","title","name","type","onChange","accept","disabled","viewBox","fill","stroke","d","placeholder","onInput","onKeyDown"],"sources":["src/components/pcm-chat-modal/pcm-chat-modal.css?tag=pcm-chat-modal&encapsulation=shadow","src/components/pcm-chat-modal/pcm-chat-modal.tsx"],"sourcesContent":[":host {\r\n display: block;\r\n}\r\n\r\n.modal-overlay {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n bottom: 0;\r\n background-color: rgba(0, 0, 0, 0.5);\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n overflow-y: auto;\r\n padding: 20px;\r\n z-index: 1000;\r\n}\r\n\r\n/* 全屏模式下取消 padding */\r\n.fullscreen-overlay {\r\n padding: 0;\r\n}\r\n\r\n.modal-container {\r\n background: white;\r\n border-radius: 8px;\r\n width: 100%;\r\n max-width: 800px;\r\n height: 80vh;\r\n min-height: 600px;\r\n display: flex;\r\n flex-direction: column;\r\n position: relative;\r\n margin: auto;\r\n}\r\n\r\n/* 全屏模式样式 */\r\n.modal-container.fullscreen {\r\n width: 100vw;\r\n max-width: none;\r\n height: 100vh;\r\n min-height: 100vh;\r\n border-radius: 0;\r\n margin: 0;\r\n}\r\n\r\n\r\n/* 响应式布局 */\r\n@media screen and (max-width: 768px) {\r\n .modal-overlay {\r\n padding: 0;\r\n }\r\n\r\n .modal-container {\r\n width: 95%;\r\n height: 70vh;\r\n min-height: 400px;\r\n }\r\n\r\n .chat-history {\r\n min-height: 200px;\r\n max-height: 50vh;\r\n }\r\n\r\n .modal-container.fullscreen {\r\n /* 支持 iOS Safari */\r\n height: -webkit-fill-available;\r\n max-height: -webkit-fill-available;\r\n /* 确保内容不会被顶部状态栏和底部工具栏遮挡 */\r\n padding: env(safe-area-inset-top) 0 env(safe-area-inset-bottom);\r\n }\r\n\r\n .modal-container.fullscreen .chat-history {\r\n max-height: unset;\r\n }\r\n\r\n .modal-container.fullscreen .modal-header {\r\n padding: 0px 16px;\r\n /* 确保头部不被状态栏遮挡 */\r\n padding-top: max(30px, env(safe-area-inset-top));\r\n }\r\n}\r\n\r\n.modal-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 4px 16px;\r\n height: 50px;\r\n border-bottom: 1px solid #e8e8e8;\r\n}\r\n\r\n.header-left {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n}\r\n\r\n.header-icon {\r\n width: 24px;\r\n height: 24px;\r\n}\r\n\r\n.close-button {\r\n background: transparent;\r\n border: none;\r\n cursor: pointer;\r\n padding: 8px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 4px;\r\n}\r\n\r\n.close-button:hover {\r\n background-color: rgba(0, 0, 0, 0.04);\r\n}\r\n\r\n.close-button span {\r\n font-size: 24px;\r\n line-height: 1;\r\n color: #999;\r\n}\r\n\r\n.close-button:hover span {\r\n color: #666;\r\n}\r\n\r\n.chat-history {\r\n position: relative;\r\n flex: 1;\r\n overflow-y: auto;\r\n padding: 16px;\r\n scroll-behavior: smooth;\r\n min-height: 400px; /* PC端默认高度 */\r\n}\r\n\r\n/* 移动端样式 */\r\n@media screen and (max-width: 480px) {\r\n .chat-history {\r\n min-height: 200px; /* 移动端更小的高度 */\r\n max-height: 50vh;\r\n }\r\n}\r\n\r\n.message-input {\r\n padding: 16px;\r\n border-top: 1px solid #eee;\r\n display: flex;\r\n gap: 8px;\r\n align-items: center;\r\n}\r\n\r\n.message-input input {\r\n flex: 1;\r\n padding: 8px 12px;\r\n border: 1px solid #ddd;\r\n border-radius: 4px;\r\n outline: none;\r\n transition: border-color 0.2s ease;\r\n}\r\n\r\n.message-input input:focus {\r\n border-color: #bbb;\r\n}\r\n\r\n/* 消息样式 */\r\n.message {\r\n margin-bottom: 16px;\r\n opacity: 1;\r\n transition: opacity 0.3s ease;\r\n}\r\n\r\n.message-content {\r\n max-width: 70%;\r\n padding: 8px 12px;\r\n border-radius: 8px;\r\n word-break: break-word;\r\n}\r\n\r\n.message-content p {\r\n margin: 0;\r\n word-break: break-word;\r\n}\r\n\r\n.user-message {\r\n display: flex;\r\n justify-content: flex-end;\r\n}\r\n\r\n.agent-message {\r\n display: flex;\r\n justify-content: flex-start;\r\n}\r\n\r\n.user-message .message-content {\r\n background-color: #007bff;\r\n color: white;\r\n}\r\n\r\n.agent-message .message-content {\r\n background-color: #f1f1f1;\r\n}\r\n\r\n.message-time {\r\n font-size: 12px;\r\n color: #999;\r\n margin-top: 4px;\r\n display: block;\r\n}\r\n\r\n.send-button {\r\n background-color: #1890ff;\r\n color: white;\r\n border: none;\r\n border-radius: 4px;\r\n padding: 8px 16px;\r\n cursor: pointer;\r\n font-weight: 500;\r\n}\r\n\r\n.send-button:disabled {\r\n background-color: #ccc;\r\n cursor: not-allowed;\r\n}\r\n\r\n.empty-state {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n height: 100%;\r\n color: #999;\r\n text-align: center;\r\n}\r\n\r\n.loading-container {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n bottom: 0;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n background-color: rgba(255, 255, 255, 0.98);\r\n z-index: 1;\r\n opacity: 1;\r\n transition: opacity 0.3s ease;\r\n}\r\n\r\n.loading-container p {\r\n margin-top: 16px;\r\n color: #666;\r\n font-size: 14px;\r\n}\r\n\r\n.loading-spinner {\r\n width: 40px;\r\n height: 40px;\r\n border: 3px solid #f3f3f3;\r\n border-top: 3px solid #1890ff;\r\n border-radius: 50%;\r\n animation: spin 1s linear infinite;\r\n}\r\n\r\n@keyframes spin {\r\n 0% { transform: rotate(0deg); }\r\n 100% { transform: rotate(360deg); }\r\n}\r\n\r\n/* 修改 messages-wrapper 的样式 */\r\n.messages-wrapper {\r\n width: 100%;\r\n min-height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n /* 当内容少时,将内容放在底部 */\r\n justify-content: flex-end;\r\n}\r\n\r\n/* 当有很多消息时,取消固定在底部 */\r\n.messages-wrapper.has-overflow {\r\n justify-content: flex-start;\r\n}\r\n\r\n.suggested-questions {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n padding: 16px;\r\n}\r\n\r\n.suggested-question {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 8px 12px;\r\n background-color: #f3f4f6;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n font-size: 14px;\r\n color: #374151;\r\n transition: background-color 0.2s;\r\n}\r\n\r\n.suggested-question:hover {\r\n background-color: #e5e7eb;\r\n}\r\n\r\n.arrow-right {\r\n margin-left: 8px;\r\n}\r\n\r\n.loading-suggestions {\r\n display: flex;\r\n justify-content: center;\r\n padding: 16px;\r\n}\r\n\r\n.loading-spinner-small {\r\n width: 20px;\r\n height: 20px;\r\n border: 2px solid #e5e7eb;\r\n border-top-color: #6b7280;\r\n border-radius: 50%;\r\n animation: spin 1s linear infinite;\r\n}\r\n\r\n/* 添加上传按钮样式 */\r\n.upload-button {\r\n background: transparent;\r\n border: none;\r\n cursor: pointer;\r\n padding: 8px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n color: #666;\r\n border-radius: 4px;\r\n transition: background-color 0.2s;\r\n}\r\n\r\n.upload-button:hover {\r\n background-color: rgba(0, 0, 0, 0.04);\r\n}\r\n\r\n.upload-button svg {\r\n width: 20px;\r\n height: 20px;\r\n}\r\n\r\n/* 隐藏原生文件输入框 */\r\n.file-input {\r\n display: none;\r\n}\r\n\r\n/* 添加文件名显示区域样式 */\r\n.selected-file {\r\n font-size: 12px;\r\n color: #666;\r\n margin-left: 8px;\r\n max-width: 150px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.input-wrapper {\r\n flex: 1;\r\n display: flex;\r\n align-items: center;\r\n border: 1px solid #ddd;\r\n border-radius: 4px;\r\n padding: 0 4px;\r\n background: white;\r\n}\r\n\r\n.input-wrapper input {\r\n border: none;\r\n flex: 1;\r\n padding: 8px;\r\n outline: none;\r\n}\r\n\r\n.input-wrapper:focus-within {\r\n border-color: #bbb;\r\n}\r\n\r\n/* 文件预览区域样式 */\r\n.file-preview {\r\n padding: 8px 16px;\r\n border-top: 1px solid #eee;\r\n background-color: #f9f9f9;\r\n}\r\n\r\n.file-info {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 8px;\r\n background: white;\r\n border: 1px solid #e8e8e8;\r\n border-radius: 4px;\r\n}\r\n\r\n.file-name {\r\n font-size: 13px;\r\n color: #333;\r\n margin-right: 8px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n max-width: calc(100% - 30px);\r\n}\r\n\r\n.remove-file {\r\n background: transparent;\r\n border: none;\r\n color: #999;\r\n cursor: pointer;\r\n padding: 4px 8px;\r\n font-size: 16px;\r\n line-height: 1;\r\n border-radius: 4px;\r\n transition: all 0.2s;\r\n}\r\n\r\n.remove-file:hover {\r\n background-color: #f0f0f0;\r\n color: #666;\r\n}\r\n\r\n\r\n","import { Component, Prop, h, State, Event, EventEmitter, Element, Watch } from '@stencil/core';\r\nimport { convertWorkflowStreamNodeToMessageRound, UserInputMessageType, sendSSERequest, sendHttpRequest } from '../../utils/utils';\r\nimport { ChatMessage } from '../../interfaces/chat';\r\n\r\n@Component({\r\n tag: 'pcm-chat-modal',\r\n styleUrl: 'pcm-chat-modal.css',\r\n shadow: true,\r\n})\r\nexport class ChatModal {\r\n /**\r\n * 模态框标题\r\n */\r\n @Prop() modalTitle: string = '在线客服';\r\n\r\n /**\r\n * 是否显示聊天模态框\r\n */\r\n @Prop({ mutable: true }) isOpen: boolean = false;\r\n\r\n /**\r\n * 聊天消息历史\r\n */\r\n @State() messages: ChatMessage[] = [];\r\n\r\n /**\r\n * 当前输入的消息\r\n */\r\n @State() currentMessage: string = '';\r\n\r\n /**\r\n * 当发送消息时触发\r\n */\r\n @Event() messageSent: EventEmitter<string>;\r\n\r\n /**\r\n * 点击模态框关闭时触发\r\n */\r\n @Event() modalClosed: EventEmitter<void>;\r\n\r\n /**\r\n * 应用图标URL\r\n */\r\n @Prop() icon?: string;\r\n\r\n /**\r\n * 聊天框的页面层级\r\n */\r\n @Prop() zIndex?: number = 1000;\r\n\r\n /**\r\n * 是否展示顶部标题栏\r\n */\r\n @Prop() isShowHeader: boolean = true;\r\n\r\n /**\r\n * 是否展示右上角的关闭按钮\r\n */\r\n @Prop() isNeedClose: boolean = true;\r\n\r\n /**\r\n * 机器人ID\r\n */\r\n @Prop() botId: string;\r\n\r\n /**\r\n * 会话ID\r\n */\r\n @Prop({ mutable: true }) conversationId?: string;\r\n\r\n\r\n /**\r\n * 当前助手回复的消息\r\n */\r\n @State() currentAssistantMessage: string = '';\r\n\r\n /**\r\n * 是否正在加载回复\r\n */\r\n @State() isLoading: boolean = false;\r\n\r\n /**\r\n * 当前正在流式输出的消息\r\n */\r\n @State() currentStreamingMessage: ChatMessage | null = null;\r\n\r\n // 添加新的状态控制\r\n @State() shouldAutoScroll: boolean = true;\r\n private readonly SCROLL_THRESHOLD = 30;\r\n\r\n @State() isLoadingHistory: boolean = false;\r\n\r\n // 使用 @Element 装饰器获取组件的 host 元素\r\n @Element() hostElement: HTMLElement;\r\n\r\n // 添加新的 Event\r\n @Event() streamComplete: EventEmitter<{\r\n conversation_id: string;\r\n event: string;\r\n message_id: string;\r\n id: string;\r\n }>;\r\n\r\n @State() suggestedQuestions: string[] = [];\r\n @State() suggestedQuestionsLoading: boolean = false;\r\n private stopSuggestedQuestionsRef: { current: boolean } = { current: false };\r\n\r\n @State() selectedFile: File | null = null;\r\n @State() isUploading: boolean = false;\r\n @State() uploadedFileInfo: { cos_key: string, filename: string, ext: string, presigned_url: string }[] = [];\r\n\r\n /**\r\n * 默认查询文本\r\n */\r\n @Prop() defaultQuery: string = '';\r\n\r\n /**\r\n * 是否以全屏模式打开\r\n */\r\n @Prop() fullscreen: boolean = false;\r\n\r\n private handleClose = () => {\r\n this.modalClosed.emit();\r\n };\r\n\r\n private handleInputChange = (event: Event) => {\r\n const input = event.target as HTMLInputElement;\r\n this.currentMessage = input.value;\r\n };\r\n\r\n private async getSuggestedQuestions(messageId: string) {\r\n this.stopSuggestedQuestionsRef.current = false;\r\n this.suggestedQuestionsLoading = true;\r\n\r\n try {\r\n const response = await sendHttpRequest({\r\n url: `https://pcm_api.ylzhaopin.com/share/messages/${messageId}/suggested`,\r\n params: {\r\n bot_id: this.botId\r\n }\r\n });\r\n\r\n if (this.stopSuggestedQuestionsRef.current) return;\r\n\r\n if (response.isOk && response.data?.result === 'success') {\r\n this.suggestedQuestions = response.data?.data || [];\r\n }\r\n } catch (error) {\r\n console.error('获取问题建议失败:', error);\r\n } finally {\r\n this.suggestedQuestionsLoading = false;\r\n }\r\n }\r\n\r\n private handleStopSuggestedQuestions = () => {\r\n this.suggestedQuestions = [];\r\n this.stopSuggestedQuestionsRef.current = true;\r\n };\r\n\r\n private handleFileChange = async (event: Event) => {\r\n const input = event.target as HTMLInputElement;\r\n if (input.files && input.files.length > 0) {\r\n this.selectedFile = input.files[0];\r\n \r\n // 文件选择后立即上传\r\n await this.uploadFile();\r\n }\r\n };\r\n\r\n private async uploadFile() {\r\n if (!this.selectedFile) return;\r\n \r\n this.isUploading = true;\r\n \r\n try {\r\n const formData = new FormData();\r\n formData.append('file', this.selectedFile);\r\n \r\n const response = await fetch('https://pcm_api.ylzhaopin.com/external/v1/files/upload', {\r\n method: 'POST',\r\n body: formData\r\n });\r\n \r\n \r\n const result = await response.json();\r\n console.log('result', result);\r\n if (result) {\r\n this.uploadedFileInfo.push({\r\n cos_key: result.cos_key,\r\n filename: result.filename,\r\n ext: result.ext,\r\n presigned_url: result.presigned_url\r\n });\r\n } \r\n } catch (error) {\r\n console.error('文件上传错误:', error);\r\n this.clearSelectedFile();\r\n alert('文件上传失败,请重试');\r\n } finally {\r\n this.isUploading = false;\r\n }\r\n }\r\n\r\n private handleUploadClick = () => {\r\n const fileInput = this.hostElement.shadowRoot?.querySelector('.file-input') as HTMLInputElement;\r\n fileInput?.click();\r\n };\r\n\r\n private clearSelectedFile = () => {\r\n this.selectedFile = null;\r\n this.uploadedFileInfo = [];\r\n const fileInput = this.hostElement.shadowRoot?.querySelector('.file-input') as HTMLInputElement;\r\n if (fileInput) {\r\n fileInput.value = '';\r\n }\r\n };\r\n\r\n\r\n private async sendMessageToAPI(message: string) {\r\n this.handleStopSuggestedQuestions();\r\n console.log('开始发送消息:', message);\r\n this.isLoading = true;\r\n let answer = '';\r\n\r\n const now = new Date();\r\n const time = `${now.getHours()}:${now.getMinutes().toString().padStart(2, '0')}`;\r\n\r\n // 如果消息为空但有文件,使用默认文本\r\n const queryText = message.trim() || (this.uploadedFileInfo.length > 0 ? '请分析这个文件' : '');\r\n\r\n // 创建新的消息对象时确保必填字段都有值\r\n const newMessage: ChatMessage = {\r\n id: `temp-${Date.now()}`, // 消息唯一标识\r\n time: time, // 消息时间\r\n query: queryText, // 用户输入的消息内容\r\n answer: '', // AI助手的回复内容\r\n bot_id: this.botId, // 机器人ID\r\n isStreaming: true, // 是否正在流式输出\r\n conversation_id: this.conversationId, // 会话ID\r\n inputs: {}, // 输入参数\r\n status: \"normal\", // 消息状态\r\n error: null // 错误信息\r\n };\r\n\r\n // 设置当前流式消息\r\n this.currentStreamingMessage = newMessage;\r\n\r\n this.shouldAutoScroll = true;\r\n // 滚动到底部\r\n this.scrollToBottom();\r\n\r\n // 准备请求数据\r\n const requestData: any = {\r\n bot_id: this.botId,\r\n response_mode: 'streaming',\r\n conversation_id: this.conversationId,\r\n query: queryText,\r\n user: '1234567890'\r\n };\r\n // 如果有上传的文件,添加到inputs参数\r\n if (this.uploadedFileInfo.length > 0) {\r\n const fileUrls = this.uploadedFileInfo.map(fileInfo => fileInfo.cos_key).join(',');\r\n\r\n requestData.inputs = {\r\n ...requestData.inputs,\r\n input: fileUrls\r\n };\r\n }\r\n\r\n await sendSSERequest({\r\n url: `https://pcm_api.ylzhaopin.com/share/chat-messages`,\r\n method: 'POST',\r\n data: requestData,\r\n onMessage: (data) => {\r\n console.log('收到Stream数据:', data);\r\n\r\n if (data.conversation_id && !this.conversationId) {\r\n this.conversationId = data.conversation_id;\r\n this.updateUrlWithConversationId(data.conversation_id);\r\n }\r\n\r\n if (data.event === 'message') {\r\n const inputMessage: UserInputMessageType = { message: message };\r\n convertWorkflowStreamNodeToMessageRound('message', inputMessage, data);\r\n\r\n if (data.event === 'agent_message' || data.event === 'message') {\r\n if (data.answer) {\r\n answer += data.answer;\r\n const updatedMessage: ChatMessage = {\r\n ...this.currentStreamingMessage,\r\n answer,\r\n isStreaming: true\r\n };\r\n this.currentStreamingMessage = updatedMessage;\r\n this.scrollToBottom();\r\n }\r\n }\r\n }\r\n if (data.event === \"message_end\") {\r\n this.streamComplete.emit({\r\n conversation_id: data.conversation_id || '',\r\n event: data.event,\r\n message_id: data.message_id,\r\n id: data.id,\r\n });\r\n }\r\n },\r\n onError: (error) => {\r\n console.error('发生错误:', error);\r\n this.messages = [...this.messages, {\r\n ...newMessage,\r\n answer: '抱歉,发生了错误,请稍后再试。',\r\n error: error,\r\n isStreaming: false\r\n }];\r\n this.currentStreamingMessage = null;\r\n this.isLoading = false;\r\n },\r\n onComplete: () => {\r\n console.log('请求完成');\r\n this.isLoading = false;\r\n this.messages = [...this.messages, this.currentStreamingMessage];\r\n\r\n // 在消息完成后获取问题建议\r\n if (this.currentStreamingMessage) {\r\n this.getSuggestedQuestions(this.currentStreamingMessage.conversation_id);\r\n }\r\n\r\n this.currentStreamingMessage = null;\r\n }\r\n });\r\n }\r\n // 监听滚动事件,用于控制聊天历史记录的自动滚动行为。\r\n private handleScroll = () => {\r\n const chatHistory = this.hostElement.shadowRoot?.querySelector('.chat-history');\r\n if (!chatHistory) return;\r\n\r\n const { scrollTop, scrollHeight, clientHeight } = chatHistory;\r\n const distanceFromBottom = scrollHeight - scrollTop - clientHeight;\r\n\r\n // 更新是否应该自动滚动的状态\r\n this.shouldAutoScroll = distanceFromBottom <= this.SCROLL_THRESHOLD;\r\n };\r\n\r\n private scrollToBottom() {\r\n if (!this.shouldAutoScroll) return;\r\n const chatHistory = this.hostElement.shadowRoot?.querySelector('.chat-history');\r\n console.log('chatHistory', chatHistory);\r\n if (chatHistory && this.isOpen) {\r\n // 强制浏览器重新计算布局\r\n chatHistory.scrollTop = chatHistory.scrollHeight;\r\n }\r\n }\r\n\r\n // 添加 componentDidRender 生命周期方法,用于在组件渲染后滚动到底部\r\n componentDidRender() {\r\n if (this.isLoadingHistory || (this.shouldAutoScroll && this.isOpen)) {\r\n const chatHistory = this.hostElement.shadowRoot?.querySelector('.chat-history');\r\n if (chatHistory) {\r\n chatHistory.scrollTop = chatHistory.scrollHeight;\r\n }\r\n }\r\n }\r\n\r\n private updateUrlWithConversationId(conversationId: string) {\r\n const urlParams = new URLSearchParams(window.location.search);\r\n if (!urlParams.get('conversation_id')) {\r\n const newUrl = new URL(window.location.href);\r\n newUrl.searchParams.set('conversation_id', conversationId);\r\n window.history.replaceState({}, '', newUrl);\r\n }\r\n }\r\n\r\n private handleSendMessage = () => {\r\n if ((!this.currentMessage.trim() && this.uploadedFileInfo.length === 0) || this.isLoading) return;\r\n\r\n // 触发消息发送事件\r\n this.messageSent.emit(this.currentMessage);\r\n\r\n // 发送消息到API\r\n this.sendMessageToAPI(this.currentMessage);\r\n\r\n // 清空输入框\r\n this.currentMessage = '';\r\n \r\n // 清除已选择的文件\r\n this.clearSelectedFile();\r\n\r\n // 保持输入框焦点\r\n const inputElement = this.hostElement.shadowRoot?.querySelector('input');\r\n inputElement?.focus();\r\n };\r\n\r\n private handleKeyDown = (event: KeyboardEvent) => {\r\n if (event.key === 'Enter' && !event.shiftKey) {\r\n event.preventDefault();\r\n this.handleSendMessage();\r\n }\r\n };\r\n\r\n // 修改 loadHistoryMessages 方法\r\n private async loadHistoryMessages() {\r\n if (!this.conversationId) return;\r\n\r\n this.isLoadingHistory = true;\r\n\r\n try {\r\n const response = await sendHttpRequest({\r\n url: `https://pcm_api.ylzhaopin.com/share/messages`,\r\n params: {\r\n conversation_id: this.conversationId,\r\n bot_id: this.botId,\r\n user: '1234567890',\r\n limit: 20\r\n }\r\n });\r\n\r\n if (!response.isOk || !response.data) {\r\n throw new Error('加载历史消息失败');\r\n }\r\n\r\n // 适配新的接口返回格式\r\n const historyData = response.data.data || [];\r\n\r\n // 清空现有消息,确保不会重复\r\n this.currentStreamingMessage = null;\r\n this.messages = [];\r\n\r\n const formattedMessages: ChatMessage[] = historyData.map(msg => {\r\n const time = new Date(msg.created_at * 1000);\r\n const timeStr = `${time.getHours()}:${time.getMinutes().toString().padStart(2, '0')}`;\r\n\r\n return {\r\n ...msg,\r\n time: timeStr,\r\n bot_id: this.botId,\r\n isStreaming: false,\r\n status: msg.status === 'error' ? 'error' : 'normal' as const\r\n };\r\n });\r\n\r\n this.messages = formattedMessages;\r\n\r\n // 使用 requestAnimationFrame 确保在下一帧渲染后滚动\r\n requestAnimationFrame(() => {\r\n this.shouldAutoScroll = true;\r\n this.scrollToBottom();\r\n });\r\n\r\n } catch (error) {\r\n console.error('加载历史消息失败:', error);\r\n } finally {\r\n this.isLoadingHistory = false;\r\n }\r\n }\r\n\r\n\r\n // 添加 isOpen 的 watch 方法\r\n @Watch('isOpen')\r\n async handleIsOpenChange(newValue: boolean) {\r\n if (newValue && this.conversationId) {\r\n await this.loadHistoryMessages();\r\n }\r\n }\r\n\r\n @Watch('defaultQuery')\r\n handleDefaultQueryChange(newValue: string) {\r\n if (newValue && !this.currentMessage) {\r\n this.currentMessage = newValue;\r\n }\r\n }\r\n\r\n componentWillLoad() {\r\n // 组件加载时设置默认查询\r\n if (this.defaultQuery) {\r\n this.currentMessage = this.defaultQuery;\r\n }\r\n }\r\n\r\n render() {\r\n if (!this.isOpen) return null;\r\n\r\n const modalStyle = {\r\n zIndex: String(this.zIndex)\r\n };\r\n\r\n const containerClass = {\r\n 'modal-container': true,\r\n 'fullscreen': this.fullscreen\r\n };\r\n\r\n const overlayClass = {\r\n 'modal-overlay': true,\r\n 'fullscreen-overlay': this.fullscreen\r\n };\r\n\r\n return (\r\n <div class={overlayClass} style={modalStyle}>\r\n <div class={containerClass}>\r\n {this.isShowHeader && (\r\n <div class=\"modal-header\">\r\n <div class=\"header-left\">\r\n {this.icon && <img src={this.icon} class=\"header-icon\" alt=\"应用图标\" />}\r\n <h3>{this.modalTitle}</h3>\r\n </div>\r\n {this.isNeedClose && (\r\n <button class=\"close-button\" onClick={this.handleClose}>\r\n <span>×</span>\r\n </button>\r\n )}\r\n </div>\r\n )}\r\n\r\n <div class=\"chat-history\" onScroll={this.handleScroll}>\r\n {this.isLoadingHistory ? (\r\n <div class=\"loading-container\">\r\n <div class=\"loading-spinner\"></div>\r\n <p>加载历史消息中...</p>\r\n </div>\r\n ) : (\r\n <>\r\n {this.messages.map((message) => (\r\n <div id={`message_${message.id}`} key={message.id}>\r\n <pcm-chat-message\r\n message={message}\r\n onMessageChange={(event) => {\r\n const updatedMessages = this.messages.map(msg =>\r\n msg.id === message.id ? { ...msg, ...event.detail } : msg\r\n );\r\n this.messages = updatedMessages;\r\n }}\r\n ></pcm-chat-message>\r\n </div>\r\n ))}\r\n {this.currentStreamingMessage && (\r\n <div id={`message_${this.currentStreamingMessage.id}`}>\r\n <pcm-chat-message\r\n message={this.currentStreamingMessage}\r\n ></pcm-chat-message>\r\n </div>\r\n )}\r\n {this.messages.length === 0 && !this.currentStreamingMessage && (\r\n <div class=\"empty-state\">\r\n <p>请输入消息</p>\r\n </div>\r\n )}\r\n </>\r\n )}\r\n\r\n {this.suggestedQuestionsLoading ? (\r\n <div class=\"loading-suggestions\">\r\n <div class=\"loading-spinner-small\"></div>\r\n </div>\r\n ) : (\r\n this.suggestedQuestions.length > 0 && (\r\n <div class=\"suggested-questions\">\r\n {this.suggestedQuestions.map((question, index) => (\r\n <div\r\n key={index}\r\n class=\"suggested-question\"\r\n onClick={() => {\r\n this.currentMessage = question;\r\n this.handleSendMessage();\r\n }}\r\n >\r\n {question}\r\n <span class=\"arrow-right\">→</span>\r\n </div>\r\n ))}\r\n </div>\r\n )\r\n )}\r\n </div>\r\n\r\n {/* 添加文件预览区域 */}\r\n {this.selectedFile && (\r\n <div class=\"file-preview\">\r\n <div class=\"file-info\">\r\n <span class=\"file-name\" title={this.selectedFile.name}>\r\n {this.selectedFile.name}\r\n {this.isUploading && <span class=\"uploading-indicator\"> (上传中...)</span>}\r\n {this.uploadedFileInfo.length > 0 && <span class=\"upload-success\"> (已上传)</span>}\r\n </span>\r\n <button class=\"remove-file\" onClick={this.clearSelectedFile}>\r\n ×\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n\r\n <div class=\"message-input\">\r\n <input\r\n type=\"file\"\r\n class=\"file-input\"\r\n onChange={this.handleFileChange}\r\n accept=\"image/*,.pdf,.doc,.docx,.txt\"\r\n />\r\n <button\r\n class=\"upload-button\"\r\n onClick={this.handleUploadClick}\r\n title=\"上传文件\"\r\n disabled={this.isUploading}\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\">\r\n <path\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n stroke-width=\"2\"\r\n d=\"M12 4v16m0-16l-4 4m4-4l4 4\"\r\n />\r\n </svg>\r\n </button>\r\n\r\n <div class=\"input-wrapper\">\r\n <input\r\n type=\"text\"\r\n placeholder=\"请输入消息...\"\r\n value={this.currentMessage}\r\n onInput={this.handleInputChange}\r\n onKeyDown={this.handleKeyDown}\r\n disabled={this.isLoading}\r\n />\r\n </div>\r\n\r\n <button\r\n class=\"send-button\"\r\n onClick={() => this.handleSendMessage()}\r\n disabled={(!this.currentMessage.trim() && this.uploadedFileInfo.length === 0) || this.isLoading || this.isUploading}\r\n >\r\n {this.isLoading ? '发送中...' : '发送'}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n }\r\n} "],"mappings":"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","ignoreList":[]}
@@ -1,2 +0,0 @@
1
- import{r as e,c as t,g as i,h as s}from"./p-DaMwkrv5.js";import{a as o,s as r}from"./p-B2EtEi7v.js";const n=":host{display:block}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:rgba(0, 0, 0, 0.5);display:flex;justify-content:center;align-items:center;overflow-y:auto;padding:20px;z-index:1000}.fullscreen-overlay{padding:0}.modal-container{background:white;border-radius:8px;width:100%;max-width:800px;display:flex;flex-direction:column;position:relative;margin:auto}.modal-container.fullscreen{width:100vw;max-width:none;height:100%;border-radius:0;margin:0;display:flex;flex-direction:column;height:100vh;max-height:100vh}.modal-container.fullscreen>div:not(.modal-header):not(.initial-upload){display:flex;flex-direction:column;flex:1;overflow:hidden;height:100%}.pc-layout{width:80%;max-width:800px;min-width:320px;min-height:400px}.mobile-layout{width:100%;height:100%;border-radius:0}@media screen and (max-width: 768px){.pc-layout{width:95%;}.modal-overlay{padding:0}.modal-container.fullscreen{height:-webkit-fill-available;max-height:-webkit-fill-available;padding:env(safe-area-inset-top) 0 env(safe-area-inset-bottom)}.modal-container.mobile-layout{width:100%;height:100vh;max-height:100vh;min-height:100vh;border-radius:0;margin:0;display:flex;flex-direction:column}}.video-preview.placeholder{display:flex;justify-content:center;align-items:center;background:#EAEAEA}.placeholder-status{color:#00000066}.waiting-message p{margin:0;font-size:16px;color:white;font-weight:500}.recording-container{width:100%;display:flex;flex-direction:column;align-items:center}.video-area{width:100%;display:flex;flex-direction:column;align-items:center}.stop-recording-button{width:100%;height:100%;font-size:16px;background:#f44336;border-radius:6px;color:white;border:none;cursor:pointer}.stop-recording-button:hover{background:#d32f2f}.play-audio-container{width:100%;height:100%;display:flex;justify-content:center;align-items:center}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:4px 16px;height:50px;border-bottom:1px solid #e8e8e8;flex-shrink:0;}.header-left{display:flex;align-items:center;gap:8px}.header-icon{width:24px;height:24px}.close-button{background:transparent;border:none;cursor:pointer;padding:8px;display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:4px}.close-button:hover{background-color:rgba(0, 0, 0, 0.04)}.close-button span{font-size:24px;line-height:1;color:#999}.close-button:hover span{color:#666}.chat-history{position:relative;flex:1;overflow-y:auto;padding:20px;scroll-behavior:smooth;min-height:200px;background:url(https://pcm-resource-1312611446.cos.ap-guangzhou.myqcloud.com/web/sdk/chat_bg.png);background-size:100%}.fullscreen .chat-history{height:auto;flex:1 1 auto}.message-input{padding:16px;border-top:1px solid #eee;display:flex;gap:8px;align-items:center}.message-input input{flex:1;padding:8px 12px;border:1px solid #ddd;border-radius:4px;outline:none;transition:border-color 0.2s ease}.message-input input:focus{border-color:#bbb}.message{margin-bottom:16px;opacity:1;transition:opacity 0.3s ease}.message-content{max-width:70%;padding:8px 12px;border-radius:8px;word-break:break-word}.message-content p{margin:0;word-break:break-word}.user-message{display:flex;justify-content:flex-end}.agent-message{display:flex;justify-content:flex-start}.user-message .message-content{background-color:#007bff;color:white}.agent-message .message-content{background-color:#f1f1f1}.message-time{font-size:12px;color:#999;margin-top:4px;display:block}.send-button{background-color:#1890ff;color:white;border:none;border-radius:4px;padding:8px 16px;cursor:pointer;font-weight:500}.send-button:disabled{background-color:#ccc;cursor:not-allowed}.empty-state{display:flex;justify-content:center;align-items:center;height:100%;color:#999;text-align:center}.loading-container{position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;justify-content:center;align-items:center;background-color:rgba(255, 255, 255, 0.98);z-index:1;opacity:1;transition:opacity 0.3s ease}.loading-container p{margin-top:16px;color:#666;font-size:14px}.loading-spinner{width:40px;height:40px;border:3px solid #f3f3f3;border-top:3px solid #1890ff;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.messages-wrapper{width:100%;min-height:100%;display:flex;flex-direction:column;justify-content:flex-end}.messages-wrapper.has-overflow{justify-content:flex-start}.suggested-questions{display:flex;flex-direction:column;gap:8px;padding:16px}.suggested-question{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background-color:#f3f4f6;border-radius:4px;cursor:pointer;font-size:14px;color:#374151;transition:background-color 0.2s}.suggested-question:hover{background-color:#e5e7eb}.arrow-right{margin-left:8px}.loading-suggestions{display:flex;justify-content:center;padding:16px}.loading-spinner-small{width:20px;height:20px;border:2px solid #e5e7eb;border-top-color:#6b7280;border-radius:50%;animation:spin 1s linear infinite}.upload-button{background:transparent;border:none;cursor:pointer;padding:8px;display:flex;align-items:center;justify-content:center;color:#666;border-radius:4px;transition:background-color 0.2s}.upload-button:hover{background-color:rgba(0, 0, 0, 0.04)}.upload-button svg{width:20px;height:20px}.file-input{display:none}.selected-file{font-size:12px;color:#666;margin-left:8px;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.input-wrapper{flex:1;display:flex;align-items:center;border:1px solid #ddd;border-radius:4px;padding:0 4px;background:white}.input-wrapper input{border:none;flex:1;padding:8px;outline:none}.input-wrapper:focus-within{border-color:#bbb}.file-preview{padding:8px 16px;border-top:1px solid #eee;background-color:#f9f9f9}.file-info{display:flex;align-items:center;justify-content:space-between;padding:8px;background:white;border:1px solid #e8e8e8;border-radius:4px}.file-name{font-size:13px;color:#333;margin-right:8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:calc(100% - 30px)}.remove-file{background:transparent;border:none;color:#999;cursor:pointer;padding:4px 8px;font-size:16px;line-height:1;border-radius:4px;transition:all 0.2s}.remove-file:hover{background-color:#f0f0f0;color:#666}.initial-upload{padding:1rem 1rem;display:flex;flex-direction:column;align-items:center;height:100%}.upload-section{max-width:600px;width:100%;text-align:center}.upload-area{border:2px dashed #ddd;border-radius:8px;padding:1rem;cursor:pointer;transition:all 0.3s ease}.upload-area:hover{border-color:#1890ff;background-color:rgba(24, 144, 255, 0.05)}.upload-placeholder{display:flex;flex-direction:column;align-items:center;color:#666}.upload-hint{font-size:0.8rem;color:#999;margin-top:0.5rem}.function-select{margin-top:2rem}.function-options{display:flex;flex-wrap:wrap;gap:1rem;justify-content:center;margin-top:1rem}.function-button{padding:0.5rem 1rem;border:1px solid #ddd;border-radius:4px;background:white;cursor:pointer;transition:all 0.3s ease}.function-button:hover{border-color:#1890ff;color:#1890ff}.function-button.selected{background:#1890ff;color:white;border-color:#1890ff}.submit-button{margin-top:1rem;padding:0.8rem 2rem;background:#1890ff;color:white;border:none;border-radius:4px;font-size:1rem;cursor:pointer;transition:all 0.3s ease;width:95%}.submit-button:disabled{background:#ccc;cursor:not-allowed}.submit-button:hover:not(:disabled){background:#40a9ff}.category-select,.dimension-select{margin:30px 0}.category-options,.dimension-options{display:flex;flex-wrap:wrap;gap:10px;margin-top:10px}.category-button,.dimension-button{padding:12px 16px;border:1px solid #E5E5E5;border-radius:6px;background:white;cursor:pointer;transition:all 0.3s}.category-button:hover,.dimension-button:hover{background:#f5f5f5}.category-button.selected{background-image:linear-gradient(111deg, #4A9FFF 0%, #1058FF 100%);color:white}.dimension-button.selected{background-image:linear-gradient(111deg, #4A9FFF 0%, #1058FF 100%);color:white}.recording-section{border-top:1px solid #eee;display:flex;flex-direction:column;align-items:center;padding:20px;border-radius:14px 14px 0 0;flex:0 0 auto}.recording-section .video-preview{width:100%;height:200px;max-width:400px;position:relative;margin-bottom:10px;border:1px solid #ddd;border-radius:12px;overflow:hidden}.recording-section video{width:100%;height:100%;object-fit:cover}.recording-status{position:absolute;top:10px;left:10px;background-color:rgba(0, 0, 0, 0.6);color:white;padding:4px 8px;border-radius:4px;display:flex;align-items:center;gap:5px;font-size:0.8rem;z-index:2}.recording-status .recording-dot{display:inline-block;width:10px;height:10px;background-color:red;border-radius:50%;margin-right:5px;animation:blink 1s infinite}.recording-status.warning{color:#ff4d4f;animation:blink 1s infinite}@keyframes blink{0%{opacity:1}50%{opacity:0.5}100%{opacity:1}}.recording-section .stop-recording-button{background-color:#f44336;color:white;border:none;cursor:pointer;font-weight:bold}.recording-section .stop-recording-button:hover{background-color:#d32f2f}.fullscreen{width:100vw;border-radius:0;height:100vh;display:flex;flex-direction:column;overflow-y:auto}.recording-controls{margin-top:10px;height:53px;width:100%;max-width:400px;display:flex;justify-content:center}.recording-controls .waiting-message{text-align:center;color:white;font-size:1rem;background-image:linear-gradient(100deg, #4A9FFF 0%, #1058FF 100%);border-radius:6px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.15);width:95%;display:flex;justify-content:center;align-items:center;cursor:pointer}.recording-controls .waiting-message.loading{background:#faad14}.recording-controls .waiting-message p{margin:0;font-size:16px;color:white;font-weight:500}.recording-controls .stop-recording-button{background-color:#dc3545;color:white;border:none;cursor:pointer;font-size:1rem}.recording-controls .stop-recording-button:hover{background-color:#c82333}.recording-controls .stop-recording-button.disabled{background:#ccc;cursor:not-allowed}.recording-controls .stop-recording-button.disabled:hover{background:#ccc}.progress-container{display:flex;justify-content:space-between;align-items:center;width:100%;max-width:400px;margin-top:10px;padding:0 5px}.progress-bar-container{height:4px;background-color:#E5E5E5;border-radius:2px;overflow:hidden;margin-right:10px;width:75px}.progress-bar{height:100%;background-image:linear-gradient(111deg, #4A9FFF 0%, #1058FF 100%);border-radius:2px;transition:width 0.3s ease}.progress-text{font-size:14px;color:#666;white-space:nowrap}";const a=class{constructor(i){e(this,i);this.modalClosed=t(this,"modalClosed");this.streamComplete=t(this,"streamComplete");this.interviewComplete=t(this,"interviewComplete");this.recordingError=t(this,"recordingError");this.recordingStatusChange=t(this,"recordingStatusChange")}modalTitle="在线客服";apiKey="";isOpen=false;messages=[];modalClosed;icon;zIndex=1e3;isShowHeader=true;isNeedClose=true;conversationId;currentAssistantMessage="";isLoading=false;currentStreamingMessage=null;shouldAutoScroll=true;isLoadingHistory=false;get hostElement(){return i(this)}streamComplete;selectedFile=null;isUploading=false;uploadedFileInfo=[];defaultQuery="";showInitialUpload=true;selectedJobCategory="";jobCategories=["人力资源学生(实习)","人力资源专员","人力资源主管","人力资源经理","人力资源总监"];dimensions=["人力资源规划","招聘与配置","员工关系","培训与开发","薪酬与绩效","组织与人才发展"];selectedDimensions=[];isRecording=false;recordingStream=null;recordedBlob=null;mediaRecorder=null;recordingTimeLeft=0;showRecordingUI=false;recordingTimer=null;recordingStartTime=0;recordingMaxTime=120;waitingToRecord=false;waitingTimer=null;waitingTimeLeft=10;videoRef=null;totalQuestions=2;currentQuestionNumber=0;isInterviewComplete=false;interviewComplete;SCROLL_THRESHOLD=30;maxRecordingTime=120;countdownWarningTime=30;showCountdownWarning=false;toEmail="";fullscreen=false;isUploadingVideo=false;requireResume=false;isPlayingAudio=false;audioUrl=null;audioElement=null;recordingError;recordingStatusChange;enableVoice=true;displayContentStatus="1";userId="";handleClose=()=>{this.stopRecording();this.modalClosed.emit()};handleFileChange=async e=>{const t=e.target;if(t.files&&t.files.length>0){this.selectedFile=t.files[0]}};async uploadFile(){if(!this.selectedFile||this.uploadedFileInfo.length>0)return;this.isUploading=true;try{const e=new FormData;e.append("file",this.selectedFile);const t=await fetch("https://pcm_api.ylzhaopin.com/external/v1/files/upload",{method:"POST",headers:{authorization:"Bearer "+this.apiKey},body:e});const i=await t.json();if(!t.ok){throw new Error(i.message||"文件上传失败")}if(i){this.uploadedFileInfo=[{cos_key:i.cos_key,filename:i.filename,ext:i.ext,presigned_url:i.presigned_url}]}}catch(e){console.error("文件上传错误:",e);this.clearSelectedFile();alert(e instanceof Error?e.message:"文件上传失败,请重试")}finally{this.isUploading=false}}handleUploadClick=()=>{const e=this.hostElement.shadowRoot?.querySelector(".file-input");e?.click()};clearSelectedFile=()=>{this.selectedFile=null;this.uploadedFileInfo=[];const e=this.hostElement.shadowRoot?.querySelector(".file-input");if(e){e.value=""}};async sendMessageToAPI(e){this.isLoading=true;let t="";let i="";const s=new Date;const r=`${s.getHours()}:${s.getMinutes().toString().padStart(2,"0")}`;const n=e.trim()||(this.uploadedFileInfo.length>0?"请分析这个文件":"");const a=this.messages.length>0?this.messages[this.messages.length-1]:null;if(a&&this.conversationId&&e!=="下一题"){this.saveAnswer(this.conversationId,a.answer,n)}const l=this.currentQuestionNumber>=this.totalQuestions&&e==="下一题";const d={id:`temp-${Date.now()}`,time:r,query:n,answer:"",isStreaming:true,conversation_id:this.conversationId,inputs:{},status:"normal",error:null};this.currentStreamingMessage=d;this.shouldAutoScroll=true;this.scrollToBottom();if(l){this.messages=[...this.messages,d];this.currentStreamingMessage=null;this.isLoading=false;this.isInterviewComplete=true;await this.completeInterview();this.interviewComplete.emit({conversation_id:this.conversationId,total_questions:this.totalQuestions});return}const c={response_mode:"streaming",conversation_id:this.conversationId,query:n,user:this.userId};c.inputs={job_info:this.selectedJobCategory,dimensional_info:this.selectedDimensions.join(","),email:this.toEmail,display_content_status:this.displayContentStatus};if(this.uploadedFileInfo.length>0){const e=this.uploadedFileInfo.map((e=>e.cos_key)).join(",");c.inputs.file_urls=e}await o({url:`https://pcm_api.ylzhaopin.com/external/v1/chat/chat-messages`,method:"POST",headers:{authorization:"Bearer "+this.apiKey},data:c,onMessage:e=>{console.log("收到Stream数据:",e);if(e.conversation_id&&!this.conversationId){this.conversationId=e.conversation_id;this.updateUrlWithConversationId(e.conversation_id)}if(e.event==="node_finished"&&e.data.inputs&&e.data.inputs.LLMText){i=e.data.inputs.LLMText;console.log("获取到 LLMText:",i)}if(e.event==="message"){if(e.event==="agent_message"||e.event==="message"){if(e.answer){t+=e.answer;const i={...this.currentStreamingMessage,answer:t,isStreaming:true};this.currentStreamingMessage=i;this.scrollToBottom()}}}if(e.event==="message_end"){this.streamComplete.emit({conversation_id:e.conversation_id||"",event:e.event,message_id:e.message_id,id:e.id})}},onError:e=>{console.error("发生错误:",e);alert(e instanceof Error?e.message:"消息发送失败,请稍后再试");this.messages=[...this.messages,{...d,answer:"抱歉,发生了错误,请稍后再试。",error:e,isStreaming:false}];this.currentStreamingMessage=null;this.isLoading=false},onComplete:async()=>{console.log("请求完成");this.isLoading=false;const t=this.currentStreamingMessage;this.messages=[...this.messages,this.currentStreamingMessage];this.currentStreamingMessage=null;if(e==="下一题"||this.currentQuestionNumber===0){this.currentQuestionNumber++}console.log(this.currentQuestionNumber);console.log(e);if(t&&t.answer){const e=i||t.answer;if(e){const t=await this.synthesizeAudio(e);if(this.enableVoice){await this.playAudio(t);this.startWaitingToRecord()}else{this.audioUrl=t}}}}})}async saveAnswer(e,t,i){try{await r({url:"https://pcm_api.ylzhaopin.com/agents/hr_competition/answer",method:"POST",headers:{authorization:"Bearer "+this.apiKey},data:{conversation_id:e,user:this.userId,question:t,answer:i}})}catch(e){console.error("保存答案失败:",e)}}handleScroll=()=>{const e=this.hostElement.shadowRoot?.querySelector(".chat-history");if(!e)return;const{scrollTop:t,scrollHeight:i,clientHeight:s}=e;const o=i-t-s;this.shouldAutoScroll=o<=this.SCROLL_THRESHOLD};scrollToBottom(){if(!this.shouldAutoScroll)return;const e=this.hostElement.shadowRoot?.querySelector(".chat-history");if(e&&this.isOpen){e.scrollTop=e.scrollHeight}}componentDidRender(){if(this.isLoadingHistory||this.shouldAutoScroll&&this.isOpen){const e=this.hostElement.shadowRoot?.querySelector(".chat-history");if(e){e.scrollTop=e.scrollHeight}}}updateUrlWithConversationId(e){const t=new URLSearchParams(window.location.search);if(!t.get("conversation_id")){const t=new URL(window.location.href);t.searchParams.set("conversation_id",e);window.history.replaceState({},"",t)}}async loadHistoryMessages(){if(!this.conversationId)return;this.isLoadingHistory=true;console.log("加载历史消息...");try{await r({url:`https://pcm_api.ylzhaopin.com/external/v1/chat/messages`,method:"GET",headers:{authorization:"Bearer "+this.apiKey},data:{conversation_id:this.conversationId,limit:20},onMessage:e=>{if(e.data){const t=e.data||[];const i=t.map((e=>{const t=new Date(e.created_at*1e3);const i=t.getHours().toString().padStart(2,"0");const s=t.getMinutes().toString().padStart(2,"0");const o=`${i}:${s}`;const{inputs:r,...n}=e;return{...n,time:o,isStreaming:false,status:e.status==="error"?"error":"normal"}}));this.messages=i;this.isLoadingHistory=false;requestAnimationFrame((()=>{this.shouldAutoScroll=true;this.scrollToBottom()}))}else{this.isLoadingHistory=false}},onError:e=>{console.error("加载历史消息失败:",e);alert(e instanceof Error?e.message:"加载历史消息失败,请刷新重试");this.isLoadingHistory=false},onComplete:()=>{this.isLoadingHistory=false}})}catch(e){console.error("加载历史消息失败:",e);alert(e instanceof Error?e.message:"加载历史消息失败,请刷新重试");this.isLoadingHistory=false}}async handleIsOpenChange(e){if(e){if(this.conversationId){await this.loadHistoryMessages()}}}handleJobCategorySelect=e=>{this.selectedJobCategory=e};handleDimensionSelect=e=>{if(this.selectedDimensions.includes(e)){this.selectedDimensions=this.selectedDimensions.filter((t=>t!==e))}else{this.selectedDimensions=[...this.selectedDimensions,e]}};handleInitialSubmit=async()=>{if(this.requireResume&&!this.selectedFile){alert("请上传简历");return}if(!this.selectedJobCategory){alert("请选择职能类别");return}if(this.selectedDimensions.length===0){alert("请至少选择一个关注模块");return}const e=confirm('如果您已做好准备请点击"确定"开始面试。');if(!e){return}if(this.requireResume){await this.uploadFile();if(this.uploadedFileInfo.length===0){return}}this.showInitialUpload=false;const t=`我是一名${this.selectedJobCategory},请您开始提问`;this.sendMessageToAPI(t)};startWaitingToRecord(){if(this.waitingTimer){clearInterval(this.waitingTimer)}if(this.recordingTimer){clearInterval(this.recordingTimer)}this.waitingToRecord=true;this.waitingTimeLeft=10;this.waitingTimer=setInterval((()=>{this.waitingTimeLeft--;if(this.waitingTimeLeft<=0){clearInterval(this.waitingTimer);this.waitingTimer=null;this.waitingToRecord=false;this.startRecording()}}),1e3)}async startRecording(){try{const e=await navigator.mediaDevices.getUserMedia({audio:true,video:{width:{ideal:1280},height:{ideal:720},frameRate:{ideal:30}}});this.recordingStream=e;this.showRecordingUI=true;this.showCountdownWarning=false;this.videoRef=null;this.setupVideoPreview(e);const t=this.getSupportedMimeType();let i;try{i=new MediaRecorder(e,{mimeType:t})}catch(t){console.warn("指定的MIME类型不受支持,使用默认设置:",t);try{i=new MediaRecorder(e)}catch(e){this.recordingError.emit({type:"recorder_creation_failed",message:"无法创建媒体录制器,您的浏览器可能不支持此功能",details:e});this.showRecordingUI=false;return}}this.mediaRecorder=i;const s=[];i.ondataavailable=e=>{if(e.data.size>0){s.push(e.data)}};i.onerror=e=>{this.recordingError.emit({type:"recording_error",message:"录制过程中发生错误",details:e});this.stopRecording()};i.onstop=()=>{try{const e=t||"video/mp4";const i=new Blob(s,{type:e});if(i.size===0){this.recordingError.emit({type:"empty_recording",message:"录制的视频为空"});this.showRecordingUI=false;return}this.recordedBlob=i;this.recordingStatusChange.emit({status:"stopped",details:{duration:Math.floor((Date.now()-this.recordingStartTime)/1e3),size:i.size,type:i.type}});this.uploadRecordedVideo()}catch(e){this.recordingError.emit({type:"processing_error",message:"处理录制视频时出错",details:e});this.showRecordingUI=false}};try{i.start();this.isRecording=true;this.recordingStartTime=Date.now();this.recordingTimeLeft=this.maxRecordingTime;this.recordingStatusChange.emit({status:"started",details:{maxDuration:this.maxRecordingTime,mimeType:i.mimeType}})}catch(e){this.recordingError.emit({type:"start_failed",message:"开始录制失败,请检查您的设备权限",details:e});this.showRecordingUI=false;return}this.recordingTimer=setInterval((()=>{const e=Math.floor((Date.now()-this.recordingStartTime)/1e3);this.recordingTimeLeft=Math.max(0,this.maxRecordingTime-e);if(this.recordingTimeLeft<=this.countdownWarningTime&&!this.showCountdownWarning){this.showCountdownWarning=true}if(this.recordingTimeLeft<=0){this.stopRecording()}}),1e3)}catch(e){console.error("无法访问摄像头或麦克风:",e);this.recordingError.emit({type:"media_access_failed",message:"无法访问摄像头或麦克风,请确保已授予权限",details:e});this.showRecordingUI=false}}setupVideoPreview(e){setTimeout((()=>{const t=this.hostElement.shadowRoot?.querySelector("video");if(t&&e){try{t.srcObject=e;t.play().catch((e=>{console.error("视频播放失败:",e)}))}catch(i){console.warn("设置srcObject失败,尝试替代方法:",i);try{const i=URL.createObjectURL(e);t.src=i;t.onended=()=>{URL.revokeObjectURL(i)}}catch(e){console.error("创建对象URL失败:",e)}}}else{console.warn("未找到视频元素或媒体流无效")}}),100)}getSupportedMimeType(){const e=["video/webm;codecs=vp8,opus","video/webm;codecs=vp9,opus","video/webm","video/mp4","video/mp4;codecs=h264,aac",""];if(!window.MediaRecorder){console.warn("MediaRecorder API不可用");return""}for(const t of e){if(!t)return"";try{if(MediaRecorder.isTypeSupported(t)){console.log("使用支持的MIME类型:",t);return t}}catch(e){console.warn(`检查MIME类型支持时出错 ${t}:`,e)}}console.warn("没有找到支持的MIME类型,将使用浏览器默认值");return""}stopRecording(){if(this.mediaRecorder&&this.isRecording){this.mediaRecorder.stop();this.isRecording=false;if(this.recordingTimer){clearInterval(this.recordingTimer);this.recordingTimer=null}if(this.recordingStream){this.recordingStream.getTracks().forEach((e=>e.stop()));this.recordingStream=null}this.videoRef=null}}async uploadRecordedVideo(){if(!this.recordedBlob)return;try{this.isUploadingVideo=true;this.showRecordingUI=false;const e=this.recordedBlob.type.includes("webm")?"webm":"mp4";const t=`answer.${e}`;const i=new FormData;i.append("file",this.recordedBlob,t);const s=await fetch("https://pcm_api.ylzhaopin.com/external/v1/files/upload",{method:"POST",headers:{authorization:"Bearer "+this.apiKey},body:i});const o=await s.json();console.log("视频上传结果:",o);if(o&&o.cos_key){await this.saveVideoAnswer(o.cos_key);this.sendNextQuestion()}else{throw new Error("视频上传失败")}}catch(e){console.error("视频上传错误:",e);this.recordingError.emit({type:"upload_failed",message:"视频上传失败",details:e})}finally{this.isUploadingVideo=false;this.showRecordingUI=false;this.recordedBlob=null}}async saveVideoAnswer(e){if(!this.conversationId)return;try{const t=this.messages.length>0?this.messages[this.messages.length-1]:null;if(!t)return;await r({url:"https://pcm_api.ylzhaopin.com/agents/hr_competition/answer",method:"POST",headers:{authorization:"Bearer "+this.apiKey},data:{conversation_id:this.conversationId,user:this.userId,question:t.answer,file_url:e}})}catch(e){console.error("保存视频答案失败:",e)}}sendNextQuestion(){this.sendMessageToAPI("下一题")}async completeInterview(){if(!this.conversationId)return;try{await r({url:`https://pcm_api.ylzhaopin.com/agents/hr_competition/${this.conversationId}/end`,method:"POST",headers:{authorization:"Bearer "+this.apiKey}})}catch(e){console.error("发送面试完成请求失败:",e)}}async synthesizeAudio(e){try{const t=await fetch("https://pcm_api.ylzhaopin.com/external/v1/tts/synthesize_audio",{method:"POST",headers:{"Content-Type":"application/json",authorization:"Bearer "+this.apiKey},body:JSON.stringify({text:e})});if(!t.ok){throw new Error("语音合成失败")}const i=await t.blob();return URL.createObjectURL(i)}catch(e){console.error("语音合成错误:",e);throw e}}playAudio(e){return new Promise((t=>{this.isPlayingAudio=true;this.audioUrl=e;if(!this.audioElement){this.audioElement=new Audio}this.audioElement.src=e;this.audioElement.onended=()=>{this.isPlayingAudio=false;this.audioUrl=null;t()};this.audioElement.onerror=()=>{console.error("音频播放错误");this.isPlayingAudio=false;this.audioUrl=null;t()};this.audioElement.play().catch((e=>{console.error("音频播放失败:",e);this.isPlayingAudio=false;this.audioUrl=null;t()}))}))}disconnectedCallback(){if(this.audioElement){this.audioElement.pause();this.audioElement.src="";this.audioElement=null}if(this.audioUrl){URL.revokeObjectURL(this.audioUrl);this.audioUrl=null}if(this.waitingTimer){clearInterval(this.waitingTimer);this.waitingTimer=null}if(this.recordingTimer){clearInterval(this.recordingTimer);this.recordingTimer=null}this.stopRecording()}handlePlayAudio=async()=>{if(this.audioUrl){await this.playAudio(this.audioUrl);this.startWaitingToRecord()}};render(){if(!this.isOpen)return null;const e={zIndex:String(this.zIndex)};const t={"modal-container":true,fullscreen:this.fullscreen};const i={"modal-overlay":true,"fullscreen-overlay":this.fullscreen};const o=()=>s("div",{class:"video-preview"},s("video",{autoPlay:true,playsInline:true,muted:true,style:{transform:"scaleX(-1)"},ref:e=>{if(e&&this.recordingStream&&!this.videoRef){this.videoRef=e}}}),s("div",{class:{"recording-status":true,warning:this.showCountdownWarning}},s("span",{class:"recording-dot"}),s("span",null,"录制中 ",Math.floor(this.recordingTimeLeft/60),":",(this.recordingTimeLeft%60).toString().padStart(2,"0"),this.showCountdownWarning&&` (即将自动完成)`)));const r=()=>{if(this.isPlayingAudio){return s("div",{class:"placeholder-status"},s("p",null,"正在播放问题,请听完后准备回答..."))}if(this.isUploadingVideo){return s("div",{class:"placeholder-status"},s("p",null,"正在上传视频,请稍候..."))}if(this.isLoading||this.currentStreamingMessage){return s("div",{class:"placeholder-status"},s("p",null,"请等待题目..."))}if(this.waitingToRecord){return s("div",{class:"placeholder-status"},s("p",null,"请准备好,",this.waitingTimeLeft,"秒后将开始录制您的回答..."))}return s("div",{class:"placeholder-status default-status"},s("p",null,"准备中..."))};return s("div",{class:i,style:e},s("div",{class:t},this.isShowHeader&&s("div",{class:"modal-header"},s("div",{class:"header-left"},this.icon&&s("img",{src:this.icon,class:"header-icon",alt:"应用图标"}),s("div",null,this.modalTitle)),this.isNeedClose&&s("button",{class:"close-button",onClick:this.handleClose},s("span",null,"×"))),this.showInitialUpload?s("div",{class:"initial-upload"},s("div",{class:"upload-section"},this.requireResume&&s(s.Fragment,null,s("h3",null,"开始前,请上传您的简历"),s("div",{class:"upload-area",onClick:this.handleUploadClick},this.selectedFile?s("div",{class:"file-info"},s("span",null,this.selectedFile.name),s("button",{class:"remove-file",onClick:e=>{e.stopPropagation();this.clearSelectedFile()}},"×")):s("div",{class:"upload-placeholder"},s("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",width:"48",height:"48"},s("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4v16m0-16l-4 4m4-4l4 4"})),s("p",null,"点击上传简历"),s("p",{class:"upload-hint"},"支持 txt、 markdown、 pdf、 docx、 md 格式")))),s("div",{class:"category-select"},s("h3",null,"请选择您的职能类别(单选)"),s("div",{class:"category-options"},this.jobCategories.map((e=>s("button",{class:{"category-button":true,selected:this.selectedJobCategory===e},onClick:()=>this.handleJobCategorySelect(e)},e))))),s("div",{class:"dimension-select"},s("h3",null,"请选择关注的模块(可多选)"),s("div",{class:"dimension-options"},this.dimensions.map((e=>s("button",{class:{"dimension-button":true,selected:this.selectedDimensions.includes(e)},onClick:()=>this.handleDimensionSelect(e)},e))))),s("button",{class:"submit-button",disabled:this.requireResume&&!this.selectedFile||!this.selectedJobCategory||this.selectedDimensions.length===0||this.requireResume&&this.isUploading,onClick:this.handleInitialSubmit},this.requireResume&&this.isUploading?"上传中...":"开始面试")),this.requireResume&&s("input",{type:"file",class:"file-input",onChange:this.handleFileChange,accept:".pdf,.doc,.docx,.txt"})):s("div",{style:{height:"100%"}},s("div",{class:"chat-history",onScroll:this.handleScroll},this.isLoadingHistory?s("div",{class:"loading-container"},s("div",{class:"loading-spinner"}),s("p",null,"加载历史消息中...")):s("div",null,this.messages.map((e=>s("div",{id:`message_${e.id}`,key:e.id},s("pcm-chat-message",{message:e,onMessageChange:t=>{const i=this.messages.map((i=>i.id===e.id?{...i,...t.detail}:i));this.messages=i}})))),this.currentStreamingMessage&&s("div",{id:`message_${this.currentStreamingMessage.id}`},s("pcm-chat-message",{message:this.currentStreamingMessage})),this.messages.length===0&&!this.currentStreamingMessage&&s("div",{class:"empty-state"},s("p",null,"请上传简历开始面试")))),s("div",{class:"recording-section"},s("div",{class:"recording-container"},s("div",{class:"video-area"},this.showRecordingUI?o():s("div",{class:"video-preview placeholder"},r())),s("div",{class:"progress-container"},s("div",{class:"progress-bar-container"},s("div",{class:"progress-bar",style:{width:`${Math.max(0,this.currentQuestionNumber-1)/this.totalQuestions*100}%`}})),s("div",{class:"progress-text"},"已完成",Math.max(0,this.currentQuestionNumber-1),"/",this.totalQuestions)),s("div",{class:"recording-controls"},this.showRecordingUI?s("button",{class:"stop-recording-button",onClick:()=>this.stopRecording()},"完成本题回答"):s("div",{class:"waiting-message"},(()=>{if(!this.enableVoice&&this.audioUrl&&!this.isPlayingAudio){return s("div",{class:"play-audio-container",onClick:this.handlePlayAudio},s("p",null,s("svg",{viewBox:"0 0 24 24",width:"24",height:"24",fill:"currentColor",style:{verticalAlign:"middle",marginRight:"8px"}},s("path",{d:"M8 5v14l11-7z"})),s("span",{style:{verticalAlign:"middle"}},"播放题目")))}return s("button",{class:"stop-recording-button disabled",disabled:true},"完成回答")})())))))))}static get watchers(){return{isOpen:["handleIsOpenChange"]}}};a.style=n;export{a as pcm_hr_chat_modal};
2
- //# sourceMappingURL=p-9a1fb6ca.entry.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["pcmHrChatModalCss","ChatHRModal","modalTitle","apiKey","isOpen","messages","modalClosed","icon","zIndex","isShowHeader","isNeedClose","conversationId","currentAssistantMessage","isLoading","currentStreamingMessage","shouldAutoScroll","isLoadingHistory","streamComplete","selectedFile","isUploading","uploadedFileInfo","defaultQuery","showInitialUpload","selectedJobCategory","jobCategories","dimensions","selectedDimensions","isRecording","recordingStream","recordedBlob","mediaRecorder","recordingTimeLeft","showRecordingUI","recordingTimer","recordingStartTime","recordingMaxTime","waitingToRecord","waitingTimer","waitingTimeLeft","videoRef","totalQuestions","currentQuestionNumber","isInterviewComplete","interviewComplete","SCROLL_THRESHOLD","maxRecordingTime","countdownWarningTime","showCountdownWarning","toEmail","fullscreen","isUploadingVideo","requireResume","isPlayingAudio","audioUrl","audioElement","recordingError","recordingStatusChange","enableVoice","displayContentStatus","userId","handleClose","this","stopRecording","emit","handleFileChange","async","event","input","target","files","length","uploadFile","formData","FormData","append","response","fetch","method","headers","authorization","body","result","json","ok","Error","message","cos_key","filename","ext","presigned_url","error","console","clearSelectedFile","alert","handleUploadClick","fileInput","hostElement","shadowRoot","querySelector","click","value","sendMessageToAPI","answer","llmText","now","Date","time","getHours","getMinutes","toString","padStart","queryText","trim","lastAIMessage","saveAnswer","isLastQuestion","newMessage","id","query","isStreaming","conversation_id","inputs","status","scrollToBottom","completeInterview","total_questions","requestData","response_mode","user","job_info","dimensional_info","join","email","display_content_status","fileUrls","map","fileInfo","file_urls","sendSSERequest","url","data","onMessage","log","updateUrlWithConversationId","LLMText","updatedMessage","message_id","onError","onComplete","latestAIMessage","textForSynthesis","synthesizeAudio","playAudio","startWaitingToRecord","question","sendHttpRequest","handleScroll","chatHistory","scrollTop","scrollHeight","clientHeight","distanceFromBottom","componentDidRender","urlParams","URLSearchParams","window","location","search","get","newUrl","URL","href","searchParams","set","history","replaceState","loadHistoryMessages","limit","historyData","formattedMessages","msg","created_at","hours","minutes","timeStr","msgWithoutInputs","requestAnimationFrame","handleIsOpenChange","newValue","handleJobCategorySelect","category","handleDimensionSelect","dimension","includes","filter","d","handleInitialSubmit","confirmed","confirm","clearInterval","setInterval","startRecording","stream","navigator","mediaDevices","getUserMedia","audio","video","width","ideal","height","frameRate","setupVideoPreview","mimeType","getSupportedMimeType","MediaRecorder","e","warn","recorderError","type","details","chunks","ondataavailable","size","push","onerror","onstop","blobType","blob","Blob","duration","Math","floor","uploadRecordedVideo","start","maxDuration","startError","elapsedTime","max","setTimeout","videoElement","srcObject","play","catch","err","objectUrl","createObjectURL","src","onended","revokeObjectURL","urlError","mimeTypes","isTypeSupported","stop","getTracks","forEach","track","fileExtension","fileName","saveVideoAnswer","sendNextQuestion","cosKey","file_url","text","JSON","stringify","audioBlob","Promise","resolve","Audio","disconnectedCallback","pause","handlePlayAudio","render","modalStyle","String","containerClass","overlayClass","renderVideoPreview","h","class","autoPlay","playsInline","muted","style","transform","ref","el","warning","renderPlaceholderStatus","alt","onClick","Fragment","name","stopPropagation","viewBox","fill","stroke","selected","disabled","onChange","accept","onScroll","key","onMessageChange","updatedMessages","detail","verticalAlign","marginRight"],"sources":["src/components/pcm-hr-chat-modal/pcm-hr-chat-modal.css?tag=pcm-hr-chat-modal&encapsulation=shadow","src/components/pcm-hr-chat-modal/pcm-hr-chat-modal.tsx"],"sourcesContent":[":host {\r\n display: block;\r\n}\r\n\r\n.modal-overlay {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n bottom: 0;\r\n background-color: rgba(0, 0, 0, 0.5);\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n overflow-y: auto;\r\n padding: 20px;\r\n z-index: 1000;\r\n}\r\n\r\n/* 全屏模式下取消 padding */\r\n.fullscreen-overlay {\r\n padding: 0;\r\n}\r\n\r\n.modal-container {\r\n background: white;\r\n border-radius: 8px;\r\n width: 100%;\r\n max-width: 800px;\r\n display: flex;\r\n flex-direction: column;\r\n position: relative;\r\n margin: auto;\r\n}\r\n\r\n/* 全屏模式样式 */\r\n.modal-container.fullscreen {\r\n width: 100vw;\r\n max-width: none;\r\n height: 100%;\r\n border-radius: 0;\r\n margin: 0;\r\n display: flex;\r\n flex-direction: column;\r\n height: 100vh;\r\n max-height: 100vh;\r\n}\r\n\r\n/* 确保内容区域也使用 flex 布局并占满剩余空间 */\r\n.modal-container.fullscreen > div:not(.modal-header):not(.initial-upload) {\r\n display: flex;\r\n flex-direction: column;\r\n flex: 1;\r\n overflow: hidden; /* 防止内容溢出 */\r\n height: 100%;\r\n}\r\n\r\n/* PC端布局 */\r\n.pc-layout {\r\n width: 80%;\r\n max-width: 800px;\r\n /* height: 80vh; */\r\n /* max-height: 700px; */\r\n min-width: 320px;\r\n min-height: 400px;\r\n}\r\n\r\n/* 移动端布局 */\r\n.mobile-layout {\r\n width: 100%;\r\n height: 100%;\r\n border-radius: 0;\r\n}\r\n\r\n/* 响应式布局 */\r\n@media screen and (max-width: 768px) {\r\n .pc-layout {\r\n width: 95%;\r\n /* height: 90vh; */\r\n }\r\n\r\n .modal-overlay {\r\n padding: 0;\r\n }\r\n\r\n .modal-container.fullscreen {\r\n /* 支持 iOS Safari */\r\n height: -webkit-fill-available;\r\n max-height: -webkit-fill-available;\r\n /* 确保内容不会被顶部状态栏和底部工具栏遮挡 */\r\n padding: env(safe-area-inset-top) 0 env(safe-area-inset-bottom);\r\n }\r\n\r\n\r\n .modal-container.mobile-layout {\r\n width: 100%;\r\n height: 100vh;\r\n max-height: 100vh;\r\n min-height: 100vh;\r\n border-radius: 0;\r\n margin: 0;\r\n display: flex;\r\n flex-direction: column;\r\n }\r\n\r\n}\r\n.video-preview.placeholder {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n background: #EAEAEA;\r\n}\r\n\r\n.placeholder-status {\r\n color: #00000066;\r\n}\r\n\r\n.waiting-message p {\r\n margin: 0;\r\n font-size: 16px;\r\n color: white;\r\n font-weight: 500;\r\n}\r\n\r\n.recording-container {\r\n width: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n}\r\n\r\n\r\n\r\n.video-area {\r\n width: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n}\r\n\r\n.stop-recording-button {\r\n width: 100%;\r\n height: 100%;\r\n font-size: 16px;\r\n background: #f44336;\r\n border-radius: 6px;\r\n color: white;\r\n border: none;\r\n cursor: pointer;\r\n}\r\n\r\n.stop-recording-button:hover {\r\n background: #d32f2f;\r\n}\r\n\r\n.play-audio-container {\r\n width: 100%;\r\n height: 100%;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n}\r\n\r\n.modal-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 4px 16px;\r\n height: 50px;\r\n border-bottom: 1px solid #e8e8e8;\r\n flex-shrink: 0; /* 防止头部被压缩 */\r\n}\r\n\r\n.header-left {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n}\r\n\r\n.header-icon {\r\n width: 24px;\r\n height: 24px;\r\n}\r\n\r\n.close-button {\r\n background: transparent;\r\n border: none;\r\n cursor: pointer;\r\n padding: 8px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 32px;\r\n height: 32px;\r\n border-radius: 4px;\r\n}\r\n\r\n.close-button:hover {\r\n background-color: rgba(0, 0, 0, 0.04);\r\n}\r\n\r\n.close-button span {\r\n font-size: 24px;\r\n line-height: 1;\r\n color: #999;\r\n}\r\n\r\n.close-button:hover span {\r\n color: #666;\r\n}\r\n\r\n.chat-history {\r\n position: relative;\r\n flex: 1;\r\n overflow-y: auto;\r\n padding: 20px;\r\n scroll-behavior: smooth;\r\n min-height: 200px;\r\n background: url(https://pcm-resource-1312611446.cos.ap-guangzhou.myqcloud.com/web/sdk/chat_bg.png);\r\n background-size: 100%;\r\n}\r\n\r\n/* 添加全屏模式下的样式 */\r\n.fullscreen .chat-history {\r\n height: auto;\r\n flex: 1 1 auto;\r\n}\r\n\r\n\r\n.message-input {\r\n padding: 16px;\r\n border-top: 1px solid #eee;\r\n display: flex;\r\n gap: 8px;\r\n align-items: center;\r\n}\r\n\r\n.message-input input {\r\n flex: 1;\r\n padding: 8px 12px;\r\n border: 1px solid #ddd;\r\n border-radius: 4px;\r\n outline: none;\r\n transition: border-color 0.2s ease;\r\n}\r\n\r\n.message-input input:focus {\r\n border-color: #bbb;\r\n}\r\n\r\n/* 消息样式 */\r\n.message {\r\n margin-bottom: 16px;\r\n opacity: 1;\r\n transition: opacity 0.3s ease;\r\n}\r\n\r\n.message-content {\r\n max-width: 70%;\r\n padding: 8px 12px;\r\n border-radius: 8px;\r\n word-break: break-word;\r\n}\r\n\r\n.message-content p {\r\n margin: 0;\r\n word-break: break-word;\r\n}\r\n\r\n.user-message {\r\n display: flex;\r\n justify-content: flex-end;\r\n}\r\n\r\n.agent-message {\r\n display: flex;\r\n justify-content: flex-start;\r\n}\r\n\r\n.user-message .message-content {\r\n background-color: #007bff;\r\n color: white;\r\n}\r\n\r\n.agent-message .message-content {\r\n background-color: #f1f1f1;\r\n}\r\n\r\n.message-time {\r\n font-size: 12px;\r\n color: #999;\r\n margin-top: 4px;\r\n display: block;\r\n}\r\n\r\n.send-button {\r\n background-color: #1890ff;\r\n color: white;\r\n border: none;\r\n border-radius: 4px;\r\n padding: 8px 16px;\r\n cursor: pointer;\r\n font-weight: 500;\r\n}\r\n\r\n.send-button:disabled {\r\n background-color: #ccc;\r\n cursor: not-allowed;\r\n}\r\n\r\n.empty-state {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n height: 100%;\r\n color: #999;\r\n text-align: center;\r\n}\r\n\r\n.loading-container {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n bottom: 0;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n background-color: rgba(255, 255, 255, 0.98);\r\n z-index: 1;\r\n opacity: 1;\r\n transition: opacity 0.3s ease;\r\n}\r\n\r\n.loading-container p {\r\n margin-top: 16px;\r\n color: #666;\r\n font-size: 14px;\r\n}\r\n\r\n.loading-spinner {\r\n width: 40px;\r\n height: 40px;\r\n border: 3px solid #f3f3f3;\r\n border-top: 3px solid #1890ff;\r\n border-radius: 50%;\r\n animation: spin 1s linear infinite;\r\n}\r\n\r\n@keyframes spin {\r\n 0% {\r\n transform: rotate(0deg);\r\n }\r\n\r\n 100% {\r\n transform: rotate(360deg);\r\n }\r\n}\r\n\r\n/* 修改 messages-wrapper 的样式 */\r\n.messages-wrapper {\r\n width: 100%;\r\n min-height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n /* 当内容少时,将内容放在底部 */\r\n justify-content: flex-end;\r\n}\r\n\r\n/* 当有很多消息时,取消固定在底部 */\r\n.messages-wrapper.has-overflow {\r\n justify-content: flex-start;\r\n}\r\n\r\n.suggested-questions {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n padding: 16px;\r\n}\r\n\r\n.suggested-question {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 8px 12px;\r\n background-color: #f3f4f6;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n font-size: 14px;\r\n color: #374151;\r\n transition: background-color 0.2s;\r\n}\r\n\r\n.suggested-question:hover {\r\n background-color: #e5e7eb;\r\n}\r\n\r\n.arrow-right {\r\n margin-left: 8px;\r\n}\r\n\r\n.loading-suggestions {\r\n display: flex;\r\n justify-content: center;\r\n padding: 16px;\r\n}\r\n\r\n.loading-spinner-small {\r\n width: 20px;\r\n height: 20px;\r\n border: 2px solid #e5e7eb;\r\n border-top-color: #6b7280;\r\n border-radius: 50%;\r\n animation: spin 1s linear infinite;\r\n}\r\n\r\n/* 添加上传按钮样式 */\r\n.upload-button {\r\n background: transparent;\r\n border: none;\r\n cursor: pointer;\r\n padding: 8px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n color: #666;\r\n border-radius: 4px;\r\n transition: background-color 0.2s;\r\n}\r\n\r\n.upload-button:hover {\r\n background-color: rgba(0, 0, 0, 0.04);\r\n}\r\n\r\n.upload-button svg {\r\n width: 20px;\r\n height: 20px;\r\n}\r\n\r\n/* 隐藏原生文件输入框 */\r\n.file-input {\r\n display: none;\r\n}\r\n\r\n/* 添加文件名显示区域样式 */\r\n.selected-file {\r\n font-size: 12px;\r\n color: #666;\r\n margin-left: 8px;\r\n max-width: 150px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.input-wrapper {\r\n flex: 1;\r\n display: flex;\r\n align-items: center;\r\n border: 1px solid #ddd;\r\n border-radius: 4px;\r\n padding: 0 4px;\r\n background: white;\r\n}\r\n\r\n.input-wrapper input {\r\n border: none;\r\n flex: 1;\r\n padding: 8px;\r\n outline: none;\r\n}\r\n\r\n.input-wrapper:focus-within {\r\n border-color: #bbb;\r\n}\r\n\r\n/* 文件预览区域样式 */\r\n.file-preview {\r\n padding: 8px 16px;\r\n border-top: 1px solid #eee;\r\n background-color: #f9f9f9;\r\n}\r\n\r\n.file-info {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 8px;\r\n background: white;\r\n border: 1px solid #e8e8e8;\r\n border-radius: 4px;\r\n}\r\n\r\n.file-name {\r\n font-size: 13px;\r\n color: #333;\r\n margin-right: 8px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n max-width: calc(100% - 30px);\r\n}\r\n\r\n.remove-file {\r\n background: transparent;\r\n border: none;\r\n color: #999;\r\n cursor: pointer;\r\n padding: 4px 8px;\r\n font-size: 16px;\r\n line-height: 1;\r\n border-radius: 4px;\r\n transition: all 0.2s;\r\n}\r\n\r\n.remove-file:hover {\r\n background-color: #f0f0f0;\r\n color: #666;\r\n}\r\n\r\n.initial-upload {\r\n padding: 1rem 1rem;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n height: 100%;\r\n}\r\n\r\n.upload-section {\r\n max-width: 600px;\r\n width: 100%;\r\n text-align: center;\r\n}\r\n\r\n.upload-area {\r\n border: 2px dashed #ddd;\r\n border-radius: 8px;\r\n padding: 1rem;\r\n cursor: pointer;\r\n transition: all 0.3s ease;\r\n}\r\n\r\n.upload-area:hover {\r\n border-color: #1890ff;\r\n background-color: rgba(24, 144, 255, 0.05);\r\n}\r\n\r\n.upload-placeholder {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n color: #666;\r\n}\r\n\r\n.upload-hint {\r\n font-size: 0.8rem;\r\n color: #999;\r\n margin-top: 0.5rem;\r\n}\r\n\r\n.function-select {\r\n margin-top: 2rem;\r\n}\r\n\r\n.function-options {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 1rem;\r\n justify-content: center;\r\n margin-top: 1rem;\r\n}\r\n\r\n.function-button {\r\n padding: 0.5rem 1rem;\r\n border: 1px solid #ddd;\r\n border-radius: 4px;\r\n background: white;\r\n cursor: pointer;\r\n transition: all 0.3s ease;\r\n}\r\n\r\n.function-button:hover {\r\n border-color: #1890ff;\r\n color: #1890ff;\r\n}\r\n\r\n.function-button.selected {\r\n background: #1890ff;\r\n color: white;\r\n border-color: #1890ff;\r\n}\r\n\r\n.submit-button {\r\n margin-top: 1rem;\r\n padding: 0.8rem 2rem;\r\n background: #1890ff;\r\n color: white;\r\n border: none;\r\n border-radius: 4px;\r\n font-size: 1rem;\r\n cursor: pointer;\r\n transition: all 0.3s ease;\r\n width: 95%;\r\n}\r\n\r\n.submit-button:disabled {\r\n background: #ccc;\r\n cursor: not-allowed;\r\n}\r\n\r\n.submit-button:hover:not(:disabled) {\r\n background: #40a9ff;\r\n}\r\n\r\n.category-select,\r\n.dimension-select {\r\n margin: 30px 0;\r\n}\r\n\r\n.category-options,\r\n.dimension-options {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 10px;\r\n margin-top: 10px;\r\n}\r\n\r\n.category-button,\r\n.dimension-button {\r\n padding: 12px 16px;\r\n border: 1px solid #E5E5E5;\r\n border-radius: 6px;\r\n background: white;\r\n cursor: pointer;\r\n transition: all 0.3s;\r\n}\r\n\r\n.category-button:hover,\r\n.dimension-button:hover {\r\n background: #f5f5f5;\r\n}\r\n\r\n.category-button.selected {\r\n background-image: linear-gradient(111deg, #4A9FFF 0%, #1058FF 100%);\r\n color: white;\r\n}\r\n\r\n.dimension-button.selected {\r\n background-image: linear-gradient(111deg, #4A9FFF 0%, #1058FF 100%);\r\n color: white;\r\n}\r\n\r\n.recording-section {\r\n border-top: 1px solid #eee;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n padding: 20px;\r\n border-radius: 14px 14px 0 0;\r\n flex: 0 0 auto;\r\n}\r\n\r\n.recording-section .video-preview {\r\n width: 100%;\r\n height: 200px;\r\n max-width: 400px;\r\n position: relative;\r\n margin-bottom: 10px;\r\n border: 1px solid #ddd;\r\n border-radius: 12px;\r\n overflow: hidden;\r\n}\r\n\r\n.recording-section video {\r\n width: 100%;\r\n height: 100%;\r\n object-fit: cover;\r\n}\r\n\r\n/* 修改 recording-status 样式 */\r\n.recording-status {\r\n position: absolute;\r\n top: 10px;\r\n left: 10px;\r\n background-color: rgba(0, 0, 0, 0.6);\r\n color: white;\r\n padding: 4px 8px;\r\n border-radius: 4px;\r\n display: flex;\r\n align-items: center;\r\n gap: 5px;\r\n font-size: 0.8rem;\r\n z-index: 2;\r\n}\r\n\r\n.recording-status .recording-dot {\r\n display: inline-block;\r\n width: 10px;\r\n height: 10px;\r\n background-color: red;\r\n border-radius: 50%;\r\n margin-right: 5px;\r\n animation: blink 1s infinite;\r\n}\r\n\r\n.recording-status.warning {\r\n color: #ff4d4f;\r\n animation: blink 1s infinite;\r\n}\r\n\r\n@keyframes blink {\r\n 0% {\r\n opacity: 1;\r\n }\r\n\r\n 50% {\r\n opacity: 0.5;\r\n }\r\n\r\n 100% {\r\n opacity: 1;\r\n }\r\n}\r\n\r\n.recording-section .stop-recording-button {\r\n background-color: #f44336;\r\n color: white;\r\n border: none;\r\n cursor: pointer;\r\n font-weight: bold;\r\n}\r\n\r\n.recording-section .stop-recording-button:hover {\r\n background-color: #d32f2f;\r\n}\r\n\r\n.fullscreen {\r\n width: 100vw;\r\n border-radius: 0;\r\n height: 100vh;\r\n display: flex;\r\n flex-direction: column;\r\n overflow-y: auto;\r\n}\r\n\r\n.recording-controls {\r\n margin-top: 10px;\r\n height: 53px;\r\n width: 100%;\r\n max-width: 400px;\r\n display: flex;\r\n justify-content: center;\r\n}\r\n\r\n.recording-controls .waiting-message {\r\n text-align: center;\r\n color: white;\r\n font-size: 1rem;\r\n background-image: linear-gradient(100deg, #4A9FFF 0%, #1058FF 100%);\r\n border-radius: 6px;\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\r\n width: 95%;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n cursor: pointer;\r\n}\r\n\r\n.recording-controls .waiting-message.loading {\r\n background: #faad14;\r\n}\r\n\r\n.recording-controls .waiting-message p {\r\n margin: 0;\r\n font-size: 16px;\r\n color: white;\r\n font-weight: 500;\r\n}\r\n\r\n.recording-controls .stop-recording-button {\r\n background-color: #dc3545;\r\n color: white;\r\n border: none;\r\n cursor: pointer;\r\n font-size: 1rem;\r\n}\r\n\r\n.recording-controls .stop-recording-button:hover {\r\n background-color: #c82333;\r\n}\r\n\r\n/* 添加禁用状态的样式 */\r\n.recording-controls .stop-recording-button.disabled {\r\n background: #ccc;\r\n cursor: not-allowed;\r\n}\r\n\r\n.recording-controls .stop-recording-button.disabled:hover {\r\n background: #ccc;\r\n}\r\n\r\n/* 添加进度条和数字进度的样式 */\r\n.progress-container {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n width: 100%;\r\n max-width: 400px;\r\n margin-top: 10px;\r\n padding: 0 5px;\r\n}\r\n\r\n.progress-bar-container {\r\n height: 4px;\r\n background-color: #E5E5E5;\r\n border-radius: 2px;\r\n overflow: hidden;\r\n margin-right: 10px;\r\n width: 75px;\r\n}\r\n\r\n.progress-bar {\r\n height: 100%;\r\n background-image: linear-gradient(111deg, #4A9FFF 0%, #1058FF 100%);\r\n border-radius: 2px;\r\n transition: width 0.3s ease;\r\n}\r\n\r\n.progress-text {\r\n font-size: 14px;\r\n color: #666;\r\n white-space: nowrap;\r\n}","import { Component, Prop, h, State, Event, EventEmitter, Element, Watch } from '@stencil/core';\r\nimport { convertWorkflowStreamNodeToMessageRound, UserInputMessageType, sendSSERequest, sendHttpRequest } from '../../utils/utils';\r\nimport { ChatMessage } from '../../interfaces/chat';\r\n\r\n@Component({\r\n tag: 'pcm-hr-chat-modal',\r\n styleUrl: 'pcm-hr-chat-modal.css',\r\n shadow: true,\r\n})\r\nexport class ChatHRModal {\r\n /**\r\n * 模态框标题\r\n */\r\n @Prop() modalTitle: string = '在线客服';\r\n\r\n /**\r\n * API鉴权密钥\r\n */\r\n @Prop({ attribute: 'api-key' }) apiKey: string = '';\r\n\r\n /**\r\n * 是否显示聊天模态框\r\n */\r\n @Prop({ mutable: true }) isOpen: boolean = false;\r\n\r\n /**\r\n * 聊天消息历史\r\n */\r\n @State() messages: ChatMessage[] = [];\r\n\r\n\r\n /**\r\n * 当点击模态框关闭时触发\r\n */\r\n @Event() modalClosed: EventEmitter<void>;\r\n\r\n /**\r\n * 应用图标URL\r\n */\r\n @Prop() icon?: string;\r\n\r\n /**\r\n * 聊天框的页面层级\r\n */\r\n @Prop() zIndex?: number = 1000;\r\n\r\n /**\r\n * 是否展示顶部标题栏\r\n */\r\n @Prop() isShowHeader: boolean = true;\r\n\r\n /**\r\n * 是否展示右上角的关闭按钮\r\n */\r\n @Prop() isNeedClose: boolean = true;\r\n\r\n\r\n /**\r\n * 会话ID\r\n */\r\n @Prop({ mutable: true }) conversationId?: string;\r\n\r\n /**\r\n * 当前助手回复的消息\r\n */\r\n @State() currentAssistantMessage: string = '';\r\n\r\n /**\r\n * 是否正在加载回复\r\n */\r\n @State() isLoading: boolean = false;\r\n\r\n /**\r\n * 当前正在流式输出的消息\r\n */\r\n @State() currentStreamingMessage: ChatMessage | null = null;\r\n\r\n // 添加新的状态控制\r\n @State() shouldAutoScroll: boolean = true;\r\n\r\n @State() isLoadingHistory: boolean = false;\r\n\r\n // 使用 @Element 装饰器获取组件的 host 元素\r\n @Element() hostElement: HTMLElement;\r\n\r\n // 添加新的 Event\r\n @Event() streamComplete: EventEmitter<{\r\n conversation_id: string;\r\n event: string;\r\n message_id: string;\r\n id: string;\r\n }>;\r\n\r\n @State() selectedFile: File | null = null;\r\n @State() isUploading: boolean = false;\r\n @State() uploadedFileInfo: { cos_key: string, filename: string, ext: string, presigned_url: string }[] = [];\r\n\r\n /**\r\n * 默认查询文本\r\n */\r\n @Prop() defaultQuery: string = '';\r\n\r\n // 添加新的状态\r\n @State() showInitialUpload: boolean = true;\r\n @State() selectedJobCategory: string = '';\r\n @State() jobCategories: string[] = [\r\n '人力资源学生(实习)',\r\n '人力资源专员',\r\n '人力资源主管',\r\n '人力资源经理',\r\n '人力资源总监'\r\n ];\r\n\r\n @State() dimensions: string[] = [\r\n '人力资源规划',\r\n '招聘与配置',\r\n '员工关系',\r\n '培训与开发',\r\n '薪酬与绩效',\r\n '组织与人才发展'\r\n ];\r\n\r\n @State() selectedDimensions: string[] = [];\r\n\r\n // 添加视频录制相关状态\r\n @State() isRecording: boolean = false;\r\n @State() recordingStream: MediaStream | null = null;\r\n @State() recordedBlob: Blob | null = null;\r\n @State() mediaRecorder: MediaRecorder | null = null;\r\n @State() recordingTimeLeft: number = 0;\r\n @State() showRecordingUI: boolean = false;\r\n @State() recordingTimer: any = null;\r\n @State() recordingStartTime: number = 0;\r\n @State() recordingMaxTime: number = 120; // 最大录制时间(秒)\r\n @State() waitingToRecord: boolean = false;\r\n @State() waitingTimer: any = null;\r\n @State() waitingTimeLeft: number = 10; // 等待时间(秒)\r\n\r\n // 添加一个新的私有属性来存储视频元素的引用\r\n private videoRef: HTMLVideoElement | null = null;\r\n\r\n /**\r\n * 总题目数量\r\n */\r\n @Prop() totalQuestions: number = 2;\r\n\r\n /**\r\n * 当前题目序号\r\n */\r\n @State() currentQuestionNumber: number = 0;\r\n\r\n /**\r\n * 面试是否已完成\r\n */\r\n @State() isInterviewComplete: boolean = false;\r\n\r\n /**\r\n * 当面试完成时触发\r\n */\r\n @Event() interviewComplete: EventEmitter<{\r\n conversation_id: string;\r\n total_questions: number;\r\n }>;\r\n\r\n private readonly SCROLL_THRESHOLD = 30;\r\n\r\n /**\r\n * 视频录制最大时长(秒)\r\n */\r\n @Prop() maxRecordingTime: number = 120;\r\n\r\n /**\r\n * 录制倒计时提醒时间(秒)\r\n * 当剩余时间小于此值时,显示倒计时警告\r\n */\r\n @Prop() countdownWarningTime: number = 30;\r\n\r\n @State() showCountdownWarning: boolean = false;\r\n\r\n /**\r\n * 接收报告的邮箱地址\r\n */\r\n @Prop() toEmail: string = '';\r\n\r\n /**\r\n * 是否以全屏模式打开\r\n */\r\n @Prop() fullscreen: boolean = false;\r\n\r\n // 添加新的状态来跟踪视频上传\r\n @State() isUploadingVideo: boolean = false;\r\n\r\n /**\r\n * 是否需要上传简历\r\n */\r\n @Prop() requireResume: boolean = false;\r\n\r\n // 添加新的状态和属性\r\n @State() isPlayingAudio: boolean = false;\r\n @State() audioUrl: string | null = null;\r\n private audioElement: HTMLAudioElement | null = null;\r\n\r\n /**\r\n * 录制错误事件\r\n */\r\n @Event() recordingError: EventEmitter<{\r\n type: string;\r\n message: string;\r\n details?: any;\r\n }>;\r\n\r\n /**\r\n * 录制状态变化事件\r\n */\r\n @Event() recordingStatusChange: EventEmitter<{\r\n status: 'started' | 'stopped' | 'paused' | 'resumed' | 'failed';\r\n details?: any;\r\n }>;\r\n\r\n /**\r\n * 是否播放语音问题\r\n */\r\n @Prop() enableVoice: boolean = true;\r\n\r\n /**\r\n * 是否显示题干内容\r\n * 1: 显示题干内容\r\n * 0: 不显示题干内容\r\n */\r\n @Prop() displayContentStatus: string = \"1\";\r\n\r\n /**\r\n * 用户ID\r\n */\r\n @Prop() userId: string = '';\r\n\r\n private handleClose = () => {\r\n this.stopRecording();\r\n this.modalClosed.emit();\r\n };\r\n\r\n private handleFileChange = async (event: Event) => {\r\n const input = event.target as HTMLInputElement;\r\n if (input.files && input.files.length > 0) {\r\n this.selectedFile = input.files[0];\r\n }\r\n };\r\n\r\n private async uploadFile() {\r\n if (!this.selectedFile || this.uploadedFileInfo.length > 0) return;\r\n\r\n this.isUploading = true;\r\n\r\n try {\r\n const formData = new FormData();\r\n formData.append('file', this.selectedFile);\r\n\r\n const response = await fetch('https://pcm_api.ylzhaopin.com/external/v1/files/upload', {\r\n method: 'POST',\r\n headers: {\r\n 'authorization': 'Bearer ' + this.apiKey\r\n },\r\n body: formData\r\n });\r\n\r\n const result = await response.json();\r\n if (!response.ok) {\r\n throw new Error(result.message || '文件上传失败');\r\n }\r\n\r\n if (result) {\r\n this.uploadedFileInfo = [{\r\n cos_key: result.cos_key,\r\n filename: result.filename,\r\n ext: result.ext,\r\n presigned_url: result.presigned_url\r\n }];\r\n }\r\n } catch (error) {\r\n console.error('文件上传错误:', error);\r\n this.clearSelectedFile();\r\n alert(error instanceof Error ? error.message : '文件上传失败,请重试');\r\n } finally {\r\n this.isUploading = false;\r\n }\r\n }\r\n\r\n private handleUploadClick = () => {\r\n const fileInput = this.hostElement.shadowRoot?.querySelector('.file-input') as HTMLInputElement;\r\n fileInput?.click();\r\n };\r\n\r\n private clearSelectedFile = () => {\r\n this.selectedFile = null;\r\n this.uploadedFileInfo = [];\r\n const fileInput = this.hostElement.shadowRoot?.querySelector('.file-input') as HTMLInputElement;\r\n if (fileInput) {\r\n fileInput.value = '';\r\n }\r\n };\r\n\r\n private async sendMessageToAPI(message: string) {\r\n this.isLoading = true;\r\n let answer = '';\r\n let llmText = ''; // 添加变量存储 LLMText\r\n\r\n const now = new Date();\r\n const time = `${now.getHours()}:${now.getMinutes().toString().padStart(2, '0')}`;\r\n\r\n // 如果消息为空但有文件,使用默认文本\r\n const queryText = message.trim() || (this.uploadedFileInfo.length > 0 ? '请分析这个文件' : '');\r\n\r\n // 获取上一条AI消息的回答内容\r\n const lastAIMessage = this.messages.length > 0 ? this.messages[this.messages.length - 1] : null;\r\n\r\n // 保存AI提问和用户回答\r\n if (lastAIMessage && this.conversationId && message !== \"下一题\") {\r\n this.saveAnswer(\r\n this.conversationId,\r\n lastAIMessage.answer, // AI的提问作为question\r\n queryText // 用户的输入作为answer\r\n );\r\n }\r\n\r\n // 检查是否是最后一题的\"下一题\"请求\r\n const isLastQuestion = (this.currentQuestionNumber >= this.totalQuestions) && message === \"下一题\";\r\n\r\n // 创建新的消息对象\r\n const newMessage: ChatMessage = {\r\n id: `temp-${Date.now()}`, // 消息唯一标识\r\n time: time, // 消息时间\r\n query: queryText, // 用户输入的消息内容\r\n answer: '',\r\n isStreaming: true, // 是否正在流式输出\r\n conversation_id: this.conversationId, // 会话ID\r\n inputs: {}, // 输入参数\r\n status: \"normal\", // 消息状态\r\n error: null // 错误信息\r\n };\r\n\r\n // 设置当前流式消息\r\n this.currentStreamingMessage = newMessage;\r\n\r\n this.shouldAutoScroll = true;\r\n // 滚动到底部\r\n this.scrollToBottom();\r\n\r\n // 如果是最后一题,直接显示结束消息并完成面试\r\n if (isLastQuestion) {\r\n this.messages = [...this.messages, newMessage];\r\n this.currentStreamingMessage = null;\r\n this.isLoading = false;\r\n this.isInterviewComplete = true;\r\n await this.completeInterview();\r\n this.interviewComplete.emit({\r\n conversation_id: this.conversationId,\r\n total_questions: this.totalQuestions\r\n });\r\n return;\r\n }\r\n\r\n // 准备请求数据\r\n const requestData: any = {\r\n response_mode: 'streaming',\r\n conversation_id: this.conversationId,\r\n query: queryText,\r\n user: this.userId // 使用传入的 userId\r\n };\r\n requestData.inputs = {\r\n job_info: this.selectedJobCategory,\r\n dimensional_info: this.selectedDimensions.join(','),\r\n email: this.toEmail,\r\n display_content_status: this.displayContentStatus\r\n };\r\n // 如果有上传的文件,添加到inputs参数\r\n if (this.uploadedFileInfo.length > 0) {\r\n const fileUrls = this.uploadedFileInfo.map(fileInfo => fileInfo.cos_key).join(',');\r\n requestData.inputs.file_urls = fileUrls;\r\n }\r\n\r\n\r\n await sendSSERequest({\r\n url: `https://pcm_api.ylzhaopin.com/external/v1/chat/chat-messages`,\r\n method: 'POST',\r\n headers: {\r\n 'authorization': 'Bearer ' + this.apiKey\r\n },\r\n data: requestData,\r\n onMessage: (data) => {\r\n console.log('收到Stream数据:', data);\r\n\r\n if (data.conversation_id && !this.conversationId) {\r\n this.conversationId = data.conversation_id;\r\n this.updateUrlWithConversationId(data.conversation_id);\r\n }\r\n\r\n // 检查是否有 node_finished 事件和 LLMText\r\n if (data.event === 'node_finished' && data.data.inputs && data.data.inputs.LLMText) {\r\n llmText = data.data.inputs.LLMText;\r\n console.log('获取到 LLMText:', llmText);\r\n }\r\n\r\n if (data.event === 'message') {\r\n const inputMessage: UserInputMessageType = { message: message };\r\n convertWorkflowStreamNodeToMessageRound('message', inputMessage, data);\r\n\r\n if (data.event === 'agent_message' || data.event === 'message') {\r\n if (data.answer) {\r\n answer += data.answer;\r\n const updatedMessage: ChatMessage = {\r\n ...this.currentStreamingMessage,\r\n answer,\r\n isStreaming: true\r\n };\r\n this.currentStreamingMessage = updatedMessage;\r\n this.scrollToBottom();\r\n }\r\n }\r\n }\r\n if (data.event === \"message_end\") {\r\n this.streamComplete.emit({\r\n conversation_id: data.conversation_id || '',\r\n event: data.event,\r\n message_id: data.message_id,\r\n id: data.id,\r\n });\r\n }\r\n },\r\n onError: (error) => {\r\n console.error('发生错误:', error);\r\n alert(error instanceof Error ? error.message : '消息发送失败,请稍后再试');\r\n this.messages = [...this.messages, {\r\n ...newMessage,\r\n answer: '抱歉,发生了错误,请稍后再试。',\r\n error: error,\r\n isStreaming: false\r\n }];\r\n this.currentStreamingMessage = null;\r\n this.isLoading = false;\r\n },\r\n onComplete: async () => {\r\n console.log('请求完成');\r\n this.isLoading = false;\r\n\r\n // 获取最新的AI回复内容\r\n const latestAIMessage = this.currentStreamingMessage;\r\n\r\n // 更新消息列表\r\n this.messages = [...this.messages, this.currentStreamingMessage];\r\n this.currentStreamingMessage = null;\r\n\r\n // 如果是初始消息或\"下一题\"消息,增加题目计数\r\n if (message === \"下一题\" || this.currentQuestionNumber === 0) {\r\n this.currentQuestionNumber++;\r\n }\r\n console.log(this.currentQuestionNumber);\r\n console.log(message);\r\n\r\n if (latestAIMessage && latestAIMessage.answer) {\r\n // 优先使用 LLMText,如果没有则使用 answer\r\n const textForSynthesis = llmText || latestAIMessage.answer;\r\n \r\n if (textForSynthesis) {\r\n // 合成语音\r\n const audioUrl = await this.synthesizeAudio(textForSynthesis);\r\n\r\n if (this.enableVoice) {\r\n // 自动播放语音\r\n await this.playAudio(audioUrl);\r\n // 自动播放模式下,播放完成后立即开始等待录制\r\n this.startWaitingToRecord();\r\n } else {\r\n // 只保存音频URL,不自动播放\r\n this.audioUrl = audioUrl;\r\n // 非自动播放模式下,不立即开始等待录制\r\n }\r\n }\r\n }\r\n }\r\n });\r\n }\r\n\r\n // 添加保存答案的方法\r\n private async saveAnswer(conversationId: string, question: string, answer: string) {\r\n try {\r\n await sendHttpRequest({\r\n url: 'https://pcm_api.ylzhaopin.com/agents/hr_competition/answer',\r\n method: 'POST',\r\n headers: {\r\n 'authorization': 'Bearer ' + this.apiKey\r\n },\r\n data: {\r\n conversation_id: conversationId,\r\n user: this.userId, // 使用传入的 userId\r\n question: question,\r\n answer: answer\r\n },\r\n });\r\n } catch (error) {\r\n console.error('保存答案失败:', error);\r\n }\r\n }\r\n\r\n // 监听滚动事件,用于控制聊天历史记录的自动滚动行为。\r\n private handleScroll = () => {\r\n const chatHistory = this.hostElement.shadowRoot?.querySelector('.chat-history');\r\n if (!chatHistory) return;\r\n\r\n const { scrollTop, scrollHeight, clientHeight } = chatHistory;\r\n const distanceFromBottom = scrollHeight - scrollTop - clientHeight;\r\n\r\n // 更新是否应该自动滚动的状态\r\n this.shouldAutoScroll = distanceFromBottom <= this.SCROLL_THRESHOLD;\r\n };\r\n\r\n private scrollToBottom() {\r\n if (!this.shouldAutoScroll) return;\r\n const chatHistory = this.hostElement.shadowRoot?.querySelector('.chat-history');\r\n if (chatHistory && this.isOpen) {\r\n // 强制浏览器重新计算布局\r\n chatHistory.scrollTop = chatHistory.scrollHeight;\r\n }\r\n }\r\n\r\n // 添加 componentDidRender 生命周期方法,用于在组件渲染后滚动到底部\r\n componentDidRender() {\r\n if (this.isLoadingHistory || (this.shouldAutoScroll && this.isOpen)) {\r\n const chatHistory = this.hostElement.shadowRoot?.querySelector('.chat-history');\r\n if (chatHistory) {\r\n chatHistory.scrollTop = chatHistory.scrollHeight;\r\n }\r\n }\r\n }\r\n\r\n private updateUrlWithConversationId(conversationId: string) {\r\n const urlParams = new URLSearchParams(window.location.search);\r\n if (!urlParams.get('conversation_id')) {\r\n const newUrl = new URL(window.location.href);\r\n newUrl.searchParams.set('conversation_id', conversationId);\r\n window.history.replaceState({}, '', newUrl);\r\n }\r\n }\r\n\r\n // 修改 loadHistoryMessages 方法\r\n private async loadHistoryMessages() {\r\n if (!this.conversationId) return;\r\n\r\n this.isLoadingHistory = true;\r\n console.log('加载历史消息...');\r\n\r\n try {\r\n await sendHttpRequest({\r\n url: `https://pcm_api.ylzhaopin.com/external/v1/chat/messages`,\r\n method: 'GET',\r\n headers: {\r\n 'authorization': 'Bearer ' + this.apiKey\r\n },\r\n data: {\r\n conversation_id: this.conversationId,\r\n limit: 20\r\n },\r\n onMessage: (data) => {\r\n if (data.data) {\r\n const historyData = data.data || [];\r\n const formattedMessages: ChatMessage[] = historyData.map(msg => {\r\n const time = new Date(msg.created_at * 1000);\r\n const hours = time.getHours().toString().padStart(2, '0');\r\n const minutes = time.getMinutes().toString().padStart(2, '0');\r\n const timeStr = `${hours}:${minutes}`;\r\n\r\n // 创建新的消息对象,不包含 inputs 字段\r\n const { inputs, ...msgWithoutInputs } = msg;\r\n\r\n return {\r\n ...msgWithoutInputs,\r\n time: timeStr,\r\n isStreaming: false,\r\n status: msg.status === 'error' ? 'error' : 'normal' as const\r\n };\r\n });\r\n\r\n this.messages = formattedMessages;\r\n this.isLoadingHistory = false;\r\n\r\n requestAnimationFrame(() => {\r\n this.shouldAutoScroll = true;\r\n this.scrollToBottom();\r\n });\r\n } else {\r\n this.isLoadingHistory = false;\r\n }\r\n },\r\n onError: (error) => {\r\n console.error('加载历史消息失败:', error);\r\n alert(error instanceof Error ? error.message : '加载历史消息失败,请刷新重试');\r\n this.isLoadingHistory = false;\r\n },\r\n onComplete: () => {\r\n this.isLoadingHistory = false;\r\n }\r\n });\r\n } catch (error) {\r\n console.error('加载历史消息失败:', error);\r\n alert(error instanceof Error ? error.message : '加载历史消息失败,请刷新重试');\r\n this.isLoadingHistory = false;\r\n }\r\n }\r\n\r\n // 修改 isOpen 的 watch 方法\r\n @Watch('isOpen')\r\n async handleIsOpenChange(newValue: boolean) {\r\n if (newValue) {\r\n if (this.conversationId) {\r\n await this.loadHistoryMessages();\r\n } \r\n }\r\n }\r\n\r\n\r\n private handleJobCategorySelect = (category: string) => {\r\n this.selectedJobCategory = category;\r\n };\r\n\r\n private handleDimensionSelect = (dimension: string) => {\r\n if (this.selectedDimensions.includes(dimension)) {\r\n this.selectedDimensions = this.selectedDimensions.filter(d => d !== dimension);\r\n } else {\r\n this.selectedDimensions = [...this.selectedDimensions, dimension];\r\n }\r\n };\r\n\r\n private handleInitialSubmit = async () => {\r\n // 修改验证逻辑\r\n if (this.requireResume && !this.selectedFile) {\r\n alert('请上传简历');\r\n return;\r\n }\r\n\r\n if (!this.selectedJobCategory) {\r\n alert('请选择职能类别');\r\n return;\r\n }\r\n\r\n if (this.selectedDimensions.length === 0) {\r\n alert('请至少选择一个关注模块');\r\n return;\r\n }\r\n\r\n // 不再显示欢迎确认对话框,因为已经在组件打开时显示了\r\n // 直接询问用户是否准备好开始面试\r\n const confirmed = confirm('如果您已做好准备请点击\"确定\"开始面试。');\r\n\r\n if (!confirmed) {\r\n return;\r\n }\r\n\r\n // 修改文件上传逻辑\r\n if (this.requireResume) {\r\n await this.uploadFile();\r\n if (this.uploadedFileInfo.length === 0) {\r\n return;\r\n }\r\n }\r\n\r\n this.showInitialUpload = false;\r\n const message = `我是一名${this.selectedJobCategory},请您开始提问`;\r\n this.sendMessageToAPI(message);\r\n };\r\n\r\n // 开始等待录制\r\n private startWaitingToRecord() {\r\n // 清除可能存在的计时器\r\n if (this.waitingTimer) {\r\n clearInterval(this.waitingTimer);\r\n }\r\n if (this.recordingTimer) {\r\n clearInterval(this.recordingTimer);\r\n }\r\n\r\n this.waitingToRecord = true;\r\n this.waitingTimeLeft = 10;\r\n\r\n this.waitingTimer = setInterval(() => {\r\n this.waitingTimeLeft--;\r\n if (this.waitingTimeLeft <= 0) {\r\n clearInterval(this.waitingTimer);\r\n this.waitingTimer = null;\r\n this.waitingToRecord = false;\r\n this.startRecording();\r\n }\r\n }, 1000);\r\n }\r\n\r\n // 开始录制视频\r\n private async startRecording() {\r\n try {\r\n const stream = await navigator.mediaDevices.getUserMedia({\r\n audio: true,\r\n video: {\r\n width: { ideal: 1280 },\r\n height: { ideal: 720 },\r\n frameRate: { ideal: 30 }\r\n }\r\n });\r\n\r\n this.recordingStream = stream;\r\n this.showRecordingUI = true;\r\n this.showCountdownWarning = false;\r\n\r\n // 重置视频引用\r\n this.videoRef = null;\r\n\r\n // 确保视频元素获取到流\r\n this.setupVideoPreview(stream);\r\n\r\n // 检测浏览器支持的MIME类型\r\n const mimeType = this.getSupportedMimeType();\r\n\r\n // 创建MediaRecorder实例\r\n let mediaRecorder;\r\n try {\r\n mediaRecorder = new MediaRecorder(stream, {\r\n mimeType: mimeType\r\n });\r\n } catch (e) {\r\n // 如果指定MIME类型失败,尝试使用默认设置\r\n console.warn('指定的MIME类型不受支持,使用默认设置:', e);\r\n try {\r\n mediaRecorder = new MediaRecorder(stream);\r\n } catch (recorderError) {\r\n // 通知父组件录制器创建失败\r\n this.recordingError.emit({\r\n type: 'recorder_creation_failed',\r\n message: '无法创建媒体录制器,您的浏览器可能不支持此功能',\r\n details: recorderError\r\n });\r\n this.showRecordingUI = false;\r\n return;\r\n }\r\n }\r\n\r\n this.mediaRecorder = mediaRecorder;\r\n\r\n const chunks: BlobPart[] = [];\r\n\r\n mediaRecorder.ondataavailable = (event) => {\r\n if (event.data.size > 0) {\r\n chunks.push(event.data);\r\n }\r\n };\r\n\r\n mediaRecorder.onerror = (event) => {\r\n // 通知父组件录制过程中发生错误\r\n this.recordingError.emit({\r\n type: 'recording_error',\r\n message: '录制过程中发生错误',\r\n details: event\r\n });\r\n this.stopRecording();\r\n };\r\n\r\n mediaRecorder.onstop = () => {\r\n try {\r\n // 根据实际使用的MIME类型创建Blob\r\n const blobType = mimeType || 'video/mp4';\r\n const blob = new Blob(chunks, { type: blobType });\r\n\r\n if (blob.size === 0) {\r\n // 通知父组件录制的视频为空\r\n this.recordingError.emit({\r\n type: 'empty_recording',\r\n message: '录制的视频为空'\r\n });\r\n this.showRecordingUI = false;\r\n return;\r\n }\r\n\r\n this.recordedBlob = blob;\r\n\r\n // 通知父组件录制已完成\r\n this.recordingStatusChange.emit({\r\n status: 'stopped',\r\n details: {\r\n duration: Math.floor((Date.now() - this.recordingStartTime) / 1000),\r\n size: blob.size,\r\n type: blob.type\r\n }\r\n });\r\n\r\n this.uploadRecordedVideo();\r\n } catch (error) {\r\n // 通知父组件处理录制视频时出错\r\n this.recordingError.emit({\r\n type: 'processing_error',\r\n message: '处理录制视频时出错',\r\n details: error\r\n });\r\n this.showRecordingUI = false;\r\n }\r\n };\r\n\r\n // 开始录制\r\n try {\r\n mediaRecorder.start();\r\n this.isRecording = true;\r\n this.recordingStartTime = Date.now();\r\n this.recordingTimeLeft = this.maxRecordingTime;\r\n\r\n // 通知父组件录制已开始\r\n this.recordingStatusChange.emit({\r\n status: 'started',\r\n details: {\r\n maxDuration: this.maxRecordingTime,\r\n mimeType: mediaRecorder.mimeType\r\n }\r\n });\r\n } catch (startError) {\r\n // 通知父组件开始录制失败\r\n this.recordingError.emit({\r\n type: 'start_failed',\r\n message: '开始录制失败,请检查您的设备权限',\r\n details: startError\r\n });\r\n this.showRecordingUI = false;\r\n return;\r\n }\r\n\r\n // 设置录制计时器\r\n this.recordingTimer = setInterval(() => {\r\n const elapsedTime = Math.floor((Date.now() - this.recordingStartTime) / 1000);\r\n this.recordingTimeLeft = Math.max(0, this.maxRecordingTime - elapsedTime);\r\n\r\n // 检查是否需要显示倒计时警告\r\n if (this.recordingTimeLeft <= this.countdownWarningTime && !this.showCountdownWarning) {\r\n this.showCountdownWarning = true;\r\n }\r\n\r\n // 时间到自动停止录制\r\n if (this.recordingTimeLeft <= 0) {\r\n this.stopRecording();\r\n }\r\n }, 1000);\r\n\r\n } catch (error) {\r\n console.error('无法访问摄像头或麦克风:', error);\r\n // 通知父组件无法访问媒体设备\r\n this.recordingError.emit({\r\n type: 'media_access_failed',\r\n message: '无法访问摄像头或麦克风,请确保已授予权限',\r\n details: error\r\n });\r\n this.showRecordingUI = false;\r\n }\r\n }\r\n\r\n // 添加新方法来设置视频预览\r\n private setupVideoPreview(stream: MediaStream) {\r\n // 延迟执行以确保DOM已更新\r\n setTimeout(() => {\r\n const videoElement = this.hostElement.shadowRoot?.querySelector('video') as HTMLVideoElement;\r\n if (videoElement && stream) {\r\n // 先尝试使用标准方法\r\n try {\r\n videoElement.srcObject = stream;\r\n videoElement.play().catch(err => {\r\n console.error('视频播放失败:', err);\r\n });\r\n } catch (e) {\r\n console.warn('设置srcObject失败,尝试替代方法:', e);\r\n\r\n // 对于不支持srcObject的旧浏览器,使用URL.createObjectURL\r\n try {\r\n // 使用类型断言解决TypeScript错误\r\n const objectUrl = URL.createObjectURL(stream as unknown as MediaSource);\r\n videoElement.src = objectUrl;\r\n\r\n // 确保在视频元素不再使用时释放URL\r\n videoElement.onended = () => {\r\n URL.revokeObjectURL(objectUrl);\r\n };\r\n } catch (urlError) {\r\n console.error('创建对象URL失败:', urlError);\r\n }\r\n }\r\n } else {\r\n console.warn('未找到视频元素或媒体流无效');\r\n }\r\n }, 100);\r\n }\r\n\r\n // 添加一个新方法来检测浏览器支持的MIME类型\r\n private getSupportedMimeType(): string {\r\n // 按优先级排列的MIME类型列表\r\n const mimeTypes = [\r\n 'video/webm;codecs=vp8,opus',\r\n 'video/webm;codecs=vp9,opus',\r\n 'video/webm',\r\n 'video/mp4',\r\n 'video/mp4;codecs=h264,aac',\r\n '' // 空字符串表示使用浏览器默认值\r\n ];\r\n\r\n // 检查MediaRecorder是否可用\r\n if (!window.MediaRecorder) {\r\n console.warn('MediaRecorder API不可用');\r\n return '';\r\n }\r\n\r\n // 检查每种MIME类型是否受支持\r\n for (const type of mimeTypes) {\r\n if (!type) return ''; // 如果是空字符串,直接返回\r\n\r\n try {\r\n if (MediaRecorder.isTypeSupported(type)) {\r\n console.log('使用支持的MIME类型:', type);\r\n return type;\r\n }\r\n } catch (e) {\r\n console.warn(`检查MIME类型支持时出错 ${type}:`, e);\r\n }\r\n }\r\n\r\n // 如果没有找到支持的类型,返回空字符串\r\n console.warn('没有找到支持的MIME类型,将使用浏览器默认值');\r\n return '';\r\n }\r\n\r\n // 停止录制\r\n private stopRecording() {\r\n if (this.mediaRecorder && this.isRecording) {\r\n this.mediaRecorder.stop();\r\n this.isRecording = false;\r\n\r\n // 清理计时器\r\n if (this.recordingTimer) {\r\n clearInterval(this.recordingTimer);\r\n this.recordingTimer = null;\r\n }\r\n\r\n // 停止并释放媒体流\r\n if (this.recordingStream) {\r\n this.recordingStream.getTracks().forEach(track => track.stop());\r\n this.recordingStream = null;\r\n }\r\n\r\n // 清理视频引用\r\n this.videoRef = null;\r\n }\r\n }\r\n\r\n // 上传录制的视频\r\n private async uploadRecordedVideo() {\r\n if (!this.recordedBlob) return;\r\n\r\n try {\r\n this.isUploadingVideo = true; // 开始上传时设置状态\r\n this.showRecordingUI = false; // 隐藏视频预览\r\n\r\n // 根据Blob类型确定文件扩展名\r\n const fileExtension = this.recordedBlob.type.includes('webm') ? 'webm' : 'mp4';\r\n const fileName = `answer.${fileExtension}`;\r\n\r\n const formData = new FormData();\r\n formData.append('file', this.recordedBlob, fileName);\r\n\r\n const response = await fetch('https://pcm_api.ylzhaopin.com/external/v1/files/upload', {\r\n method: 'POST',\r\n headers: {\r\n 'authorization': 'Bearer ' + this.apiKey\r\n },\r\n body: formData\r\n });\r\n\r\n const result = await response.json();\r\n console.log('视频上传结果:', result);\r\n\r\n if (result && result.cos_key) {\r\n // 保存视频答案\r\n await this.saveVideoAnswer(result.cos_key);\r\n\r\n // 发送\"下一题\"请求\r\n this.sendNextQuestion();\r\n } else {\r\n throw new Error('视频上传失败');\r\n }\r\n } catch (error) {\r\n console.error('视频上传错误:', error);\r\n // 通知父组件视频上传失败\r\n this.recordingError.emit({\r\n type: 'upload_failed',\r\n message: '视频上传失败',\r\n details: error\r\n });\r\n } finally {\r\n this.isUploadingVideo = false; // 上传完成后重置状态\r\n this.showRecordingUI = false;\r\n this.recordedBlob = null;\r\n }\r\n }\r\n\r\n // 保存视频答案\r\n private async saveVideoAnswer(cosKey: string) {\r\n if (!this.conversationId) return;\r\n\r\n try {\r\n const lastAIMessage = this.messages.length > 0 ? this.messages[this.messages.length - 1] : null;\r\n\r\n if (!lastAIMessage) return;\r\n\r\n await sendHttpRequest({\r\n url: 'https://pcm_api.ylzhaopin.com/agents/hr_competition/answer',\r\n method: 'POST',\r\n headers: {\r\n 'authorization': 'Bearer ' + this.apiKey\r\n },\r\n data: {\r\n conversation_id: this.conversationId,\r\n user: this.userId, // 使用传入的 userId\r\n question: lastAIMessage.answer,\r\n file_url: cosKey\r\n },\r\n });\r\n } catch (error) {\r\n console.error('保存视频答案失败:', error);\r\n }\r\n }\r\n\r\n // 发送\"下一题\"请求\r\n private sendNextQuestion() {\r\n this.sendMessageToAPI(\"下一题\");\r\n }\r\n\r\n /**\r\n * 发送面试完成请求\r\n */\r\n private async completeInterview() {\r\n if (!this.conversationId) return;\r\n\r\n try {\r\n await sendHttpRequest({\r\n url: `https://pcm_api.ylzhaopin.com/agents/hr_competition/${this.conversationId}/end`,\r\n method: 'POST',\r\n headers: {\r\n 'authorization': 'Bearer ' + this.apiKey\r\n },\r\n });\r\n\r\n } catch (error) {\r\n console.error('发送面试完成请求失败:', error);\r\n }\r\n }\r\n\r\n // 添加TTS合成音频的方法\r\n private async synthesizeAudio(text: string): Promise<string> {\r\n try {\r\n const response = await fetch('https://pcm_api.ylzhaopin.com/external/v1/tts/synthesize_audio', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'authorization': 'Bearer ' + this.apiKey\r\n },\r\n body: JSON.stringify({ text })\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error('语音合成失败');\r\n }\r\n\r\n // 获取音频数据并创建Blob URL\r\n const audioBlob = await response.blob();\r\n return URL.createObjectURL(audioBlob);\r\n } catch (error) {\r\n console.error('语音合成错误:', error);\r\n throw error;\r\n }\r\n }\r\n\r\n // 播放音频的方法\r\n private playAudio(audioUrl: string): Promise<void> {\r\n return new Promise((resolve) => {\r\n this.isPlayingAudio = true;\r\n this.audioUrl = audioUrl;\r\n\r\n // 创建音频元素\r\n if (!this.audioElement) {\r\n this.audioElement = new Audio();\r\n }\r\n\r\n this.audioElement.src = audioUrl;\r\n this.audioElement.onended = () => {\r\n this.isPlayingAudio = false;\r\n this.audioUrl = null;\r\n resolve();\r\n };\r\n\r\n this.audioElement.onerror = () => {\r\n console.error('音频播放错误');\r\n this.isPlayingAudio = false;\r\n this.audioUrl = null;\r\n resolve();\r\n };\r\n\r\n this.audioElement.play().catch(error => {\r\n console.error('音频播放失败:', error);\r\n this.isPlayingAudio = false;\r\n this.audioUrl = null;\r\n resolve();\r\n });\r\n });\r\n }\r\n\r\n // 修改 componentDidLoad 生命周期方法,确保组件卸载时释放资源\r\n disconnectedCallback() {\r\n // 释放音频资源\r\n if (this.audioElement) {\r\n this.audioElement.pause();\r\n this.audioElement.src = '';\r\n this.audioElement = null;\r\n }\r\n\r\n // 释放 Blob URL\r\n if (this.audioUrl) {\r\n URL.revokeObjectURL(this.audioUrl);\r\n this.audioUrl = null;\r\n }\r\n\r\n // 清理其他计时器\r\n if (this.waitingTimer) {\r\n clearInterval(this.waitingTimer);\r\n this.waitingTimer = null;\r\n }\r\n\r\n if (this.recordingTimer) {\r\n clearInterval(this.recordingTimer);\r\n this.recordingTimer = null;\r\n }\r\n\r\n // 停止录制\r\n this.stopRecording();\r\n }\r\n\r\n // 修改手动播放音频的方法\r\n private handlePlayAudio = async () => {\r\n if (this.audioUrl) {\r\n await this.playAudio(this.audioUrl);\r\n // 手动播放完成后开始等待录制\r\n this.startWaitingToRecord();\r\n }\r\n };\r\n\r\n render() {\r\n if (!this.isOpen) return null;\r\n\r\n const modalStyle = {\r\n zIndex: String(this.zIndex)\r\n };\r\n\r\n const containerClass = {\r\n 'modal-container': true,\r\n 'fullscreen': this.fullscreen\r\n };\r\n\r\n const overlayClass = {\r\n 'modal-overlay': true,\r\n 'fullscreen-overlay': this.fullscreen\r\n };\r\n\r\n const renderVideoPreview = () => (\r\n <div class=\"video-preview\">\r\n <video\r\n autoPlay\r\n playsInline\r\n muted\r\n style={{ transform: 'scaleX(-1)' }}\r\n ref={(el) => {\r\n if (el && this.recordingStream && !this.videoRef) {\r\n this.videoRef = el;\r\n // 不在这里设置srcObject,而是使用setupVideoPreview方法\r\n }\r\n }}\r\n ></video>\r\n <div class={{\r\n 'recording-status': true,\r\n 'warning': this.showCountdownWarning\r\n }}>\r\n <span class=\"recording-dot\"></span>\r\n <span>\r\n 录制中 {Math.floor(this.recordingTimeLeft / 60)}:{(this.recordingTimeLeft % 60).toString().padStart(2, '0')}\r\n {this.showCountdownWarning && ` (即将自动完成)`}\r\n </span>\r\n </div>\r\n </div>\r\n );\r\n\r\n // 渲染占位符状态信息\r\n const renderPlaceholderStatus = () => {\r\n // 正在播放音频\r\n if (this.isPlayingAudio) {\r\n return (\r\n <div class=\"placeholder-status\">\r\n <p>正在播放问题,请听完后准备回答...</p>\r\n </div>\r\n );\r\n }\r\n\r\n // 正在上传视频\r\n if (this.isUploadingVideo) {\r\n return (\r\n <div class=\"placeholder-status\">\r\n <p>正在上传视频,请稍候...</p>\r\n </div>\r\n );\r\n }\r\n\r\n // 正在加载或等待AI回复\r\n if (this.isLoading || this.currentStreamingMessage) {\r\n return (\r\n <div class=\"placeholder-status\">\r\n <p>请等待题目...</p>\r\n </div>\r\n );\r\n }\r\n\r\n // 等待开始录制\r\n if (this.waitingToRecord) {\r\n return (\r\n <div class=\"placeholder-status\">\r\n <p>请准备好,{this.waitingTimeLeft}秒后将开始录制您的回答...</p>\r\n </div>\r\n );\r\n }\r\n \r\n // 添加默认状态\r\n return (\r\n <div class=\"placeholder-status default-status\">\r\n <p>准备中...</p>\r\n </div>\r\n );\r\n };\r\n\r\n return (\r\n <div class={overlayClass} style={modalStyle}>\r\n <div class={containerClass}>\r\n {this.isShowHeader && (\r\n <div class=\"modal-header\">\r\n <div class=\"header-left\">\r\n {this.icon && <img src={this.icon} class=\"header-icon\" alt=\"应用图标\" />}\r\n <div>{this.modalTitle}</div>\r\n </div>\r\n {this.isNeedClose && (\r\n <button class=\"close-button\" onClick={this.handleClose}>\r\n <span>×</span>\r\n </button>\r\n )}\r\n </div>\r\n )}\r\n\r\n {this.showInitialUpload ? (\r\n <div class=\"initial-upload\">\r\n <div class=\"upload-section\">\r\n {/* 根据 requireResume 条件渲染简历上传部分 */}\r\n {this.requireResume && (\r\n <>\r\n <h3>开始前,请上传您的简历</h3>\r\n <div class=\"upload-area\" onClick={this.handleUploadClick}>\r\n {this.selectedFile ? (\r\n <div class=\"file-info\">\r\n <span>{this.selectedFile.name}</span>\r\n <button class=\"remove-file\" onClick={(e) => {\r\n e.stopPropagation();\r\n this.clearSelectedFile();\r\n }}>×</button>\r\n </div>\r\n ) : (\r\n <div class=\"upload-placeholder\">\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" width=\"48\" height=\"48\">\r\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m0-16l-4 4m4-4l4 4\" />\r\n </svg>\r\n <p>点击上传简历</p>\r\n <p class=\"upload-hint\">支持 txt、 markdown、 pdf、 docx、 md 格式</p>\r\n </div>\r\n )}\r\n </div>\r\n </>\r\n )}\r\n\r\n <div class=\"category-select\">\r\n <h3>请选择您的职能类别(单选)</h3>\r\n <div class=\"category-options\">\r\n {this.jobCategories.map(category => (\r\n <button\r\n class={{\r\n 'category-button': true,\r\n 'selected': this.selectedJobCategory === category\r\n }}\r\n onClick={() => this.handleJobCategorySelect(category)}\r\n >\r\n {category}\r\n </button>\r\n ))}\r\n </div>\r\n </div>\r\n\r\n <div class=\"dimension-select\">\r\n <h3>请选择关注的模块(可多选)</h3>\r\n <div class=\"dimension-options\">\r\n {this.dimensions.map(dimension => (\r\n <button\r\n class={{\r\n 'dimension-button': true,\r\n 'selected': this.selectedDimensions.includes(dimension)\r\n }}\r\n onClick={() => this.handleDimensionSelect(dimension)}\r\n >\r\n {dimension}\r\n </button>\r\n ))}\r\n </div>\r\n </div>\r\n\r\n <button\r\n class=\"submit-button\"\r\n disabled={\r\n (this.requireResume && !this.selectedFile) ||\r\n !this.selectedJobCategory ||\r\n this.selectedDimensions.length === 0 ||\r\n (this.requireResume && this.isUploading)\r\n }\r\n onClick={this.handleInitialSubmit}\r\n >\r\n {this.requireResume && this.isUploading ? '上传中...' : '开始面试'}\r\n </button>\r\n </div>\r\n {this.requireResume && (\r\n <input\r\n type=\"file\"\r\n class=\"file-input\"\r\n onChange={this.handleFileChange}\r\n accept=\".pdf,.doc,.docx,.txt\"\r\n />\r\n )}\r\n </div>\r\n ) : (\r\n <div style={{ height: '100%' }}>\r\n <div class=\"chat-history\" onScroll={this.handleScroll}>\r\n {this.isLoadingHistory ? (\r\n <div class=\"loading-container\">\r\n <div class=\"loading-spinner\"></div>\r\n <p>加载历史消息中...</p>\r\n </div>\r\n ) : (\r\n <div>\r\n {this.messages.map((message) => (\r\n <div id={`message_${message.id}`} key={message.id}>\r\n <pcm-chat-message\r\n message={message}\r\n onMessageChange={(event) => {\r\n const updatedMessages = this.messages.map(msg =>\r\n msg.id === message.id ? { ...msg, ...event.detail } : msg\r\n );\r\n this.messages = updatedMessages;\r\n }}\r\n ></pcm-chat-message>\r\n </div>\r\n ))}\r\n {this.currentStreamingMessage && (\r\n <div id={`message_${this.currentStreamingMessage.id}`}>\r\n <pcm-chat-message\r\n message={this.currentStreamingMessage}\r\n ></pcm-chat-message>\r\n </div>\r\n )}\r\n {this.messages.length === 0 && !this.currentStreamingMessage && (\r\n <div class=\"empty-state\">\r\n <p>请上传简历开始面试</p>\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n\r\n <div class=\"recording-section\">\r\n <div class=\"recording-container\">\r\n <div class=\"video-area\">\r\n {this.showRecordingUI ? (\r\n renderVideoPreview()\r\n ) : (\r\n <div class=\"video-preview placeholder\">\r\n {renderPlaceholderStatus()}\r\n </div>\r\n )}\r\n </div>\r\n {/* 添加进度条和数字进度 */}\r\n <div class=\"progress-container\">\r\n <div class=\"progress-bar-container\">\r\n <div \r\n class=\"progress-bar\" \r\n style={{ \r\n width: `${Math.max(0, this.currentQuestionNumber - 1) / this.totalQuestions * 100}%` \r\n }}\r\n ></div>\r\n </div>\r\n <div class=\"progress-text\">\r\n 已完成{Math.max(0, this.currentQuestionNumber - 1)}/{this.totalQuestions}\r\n </div>\r\n </div>\r\n <div class=\"recording-controls\">\r\n {this.showRecordingUI ? (\r\n <button\r\n class=\"stop-recording-button\"\r\n onClick={() => this.stopRecording()}\r\n >\r\n 完成本题回答\r\n </button>\r\n ) : (\r\n <div class=\"waiting-message\">\r\n {(() => {\r\n // 显示播放按钮(当不自动播放且有音频URL时)\r\n if (!this.enableVoice && this.audioUrl && !this.isPlayingAudio) {\r\n return (\r\n <div class=\"play-audio-container\" onClick={this.handlePlayAudio}>\r\n <p>\r\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"currentColor\" style={{ verticalAlign: 'middle', marginRight: '8px' }}>\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n <span style={{ verticalAlign: 'middle' }}>播放题目</span>\r\n </p>\r\n </div>\r\n );\r\n }\r\n \r\n // 其他状态下显示禁用的\"完成回答\"按钮\r\n return (\r\n <button class=\"stop-recording-button disabled\" disabled>\r\n 完成回答\r\n </button>\r\n );\r\n })()}\r\n </div>\r\n )}\r\n </div>\r\n \r\n \r\n </div>\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n }\r\n}"],"mappings":"oGAAA,MAAMA,EAAoB,03U,MCSbC,EAAW,M,wQAIdC,WAAqB,OAKGC,OAAiB,GAKxBC,OAAkB,MAKlCC,SAA0B,GAM1BC,YAKDC,KAKAC,OAAkB,IAKlBC,aAAwB,KAKxBC,YAAuB,KAMNC,eAKhBC,wBAAkC,GAKlCC,UAAqB,MAKrBC,wBAA8C,KAG9CC,iBAA4B,KAE5BC,iBAA4B,M,iCAM5BC,eAOAC,aAA4B,KAC5BC,YAAuB,MACvBC,iBAAgG,GAKjGC,aAAuB,GAGtBC,kBAA6B,KAC7BC,oBAA8B,GAC9BC,cAA0B,CACjC,aACA,SACA,SACA,SACA,UAGOC,WAAuB,CAC9B,SACA,QACA,OACA,QACA,QACA,WAGOC,mBAA+B,GAG/BC,YAAuB,MACvBC,gBAAsC,KACtCC,aAA4B,KAC5BC,cAAsC,KACtCC,kBAA4B,EAC5BC,gBAA2B,MAC3BC,eAAsB,KACtBC,mBAA6B,EAC7BC,iBAA2B,IAC3BC,gBAA2B,MAC3BC,aAAoB,KACpBC,gBAA0B,GAG3BC,SAAoC,KAKpCC,eAAyB,EAKxBC,sBAAgC,EAKhCC,oBAA+B,MAK/BC,kBAKQC,iBAAmB,GAK5BC,iBAA2B,IAM3BC,qBAA+B,GAE9BC,qBAAgC,MAKjCC,QAAkB,GAKlBC,WAAsB,MAGrBC,iBAA4B,MAK7BC,cAAyB,MAGxBC,eAA0B,MAC1BC,SAA0B,KAC3BC,aAAwC,KAKvCC,eASAC,sBAQDC,YAAuB,KAOvBC,qBAA+B,IAK/BC,OAAiB,GAEjBC,YAAc,KACpBC,KAAKC,gBACLD,KAAKvD,YAAYyD,MAAM,EAGjBC,iBAAmBC,MAAOC,IAChC,MAAMC,EAAQD,EAAME,OACpB,GAAID,EAAME,OAASF,EAAME,MAAMC,OAAS,EAAG,CACzCT,KAAK3C,aAAeiD,EAAME,MAAM,E,GAI5B,gBAAME,GACZ,IAAKV,KAAK3C,cAAgB2C,KAAKzC,iBAAiBkD,OAAS,EAAG,OAE5DT,KAAK1C,YAAc,KAEnB,IACE,MAAMqD,EAAW,IAAIC,SACrBD,EAASE,OAAO,OAAQb,KAAK3C,cAE7B,MAAMyD,QAAiBC,MAAM,yDAA0D,CACrFC,OAAQ,OACRC,QAAS,CACPC,cAAiB,UAAYlB,KAAK1D,QAEpC6E,KAAMR,IAGR,MAAMS,QAAeN,EAASO,OAC9B,IAAKP,EAASQ,GAAI,CAChB,MAAM,IAAIC,MAAMH,EAAOI,SAAW,S,CAGpC,GAAIJ,EAAQ,CACVpB,KAAKzC,iBAAmB,CAAC,CACvBkE,QAASL,EAAOK,QAChBC,SAAUN,EAAOM,SACjBC,IAAKP,EAAOO,IACZC,cAAeR,EAAOQ,e,EAG1B,MAAOC,GACPC,QAAQD,MAAM,UAAWA,GACzB7B,KAAK+B,oBACLC,MAAMH,aAAiBN,MAAQM,EAAML,QAAU,a,SAE/CxB,KAAK1C,YAAc,K,EAIf2E,kBAAoB,KAC1B,MAAMC,EAAYlC,KAAKmC,YAAYC,YAAYC,cAAc,eAC7DH,GAAWI,OAAO,EAGZP,kBAAoB,KAC1B/B,KAAK3C,aAAe,KACpB2C,KAAKzC,iBAAmB,GACxB,MAAM2E,EAAYlC,KAAKmC,YAAYC,YAAYC,cAAc,eAC7D,GAAIH,EAAW,CACbA,EAAUK,MAAQ,E,GAId,sBAAMC,CAAiBhB,GAC7BxB,KAAKhD,UAAY,KACjB,IAAIyF,EAAS,GACb,IAAIC,EAAU,GAEd,MAAMC,EAAM,IAAIC,KAChB,MAAMC,EAAO,GAAGF,EAAIG,cAAcH,EAAII,aAAaC,WAAWC,SAAS,EAAG,OAG1E,MAAMC,EAAY1B,EAAQ2B,SAAWnD,KAAKzC,iBAAiBkD,OAAS,EAAI,UAAY,IAGpF,MAAM2C,EAAgBpD,KAAKxD,SAASiE,OAAS,EAAIT,KAAKxD,SAASwD,KAAKxD,SAASiE,OAAS,GAAK,KAG3F,GAAI2C,GAAiBpD,KAAKlD,gBAAkB0E,IAAY,MAAO,CAC7DxB,KAAKqD,WACHrD,KAAKlD,eACLsG,EAAcX,OACdS,E,CAKJ,MAAMI,EAAkBtD,KAAKpB,uBAAyBoB,KAAKrB,gBAAmB6C,IAAY,MAG1F,MAAM+B,EAA0B,CAC9BC,GAAI,QAAQZ,KAAKD,QACjBE,KAAMA,EACNY,MAAOP,EACPT,OAAQ,GACRiB,YAAa,KACbC,gBAAiB3D,KAAKlD,eACtB8G,OAAQ,GACRC,OAAQ,SACRhC,MAAO,MAIT7B,KAAK/C,wBAA0BsG,EAE/BvD,KAAK9C,iBAAmB,KAExB8C,KAAK8D,iBAGL,GAAIR,EAAgB,CAClBtD,KAAKxD,SAAW,IAAIwD,KAAKxD,SAAU+G,GACnCvD,KAAK/C,wBAA0B,KAC/B+C,KAAKhD,UAAY,MACjBgD,KAAKnB,oBAAsB,WACrBmB,KAAK+D,oBACX/D,KAAKlB,kBAAkBoB,KAAK,CAC1ByD,gBAAiB3D,KAAKlD,eACtBkH,gBAAiBhE,KAAKrB,iBAExB,M,CAIF,MAAMsF,EAAmB,CACvBC,cAAe,YACfP,gBAAiB3D,KAAKlD,eACtB2G,MAAOP,EACPiB,KAAMnE,KAAKF,QAEbmE,EAAYL,OAAS,CACnBQ,SAAUpE,KAAKtC,oBACf2G,iBAAkBrE,KAAKnC,mBAAmByG,KAAK,KAC/CC,MAAOvE,KAAKb,QACZqF,uBAAwBxE,KAAKH,sBAG/B,GAAIG,KAAKzC,iBAAiBkD,OAAS,EAAG,CACpC,MAAMgE,EAAWzE,KAAKzC,iBAAiBmH,KAAIC,GAAYA,EAASlD,UAAS6C,KAAK,KAC9EL,EAAYL,OAAOgB,UAAYH,C,OAI3BI,EAAe,CACnBC,IAAK,+DACL9D,OAAQ,OACRC,QAAS,CACPC,cAAiB,UAAYlB,KAAK1D,QAEpCyI,KAAMd,EACNe,UAAYD,IACVjD,QAAQmD,IAAI,cAAeF,GAE3B,GAAIA,EAAKpB,kBAAoB3D,KAAKlD,eAAgB,CAChDkD,KAAKlD,eAAiBiI,EAAKpB,gBAC3B3D,KAAKkF,4BAA4BH,EAAKpB,gB,CAIxC,GAAIoB,EAAK1E,QAAU,iBAAmB0E,EAAKA,KAAKnB,QAAUmB,EAAKA,KAAKnB,OAAOuB,QAAS,CAClFzC,EAAUqC,EAAKA,KAAKnB,OAAOuB,QAC3BrD,QAAQmD,IAAI,eAAgBvC,E,CAG9B,GAAIqC,EAAK1E,QAAU,UAAW,CAI5B,GAAI0E,EAAK1E,QAAU,iBAAmB0E,EAAK1E,QAAU,UAAW,CAC9D,GAAI0E,EAAKtC,OAAQ,CACfA,GAAUsC,EAAKtC,OACf,MAAM2C,EAA8B,IAC/BpF,KAAK/C,wBACRwF,SACAiB,YAAa,MAEf1D,KAAK/C,wBAA0BmI,EAC/BpF,KAAK8D,gB,GAIX,GAAIiB,EAAK1E,QAAU,cAAe,CAChCL,KAAK5C,eAAe8C,KAAK,CACvByD,gBAAiBoB,EAAKpB,iBAAmB,GACzCtD,MAAO0E,EAAK1E,MACZgF,WAAYN,EAAKM,WACjB7B,GAAIuB,EAAKvB,I,GAIf8B,QAAUzD,IACRC,QAAQD,MAAM,QAASA,GACvBG,MAAMH,aAAiBN,MAAQM,EAAML,QAAU,gBAC/CxB,KAAKxD,SAAW,IAAIwD,KAAKxD,SAAU,IAC9B+G,EACHd,OAAQ,kBACRZ,MAAOA,EACP6B,YAAa,QAEf1D,KAAK/C,wBAA0B,KAC/B+C,KAAKhD,UAAY,KAAK,EAExBuI,WAAYnF,UACV0B,QAAQmD,IAAI,QACZjF,KAAKhD,UAAY,MAGjB,MAAMwI,EAAkBxF,KAAK/C,wBAG7B+C,KAAKxD,SAAW,IAAIwD,KAAKxD,SAAUwD,KAAK/C,yBACxC+C,KAAK/C,wBAA0B,KAG/B,GAAIuE,IAAY,OAASxB,KAAKpB,wBAA0B,EAAG,CACzDoB,KAAKpB,uB,CAEPkD,QAAQmD,IAAIjF,KAAKpB,uBACjBkD,QAAQmD,IAAIzD,GAEZ,GAAIgE,GAAmBA,EAAgB/C,OAAQ,CAE7C,MAAMgD,EAAmB/C,GAAW8C,EAAgB/C,OAEpD,GAAIgD,EAAkB,CAEpB,MAAMjG,QAAiBQ,KAAK0F,gBAAgBD,GAE5C,GAAIzF,KAAKJ,YAAa,OAEdI,KAAK2F,UAAUnG,GAErBQ,KAAK4F,sB,KACA,CAEL5F,KAAKR,SAAWA,C,OAUpB,gBAAM6D,CAAWvG,EAAwB+I,EAAkBpD,GACjE,UACQqD,EAAgB,CACpBhB,IAAK,6DACL9D,OAAQ,OACRC,QAAS,CACPC,cAAiB,UAAYlB,KAAK1D,QAEpCyI,KAAM,CACJpB,gBAAiB7G,EACjBqH,KAAMnE,KAAKF,OACX+F,SAAUA,EACVpD,OAAQA,I,CAGZ,MAAOZ,GACPC,QAAQD,MAAM,UAAWA,E,EAKrBkE,aAAe,KACrB,MAAMC,EAAchG,KAAKmC,YAAYC,YAAYC,cAAc,iBAC/D,IAAK2D,EAAa,OAElB,MAAMC,UAAEA,EAASC,aAAEA,EAAYC,aAAEA,GAAiBH,EAClD,MAAMI,EAAqBF,EAAeD,EAAYE,EAGtDnG,KAAK9C,iBAAmBkJ,GAAsBpG,KAAKjB,gBAAgB,EAG7D,cAAA+E,GACN,IAAK9D,KAAK9C,iBAAkB,OAC5B,MAAM8I,EAAchG,KAAKmC,YAAYC,YAAYC,cAAc,iBAC/D,GAAI2D,GAAehG,KAAKzD,OAAQ,CAE9ByJ,EAAYC,UAAYD,EAAYE,Y,EAKxC,kBAAAG,GACE,GAAIrG,KAAK7C,kBAAqB6C,KAAK9C,kBAAoB8C,KAAKzD,OAAS,CACnE,MAAMyJ,EAAchG,KAAKmC,YAAYC,YAAYC,cAAc,iBAC/D,GAAI2D,EAAa,CACfA,EAAYC,UAAYD,EAAYE,Y,GAKlC,2BAAAhB,CAA4BpI,GAClC,MAAMwJ,EAAY,IAAIC,gBAAgBC,OAAOC,SAASC,QACtD,IAAKJ,EAAUK,IAAI,mBAAoB,CACrC,MAAMC,EAAS,IAAIC,IAAIL,OAAOC,SAASK,MACvCF,EAAOG,aAAaC,IAAI,kBAAmBlK,GAC3C0J,OAAOS,QAAQC,aAAa,GAAI,GAAIN,E,EAKhC,yBAAMO,GACZ,IAAKnH,KAAKlD,eAAgB,OAE1BkD,KAAK7C,iBAAmB,KACxB2E,QAAQmD,IAAI,aAEZ,UACQa,EAAgB,CACpBhB,IAAK,0DACL9D,OAAQ,MACRC,QAAS,CACPC,cAAiB,UAAYlB,KAAK1D,QAEpCyI,KAAM,CACJpB,gBAAiB3D,KAAKlD,eACtBsK,MAAO,IAETpC,UAAYD,IACV,GAAIA,EAAKA,KAAM,CACb,MAAMsC,EAActC,EAAKA,MAAQ,GACjC,MAAMuC,EAAmCD,EAAY3C,KAAI6C,IACvD,MAAM1E,EAAO,IAAID,KAAK2E,EAAIC,WAAa,KACvC,MAAMC,EAAQ5E,EAAKC,WAAWE,WAAWC,SAAS,EAAG,KACrD,MAAMyE,EAAU7E,EAAKE,aAAaC,WAAWC,SAAS,EAAG,KACzD,MAAM0E,EAAU,GAAGF,KAASC,IAG5B,MAAM9D,OAAEA,KAAWgE,GAAqBL,EAExC,MAAO,IACFK,EACH/E,KAAM8E,EACNjE,YAAa,MACbG,OAAQ0D,EAAI1D,SAAW,QAAU,QAAU,SAC5C,IAGH7D,KAAKxD,SAAW8K,EAChBtH,KAAK7C,iBAAmB,MAExB0K,uBAAsB,KACpB7H,KAAK9C,iBAAmB,KACxB8C,KAAK8D,gBAAgB,G,KAElB,CACL9D,KAAK7C,iBAAmB,K,GAG5BmI,QAAUzD,IACRC,QAAQD,MAAM,YAAaA,GAC3BG,MAAMH,aAAiBN,MAAQM,EAAML,QAAU,kBAC/CxB,KAAK7C,iBAAmB,KAAK,EAE/BoI,WAAY,KACVvF,KAAK7C,iBAAmB,KAAK,G,CAGjC,MAAO0E,GACPC,QAAQD,MAAM,YAAaA,GAC3BG,MAAMH,aAAiBN,MAAQM,EAAML,QAAU,kBAC/CxB,KAAK7C,iBAAmB,K,EAM5B,wBAAM2K,CAAmBC,GACvB,GAAIA,EAAU,CACZ,GAAI/H,KAAKlD,eAAgB,OACjBkD,KAAKmH,qB,GAMTa,wBAA2BC,IACjCjI,KAAKtC,oBAAsBuK,CAAQ,EAG7BC,sBAAyBC,IAC/B,GAAInI,KAAKnC,mBAAmBuK,SAASD,GAAY,CAC/CnI,KAAKnC,mBAAqBmC,KAAKnC,mBAAmBwK,QAAOC,GAAKA,IAAMH,G,KAC/D,CACLnI,KAAKnC,mBAAqB,IAAImC,KAAKnC,mBAAoBsK,E,GAInDI,oBAAsBnI,UAE5B,GAAIJ,KAAKV,gBAAkBU,KAAK3C,aAAc,CAC5C2E,MAAM,SACN,M,CAGF,IAAKhC,KAAKtC,oBAAqB,CAC7BsE,MAAM,WACN,M,CAGF,GAAIhC,KAAKnC,mBAAmB4C,SAAW,EAAG,CACxCuB,MAAM,eACN,M,CAKF,MAAMwG,EAAYC,QAAQ,wBAE1B,IAAKD,EAAW,CACd,M,CAIF,GAAIxI,KAAKV,cAAe,OAChBU,KAAKU,aACX,GAAIV,KAAKzC,iBAAiBkD,SAAW,EAAG,CACtC,M,EAIJT,KAAKvC,kBAAoB,MACzB,MAAM+D,EAAU,OAAOxB,KAAKtC,6BAC5BsC,KAAKwC,iBAAiBhB,EAAQ,EAIxB,oBAAAoE,GAEN,GAAI5F,KAAKxB,aAAc,CACrBkK,cAAc1I,KAAKxB,a,CAErB,GAAIwB,KAAK5B,eAAgB,CACvBsK,cAAc1I,KAAK5B,e,CAGrB4B,KAAKzB,gBAAkB,KACvByB,KAAKvB,gBAAkB,GAEvBuB,KAAKxB,aAAemK,aAAY,KAC9B3I,KAAKvB,kBACL,GAAIuB,KAAKvB,iBAAmB,EAAG,CAC7BiK,cAAc1I,KAAKxB,cACnBwB,KAAKxB,aAAe,KACpBwB,KAAKzB,gBAAkB,MACvByB,KAAK4I,gB,IAEN,I,CAIG,oBAAMA,GACZ,IACE,MAAMC,QAAeC,UAAUC,aAAaC,aAAa,CACvDC,MAAO,KACPC,MAAO,CACLC,MAAO,CAAEC,MAAO,MAChBC,OAAQ,CAAED,MAAO,KACjBE,UAAW,CAAEF,MAAO,OAIxBpJ,KAAKjC,gBAAkB8K,EACvB7I,KAAK7B,gBAAkB,KACvB6B,KAAKd,qBAAuB,MAG5Bc,KAAKtB,SAAW,KAGhBsB,KAAKuJ,kBAAkBV,GAGvB,MAAMW,EAAWxJ,KAAKyJ,uBAGtB,IAAIxL,EACJ,IACEA,EAAgB,IAAIyL,cAAcb,EAAQ,CACxCW,SAAUA,G,CAEZ,MAAOG,GAEP7H,QAAQ8H,KAAK,wBAAyBD,GACtC,IACE1L,EAAgB,IAAIyL,cAAcb,E,CAClC,MAAOgB,GAEP7J,KAAKN,eAAeQ,KAAK,CACvB4J,KAAM,2BACNtI,QAAS,0BACTuI,QAASF,IAEX7J,KAAK7B,gBAAkB,MACvB,M,EAIJ6B,KAAK/B,cAAgBA,EAErB,MAAM+L,EAAqB,GAE3B/L,EAAcgM,gBAAmB5J,IAC/B,GAAIA,EAAM0E,KAAKmF,KAAO,EAAG,CACvBF,EAAOG,KAAK9J,EAAM0E,K,GAItB9G,EAAcmM,QAAW/J,IAEvBL,KAAKN,eAAeQ,KAAK,CACvB4J,KAAM,kBACNtI,QAAS,YACTuI,QAAS1J,IAEXL,KAAKC,eAAe,EAGtBhC,EAAcoM,OAAS,KACrB,IAEE,MAAMC,EAAWd,GAAY,YAC7B,MAAMe,EAAO,IAAIC,KAAKR,EAAQ,CAAEF,KAAMQ,IAEtC,GAAIC,EAAKL,OAAS,EAAG,CAEnBlK,KAAKN,eAAeQ,KAAK,CACvB4J,KAAM,kBACNtI,QAAS,YAEXxB,KAAK7B,gBAAkB,MACvB,M,CAGF6B,KAAKhC,aAAeuM,EAGpBvK,KAAKL,sBAAsBO,KAAK,CAC9B2D,OAAQ,UACRkG,QAAS,CACPU,SAAUC,KAAKC,OAAO/H,KAAKD,MAAQ3C,KAAK3B,oBAAsB,KAC9D6L,KAAMK,EAAKL,KACXJ,KAAMS,EAAKT,QAIf9J,KAAK4K,qB,CACL,MAAO/I,GAEP7B,KAAKN,eAAeQ,KAAK,CACvB4J,KAAM,mBACNtI,QAAS,YACTuI,QAASlI,IAEX7B,KAAK7B,gBAAkB,K,GAK3B,IACEF,EAAc4M,QACd7K,KAAKlC,YAAc,KACnBkC,KAAK3B,mBAAqBuE,KAAKD,MAC/B3C,KAAK9B,kBAAoB8B,KAAKhB,iBAG9BgB,KAAKL,sBAAsBO,KAAK,CAC9B2D,OAAQ,UACRkG,QAAS,CACPe,YAAa9K,KAAKhB,iBAClBwK,SAAUvL,EAAcuL,W,CAG5B,MAAOuB,GAEP/K,KAAKN,eAAeQ,KAAK,CACvB4J,KAAM,eACNtI,QAAS,mBACTuI,QAASgB,IAEX/K,KAAK7B,gBAAkB,MACvB,M,CAIF6B,KAAK5B,eAAiBuK,aAAY,KAChC,MAAMqC,EAAcN,KAAKC,OAAO/H,KAAKD,MAAQ3C,KAAK3B,oBAAsB,KACxE2B,KAAK9B,kBAAoBwM,KAAKO,IAAI,EAAGjL,KAAKhB,iBAAmBgM,GAG7D,GAAIhL,KAAK9B,mBAAqB8B,KAAKf,uBAAyBe,KAAKd,qBAAsB,CACrFc,KAAKd,qBAAuB,I,CAI9B,GAAIc,KAAK9B,mBAAqB,EAAG,CAC/B8B,KAAKC,e,IAEN,I,CAEH,MAAO4B,GACPC,QAAQD,MAAM,eAAgBA,GAE9B7B,KAAKN,eAAeQ,KAAK,CACvB4J,KAAM,sBACNtI,QAAS,uBACTuI,QAASlI,IAEX7B,KAAK7B,gBAAkB,K,EAKnB,iBAAAoL,CAAkBV,GAExBqC,YAAW,KACT,MAAMC,EAAenL,KAAKmC,YAAYC,YAAYC,cAAc,SAChE,GAAI8I,GAAgBtC,EAAQ,CAE1B,IACEsC,EAAaC,UAAYvC,EACzBsC,EAAaE,OAAOC,OAAMC,IACxBzJ,QAAQD,MAAM,UAAW0J,EAAI,G,CAE/B,MAAO5B,GACP7H,QAAQ8H,KAAK,wBAAyBD,GAGtC,IAEE,MAAM6B,EAAY3E,IAAI4E,gBAAgB5C,GACtCsC,EAAaO,IAAMF,EAGnBL,EAAaQ,QAAU,KACrB9E,IAAI+E,gBAAgBJ,EAAU,C,CAEhC,MAAOK,GACP/J,QAAQD,MAAM,aAAcgK,E,OAG3B,CACL/J,QAAQ8H,KAAK,gB,IAEd,I,CAIG,oBAAAH,GAEN,MAAMqC,EAAY,CAChB,6BACA,6BACA,aACA,YACA,4BACA,IAIF,IAAKtF,OAAOkD,cAAe,CACzB5H,QAAQ8H,KAAK,wBACb,MAAO,E,CAIT,IAAK,MAAME,KAAQgC,EAAW,CAC5B,IAAKhC,EAAM,MAAO,GAElB,IACE,GAAIJ,cAAcqC,gBAAgBjC,GAAO,CACvChI,QAAQmD,IAAI,eAAgB6E,GAC5B,OAAOA,C,EAET,MAAOH,GACP7H,QAAQ8H,KAAK,iBAAiBE,KAASH,E,EAK3C7H,QAAQ8H,KAAK,2BACb,MAAO,E,CAID,aAAA3J,GACN,GAAID,KAAK/B,eAAiB+B,KAAKlC,YAAa,CAC1CkC,KAAK/B,cAAc+N,OACnBhM,KAAKlC,YAAc,MAGnB,GAAIkC,KAAK5B,eAAgB,CACvBsK,cAAc1I,KAAK5B,gBACnB4B,KAAK5B,eAAiB,I,CAIxB,GAAI4B,KAAKjC,gBAAiB,CACxBiC,KAAKjC,gBAAgBkO,YAAYC,SAAQC,GAASA,EAAMH,SACxDhM,KAAKjC,gBAAkB,I,CAIzBiC,KAAKtB,SAAW,I,EAKZ,yBAAMkM,GACZ,IAAK5K,KAAKhC,aAAc,OAExB,IACEgC,KAAKX,iBAAmB,KACxBW,KAAK7B,gBAAkB,MAGvB,MAAMiO,EAAgBpM,KAAKhC,aAAa8L,KAAK1B,SAAS,QAAU,OAAS,MACzE,MAAMiE,EAAW,UAAUD,IAE3B,MAAMzL,EAAW,IAAIC,SACrBD,EAASE,OAAO,OAAQb,KAAKhC,aAAcqO,GAE3C,MAAMvL,QAAiBC,MAAM,yDAA0D,CACrFC,OAAQ,OACRC,QAAS,CACPC,cAAiB,UAAYlB,KAAK1D,QAEpC6E,KAAMR,IAGR,MAAMS,QAAeN,EAASO,OAC9BS,QAAQmD,IAAI,UAAW7D,GAEvB,GAAIA,GAAUA,EAAOK,QAAS,OAEtBzB,KAAKsM,gBAAgBlL,EAAOK,SAGlCzB,KAAKuM,kB,KACA,CACL,MAAM,IAAIhL,MAAM,S,EAElB,MAAOM,GACPC,QAAQD,MAAM,UAAWA,GAEzB7B,KAAKN,eAAeQ,KAAK,CACvB4J,KAAM,gBACNtI,QAAS,SACTuI,QAASlI,G,SAGX7B,KAAKX,iBAAmB,MACxBW,KAAK7B,gBAAkB,MACvB6B,KAAKhC,aAAe,I,EAKhB,qBAAMsO,CAAgBE,GAC5B,IAAKxM,KAAKlD,eAAgB,OAE1B,IACE,MAAMsG,EAAgBpD,KAAKxD,SAASiE,OAAS,EAAIT,KAAKxD,SAASwD,KAAKxD,SAASiE,OAAS,GAAK,KAE3F,IAAK2C,EAAe,aAEd0C,EAAgB,CACpBhB,IAAK,6DACL9D,OAAQ,OACRC,QAAS,CACPC,cAAiB,UAAYlB,KAAK1D,QAEpCyI,KAAM,CACJpB,gBAAiB3D,KAAKlD,eACtBqH,KAAMnE,KAAKF,OACX+F,SAAUzC,EAAcX,OACxBgK,SAAUD,I,CAGd,MAAO3K,GACPC,QAAQD,MAAM,YAAaA,E,EAKvB,gBAAA0K,GACNvM,KAAKwC,iBAAiB,M,CAMhB,uBAAMuB,GACZ,IAAK/D,KAAKlD,eAAgB,OAE1B,UACQgJ,EAAgB,CACpBhB,IAAK,uDAAuD9E,KAAKlD,qBACjEkE,OAAQ,OACRC,QAAS,CACPC,cAAiB,UAAYlB,KAAK1D,S,CAItC,MAAOuF,GACPC,QAAQD,MAAM,cAAeA,E,EAKzB,qBAAM6D,CAAgBgH,GAC5B,IACE,MAAM5L,QAAiBC,MAAM,iEAAkE,CAC7FC,OAAQ,OACRC,QAAS,CACP,eAAgB,mBAChBC,cAAiB,UAAYlB,KAAK1D,QAEpC6E,KAAMwL,KAAKC,UAAU,CAAEF,WAGzB,IAAK5L,EAASQ,GAAI,CAChB,MAAM,IAAIC,MAAM,S,CAIlB,MAAMsL,QAAkB/L,EAASyJ,OACjC,OAAO1D,IAAI4E,gBAAgBoB,E,CAC3B,MAAOhL,GACPC,QAAQD,MAAM,UAAWA,GACzB,MAAMA,C,EAKF,SAAA8D,CAAUnG,GAChB,OAAO,IAAIsN,SAASC,IAClB/M,KAAKT,eAAiB,KACtBS,KAAKR,SAAWA,EAGhB,IAAKQ,KAAKP,aAAc,CACtBO,KAAKP,aAAe,IAAIuN,K,CAG1BhN,KAAKP,aAAaiM,IAAMlM,EACxBQ,KAAKP,aAAakM,QAAU,KAC1B3L,KAAKT,eAAiB,MACtBS,KAAKR,SAAW,KAChBuN,GAAS,EAGX/M,KAAKP,aAAa2K,QAAU,KAC1BtI,QAAQD,MAAM,UACd7B,KAAKT,eAAiB,MACtBS,KAAKR,SAAW,KAChBuN,GAAS,EAGX/M,KAAKP,aAAa4L,OAAOC,OAAMzJ,IAC7BC,QAAQD,MAAM,UAAWA,GACzB7B,KAAKT,eAAiB,MACtBS,KAAKR,SAAW,KAChBuN,GAAS,GACT,G,CAKN,oBAAAE,GAEE,GAAIjN,KAAKP,aAAc,CACrBO,KAAKP,aAAayN,QAClBlN,KAAKP,aAAaiM,IAAM,GACxB1L,KAAKP,aAAe,I,CAItB,GAAIO,KAAKR,SAAU,CACjBqH,IAAI+E,gBAAgB5L,KAAKR,UACzBQ,KAAKR,SAAW,I,CAIlB,GAAIQ,KAAKxB,aAAc,CACrBkK,cAAc1I,KAAKxB,cACnBwB,KAAKxB,aAAe,I,CAGtB,GAAIwB,KAAK5B,eAAgB,CACvBsK,cAAc1I,KAAK5B,gBACnB4B,KAAK5B,eAAiB,I,CAIxB4B,KAAKC,e,CAICkN,gBAAkB/M,UACxB,GAAIJ,KAAKR,SAAU,OACXQ,KAAK2F,UAAU3F,KAAKR,UAE1BQ,KAAK4F,sB,GAIT,MAAAwH,GACE,IAAKpN,KAAKzD,OAAQ,OAAO,KAEzB,MAAM8Q,EAAa,CACjB1Q,OAAQ2Q,OAAOtN,KAAKrD,SAGtB,MAAM4Q,EAAiB,CACrB,kBAAmB,KACnBnO,WAAcY,KAAKZ,YAGrB,MAAMoO,EAAe,CACnB,gBAAiB,KACjB,qBAAsBxN,KAAKZ,YAG7B,MAAMqO,EAAqB,IACzBC,EAAA,OAAKC,MAAM,iBACTD,EAAA,SACEE,SACA,KAAAC,YAAW,KACXC,MAAK,KACLC,MAAO,CAAEC,UAAW,cACpBC,IAAMC,IACJ,GAAIA,GAAMlO,KAAKjC,kBAAoBiC,KAAKtB,SAAU,CAChDsB,KAAKtB,SAAWwP,C,KAKtBR,EAAA,OAAKC,MAAO,CACV,mBAAoB,KACpBQ,QAAWnO,KAAKd,uBAEhBwO,EAAM,QAAAC,MAAM,kBACZD,EAAA,mBACOhD,KAAKC,MAAM3K,KAAK9B,kBAAoB,IAAG,KAAI8B,KAAK9B,kBAAoB,IAAI8E,WAAWC,SAAS,EAAG,KACnGjD,KAAKd,sBAAwB,eAOtC,MAAMkP,EAA0B,KAE9B,GAAIpO,KAAKT,eAAgB,CACvB,OACEmO,EAAA,OAAKC,MAAM,sBACTD,EAAyB,+B,CAM/B,GAAI1N,KAAKX,iBAAkB,CACzB,OACEqO,EAAA,OAAKC,MAAM,sBACTD,EAAoB,0B,CAM1B,GAAI1N,KAAKhD,WAAagD,KAAK/C,wBAAyB,CAClD,OACEyQ,EAAA,OAAKC,MAAM,sBACTD,EAAe,qB,CAMrB,GAAI1N,KAAKzB,gBAAiB,CACxB,OACEmP,EAAA,OAAKC,MAAM,sBACTD,EAAA,iBAAS1N,KAAKvB,gBAAkC,kB,CAMtD,OACEiP,EAAA,OAAKC,MAAM,qCACTD,EAAa,mBACT,EAIV,OACEA,EAAA,OAAKC,MAAOH,EAAcO,MAAOV,GAC/BK,EAAK,OAAAC,MAAOJ,GACTvN,KAAKpD,cACJ8Q,EAAK,OAAAC,MAAM,gBACTD,EAAK,OAAAC,MAAM,eACR3N,KAAKtD,MAAQgR,EAAK,OAAAhC,IAAK1L,KAAKtD,KAAMiR,MAAM,cAAcU,IAAI,SAC3DX,EAAA,WAAM1N,KAAK3D,aAEZ2D,KAAKnD,aACJ6Q,EAAQ,UAAAC,MAAM,eAAeW,QAAStO,KAAKD,aACzC2N,EAAc,mBAMrB1N,KAAKvC,kBACJiQ,EAAA,OAAKC,MAAM,kBACTD,EAAK,OAAAC,MAAM,kBAER3N,KAAKV,eACJoO,IAAAa,SAAA,KACEb,EAAoB,yBACpBA,EAAK,OAAAC,MAAM,cAAcW,QAAStO,KAAKiC,mBACpCjC,KAAK3C,aACJqQ,EAAK,OAAAC,MAAM,aACTD,EAAA,YAAO1N,KAAK3C,aAAamR,MACzBd,EAAQ,UAAAC,MAAM,cAAcW,QAAU3E,IACpCA,EAAE8E,kBACFzO,KAAK+B,mBAAmB,GACzB,MAGH2L,EAAA,OAAKC,MAAM,sBACTD,EAAA,OAAKgB,QAAQ,YAAYC,KAAK,OAAOC,OAAO,eAAezF,MAAM,KAAKE,OAAO,MAC3EqE,EAAqB,iCAAwB,0BAAqB,mBAAIpF,EAAE,gCAE1EoF,EAAa,mBACbA,EAAG,KAAAC,MAAM,eAAa,0CAOhCD,EAAK,OAAAC,MAAM,mBACTD,EAAsB,2BACtBA,EAAA,OAAKC,MAAM,oBACR3N,KAAKrC,cAAc+G,KAAIuD,GACtByF,EAAA,UACEC,MAAO,CACL,kBAAmB,KACnBkB,SAAY7O,KAAKtC,sBAAwBuK,GAE3CqG,QAAS,IAAMtO,KAAKgI,wBAAwBC,IAE3CA,OAMTyF,EAAK,OAAAC,MAAM,oBACTD,EAAsB,2BACtBA,EAAA,OAAKC,MAAM,qBACR3N,KAAKpC,WAAW8G,KAAIyD,GACnBuF,EAAA,UACEC,MAAO,CACL,mBAAoB,KACpBkB,SAAY7O,KAAKnC,mBAAmBuK,SAASD,IAE/CmG,QAAS,IAAMtO,KAAKkI,sBAAsBC,IAEzCA,OAMTuF,EAAA,UACEC,MAAM,gBACNmB,SACG9O,KAAKV,gBAAkBU,KAAK3C,eAC5B2C,KAAKtC,qBACNsC,KAAKnC,mBAAmB4C,SAAW,GAClCT,KAAKV,eAAiBU,KAAK1C,YAE9BgR,QAAStO,KAAKuI,qBAEbvI,KAAKV,eAAiBU,KAAK1C,YAAc,SAAW,SAGxD0C,KAAKV,eACJoO,EAAA,SACE5D,KAAK,OACL6D,MAAM,aACNoB,SAAU/O,KAAKG,iBACf6O,OAAO,0BAKbtB,EAAA,OAAKK,MAAO,CAAE1E,OAAQ,SACpBqE,EAAK,OAAAC,MAAM,eAAesB,SAAUjP,KAAK+F,cACtC/F,KAAK7C,iBACJuQ,EAAK,OAAAC,MAAM,qBACTD,EAAK,OAAAC,MAAM,oBACXD,EAAA,wBAGFA,EAAA,WACG1N,KAAKxD,SAASkI,KAAKlD,GAClBkM,EAAA,OAAKlK,GAAI,WAAWhC,EAAQgC,KAAM0L,IAAK1N,EAAQgC,IAC7CkK,EACE,oBAAAlM,QAASA,EACT2N,gBAAkB9O,IAChB,MAAM+O,EAAkBpP,KAAKxD,SAASkI,KAAI6C,GACxCA,EAAI/D,KAAOhC,EAAQgC,GAAK,IAAK+D,KAAQlH,EAAMgP,QAAW9H,IAExDvH,KAAKxD,SAAW4S,CAAe,OAKtCpP,KAAK/C,yBACJyQ,EAAK,OAAAlK,GAAI,WAAWxD,KAAK/C,wBAAwBuG,MAC/CkK,EAAA,oBACElM,QAASxB,KAAK/C,2BAInB+C,KAAKxD,SAASiE,SAAW,IAAMT,KAAK/C,yBACnCyQ,EAAK,OAAAC,MAAM,eACTD,EAAA,yBAOVA,EAAK,OAAAC,MAAM,qBACTD,EAAK,OAAAC,MAAM,uBACTD,EAAK,OAAAC,MAAM,cACR3N,KAAK7B,gBACJsP,IAEAC,EAAA,OAAKC,MAAM,6BACRS,MAKPV,EAAK,OAAAC,MAAM,sBACTD,EAAK,OAAAC,MAAM,0BACTD,EAAA,OACEC,MAAM,eACNI,MAAO,CACL5E,MAAO,GAAGuB,KAAKO,IAAI,EAAGjL,KAAKpB,sBAAwB,GAAKoB,KAAKrB,eAAiB,WAIpF+O,EAAK,OAAAC,MAAM,iBAAe,MACpBjD,KAAKO,IAAI,EAAGjL,KAAKpB,sBAAwB,GAAE,IAAGoB,KAAKrB,iBAG3D+O,EAAA,OAAKC,MAAM,sBACR3N,KAAK7B,gBACJuP,EACE,UAAAC,MAAM,wBACNW,QAAS,IAAMtO,KAAKC,iBAAe,UAKrCyN,EAAA,OAAKC,MAAM,mBACR,MAEC,IAAK3N,KAAKJ,aAAeI,KAAKR,WAAaQ,KAAKT,eAAgB,CAC9D,OACEmO,EAAK,OAAAC,MAAM,uBAAuBW,QAAStO,KAAKmN,iBAC9CO,EAAA,SACEA,EAAK,OAAAgB,QAAQ,YAAYvF,MAAM,KAAKE,OAAO,KAAKsF,KAAK,eAAeZ,MAAO,CAAEuB,cAAe,SAAUC,YAAa,QACjH7B,EAAA,QAAMpF,EAAE,mBAEVoF,EAAM,QAAAK,MAAO,CAAEuB,cAAe,WAAuB,S,CAO7D,OACE5B,EAAA,UAAQC,MAAM,iCAAiCmB,SAEtC,aAEZ,EArBA,S","ignoreList":[]}