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,1078 +0,0 @@
1
- 'use strict';
2
-
3
- var index = require('./index-SU3eqPPs.js');
4
- var index$1 = require('./index-DfIUl99H.js');
5
-
6
- const pcmHrChatModalCss = ":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}";
7
-
8
- const ChatHRModal = class {
9
- constructor(hostRef) {
10
- index.registerInstance(this, hostRef);
11
- this.modalClosed = index.createEvent(this, "modalClosed");
12
- this.streamComplete = index.createEvent(this, "streamComplete");
13
- this.interviewComplete = index.createEvent(this, "interviewComplete");
14
- this.recordingError = index.createEvent(this, "recordingError");
15
- this.recordingStatusChange = index.createEvent(this, "recordingStatusChange");
16
- }
17
- /**
18
- * 模态框标题
19
- */
20
- modalTitle = '在线客服';
21
- /**
22
- * API鉴权密钥
23
- */
24
- apiKey = '';
25
- /**
26
- * 是否显示聊天模态框
27
- */
28
- isOpen = false;
29
- /**
30
- * 聊天消息历史
31
- */
32
- messages = [];
33
- /**
34
- * 当点击模态框关闭时触发
35
- */
36
- modalClosed;
37
- /**
38
- * 应用图标URL
39
- */
40
- icon;
41
- /**
42
- * 聊天框的页面层级
43
- */
44
- zIndex = 1000;
45
- /**
46
- * 是否展示顶部标题栏
47
- */
48
- isShowHeader = true;
49
- /**
50
- * 是否展示右上角的关闭按钮
51
- */
52
- isNeedClose = true;
53
- /**
54
- * 会话ID
55
- */
56
- conversationId;
57
- /**
58
- * 当前助手回复的消息
59
- */
60
- currentAssistantMessage = '';
61
- /**
62
- * 是否正在加载回复
63
- */
64
- isLoading = false;
65
- /**
66
- * 当前正在流式输出的消息
67
- */
68
- currentStreamingMessage = null;
69
- // 添加新的状态控制
70
- shouldAutoScroll = true;
71
- isLoadingHistory = false;
72
- get hostElement() { return index.getElement(this); }
73
- // 添加新的 Event
74
- streamComplete;
75
- selectedFile = null;
76
- isUploading = false;
77
- uploadedFileInfo = [];
78
- /**
79
- * 默认查询文本
80
- */
81
- defaultQuery = '';
82
- // 添加新的状态
83
- showInitialUpload = true;
84
- selectedJobCategory = '';
85
- jobCategories = [
86
- '人力资源学生(实习)',
87
- '人力资源专员',
88
- '人力资源主管',
89
- '人力资源经理',
90
- '人力资源总监'
91
- ];
92
- dimensions = [
93
- '人力资源规划',
94
- '招聘与配置',
95
- '员工关系',
96
- '培训与开发',
97
- '薪酬与绩效',
98
- '组织与人才发展'
99
- ];
100
- selectedDimensions = [];
101
- // 添加视频录制相关状态
102
- isRecording = false;
103
- recordingStream = null;
104
- recordedBlob = null;
105
- mediaRecorder = null;
106
- recordingTimeLeft = 0;
107
- showRecordingUI = false;
108
- recordingTimer = null;
109
- recordingStartTime = 0;
110
- recordingMaxTime = 120; // 最大录制时间(秒)
111
- waitingToRecord = false;
112
- waitingTimer = null;
113
- waitingTimeLeft = 10; // 等待时间(秒)
114
- // 添加一个新的私有属性来存储视频元素的引用
115
- videoRef = null;
116
- /**
117
- * 总题目数量
118
- */
119
- totalQuestions = 2;
120
- /**
121
- * 当前题目序号
122
- */
123
- currentQuestionNumber = 0;
124
- /**
125
- * 面试是否已完成
126
- */
127
- isInterviewComplete = false;
128
- /**
129
- * 当面试完成时触发
130
- */
131
- interviewComplete;
132
- SCROLL_THRESHOLD = 30;
133
- /**
134
- * 视频录制最大时长(秒)
135
- */
136
- maxRecordingTime = 120;
137
- /**
138
- * 录制倒计时提醒时间(秒)
139
- * 当剩余时间小于此值时,显示倒计时警告
140
- */
141
- countdownWarningTime = 30;
142
- showCountdownWarning = false;
143
- /**
144
- * 接收报告的邮箱地址
145
- */
146
- toEmail = '';
147
- /**
148
- * 是否以全屏模式打开
149
- */
150
- fullscreen = false;
151
- // 添加新的状态来跟踪视频上传
152
- isUploadingVideo = false;
153
- /**
154
- * 是否需要上传简历
155
- */
156
- requireResume = false;
157
- // 添加新的状态和属性
158
- isPlayingAudio = false;
159
- audioUrl = null;
160
- audioElement = null;
161
- /**
162
- * 录制错误事件
163
- */
164
- recordingError;
165
- /**
166
- * 录制状态变化事件
167
- */
168
- recordingStatusChange;
169
- /**
170
- * 是否播放语音问题
171
- */
172
- enableVoice = true;
173
- /**
174
- * 是否显示题干内容
175
- * 1: 显示题干内容
176
- * 0: 不显示题干内容
177
- */
178
- displayContentStatus = "1";
179
- /**
180
- * 用户ID
181
- */
182
- userId = '';
183
- handleClose = () => {
184
- this.stopRecording();
185
- this.modalClosed.emit();
186
- };
187
- handleFileChange = async (event) => {
188
- const input = event.target;
189
- if (input.files && input.files.length > 0) {
190
- this.selectedFile = input.files[0];
191
- }
192
- };
193
- async uploadFile() {
194
- if (!this.selectedFile || this.uploadedFileInfo.length > 0)
195
- return;
196
- this.isUploading = true;
197
- try {
198
- const formData = new FormData();
199
- formData.append('file', this.selectedFile);
200
- const response = await fetch('https://pcm_api.ylzhaopin.com/external/v1/files/upload', {
201
- method: 'POST',
202
- headers: {
203
- 'authorization': 'Bearer ' + this.apiKey
204
- },
205
- body: formData
206
- });
207
- const result = await response.json();
208
- if (!response.ok) {
209
- throw new Error(result.message || '文件上传失败');
210
- }
211
- if (result) {
212
- this.uploadedFileInfo = [{
213
- cos_key: result.cos_key,
214
- filename: result.filename,
215
- ext: result.ext,
216
- presigned_url: result.presigned_url
217
- }];
218
- }
219
- }
220
- catch (error) {
221
- console.error('文件上传错误:', error);
222
- this.clearSelectedFile();
223
- alert(error instanceof Error ? error.message : '文件上传失败,请重试');
224
- }
225
- finally {
226
- this.isUploading = false;
227
- }
228
- }
229
- handleUploadClick = () => {
230
- const fileInput = this.hostElement.shadowRoot?.querySelector('.file-input');
231
- fileInput?.click();
232
- };
233
- clearSelectedFile = () => {
234
- this.selectedFile = null;
235
- this.uploadedFileInfo = [];
236
- const fileInput = this.hostElement.shadowRoot?.querySelector('.file-input');
237
- if (fileInput) {
238
- fileInput.value = '';
239
- }
240
- };
241
- async sendMessageToAPI(message) {
242
- this.isLoading = true;
243
- let answer = '';
244
- let llmText = ''; // 添加变量存储 LLMText
245
- const now = new Date();
246
- const time = `${now.getHours()}:${now.getMinutes().toString().padStart(2, '0')}`;
247
- // 如果消息为空但有文件,使用默认文本
248
- const queryText = message.trim() || (this.uploadedFileInfo.length > 0 ? '请分析这个文件' : '');
249
- // 获取上一条AI消息的回答内容
250
- const lastAIMessage = this.messages.length > 0 ? this.messages[this.messages.length - 1] : null;
251
- // 保存AI提问和用户回答
252
- if (lastAIMessage && this.conversationId && message !== "下一题") {
253
- this.saveAnswer(this.conversationId, lastAIMessage.answer, // AI的提问作为question
254
- queryText // 用户的输入作为answer
255
- );
256
- }
257
- // 检查是否是最后一题的"下一题"请求
258
- const isLastQuestion = (this.currentQuestionNumber >= this.totalQuestions) && message === "下一题";
259
- // 创建新的消息对象
260
- const newMessage = {
261
- id: `temp-${Date.now()}`, // 消息唯一标识
262
- time: time, // 消息时间
263
- query: queryText, // 用户输入的消息内容
264
- answer: '',
265
- isStreaming: true, // 是否正在流式输出
266
- conversation_id: this.conversationId, // 会话ID
267
- inputs: {}, // 输入参数
268
- status: "normal", // 消息状态
269
- error: null // 错误信息
270
- };
271
- // 设置当前流式消息
272
- this.currentStreamingMessage = newMessage;
273
- this.shouldAutoScroll = true;
274
- // 滚动到底部
275
- this.scrollToBottom();
276
- // 如果是最后一题,直接显示结束消息并完成面试
277
- if (isLastQuestion) {
278
- this.messages = [...this.messages, newMessage];
279
- this.currentStreamingMessage = null;
280
- this.isLoading = false;
281
- this.isInterviewComplete = true;
282
- await this.completeInterview();
283
- this.interviewComplete.emit({
284
- conversation_id: this.conversationId,
285
- total_questions: this.totalQuestions
286
- });
287
- return;
288
- }
289
- // 准备请求数据
290
- const requestData = {
291
- response_mode: 'streaming',
292
- conversation_id: this.conversationId,
293
- query: queryText,
294
- user: this.userId // 使用传入的 userId
295
- };
296
- requestData.inputs = {
297
- job_info: this.selectedJobCategory,
298
- dimensional_info: this.selectedDimensions.join(','),
299
- email: this.toEmail,
300
- display_content_status: this.displayContentStatus
301
- };
302
- // 如果有上传的文件,添加到inputs参数
303
- if (this.uploadedFileInfo.length > 0) {
304
- const fileUrls = this.uploadedFileInfo.map(fileInfo => fileInfo.cos_key).join(',');
305
- requestData.inputs.file_urls = fileUrls;
306
- }
307
- await index$1.sendSSERequest({
308
- url: `https://pcm_api.ylzhaopin.com/external/v1/chat/chat-messages`,
309
- method: 'POST',
310
- headers: {
311
- 'authorization': 'Bearer ' + this.apiKey
312
- },
313
- data: requestData,
314
- onMessage: (data) => {
315
- console.log('收到Stream数据:', data);
316
- if (data.conversation_id && !this.conversationId) {
317
- this.conversationId = data.conversation_id;
318
- this.updateUrlWithConversationId(data.conversation_id);
319
- }
320
- // 检查是否有 node_finished 事件和 LLMText
321
- if (data.event === 'node_finished' && data.data.inputs && data.data.inputs.LLMText) {
322
- llmText = data.data.inputs.LLMText;
323
- console.log('获取到 LLMText:', llmText);
324
- }
325
- if (data.event === 'message') {
326
- if (data.event === 'agent_message' || data.event === 'message') {
327
- if (data.answer) {
328
- answer += data.answer;
329
- const updatedMessage = {
330
- ...this.currentStreamingMessage,
331
- answer,
332
- isStreaming: true
333
- };
334
- this.currentStreamingMessage = updatedMessage;
335
- this.scrollToBottom();
336
- }
337
- }
338
- }
339
- if (data.event === "message_end") {
340
- this.streamComplete.emit({
341
- conversation_id: data.conversation_id || '',
342
- event: data.event,
343
- message_id: data.message_id,
344
- id: data.id,
345
- });
346
- }
347
- },
348
- onError: (error) => {
349
- console.error('发生错误:', error);
350
- alert(error instanceof Error ? error.message : '消息发送失败,请稍后再试');
351
- this.messages = [...this.messages, {
352
- ...newMessage,
353
- answer: '抱歉,发生了错误,请稍后再试。',
354
- error: error,
355
- isStreaming: false
356
- }];
357
- this.currentStreamingMessage = null;
358
- this.isLoading = false;
359
- },
360
- onComplete: async () => {
361
- console.log('请求完成');
362
- this.isLoading = false;
363
- // 获取最新的AI回复内容
364
- const latestAIMessage = this.currentStreamingMessage;
365
- // 更新消息列表
366
- this.messages = [...this.messages, this.currentStreamingMessage];
367
- this.currentStreamingMessage = null;
368
- // 如果是初始消息或"下一题"消息,增加题目计数
369
- if (message === "下一题" || this.currentQuestionNumber === 0) {
370
- this.currentQuestionNumber++;
371
- }
372
- console.log(this.currentQuestionNumber);
373
- console.log(message);
374
- if (latestAIMessage && latestAIMessage.answer) {
375
- // 优先使用 LLMText,如果没有则使用 answer
376
- const textForSynthesis = llmText || latestAIMessage.answer;
377
- if (textForSynthesis) {
378
- // 合成语音
379
- const audioUrl = await this.synthesizeAudio(textForSynthesis);
380
- if (this.enableVoice) {
381
- // 自动播放语音
382
- await this.playAudio(audioUrl);
383
- // 自动播放模式下,播放完成后立即开始等待录制
384
- this.startWaitingToRecord();
385
- }
386
- else {
387
- // 只保存音频URL,不自动播放
388
- this.audioUrl = audioUrl;
389
- // 非自动播放模式下,不立即开始等待录制
390
- }
391
- }
392
- }
393
- }
394
- });
395
- }
396
- // 添加保存答案的方法
397
- async saveAnswer(conversationId, question, answer) {
398
- try {
399
- await index$1.sendHttpRequest({
400
- url: 'https://pcm_api.ylzhaopin.com/agents/hr_competition/answer',
401
- method: 'POST',
402
- headers: {
403
- 'authorization': 'Bearer ' + this.apiKey
404
- },
405
- data: {
406
- conversation_id: conversationId,
407
- user: this.userId, // 使用传入的 userId
408
- question: question,
409
- answer: answer
410
- },
411
- });
412
- }
413
- catch (error) {
414
- console.error('保存答案失败:', error);
415
- }
416
- }
417
- // 监听滚动事件,用于控制聊天历史记录的自动滚动行为。
418
- handleScroll = () => {
419
- const chatHistory = this.hostElement.shadowRoot?.querySelector('.chat-history');
420
- if (!chatHistory)
421
- return;
422
- const { scrollTop, scrollHeight, clientHeight } = chatHistory;
423
- const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
424
- // 更新是否应该自动滚动的状态
425
- this.shouldAutoScroll = distanceFromBottom <= this.SCROLL_THRESHOLD;
426
- };
427
- scrollToBottom() {
428
- if (!this.shouldAutoScroll)
429
- return;
430
- const chatHistory = this.hostElement.shadowRoot?.querySelector('.chat-history');
431
- if (chatHistory && this.isOpen) {
432
- // 强制浏览器重新计算布局
433
- chatHistory.scrollTop = chatHistory.scrollHeight;
434
- }
435
- }
436
- // 添加 componentDidRender 生命周期方法,用于在组件渲染后滚动到底部
437
- componentDidRender() {
438
- if (this.isLoadingHistory || (this.shouldAutoScroll && this.isOpen)) {
439
- const chatHistory = this.hostElement.shadowRoot?.querySelector('.chat-history');
440
- if (chatHistory) {
441
- chatHistory.scrollTop = chatHistory.scrollHeight;
442
- }
443
- }
444
- }
445
- updateUrlWithConversationId(conversationId) {
446
- const urlParams = new URLSearchParams(window.location.search);
447
- if (!urlParams.get('conversation_id')) {
448
- const newUrl = new URL(window.location.href);
449
- newUrl.searchParams.set('conversation_id', conversationId);
450
- window.history.replaceState({}, '', newUrl);
451
- }
452
- }
453
- // 修改 loadHistoryMessages 方法
454
- async loadHistoryMessages() {
455
- if (!this.conversationId)
456
- return;
457
- this.isLoadingHistory = true;
458
- console.log('加载历史消息...');
459
- try {
460
- await index$1.sendHttpRequest({
461
- url: `https://pcm_api.ylzhaopin.com/external/v1/chat/messages`,
462
- method: 'GET',
463
- headers: {
464
- 'authorization': 'Bearer ' + this.apiKey
465
- },
466
- data: {
467
- conversation_id: this.conversationId,
468
- limit: 20
469
- },
470
- onMessage: (data) => {
471
- if (data.data) {
472
- const historyData = data.data || [];
473
- const formattedMessages = historyData.map(msg => {
474
- const time = new Date(msg.created_at * 1000);
475
- const hours = time.getHours().toString().padStart(2, '0');
476
- const minutes = time.getMinutes().toString().padStart(2, '0');
477
- const timeStr = `${hours}:${minutes}`;
478
- // 创建新的消息对象,不包含 inputs 字段
479
- const { inputs, ...msgWithoutInputs } = msg;
480
- return {
481
- ...msgWithoutInputs,
482
- time: timeStr,
483
- isStreaming: false,
484
- status: msg.status === 'error' ? 'error' : 'normal'
485
- };
486
- });
487
- this.messages = formattedMessages;
488
- this.isLoadingHistory = false;
489
- requestAnimationFrame(() => {
490
- this.shouldAutoScroll = true;
491
- this.scrollToBottom();
492
- });
493
- }
494
- else {
495
- this.isLoadingHistory = false;
496
- }
497
- },
498
- onError: (error) => {
499
- console.error('加载历史消息失败:', error);
500
- alert(error instanceof Error ? error.message : '加载历史消息失败,请刷新重试');
501
- this.isLoadingHistory = false;
502
- },
503
- onComplete: () => {
504
- this.isLoadingHistory = false;
505
- }
506
- });
507
- }
508
- catch (error) {
509
- console.error('加载历史消息失败:', error);
510
- alert(error instanceof Error ? error.message : '加载历史消息失败,请刷新重试');
511
- this.isLoadingHistory = false;
512
- }
513
- }
514
- // 修改 isOpen 的 watch 方法
515
- async handleIsOpenChange(newValue) {
516
- if (newValue) {
517
- if (this.conversationId) {
518
- await this.loadHistoryMessages();
519
- }
520
- }
521
- }
522
- handleJobCategorySelect = (category) => {
523
- this.selectedJobCategory = category;
524
- };
525
- handleDimensionSelect = (dimension) => {
526
- if (this.selectedDimensions.includes(dimension)) {
527
- this.selectedDimensions = this.selectedDimensions.filter(d => d !== dimension);
528
- }
529
- else {
530
- this.selectedDimensions = [...this.selectedDimensions, dimension];
531
- }
532
- };
533
- handleInitialSubmit = async () => {
534
- // 修改验证逻辑
535
- if (this.requireResume && !this.selectedFile) {
536
- alert('请上传简历');
537
- return;
538
- }
539
- if (!this.selectedJobCategory) {
540
- alert('请选择职能类别');
541
- return;
542
- }
543
- if (this.selectedDimensions.length === 0) {
544
- alert('请至少选择一个关注模块');
545
- return;
546
- }
547
- // 不再显示欢迎确认对话框,因为已经在组件打开时显示了
548
- // 直接询问用户是否准备好开始面试
549
- const confirmed = confirm('如果您已做好准备请点击"确定"开始面试。');
550
- if (!confirmed) {
551
- return;
552
- }
553
- // 修改文件上传逻辑
554
- if (this.requireResume) {
555
- await this.uploadFile();
556
- if (this.uploadedFileInfo.length === 0) {
557
- return;
558
- }
559
- }
560
- this.showInitialUpload = false;
561
- const message = `我是一名${this.selectedJobCategory},请您开始提问`;
562
- this.sendMessageToAPI(message);
563
- };
564
- // 开始等待录制
565
- startWaitingToRecord() {
566
- // 清除可能存在的计时器
567
- if (this.waitingTimer) {
568
- clearInterval(this.waitingTimer);
569
- }
570
- if (this.recordingTimer) {
571
- clearInterval(this.recordingTimer);
572
- }
573
- this.waitingToRecord = true;
574
- this.waitingTimeLeft = 10;
575
- this.waitingTimer = setInterval(() => {
576
- this.waitingTimeLeft--;
577
- if (this.waitingTimeLeft <= 0) {
578
- clearInterval(this.waitingTimer);
579
- this.waitingTimer = null;
580
- this.waitingToRecord = false;
581
- this.startRecording();
582
- }
583
- }, 1000);
584
- }
585
- // 开始录制视频
586
- async startRecording() {
587
- try {
588
- const stream = await navigator.mediaDevices.getUserMedia({
589
- audio: true,
590
- video: {
591
- width: { ideal: 1280 },
592
- height: { ideal: 720 },
593
- frameRate: { ideal: 30 }
594
- }
595
- });
596
- this.recordingStream = stream;
597
- this.showRecordingUI = true;
598
- this.showCountdownWarning = false;
599
- // 重置视频引用
600
- this.videoRef = null;
601
- // 确保视频元素获取到流
602
- this.setupVideoPreview(stream);
603
- // 检测浏览器支持的MIME类型
604
- const mimeType = this.getSupportedMimeType();
605
- // 创建MediaRecorder实例
606
- let mediaRecorder;
607
- try {
608
- mediaRecorder = new MediaRecorder(stream, {
609
- mimeType: mimeType
610
- });
611
- }
612
- catch (e) {
613
- // 如果指定MIME类型失败,尝试使用默认设置
614
- console.warn('指定的MIME类型不受支持,使用默认设置:', e);
615
- try {
616
- mediaRecorder = new MediaRecorder(stream);
617
- }
618
- catch (recorderError) {
619
- // 通知父组件录制器创建失败
620
- this.recordingError.emit({
621
- type: 'recorder_creation_failed',
622
- message: '无法创建媒体录制器,您的浏览器可能不支持此功能',
623
- details: recorderError
624
- });
625
- this.showRecordingUI = false;
626
- return;
627
- }
628
- }
629
- this.mediaRecorder = mediaRecorder;
630
- const chunks = [];
631
- mediaRecorder.ondataavailable = (event) => {
632
- if (event.data.size > 0) {
633
- chunks.push(event.data);
634
- }
635
- };
636
- mediaRecorder.onerror = (event) => {
637
- // 通知父组件录制过程中发生错误
638
- this.recordingError.emit({
639
- type: 'recording_error',
640
- message: '录制过程中发生错误',
641
- details: event
642
- });
643
- this.stopRecording();
644
- };
645
- mediaRecorder.onstop = () => {
646
- try {
647
- // 根据实际使用的MIME类型创建Blob
648
- const blobType = mimeType || 'video/mp4';
649
- const blob = new Blob(chunks, { type: blobType });
650
- if (blob.size === 0) {
651
- // 通知父组件录制的视频为空
652
- this.recordingError.emit({
653
- type: 'empty_recording',
654
- message: '录制的视频为空'
655
- });
656
- this.showRecordingUI = false;
657
- return;
658
- }
659
- this.recordedBlob = blob;
660
- // 通知父组件录制已完成
661
- this.recordingStatusChange.emit({
662
- status: 'stopped',
663
- details: {
664
- duration: Math.floor((Date.now() - this.recordingStartTime) / 1000),
665
- size: blob.size,
666
- type: blob.type
667
- }
668
- });
669
- this.uploadRecordedVideo();
670
- }
671
- catch (error) {
672
- // 通知父组件处理录制视频时出错
673
- this.recordingError.emit({
674
- type: 'processing_error',
675
- message: '处理录制视频时出错',
676
- details: error
677
- });
678
- this.showRecordingUI = false;
679
- }
680
- };
681
- // 开始录制
682
- try {
683
- mediaRecorder.start();
684
- this.isRecording = true;
685
- this.recordingStartTime = Date.now();
686
- this.recordingTimeLeft = this.maxRecordingTime;
687
- // 通知父组件录制已开始
688
- this.recordingStatusChange.emit({
689
- status: 'started',
690
- details: {
691
- maxDuration: this.maxRecordingTime,
692
- mimeType: mediaRecorder.mimeType
693
- }
694
- });
695
- }
696
- catch (startError) {
697
- // 通知父组件开始录制失败
698
- this.recordingError.emit({
699
- type: 'start_failed',
700
- message: '开始录制失败,请检查您的设备权限',
701
- details: startError
702
- });
703
- this.showRecordingUI = false;
704
- return;
705
- }
706
- // 设置录制计时器
707
- this.recordingTimer = setInterval(() => {
708
- const elapsedTime = Math.floor((Date.now() - this.recordingStartTime) / 1000);
709
- this.recordingTimeLeft = Math.max(0, this.maxRecordingTime - elapsedTime);
710
- // 检查是否需要显示倒计时警告
711
- if (this.recordingTimeLeft <= this.countdownWarningTime && !this.showCountdownWarning) {
712
- this.showCountdownWarning = true;
713
- }
714
- // 时间到自动停止录制
715
- if (this.recordingTimeLeft <= 0) {
716
- this.stopRecording();
717
- }
718
- }, 1000);
719
- }
720
- catch (error) {
721
- console.error('无法访问摄像头或麦克风:', error);
722
- // 通知父组件无法访问媒体设备
723
- this.recordingError.emit({
724
- type: 'media_access_failed',
725
- message: '无法访问摄像头或麦克风,请确保已授予权限',
726
- details: error
727
- });
728
- this.showRecordingUI = false;
729
- }
730
- }
731
- // 添加新方法来设置视频预览
732
- setupVideoPreview(stream) {
733
- // 延迟执行以确保DOM已更新
734
- setTimeout(() => {
735
- const videoElement = this.hostElement.shadowRoot?.querySelector('video');
736
- if (videoElement && stream) {
737
- // 先尝试使用标准方法
738
- try {
739
- videoElement.srcObject = stream;
740
- videoElement.play().catch(err => {
741
- console.error('视频播放失败:', err);
742
- });
743
- }
744
- catch (e) {
745
- console.warn('设置srcObject失败,尝试替代方法:', e);
746
- // 对于不支持srcObject的旧浏览器,使用URL.createObjectURL
747
- try {
748
- // 使用类型断言解决TypeScript错误
749
- const objectUrl = URL.createObjectURL(stream);
750
- videoElement.src = objectUrl;
751
- // 确保在视频元素不再使用时释放URL
752
- videoElement.onended = () => {
753
- URL.revokeObjectURL(objectUrl);
754
- };
755
- }
756
- catch (urlError) {
757
- console.error('创建对象URL失败:', urlError);
758
- }
759
- }
760
- }
761
- else {
762
- console.warn('未找到视频元素或媒体流无效');
763
- }
764
- }, 100);
765
- }
766
- // 添加一个新方法来检测浏览器支持的MIME类型
767
- getSupportedMimeType() {
768
- // 按优先级排列的MIME类型列表
769
- const mimeTypes = [
770
- 'video/webm;codecs=vp8,opus',
771
- 'video/webm;codecs=vp9,opus',
772
- 'video/webm',
773
- 'video/mp4',
774
- 'video/mp4;codecs=h264,aac',
775
- '' // 空字符串表示使用浏览器默认值
776
- ];
777
- // 检查MediaRecorder是否可用
778
- if (!window.MediaRecorder) {
779
- console.warn('MediaRecorder API不可用');
780
- return '';
781
- }
782
- // 检查每种MIME类型是否受支持
783
- for (const type of mimeTypes) {
784
- if (!type)
785
- return ''; // 如果是空字符串,直接返回
786
- try {
787
- if (MediaRecorder.isTypeSupported(type)) {
788
- console.log('使用支持的MIME类型:', type);
789
- return type;
790
- }
791
- }
792
- catch (e) {
793
- console.warn(`检查MIME类型支持时出错 ${type}:`, e);
794
- }
795
- }
796
- // 如果没有找到支持的类型,返回空字符串
797
- console.warn('没有找到支持的MIME类型,将使用浏览器默认值');
798
- return '';
799
- }
800
- // 停止录制
801
- stopRecording() {
802
- if (this.mediaRecorder && this.isRecording) {
803
- this.mediaRecorder.stop();
804
- this.isRecording = false;
805
- // 清理计时器
806
- if (this.recordingTimer) {
807
- clearInterval(this.recordingTimer);
808
- this.recordingTimer = null;
809
- }
810
- // 停止并释放媒体流
811
- if (this.recordingStream) {
812
- this.recordingStream.getTracks().forEach(track => track.stop());
813
- this.recordingStream = null;
814
- }
815
- // 清理视频引用
816
- this.videoRef = null;
817
- }
818
- }
819
- // 上传录制的视频
820
- async uploadRecordedVideo() {
821
- if (!this.recordedBlob)
822
- return;
823
- try {
824
- this.isUploadingVideo = true; // 开始上传时设置状态
825
- this.showRecordingUI = false; // 隐藏视频预览
826
- // 根据Blob类型确定文件扩展名
827
- const fileExtension = this.recordedBlob.type.includes('webm') ? 'webm' : 'mp4';
828
- const fileName = `answer.${fileExtension}`;
829
- const formData = new FormData();
830
- formData.append('file', this.recordedBlob, fileName);
831
- const response = await fetch('https://pcm_api.ylzhaopin.com/external/v1/files/upload', {
832
- method: 'POST',
833
- headers: {
834
- 'authorization': 'Bearer ' + this.apiKey
835
- },
836
- body: formData
837
- });
838
- const result = await response.json();
839
- console.log('视频上传结果:', result);
840
- if (result && result.cos_key) {
841
- // 保存视频答案
842
- await this.saveVideoAnswer(result.cos_key);
843
- // 发送"下一题"请求
844
- this.sendNextQuestion();
845
- }
846
- else {
847
- throw new Error('视频上传失败');
848
- }
849
- }
850
- catch (error) {
851
- console.error('视频上传错误:', error);
852
- // 通知父组件视频上传失败
853
- this.recordingError.emit({
854
- type: 'upload_failed',
855
- message: '视频上传失败',
856
- details: error
857
- });
858
- }
859
- finally {
860
- this.isUploadingVideo = false; // 上传完成后重置状态
861
- this.showRecordingUI = false;
862
- this.recordedBlob = null;
863
- }
864
- }
865
- // 保存视频答案
866
- async saveVideoAnswer(cosKey) {
867
- if (!this.conversationId)
868
- return;
869
- try {
870
- const lastAIMessage = this.messages.length > 0 ? this.messages[this.messages.length - 1] : null;
871
- if (!lastAIMessage)
872
- return;
873
- await index$1.sendHttpRequest({
874
- url: 'https://pcm_api.ylzhaopin.com/agents/hr_competition/answer',
875
- method: 'POST',
876
- headers: {
877
- 'authorization': 'Bearer ' + this.apiKey
878
- },
879
- data: {
880
- conversation_id: this.conversationId,
881
- user: this.userId, // 使用传入的 userId
882
- question: lastAIMessage.answer,
883
- file_url: cosKey
884
- },
885
- });
886
- }
887
- catch (error) {
888
- console.error('保存视频答案失败:', error);
889
- }
890
- }
891
- // 发送"下一题"请求
892
- sendNextQuestion() {
893
- this.sendMessageToAPI("下一题");
894
- }
895
- /**
896
- * 发送面试完成请求
897
- */
898
- async completeInterview() {
899
- if (!this.conversationId)
900
- return;
901
- try {
902
- await index$1.sendHttpRequest({
903
- url: `https://pcm_api.ylzhaopin.com/agents/hr_competition/${this.conversationId}/end`,
904
- method: 'POST',
905
- headers: {
906
- 'authorization': 'Bearer ' + this.apiKey
907
- },
908
- });
909
- }
910
- catch (error) {
911
- console.error('发送面试完成请求失败:', error);
912
- }
913
- }
914
- // 添加TTS合成音频的方法
915
- async synthesizeAudio(text) {
916
- try {
917
- const response = await fetch('https://pcm_api.ylzhaopin.com/external/v1/tts/synthesize_audio', {
918
- method: 'POST',
919
- headers: {
920
- 'Content-Type': 'application/json',
921
- 'authorization': 'Bearer ' + this.apiKey
922
- },
923
- body: JSON.stringify({ text })
924
- });
925
- if (!response.ok) {
926
- throw new Error('语音合成失败');
927
- }
928
- // 获取音频数据并创建Blob URL
929
- const audioBlob = await response.blob();
930
- return URL.createObjectURL(audioBlob);
931
- }
932
- catch (error) {
933
- console.error('语音合成错误:', error);
934
- throw error;
935
- }
936
- }
937
- // 播放音频的方法
938
- playAudio(audioUrl) {
939
- return new Promise((resolve) => {
940
- this.isPlayingAudio = true;
941
- this.audioUrl = audioUrl;
942
- // 创建音频元素
943
- if (!this.audioElement) {
944
- this.audioElement = new Audio();
945
- }
946
- this.audioElement.src = audioUrl;
947
- this.audioElement.onended = () => {
948
- this.isPlayingAudio = false;
949
- this.audioUrl = null;
950
- resolve();
951
- };
952
- this.audioElement.onerror = () => {
953
- console.error('音频播放错误');
954
- this.isPlayingAudio = false;
955
- this.audioUrl = null;
956
- resolve();
957
- };
958
- this.audioElement.play().catch(error => {
959
- console.error('音频播放失败:', error);
960
- this.isPlayingAudio = false;
961
- this.audioUrl = null;
962
- resolve();
963
- });
964
- });
965
- }
966
- // 修改 componentDidLoad 生命周期方法,确保组件卸载时释放资源
967
- disconnectedCallback() {
968
- // 释放音频资源
969
- if (this.audioElement) {
970
- this.audioElement.pause();
971
- this.audioElement.src = '';
972
- this.audioElement = null;
973
- }
974
- // 释放 Blob URL
975
- if (this.audioUrl) {
976
- URL.revokeObjectURL(this.audioUrl);
977
- this.audioUrl = null;
978
- }
979
- // 清理其他计时器
980
- if (this.waitingTimer) {
981
- clearInterval(this.waitingTimer);
982
- this.waitingTimer = null;
983
- }
984
- if (this.recordingTimer) {
985
- clearInterval(this.recordingTimer);
986
- this.recordingTimer = null;
987
- }
988
- // 停止录制
989
- this.stopRecording();
990
- }
991
- // 修改手动播放音频的方法
992
- handlePlayAudio = async () => {
993
- if (this.audioUrl) {
994
- await this.playAudio(this.audioUrl);
995
- // 手动播放完成后开始等待录制
996
- this.startWaitingToRecord();
997
- }
998
- };
999
- render() {
1000
- if (!this.isOpen)
1001
- return null;
1002
- const modalStyle = {
1003
- zIndex: String(this.zIndex)
1004
- };
1005
- const containerClass = {
1006
- 'modal-container': true,
1007
- 'fullscreen': this.fullscreen
1008
- };
1009
- const overlayClass = {
1010
- 'modal-overlay': true,
1011
- 'fullscreen-overlay': this.fullscreen
1012
- };
1013
- const renderVideoPreview = () => (index.h("div", { class: "video-preview" }, index.h("video", { autoPlay: true, playsInline: true, muted: true, style: { transform: 'scaleX(-1)' }, ref: (el) => {
1014
- if (el && this.recordingStream && !this.videoRef) {
1015
- this.videoRef = el;
1016
- // 不在这里设置srcObject,而是使用setupVideoPreview方法
1017
- }
1018
- } }), index.h("div", { class: {
1019
- 'recording-status': true,
1020
- 'warning': this.showCountdownWarning
1021
- } }, index.h("span", { class: "recording-dot" }), index.h("span", null, "\u5F55\u5236\u4E2D ", Math.floor(this.recordingTimeLeft / 60), ":", (this.recordingTimeLeft % 60).toString().padStart(2, '0'), this.showCountdownWarning && ` (即将自动完成)`))));
1022
- // 渲染占位符状态信息
1023
- const renderPlaceholderStatus = () => {
1024
- // 正在播放音频
1025
- if (this.isPlayingAudio) {
1026
- return (index.h("div", { class: "placeholder-status" }, index.h("p", null, "\u6B63\u5728\u64AD\u653E\u95EE\u9898\uFF0C\u8BF7\u542C\u5B8C\u540E\u51C6\u5907\u56DE\u7B54...")));
1027
- }
1028
- // 正在上传视频
1029
- if (this.isUploadingVideo) {
1030
- return (index.h("div", { class: "placeholder-status" }, index.h("p", null, "\u6B63\u5728\u4E0A\u4F20\u89C6\u9891\uFF0C\u8BF7\u7A0D\u5019...")));
1031
- }
1032
- // 正在加载或等待AI回复
1033
- if (this.isLoading || this.currentStreamingMessage) {
1034
- return (index.h("div", { class: "placeholder-status" }, index.h("p", null, "\u8BF7\u7B49\u5F85\u9898\u76EE...")));
1035
- }
1036
- // 等待开始录制
1037
- if (this.waitingToRecord) {
1038
- return (index.h("div", { class: "placeholder-status" }, index.h("p", null, "\u8BF7\u51C6\u5907\u597D\uFF0C", this.waitingTimeLeft, "\u79D2\u540E\u5C06\u5F00\u59CB\u5F55\u5236\u60A8\u7684\u56DE\u7B54...")));
1039
- }
1040
- // 添加默认状态
1041
- return (index.h("div", { class: "placeholder-status default-status" }, index.h("p", null, "\u51C6\u5907\u4E2D...")));
1042
- };
1043
- return (index.h("div", { class: overlayClass, style: modalStyle }, index.h("div", { class: containerClass }, this.isShowHeader && (index.h("div", { class: "modal-header" }, index.h("div", { class: "header-left" }, this.icon && index.h("img", { src: this.icon, class: "header-icon", alt: "\u5E94\u7528\u56FE\u6807" }), index.h("div", null, this.modalTitle)), this.isNeedClose && (index.h("button", { class: "close-button", onClick: this.handleClose }, index.h("span", null, "\u00D7"))))), this.showInitialUpload ? (index.h("div", { class: "initial-upload" }, index.h("div", { class: "upload-section" }, this.requireResume && (index.h(index.h.Fragment, null, index.h("h3", null, "\u5F00\u59CB\u524D\uFF0C\u8BF7\u4E0A\u4F20\u60A8\u7684\u7B80\u5386"), index.h("div", { class: "upload-area", onClick: this.handleUploadClick }, this.selectedFile ? (index.h("div", { class: "file-info" }, index.h("span", null, this.selectedFile.name), index.h("button", { class: "remove-file", onClick: (e) => {
1044
- e.stopPropagation();
1045
- this.clearSelectedFile();
1046
- } }, "\u00D7"))) : (index.h("div", { class: "upload-placeholder" }, index.h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", width: "48", height: "48" }, index.h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M12 4v16m0-16l-4 4m4-4l4 4" })), index.h("p", null, "\u70B9\u51FB\u4E0A\u4F20\u7B80\u5386"), index.h("p", { class: "upload-hint" }, "\u652F\u6301 txt\u3001 markdown\u3001 pdf\u3001 docx\u3001 md \u683C\u5F0F")))))), index.h("div", { class: "category-select" }, index.h("h3", null, "\u8BF7\u9009\u62E9\u60A8\u7684\u804C\u80FD\u7C7B\u522B\uFF08\u5355\u9009\uFF09"), index.h("div", { class: "category-options" }, this.jobCategories.map(category => (index.h("button", { class: {
1047
- 'category-button': true,
1048
- 'selected': this.selectedJobCategory === category
1049
- }, onClick: () => this.handleJobCategorySelect(category) }, category))))), index.h("div", { class: "dimension-select" }, index.h("h3", null, "\u8BF7\u9009\u62E9\u5173\u6CE8\u7684\u6A21\u5757\uFF08\u53EF\u591A\u9009\uFF09"), index.h("div", { class: "dimension-options" }, this.dimensions.map(dimension => (index.h("button", { class: {
1050
- 'dimension-button': true,
1051
- 'selected': this.selectedDimensions.includes(dimension)
1052
- }, onClick: () => this.handleDimensionSelect(dimension) }, dimension))))), index.h("button", { class: "submit-button", disabled: (this.requireResume && !this.selectedFile) ||
1053
- !this.selectedJobCategory ||
1054
- this.selectedDimensions.length === 0 ||
1055
- (this.requireResume && this.isUploading), onClick: this.handleInitialSubmit }, this.requireResume && this.isUploading ? '上传中...' : '开始面试')), this.requireResume && (index.h("input", { type: "file", class: "file-input", onChange: this.handleFileChange, accept: ".pdf,.doc,.docx,.txt" })))) : (index.h("div", { style: { height: '100%' } }, index.h("div", { class: "chat-history", onScroll: this.handleScroll }, this.isLoadingHistory ? (index.h("div", { class: "loading-container" }, index.h("div", { class: "loading-spinner" }), index.h("p", null, "\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\u4E2D..."))) : (index.h("div", null, this.messages.map((message) => (index.h("div", { id: `message_${message.id}`, key: message.id }, index.h("pcm-chat-message", { message: message, onMessageChange: (event) => {
1056
- const updatedMessages = this.messages.map(msg => msg.id === message.id ? { ...msg, ...event.detail } : msg);
1057
- this.messages = updatedMessages;
1058
- } })))), this.currentStreamingMessage && (index.h("div", { id: `message_${this.currentStreamingMessage.id}` }, index.h("pcm-chat-message", { message: this.currentStreamingMessage }))), this.messages.length === 0 && !this.currentStreamingMessage && (index.h("div", { class: "empty-state" }, index.h("p", null, "\u8BF7\u4E0A\u4F20\u7B80\u5386\u5F00\u59CB\u9762\u8BD5")))))), index.h("div", { class: "recording-section" }, index.h("div", { class: "recording-container" }, index.h("div", { class: "video-area" }, this.showRecordingUI ? (renderVideoPreview()) : (index.h("div", { class: "video-preview placeholder" }, renderPlaceholderStatus()))), index.h("div", { class: "progress-container" }, index.h("div", { class: "progress-bar-container" }, index.h("div", { class: "progress-bar", style: {
1059
- width: `${Math.max(0, this.currentQuestionNumber - 1) / this.totalQuestions * 100}%`
1060
- } })), index.h("div", { class: "progress-text" }, "\u5DF2\u5B8C\u6210", Math.max(0, this.currentQuestionNumber - 1), "/", this.totalQuestions)), index.h("div", { class: "recording-controls" }, this.showRecordingUI ? (index.h("button", { class: "stop-recording-button", onClick: () => this.stopRecording() }, "\u5B8C\u6210\u672C\u9898\u56DE\u7B54")) : (index.h("div", { class: "waiting-message" }, (() => {
1061
- // 显示播放按钮(当不自动播放且有音频URL时)
1062
- if (!this.enableVoice && this.audioUrl && !this.isPlayingAudio) {
1063
- return (index.h("div", { class: "play-audio-container", onClick: this.handlePlayAudio }, index.h("p", null, index.h("svg", { viewBox: "0 0 24 24", width: "24", height: "24", fill: "currentColor", style: { verticalAlign: 'middle', marginRight: '8px' } }, index.h("path", { d: "M8 5v14l11-7z" })), index.h("span", { style: { verticalAlign: 'middle' } }, "\u64AD\u653E\u9898\u76EE"))));
1064
- }
1065
- // 其他状态下显示禁用的"完成回答"按钮
1066
- return (index.h("button", { class: "stop-recording-button disabled", disabled: true }, "\u5B8C\u6210\u56DE\u7B54"));
1067
- })()))))))))));
1068
- }
1069
- static get watchers() { return {
1070
- "isOpen": ["handleIsOpenChange"]
1071
- }; }
1072
- };
1073
- ChatHRModal.style = pcmHrChatModalCss;
1074
-
1075
- exports.pcm_hr_chat_modal = ChatHRModal;
1076
- //# sourceMappingURL=pcm-hr-chat-modal.entry.cjs.js.map
1077
-
1078
- //# sourceMappingURL=pcm-hr-chat-modal.cjs.entry.js.map