@arbidocs/blocks 0.5.107 → 0.5.108

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 (341) hide show
  1. package/dist/{ConnectedCallDock-TDYRHPAK.js → ConnectedCallDock-DV4FU376.js} +8 -8
  2. package/dist/{ConnectedCallDock-TDYRHPAK.js.map → ConnectedCallDock-DV4FU376.js.map} +1 -1
  3. package/dist/{ConnectedCallDock-H3WWFGXF.cjs → ConnectedCallDock-EM2VVGZK.cjs} +10 -10
  4. package/dist/{ConnectedCallDock-H3WWFGXF.cjs.map → ConnectedCallDock-EM2VVGZK.cjs.map} +1 -1
  5. package/dist/{ConnectedCallRoom-55O5OUN7.cjs → ConnectedCallRoom-7WEPEWUJ.cjs} +9 -9
  6. package/dist/{ConnectedCallRoom-55O5OUN7.cjs.map → ConnectedCallRoom-7WEPEWUJ.cjs.map} +1 -1
  7. package/dist/{ConnectedCallRoom-Z6F6WCLH.js → ConnectedCallRoom-RYTZP34O.js} +8 -8
  8. package/dist/{ConnectedCallRoom-Z6F6WCLH.js.map → ConnectedCallRoom-RYTZP34O.js.map} +1 -1
  9. package/dist/{ConnectedNativeDocxComparison-NVZRVNOS.js → ConnectedNativeDocxComparison-2NDXXZLQ.js} +15 -15
  10. package/dist/{ConnectedNativeDocxComparison-NVZRVNOS.js.map → ConnectedNativeDocxComparison-2NDXXZLQ.js.map} +1 -1
  11. package/dist/{ConnectedNativeDocxComparison-DGBZJ4XA.cjs → ConnectedNativeDocxComparison-7HOY3AVI.cjs} +16 -16
  12. package/dist/{ConnectedNativeDocxComparison-DGBZJ4XA.cjs.map → ConnectedNativeDocxComparison-7HOY3AVI.cjs.map} +1 -1
  13. package/dist/{ConnectedNativeDocxEditor-R4WINUUB.cjs → ConnectedNativeDocxEditor-CUWDPHXN.cjs} +16 -16
  14. package/dist/{ConnectedNativeDocxEditor-R4WINUUB.cjs.map → ConnectedNativeDocxEditor-CUWDPHXN.cjs.map} +1 -1
  15. package/dist/{ConnectedNativeDocxEditor-RSVBN7DY.js → ConnectedNativeDocxEditor-SPZB6Y27.js} +14 -14
  16. package/dist/{ConnectedNativeDocxEditor-RSVBN7DY.js.map → ConnectedNativeDocxEditor-SPZB6Y27.js.map} +1 -1
  17. package/dist/{ConnectedProductionSetDialog-2DFBXE2X.js → ConnectedProductionSetDialog-N3EDG3SU.js} +4 -4
  18. package/dist/{ConnectedProductionSetDialog-2DFBXE2X.js.map → ConnectedProductionSetDialog-N3EDG3SU.js.map} +1 -1
  19. package/dist/{ConnectedProductionSetDialog-KYDPQ7SG.cjs → ConnectedProductionSetDialog-XAEFEN3N.cjs} +4 -4
  20. package/dist/{ConnectedProductionSetDialog-KYDPQ7SG.cjs.map → ConnectedProductionSetDialog-XAEFEN3N.cjs.map} +1 -1
  21. package/dist/{ConnectedStreamedWordArtifact-HLJ5IIOU.js → ConnectedStreamedWordArtifact-6LTGEAJO.js} +15 -15
  22. package/dist/ConnectedStreamedWordArtifact-6LTGEAJO.js.map +1 -0
  23. package/dist/{ConnectedStreamedWordArtifact-JQ34AOUU.cjs → ConnectedStreamedWordArtifact-HNCRKZTV.cjs} +16 -16
  24. package/dist/ConnectedStreamedWordArtifact-HNCRKZTV.cjs.map +1 -0
  25. package/dist/{DocumentReviewCard-7JSXRJZ2.js → DocumentReviewCard-RJD3CO32.js} +8 -8
  26. package/dist/{DocumentReviewCard-7JSXRJZ2.js.map → DocumentReviewCard-RJD3CO32.js.map} +1 -1
  27. package/dist/{DocumentReviewCard-C4HVT3WM.cjs → DocumentReviewCard-VON7LOOY.cjs} +8 -8
  28. package/dist/{DocumentReviewCard-C4HVT3WM.cjs.map → DocumentReviewCard-VON7LOOY.cjs.map} +1 -1
  29. package/dist/{FilesViewerDialog-IMQTZWKD.js → FilesViewerDialog-7SJGY72Q.js} +21 -21
  30. package/dist/{FilesViewerDialog-IMQTZWKD.js.map → FilesViewerDialog-7SJGY72Q.js.map} +1 -1
  31. package/dist/{FilesViewerDialog-UP7GFHYN.cjs → FilesViewerDialog-VIPTC6OT.cjs} +27 -27
  32. package/dist/{FilesViewerDialog-UP7GFHYN.cjs.map → FilesViewerDialog-VIPTC6OT.cjs.map} +1 -1
  33. package/dist/{RechartsRenderer-GIRCW7DB.cjs → RechartsRenderer-THES2SE5.cjs} +6 -5
  34. package/dist/RechartsRenderer-THES2SE5.cjs.map +1 -0
  35. package/dist/{RechartsRenderer-23OF4H6X.js → RechartsRenderer-XIZLV6GH.js} +6 -5
  36. package/dist/RechartsRenderer-XIZLV6GH.js.map +1 -0
  37. package/dist/{WorkspaceTableView-PJM47B5F.cjs → WorkspaceTableView-FNME7TL6.cjs} +9 -9
  38. package/dist/{WorkspaceTableView-PJM47B5F.cjs.map → WorkspaceTableView-FNME7TL6.cjs.map} +1 -1
  39. package/dist/{WorkspaceTableView-YDA7A4UJ.js → WorkspaceTableView-XQ6J2JKI.js} +7 -7
  40. package/dist/{WorkspaceTableView-YDA7A4UJ.js.map → WorkspaceTableView-XQ6J2JKI.js.map} +1 -1
  41. package/dist/agents/index.cjs +202 -202
  42. package/dist/agents/index.js +8 -8
  43. package/dist/{ai-ADS6YSS2.cjs → ai-3SAMEYBB.cjs} +20 -20
  44. package/dist/ai-3SAMEYBB.cjs.map +1 -0
  45. package/dist/{ai-KHWTGBT5.js → ai-OTQWSYVK.js} +12 -12
  46. package/dist/ai-OTQWSYVK.js.map +1 -0
  47. package/dist/calls/index.cjs +57 -57
  48. package/dist/calls/index.js +9 -9
  49. package/dist/chat/index.cjs +3 -3
  50. package/dist/chat/index.js +3 -3
  51. package/dist/{chunk-O27K7KTZ.cjs → chunk-2TBQBF4Y.cjs} +5 -5
  52. package/dist/{chunk-O27K7KTZ.cjs.map → chunk-2TBQBF4Y.cjs.map} +1 -1
  53. package/dist/{chunk-YJWAGXYM.js → chunk-4ZA2RSQH.js} +3 -3
  54. package/dist/{chunk-YJWAGXYM.js.map → chunk-4ZA2RSQH.js.map} +1 -1
  55. package/dist/{chunk-6A4GNM4T.cjs → chunk-5D2ZINOJ.cjs} +16 -16
  56. package/dist/{chunk-6A4GNM4T.cjs.map → chunk-5D2ZINOJ.cjs.map} +1 -1
  57. package/dist/{chunk-BFVCYBND.cjs → chunk-5HXQOM3I.cjs} +5 -5
  58. package/dist/{chunk-BFVCYBND.cjs.map → chunk-5HXQOM3I.cjs.map} +1 -1
  59. package/dist/{chunk-GSD5U553.cjs → chunk-5KKT65WU.cjs} +3 -3
  60. package/dist/{chunk-GSD5U553.cjs.map → chunk-5KKT65WU.cjs.map} +1 -1
  61. package/dist/{chunk-AVGHTUPE.js → chunk-5PWLXLHY.js} +5 -5
  62. package/dist/chunk-5PWLXLHY.js.map +1 -0
  63. package/dist/{chunk-BBWLFK3C.js → chunk-5XIMUQ63.js} +3 -3
  64. package/dist/{chunk-BBWLFK3C.js.map → chunk-5XIMUQ63.js.map} +1 -1
  65. package/dist/{chunk-AHMRYY7J.cjs → chunk-6DAJXQZZ.cjs} +13 -13
  66. package/dist/{chunk-AHMRYY7J.cjs.map → chunk-6DAJXQZZ.cjs.map} +1 -1
  67. package/dist/{chunk-3PV25ICC.js → chunk-6KKVJ2V6.js} +3 -3
  68. package/dist/{chunk-3PV25ICC.js.map → chunk-6KKVJ2V6.js.map} +1 -1
  69. package/dist/{chunk-OLAYDOPC.js → chunk-6LUHNFEE.js} +6 -6
  70. package/dist/{chunk-OLAYDOPC.js.map → chunk-6LUHNFEE.js.map} +1 -1
  71. package/dist/{chunk-FSXQHEFQ.cjs → chunk-7CDV7Y4U.cjs} +25 -25
  72. package/dist/{chunk-FSXQHEFQ.cjs.map → chunk-7CDV7Y4U.cjs.map} +1 -1
  73. package/dist/{chunk-OAIOOVFN.cjs → chunk-7SJXYEZI.cjs} +36 -36
  74. package/dist/chunk-7SJXYEZI.cjs.map +1 -0
  75. package/dist/{chunk-LFYKB7KF.cjs → chunk-7V6APLF7.cjs} +19 -19
  76. package/dist/{chunk-LFYKB7KF.cjs.map → chunk-7V6APLF7.cjs.map} +1 -1
  77. package/dist/{chunk-4WMGFGYP.cjs → chunk-AJZ66VJC.cjs} +4 -4
  78. package/dist/{chunk-4WMGFGYP.cjs.map → chunk-AJZ66VJC.cjs.map} +1 -1
  79. package/dist/{chunk-KCP42W67.cjs → chunk-ARGQ5YZ5.cjs} +3 -3
  80. package/dist/{chunk-KCP42W67.cjs.map → chunk-ARGQ5YZ5.cjs.map} +1 -1
  81. package/dist/{chunk-BH6IKGZ3.js → chunk-AUAHGQY6.js} +5 -5
  82. package/dist/{chunk-BH6IKGZ3.js.map → chunk-AUAHGQY6.js.map} +1 -1
  83. package/dist/{chunk-KIXFXCHL.js → chunk-CEOCA7ID.js} +3 -3
  84. package/dist/{chunk-KIXFXCHL.js.map → chunk-CEOCA7ID.js.map} +1 -1
  85. package/dist/{chunk-TNKPQ3EF.js → chunk-CIRSOYGK.js} +12 -12
  86. package/dist/{chunk-TNKPQ3EF.js.map → chunk-CIRSOYGK.js.map} +1 -1
  87. package/dist/{chunk-BAUZUIHB.js → chunk-COL2TJGO.js} +83 -83
  88. package/dist/chunk-COL2TJGO.js.map +1 -0
  89. package/dist/{chunk-XHN5KOI2.cjs → chunk-DRLD4P2C.cjs} +8 -8
  90. package/dist/{chunk-XHN5KOI2.cjs.map → chunk-DRLD4P2C.cjs.map} +1 -1
  91. package/dist/{chunk-5AC52BHL.cjs → chunk-DYPFNQTQ.cjs} +140 -140
  92. package/dist/chunk-DYPFNQTQ.cjs.map +1 -0
  93. package/dist/{chunk-SCLGUHLU.cjs → chunk-E7TGSOPR.cjs} +4 -4
  94. package/dist/{chunk-SCLGUHLU.cjs.map → chunk-E7TGSOPR.cjs.map} +1 -1
  95. package/dist/{chunk-CRLFLMTK.cjs → chunk-EOXA4VJH.cjs} +3 -3
  96. package/dist/{chunk-CRLFLMTK.cjs.map → chunk-EOXA4VJH.cjs.map} +1 -1
  97. package/dist/{chunk-DBKZANTC.js → chunk-GA47YVW5.js} +3 -3
  98. package/dist/{chunk-DBKZANTC.js.map → chunk-GA47YVW5.js.map} +1 -1
  99. package/dist/{chunk-RCZRI72J.cjs → chunk-GMODJT4N.cjs} +4 -4
  100. package/dist/{chunk-RCZRI72J.cjs.map → chunk-GMODJT4N.cjs.map} +1 -1
  101. package/dist/{chunk-22KAKJ3W.cjs → chunk-GS5MFUPC.cjs} +4 -4
  102. package/dist/{chunk-22KAKJ3W.cjs.map → chunk-GS5MFUPC.cjs.map} +1 -1
  103. package/dist/{chunk-62OMGQHS.js → chunk-H6W3VXHU.js} +3 -3
  104. package/dist/{chunk-62OMGQHS.js.map → chunk-H6W3VXHU.js.map} +1 -1
  105. package/dist/{chunk-DX3Z2COD.cjs → chunk-HUYKO2ES.cjs} +3 -3
  106. package/dist/{chunk-DX3Z2COD.cjs.map → chunk-HUYKO2ES.cjs.map} +1 -1
  107. package/dist/{chunk-U5F5P4YU.js → chunk-IK234EFX.js} +12 -12
  108. package/dist/{chunk-U5F5P4YU.js.map → chunk-IK234EFX.js.map} +1 -1
  109. package/dist/{chunk-UQ2OBHMV.js → chunk-JUGEFTRC.js} +1010 -1010
  110. package/dist/chunk-JUGEFTRC.js.map +1 -0
  111. package/dist/{chunk-CHQMVIHR.js → chunk-LPGBRMYS.js} +36 -36
  112. package/dist/chunk-LPGBRMYS.js.map +1 -0
  113. package/dist/{chunk-D35EHOK5.cjs → chunk-M23W7IID.cjs} +12 -12
  114. package/dist/chunk-M23W7IID.cjs.map +1 -0
  115. package/dist/{chunk-FPOKWML2.js → chunk-MIVNNQO3.js} +21 -4
  116. package/dist/chunk-MIVNNQO3.js.map +1 -0
  117. package/dist/{chunk-WI3BVKEC.cjs → chunk-MNFI66H5.cjs} +5 -5
  118. package/dist/{chunk-WI3BVKEC.cjs.map → chunk-MNFI66H5.cjs.map} +1 -1
  119. package/dist/{chunk-T5ZMG6MC.js → chunk-NF4M7PUB.js} +3 -3
  120. package/dist/{chunk-T5ZMG6MC.js.map → chunk-NF4M7PUB.js.map} +1 -1
  121. package/dist/{chunk-ZNY5W3VX.js → chunk-NPSFPBIB.js} +3 -3
  122. package/dist/{chunk-ZNY5W3VX.js.map → chunk-NPSFPBIB.js.map} +1 -1
  123. package/dist/{chunk-LYW5A35J.js → chunk-OMIYTUVR.js} +12 -12
  124. package/dist/{chunk-LYW5A35J.js.map → chunk-OMIYTUVR.js.map} +1 -1
  125. package/dist/{chunk-ZMVF7EYJ.cjs → chunk-PBPO54KZ.cjs} +21 -3
  126. package/dist/chunk-PBPO54KZ.cjs.map +1 -0
  127. package/dist/{chunk-7V4OCZS6.cjs → chunk-RA3EGF6K.cjs} +6 -6
  128. package/dist/{chunk-7V4OCZS6.cjs.map → chunk-RA3EGF6K.cjs.map} +1 -1
  129. package/dist/{chunk-PL7O3RJJ.cjs → chunk-RKBAY7GZ.cjs} +3 -3
  130. package/dist/{chunk-PL7O3RJJ.cjs.map → chunk-RKBAY7GZ.cjs.map} +1 -1
  131. package/dist/{chunk-J3E5CSSC.js → chunk-RKFWBIKS.js} +3 -3
  132. package/dist/{chunk-J3E5CSSC.js.map → chunk-RKFWBIKS.js.map} +1 -1
  133. package/dist/{chunk-YLKHTBYA.js → chunk-RRP7MQAO.js} +3 -3
  134. package/dist/{chunk-YLKHTBYA.js.map → chunk-RRP7MQAO.js.map} +1 -1
  135. package/dist/{chunk-U6LQ6Z3P.cjs → chunk-SP3IKIPA.cjs} +6 -6
  136. package/dist/{chunk-U6LQ6Z3P.cjs.map → chunk-SP3IKIPA.cjs.map} +1 -1
  137. package/dist/{chunk-GX75YCWJ.cjs → chunk-SQUOXOY3.cjs} +560 -560
  138. package/dist/chunk-SQUOXOY3.cjs.map +1 -0
  139. package/dist/{chunk-6ZYOUT73.js → chunk-T5LORFHL.js} +7 -7
  140. package/dist/{chunk-6ZYOUT73.js.map → chunk-T5LORFHL.js.map} +1 -1
  141. package/dist/{chunk-45OU2UAA.cjs → chunk-TEMTKBBL.cjs} +10 -10
  142. package/dist/{chunk-45OU2UAA.cjs.map → chunk-TEMTKBBL.cjs.map} +1 -1
  143. package/dist/{chunk-PHDUN4SG.cjs → chunk-THCMGAFX.cjs} +41 -41
  144. package/dist/{chunk-PHDUN4SG.cjs.map → chunk-THCMGAFX.cjs.map} +1 -1
  145. package/dist/{chunk-HTGEAQOA.js → chunk-TOMDMKOD.js} +7 -7
  146. package/dist/{chunk-HTGEAQOA.js.map → chunk-TOMDMKOD.js.map} +1 -1
  147. package/dist/{chunk-WJHRWXWJ.js → chunk-TXIAF4PX.js} +3 -3
  148. package/dist/{chunk-WJHRWXWJ.js.map → chunk-TXIAF4PX.js.map} +1 -1
  149. package/dist/{chunk-6M5NAAWX.js → chunk-UCSEE52D.js} +3 -3
  150. package/dist/{chunk-6M5NAAWX.js.map → chunk-UCSEE52D.js.map} +1 -1
  151. package/dist/{chunk-I7CJDF3V.cjs → chunk-UHMP6S7M.cjs} +104 -103
  152. package/dist/chunk-UHMP6S7M.cjs.map +1 -0
  153. package/dist/{chunk-PGCKTXYH.js → chunk-UHUI6A36.js} +3 -3
  154. package/dist/{chunk-PGCKTXYH.js.map → chunk-UHUI6A36.js.map} +1 -1
  155. package/dist/{chunk-6KQVTMRL.cjs → chunk-UOGXKHTJ.cjs} +3 -3
  156. package/dist/{chunk-6KQVTMRL.cjs.map → chunk-UOGXKHTJ.cjs.map} +1 -1
  157. package/dist/{chunk-7S7YVBFK.js → chunk-VDSDG4FR.js} +3 -3
  158. package/dist/{chunk-7S7YVBFK.js.map → chunk-VDSDG4FR.js.map} +1 -1
  159. package/dist/{chunk-3GFQNJYE.js → chunk-VGWCHX6G.js} +104 -103
  160. package/dist/chunk-VGWCHX6G.js.map +1 -0
  161. package/dist/{chunk-VY3HTT37.js → chunk-VUH547VQ.js} +3 -3
  162. package/dist/{chunk-VY3HTT37.js.map → chunk-VUH547VQ.js.map} +1 -1
  163. package/dist/{chunk-AZCMKGDG.js → chunk-W37PFYFL.js} +4 -4
  164. package/dist/{chunk-AZCMKGDG.js.map → chunk-W37PFYFL.js.map} +1 -1
  165. package/dist/{chunk-JGUM4EXE.cjs → chunk-W3IX3GZS.cjs} +8 -8
  166. package/dist/{chunk-JGUM4EXE.cjs.map → chunk-W3IX3GZS.cjs.map} +1 -1
  167. package/dist/{chunk-UCA4JLLJ.cjs → chunk-X7GT57KW.cjs} +3 -3
  168. package/dist/{chunk-UCA4JLLJ.cjs.map → chunk-X7GT57KW.cjs.map} +1 -1
  169. package/dist/{chunk-IXL2KJOZ.cjs → chunk-XBEGD33U.cjs} +40 -40
  170. package/dist/{chunk-IXL2KJOZ.cjs.map → chunk-XBEGD33U.cjs.map} +1 -1
  171. package/dist/{chunk-FDNIK5YS.cjs → chunk-XBKIFGBB.cjs} +8 -8
  172. package/dist/{chunk-FDNIK5YS.cjs.map → chunk-XBKIFGBB.cjs.map} +1 -1
  173. package/dist/{chunk-HZE5MHQO.js → chunk-XT7HKQ4H.js} +3 -3
  174. package/dist/{chunk-HZE5MHQO.js.map → chunk-XT7HKQ4H.js.map} +1 -1
  175. package/dist/{chunk-M3OK6KGR.js → chunk-ZGQKED5T.js} +3 -3
  176. package/dist/{chunk-M3OK6KGR.js.map → chunk-ZGQKED5T.js.map} +1 -1
  177. package/dist/{chunk-NWBCZ4PA.js → chunk-ZKBYG6MN.js} +4 -4
  178. package/dist/{chunk-NWBCZ4PA.js.map → chunk-ZKBYG6MN.js.map} +1 -1
  179. package/dist/collaboration/index.cjs +1 -1
  180. package/dist/collaboration/index.js +1 -1
  181. package/dist/connect/docx.cjs +19 -19
  182. package/dist/connect/docx.js +15 -15
  183. package/dist/connect/index.cjs +762 -762
  184. package/dist/connect/index.js +63 -63
  185. package/dist/connect/viewer.cjs +14 -14
  186. package/dist/connect/viewer.js +13 -13
  187. package/dist/connection/dashboards/ConnectedDashboardsPage.d.ts +6 -0
  188. package/dist/dashboards/chartModel.d.ts +12 -0
  189. package/dist/dashboards/index.cjs +113 -42
  190. package/dist/dashboards/index.cjs.map +1 -1
  191. package/dist/dashboards/index.js +95 -24
  192. package/dist/dashboards/index.js.map +1 -1
  193. package/dist/embed/index.cjs +29 -29
  194. package/dist/embed/index.js +23 -23
  195. package/dist/facts/index.cjs +43 -43
  196. package/dist/facts/index.js +2 -2
  197. package/dist/files/index.cjs +1 -1
  198. package/dist/files/index.js +1 -1
  199. package/dist/home/index.cjs +31 -31
  200. package/dist/home/index.js +3 -3
  201. package/dist/index.cjs +383 -383
  202. package/dist/index.js +21 -21
  203. package/dist/settings/index.cjs +153 -153
  204. package/dist/settings/index.js +4 -4
  205. package/dist/shell/index.cjs +36 -36
  206. package/dist/shell/index.js +4 -4
  207. package/dist/site/index.cjs +5 -5
  208. package/dist/site/index.js +1 -1
  209. package/dist/studio/index.cjs +19 -19
  210. package/dist/studio/index.js +14 -14
  211. package/dist/styles.css +1 -1
  212. package/dist/workflow-canvas/index.cjs +33 -33
  213. package/dist/workflow-canvas/index.js +22 -22
  214. package/node_modules/@arbidocs/coedit/package.json +2 -2
  215. package/node_modules/@docx-editor.dev/core/dist/automation.cjs +1 -1
  216. package/node_modules/@docx-editor.dev/core/dist/automation.d.ts +60 -13
  217. package/node_modules/@docx-editor.dev/core/dist/automation.js +1 -1
  218. package/node_modules/@docx-editor.dev/core/dist/binding.cjs +1 -1
  219. package/node_modules/@docx-editor.dev/core/dist/binding.d.ts +21 -13
  220. package/node_modules/@docx-editor.dev/core/dist/binding.js +1 -1
  221. package/node_modules/@docx-editor.dev/core/dist/{index-Bsg7oIKP.d.ts → chrome-print-CAs51cpq.d.ts} +31 -21
  222. package/node_modules/@docx-editor.dev/core/dist/{chunk-VDNNLG6X.js → chunk-236O4YQW.js} +1 -1
  223. package/node_modules/@docx-editor.dev/core/dist/chunk-4HENIAFG.js +1 -0
  224. package/node_modules/@docx-editor.dev/core/dist/{chunk-SRPILEWV.cjs → chunk-6JPL6ZAS.cjs} +5 -5
  225. package/node_modules/@docx-editor.dev/core/dist/{chunk-2RC2EPND.js → chunk-6ZQP4HQ7.js} +1 -1
  226. package/node_modules/@docx-editor.dev/core/dist/{chunk-ZUHOMK52.cjs → chunk-7WQ7AAHW.cjs} +1 -1
  227. package/node_modules/@docx-editor.dev/core/dist/{chunk-Z6WBFXJU.js → chunk-BCXH4BXB.js} +3 -3
  228. package/node_modules/@docx-editor.dev/core/dist/{chunk-QMLCVPCD.cjs → chunk-BY2ACZZ6.cjs} +1 -1
  229. package/node_modules/@docx-editor.dev/core/dist/chunk-CAJEXXVG.cjs +4 -0
  230. package/node_modules/@docx-editor.dev/core/dist/chunk-CPWLFPWC.js +30 -0
  231. package/node_modules/@docx-editor.dev/core/dist/{chunk-6APGXLBB.cjs → chunk-D7ZMDMPX.cjs} +2 -2
  232. package/node_modules/@docx-editor.dev/core/dist/chunk-DOHF6FA7.cjs +30 -0
  233. package/node_modules/@docx-editor.dev/core/dist/chunk-DVX32P2P.js +27 -0
  234. package/node_modules/@docx-editor.dev/core/dist/{chunk-6EYYL7FL.js → chunk-E4LOMDNL.js} +1 -1
  235. package/node_modules/@docx-editor.dev/core/dist/chunk-EKLM7NWB.cjs +7 -0
  236. package/node_modules/@docx-editor.dev/core/dist/chunk-G3K63G7P.cjs +27 -0
  237. package/node_modules/@docx-editor.dev/core/dist/{chunk-5CSUYANV.js → chunk-G4HOFIKJ.js} +1 -1
  238. package/node_modules/@docx-editor.dev/core/dist/{chunk-K5U2PLDP.cjs → chunk-JLVBDYVQ.cjs} +1 -1
  239. package/node_modules/@docx-editor.dev/core/dist/chunk-LQX36Z5P.js +7 -0
  240. package/node_modules/@docx-editor.dev/core/dist/{chunk-TSLBZ26X.js → chunk-LSZ6KPKK.js} +1 -1
  241. package/node_modules/@docx-editor.dev/core/dist/{chunk-O2AL2HSJ.cjs → chunk-MM5QBMTE.cjs} +1 -1
  242. package/node_modules/@docx-editor.dev/core/dist/{chunk-ETTO6GK3.js → chunk-OMCG63ZF.js} +1 -1
  243. package/node_modules/@docx-editor.dev/core/dist/chunk-OSFOJTHU.cjs +18 -0
  244. package/node_modules/@docx-editor.dev/core/dist/{chunk-E7BQA6UP.js → chunk-RCLZXP3B.js} +1 -1
  245. package/node_modules/@docx-editor.dev/core/dist/chunk-T6K2QYNZ.cjs +2 -0
  246. package/node_modules/@docx-editor.dev/core/dist/chunk-TRGAVV2Y.js +4 -0
  247. package/node_modules/@docx-editor.dev/core/dist/chunk-U4JZVSOE.js +2 -0
  248. package/node_modules/@docx-editor.dev/core/dist/{chunk-2GO5T2JU.cjs → chunk-UHHYFIDT.cjs} +1 -1
  249. package/node_modules/@docx-editor.dev/core/dist/chunk-V3N4SOAK.cjs +1 -0
  250. package/node_modules/@docx-editor.dev/core/dist/{chunk-6AWEKBLB.cjs → chunk-W35WTWAO.cjs} +1 -1
  251. package/node_modules/@docx-editor.dev/core/dist/chunk-YQ5G6LC7.js +18 -0
  252. package/node_modules/@docx-editor.dev/core/dist/collaboration/replication.cjs +1 -1
  253. package/node_modules/@docx-editor.dev/core/dist/collaboration/replication.d.ts +5 -5
  254. package/node_modules/@docx-editor.dev/core/dist/collaboration/replication.js +1 -1
  255. package/node_modules/@docx-editor.dev/core/dist/collaboration.d.ts +4 -4
  256. package/node_modules/@docx-editor.dev/core/dist/contracts/document.d.ts +3 -3
  257. package/node_modules/@docx-editor.dev/core/dist/contracts/editor.d.ts +9 -10
  258. package/node_modules/@docx-editor.dev/core/dist/contracts/interaction.d.ts +2 -2
  259. package/node_modules/@docx-editor.dev/core/dist/contracts/modules.cjs +1 -1
  260. package/node_modules/@docx-editor.dev/core/dist/contracts/modules.d.ts +33 -9
  261. package/node_modules/@docx-editor.dev/core/dist/contracts/modules.js +1 -1
  262. package/node_modules/@docx-editor.dev/core/dist/contracts/types.d.ts +2 -2
  263. package/node_modules/@docx-editor.dev/core/dist/{custom-node-writes-cvL5rhcD.d.ts → custom-node-writes-Bn6uciLb.d.ts} +2 -2
  264. package/node_modules/@docx-editor.dev/core/dist/diagnostic-context-B8M79cfK.d.ts +30 -0
  265. package/node_modules/@docx-editor.dev/core/dist/diagnostic-hints-BeVjfNOv.d.ts +113 -0
  266. package/node_modules/@docx-editor.dev/core/dist/{document-protection-BAGVGqaP.d.ts → document-protection-CUb55pl_.d.ts} +2 -2
  267. package/node_modules/@docx-editor.dev/core/dist/{editor-Ci7IZGmG.d.ts → editor-CZQZmTIU.d.ts} +386 -9
  268. package/node_modules/@docx-editor.dev/core/dist/{editor-scope-OHPwVQ5l.d.ts → editor-scope-7o9xweiJ.d.ts} +1 -1
  269. package/node_modules/@docx-editor.dev/core/dist/editor.cjs +1 -1
  270. package/node_modules/@docx-editor.dev/core/dist/editor.d.ts +54 -33
  271. package/node_modules/@docx-editor.dev/core/dist/editor.js +1 -1
  272. package/node_modules/@docx-editor.dev/core/dist/export.cjs +2 -2
  273. package/node_modules/@docx-editor.dev/core/dist/export.d.ts +13 -14
  274. package/node_modules/@docx-editor.dev/core/dist/export.js +1 -1
  275. package/node_modules/@docx-editor.dev/core/dist/{font-resolver-BVQB14qT.d.ts → font-resolver-lSBLirLR.d.ts} +3 -3
  276. package/node_modules/@docx-editor.dev/core/dist/{headless-document-view-qH331moE.d.ts → headless-document-view-pp3q7wIh.d.ts} +2 -2
  277. package/node_modules/@docx-editor.dev/core/dist/{image-resources-BN-hUgCn.d.ts → image-resources-tXLQShoC.d.ts} +2 -2
  278. package/node_modules/@docx-editor.dev/core/dist/index.cjs +1 -1
  279. package/node_modules/@docx-editor.dev/core/dist/index.d.ts +65 -20
  280. package/node_modules/@docx-editor.dev/core/dist/index.js +1 -1
  281. package/node_modules/@docx-editor.dev/core/dist/{layout-shaping-BEyuiQOk.d.ts → layout-shaping-BD-dF_rn.d.ts} +1 -1
  282. package/node_modules/@docx-editor.dev/core/dist/layout.cjs +1 -1
  283. package/node_modules/@docx-editor.dev/core/dist/layout.d.ts +17 -20
  284. package/node_modules/@docx-editor.dev/core/dist/layout.js +1 -1
  285. package/node_modules/@docx-editor.dev/core/dist/{note-properties-4Y9o6Gyk.d.ts → note-properties-qQUjO1GQ.d.ts} +3 -3
  286. package/node_modules/@docx-editor.dev/core/dist/{omml-equation-CuzBL5DD.d.ts → omml-equation-BUbU0utM.d.ts} +1 -1
  287. package/node_modules/@docx-editor.dev/core/dist/output.cjs +1 -1
  288. package/node_modules/@docx-editor.dev/core/dist/output.d.ts +8 -9
  289. package/node_modules/@docx-editor.dev/core/dist/output.js +1 -1
  290. package/node_modules/@docx-editor.dev/core/dist/{protocol-Bqk4Izjc.d.ts → protocol-DYzzz2ej.d.ts} +2 -2
  291. package/node_modules/@docx-editor.dev/core/dist/{review-paragraph-order-DT-n6ouC.d.ts → review-paragraph-order-CN_0l2Cb.d.ts} +1 -1
  292. package/node_modules/@docx-editor.dev/core/dist/{semantic-cell-selection-D9D1KSXq.d.ts → semantic-cell-selection-eYag3P47.d.ts} +4 -211
  293. package/node_modules/@docx-editor.dev/core/dist/{semantic-interaction-CIC-PooQ.d.ts → semantic-interaction-kR_QB845.d.ts} +1 -1
  294. package/node_modules/@docx-editor.dev/core/dist/{semantic-paint-BME37OMv.d.ts → semantic-paint-ZayFF3tD.d.ts} +17 -5
  295. package/node_modules/@docx-editor.dev/core/dist/{semantic-record-queries-CBlZqZNa.d.ts → semantic-record-queries-Bsgth6lv.d.ts} +1 -1
  296. package/node_modules/@docx-editor.dev/core/dist/{semantic-records-4B3WwFAi.d.ts → semantic-records-DQmumPgL.d.ts} +1279 -828
  297. package/node_modules/@docx-editor.dev/core/dist/store.cjs +1 -1
  298. package/node_modules/@docx-editor.dev/core/dist/store.d.ts +1466 -1464
  299. package/node_modules/@docx-editor.dev/core/dist/store.js +1 -1
  300. package/node_modules/@docx-editor.dev/core/dist/{tree-package-fragment-BeeKxDw8.d.ts → tree-package-store-CovjNARF.d.ts} +83 -83
  301. package/node_modules/@docx-editor.dev/core/dist/{tree-session-contract-CVbmlMlq.d.ts → tree-session-contract-CZVAGl51.d.ts} +6 -6
  302. package/node_modules/@docx-editor.dev/core/dist/{types-barrel-D3xCgQX6.d.ts → types-barrel-DuC5QBNC.d.ts} +548 -527
  303. package/node_modules/@docx-editor.dev/core/package.json +3 -3
  304. package/node_modules/@docx-editor.dev/react/dist/index.js +3 -3
  305. package/node_modules/@docx-editor.dev/react/dist/index.mjs +3 -3
  306. package/node_modules/@docx-editor.dev/react/package.json +4 -4
  307. package/package.json +8 -8
  308. package/dist/ConnectedStreamedWordArtifact-HLJ5IIOU.js.map +0 -1
  309. package/dist/ConnectedStreamedWordArtifact-JQ34AOUU.cjs.map +0 -1
  310. package/dist/RechartsRenderer-23OF4H6X.js.map +0 -1
  311. package/dist/RechartsRenderer-GIRCW7DB.cjs.map +0 -1
  312. package/dist/ai-ADS6YSS2.cjs.map +0 -1
  313. package/dist/ai-KHWTGBT5.js.map +0 -1
  314. package/dist/chunk-3GFQNJYE.js.map +0 -1
  315. package/dist/chunk-5AC52BHL.cjs.map +0 -1
  316. package/dist/chunk-AVGHTUPE.js.map +0 -1
  317. package/dist/chunk-BAUZUIHB.js.map +0 -1
  318. package/dist/chunk-CHQMVIHR.js.map +0 -1
  319. package/dist/chunk-D35EHOK5.cjs.map +0 -1
  320. package/dist/chunk-FPOKWML2.js.map +0 -1
  321. package/dist/chunk-GX75YCWJ.cjs.map +0 -1
  322. package/dist/chunk-I7CJDF3V.cjs.map +0 -1
  323. package/dist/chunk-OAIOOVFN.cjs.map +0 -1
  324. package/dist/chunk-UQ2OBHMV.js.map +0 -1
  325. package/dist/chunk-ZMVF7EYJ.cjs.map +0 -1
  326. package/node_modules/@docx-editor.dev/core/dist/chunk-4GBTELJV.cjs +0 -28
  327. package/node_modules/@docx-editor.dev/core/dist/chunk-4R3TEXMN.js +0 -18
  328. package/node_modules/@docx-editor.dev/core/dist/chunk-EO4C5QJW.cjs +0 -2
  329. package/node_modules/@docx-editor.dev/core/dist/chunk-FIJZIQXA.cjs +0 -18
  330. package/node_modules/@docx-editor.dev/core/dist/chunk-FRXAOVTB.js +0 -28
  331. package/node_modules/@docx-editor.dev/core/dist/chunk-J6BVWIDB.js +0 -2
  332. package/node_modules/@docx-editor.dev/core/dist/chunk-JGNVNT2A.cjs +0 -2
  333. package/node_modules/@docx-editor.dev/core/dist/chunk-KKIJG3C6.js +0 -2
  334. package/node_modules/@docx-editor.dev/core/dist/chunk-LWDSDCMO.js +0 -27
  335. package/node_modules/@docx-editor.dev/core/dist/chunk-NBCVVZOM.cjs +0 -1
  336. package/node_modules/@docx-editor.dev/core/dist/chunk-O5INAWZQ.js +0 -1
  337. package/node_modules/@docx-editor.dev/core/dist/chunk-RCHS4ZEH.cjs +0 -27
  338. package/node_modules/@docx-editor.dev/core/dist/chunk-VHAVHCQY.js +0 -7
  339. package/node_modules/@docx-editor.dev/core/dist/chunk-VX43VBAX.cjs +0 -7
  340. package/node_modules/@docx-editor.dev/core/dist/review-pane-D9jm8DcY.d.ts +0 -128
  341. package/node_modules/@docx-editor.dev/core/dist/revision-projection-DZ4kKVev.d.ts +0 -242
@@ -1,8 +1,8 @@
1
1
  "use client";
2
- import { useMyCallStatus, useSpeakingIds } from './chunk-CHQMVIHR.js';
3
- import { TranscriptEntry } from './chunk-T5ZMG6MC.js';
4
- import { SaveTranscriptToThread, RecordingsList, RecordingCard } from './chunk-BAUZUIHB.js';
5
- import { contactsCallTitle, HeaderAction, isCallLive, formatCallDuration, CallUserPill } from './chunk-3GFQNJYE.js';
2
+ import { useMyCallStatus, useSpeakingIds } from './chunk-LPGBRMYS.js';
3
+ import { TranscriptEntry } from './chunk-NF4M7PUB.js';
4
+ import { RecordingsList, SaveTranscriptToThread, RecordingCard } from './chunk-COL2TJGO.js';
5
+ import { isCallLive, formatCallDuration, contactsCallTitle, HeaderAction, CallUserPill } from './chunk-VGWCHX6G.js';
6
6
  import { useConnectedConversations } from './chunk-TG4IICWO.js';
7
7
  import { useConnectedWorkspace } from './chunk-OWHO2TO3.js';
8
8
  import { DmInviteChip } from './chunk-2S3LT5KE.js';
@@ -10,558 +10,380 @@ import { EmptyState } from './chunk-P6GHJSLR.js';
10
10
  import { usePendingRecording } from './chunk-PFF5IZGS.js';
11
11
  import { transcriptRuns, ConversationFilter, ProvisionalTranscriptLine } from './chunk-H773F7OR.js';
12
12
  import { useBlocksEnvironment, requestCalendarComposer } from './chunk-YUHSDOPW.js';
13
- import { useCallsEnabled, useCanHostCalls, useCallSession, useCreateContact, useWorkspaces, usePeople, usePeopleSearch, useArbi, threadLiveCallId, openCall, startThreadCall, chatLiveCallId, startChatCall, userDisplayName, startDmCall, startContactsCall, useConversationItems, hasRecordingTranscript, threadTranscriptRef, useRecordingWindows, supersedeLiveTranscript, useCall, useWorkspaceUsers, threadTranscriptLines, useArbiSessionState, useConversationData, chatTranscriptRef, supersededTranscriptLines, sortTranscriptMessages, DateSeparator, useRevokeCallInviteLink, useCallInviteLinks } from '@arbidocs/react';
14
- import { useState, useEffect, useMemo, useRef, Fragment as Fragment$1 } from 'react';
15
- import { Phone, UserPlus, CalendarPlus, Disc, PhoneOff, AudioLines, Captions, CaptionsOff, Mail, X } from 'lucide-react';
16
- import { Tooltip, TooltipTrigger, Button, cn, TooltipContent, isAgent, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Input, DialogFooter, getUserDisplayName, Checkbox } from '@arbidocs/react/ui';
17
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
13
+ import { useArbi, useCall, useCallSession, openCall, usePeople, hasRecordingTranscript, chatTranscriptRef, useRecordingWindows, supersededTranscriptLines, sortTranscriptMessages, DateSeparator, useCallsEnabled, useCanHostCalls, userDisplayName, startDmCall, useCreateContact, useWorkspaces, usePeopleSearch, chatLiveCallId, startChatCall, startContactsCall, useConversationItems, threadTranscriptRef, supersedeLiveTranscript, threadLiveCallId, startThreadCall, useWorkspaceUsers, threadTranscriptLines, useArbiSessionState, useConversationData, useRevokeCallInviteLink, useCallInviteLinks } from '@arbidocs/react';
14
+ import { useState, useMemo, useRef, useEffect, Fragment as Fragment$1 } from 'react';
15
+ import { Phone, PhoneOff, AudioLines, Captions, CaptionsOff, UserPlus, CalendarPlus, Disc, Mail, X } from 'lucide-react';
16
+ import { Button, getUserDisplayName, Tooltip, TooltipTrigger, cn, TooltipContent, isAgent, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Input, DialogFooter, Checkbox } from '@arbidocs/react/ui';
17
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
18
18
  import { chats, ArbiApiError, refusalReason } from '@arbidocs/sdk/browser';
19
19
 
20
- function StartCallButton(props) {
21
- const enabled = useCallsEnabled();
22
- const canHost = useCanHostCalls();
23
- return enabled && (canHost || props.isLive) ? /* @__PURE__ */ jsx(EnabledStartCallButton, { ...props }) : null;
24
- }
25
- function EnabledStartCallButton({
26
- onStart,
27
- isLive = false,
28
- label = "Start call",
29
- testId = "start-call-button"
30
- }) {
31
- const [isStarting, setIsStarting] = useState(false);
32
- const inCall = useCallSession((s) => s.active !== null);
33
- const text = inCall ? "Already in a call" : isLive ? "Join call" : label;
34
- return /* @__PURE__ */ jsxs(Tooltip, { children: [
35
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
36
- Button,
37
- {
38
- type: "button",
39
- variant: "ghost",
40
- size: "icon",
41
- "aria-label": isLive ? "Join call" : label,
42
- "data-testid": testId,
43
- "data-live": isLive || void 0,
44
- disabled: isStarting || inCall,
45
- onClick: () => {
46
- setIsStarting(true);
47
- void onStart().finally(() => setIsStarting(false));
48
- },
49
- className: cn(
50
- "text-muted-foreground hover:text-foreground",
51
- isLive && "text-green-text hover:text-green-text"
52
- ),
53
- children: /* @__PURE__ */ jsx(Phone, { className: cn("size-5", isLive && "animate-pulse") })
54
- }
55
- ) }),
56
- /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: text }) })
57
- ] });
58
- }
59
- var INVITE_LINK_HINT = "will get an invitation to create an ARBI account and join";
60
- var NO_KEY_REASON = "Cannot receive encrypted calls yet";
61
- var NO_EMAIL_REASON = "No e-mail address to send a link to";
62
- function contactName(contact) {
63
- return contact.card?.name?.trim() || [contact.card?.given_name, contact.card?.family_name].filter(Boolean).join(" ") || (contact.user ? getUserDisplayName(contact.user) : "") || contactEmail(contact) || "Contact";
64
- }
65
- function contactEmail(contact) {
66
- return contact.email ?? contact.user?.email ?? contact.card?.emails?.[0]?.email ?? null;
67
- }
68
- function callContactOf(contact) {
69
- if (!contact.external_id || contact.status === "failed") return null;
70
- if (contact.user && isAgent(contact.user)) return null;
71
- return {
72
- contactExtId: contact.external_id,
73
- user: contact.user ?? null,
74
- name: contactName(contact),
75
- email: contactEmail(contact)
76
- };
77
- }
78
- function userCandidate(user, section, status, contactExtId = null) {
79
- const ringable = status !== "ring" || !!user.encryption_public_key;
80
- return {
81
- id: user.external_id,
82
- name: getUserDisplayName(user) || user.email,
83
- email: user.email ?? null,
84
- section,
85
- status: ringable ? status : "unavailable",
86
- reason: ringable ? void 0 : NO_KEY_REASON,
87
- user,
88
- contactExtId
89
- };
90
- }
91
- function buildCallCandidates(opts) {
92
- const seen = /* @__PURE__ */ new Set();
93
- const out = [];
94
- const add = (c) => {
95
- if (seen.has(c.id)) return;
96
- seen.add(c.id);
97
- out.push(c);
98
- };
99
- const people = (users) => users.filter((u) => !isAgent(u) && u.external_id !== opts.me);
100
- if (opts.scope !== "contacts") {
101
- for (const u of people(opts.members)) add(userCandidate(u, "members", "ring"));
102
- return out;
103
- }
104
- const contacts = (opts.contacts ?? []).filter((c) => !(c.user && isAgent(c.user)));
105
- const contactOf = new Map(
106
- contacts.filter((c) => c.user).map((c) => [c.user.external_id, c.contactExtId])
107
- );
108
- for (const u of people(opts.members)) {
109
- const contactId = contactOf.get(u.external_id) ?? opts.contactIdOf?.(u) ?? null;
110
- add(userCandidate(u, "members", contactId ? "ring" : "add-contact", contactId));
111
- }
112
- for (const c of contacts) {
113
- if (c.user) {
114
- if (c.user.external_id === opts.me) continue;
115
- add(userCandidate(c.user, "contacts", "ring", c.contactExtId));
116
- continue;
117
- }
118
- const status = c.email ? "invite" : "unavailable";
119
- add({
120
- id: c.contactExtId,
121
- name: c.name,
122
- email: c.email,
123
- section: "contacts",
124
- status,
125
- reason: c.email ? INVITE_LINK_HINT : NO_EMAIL_REASON,
126
- user: null,
127
- contactExtId: c.contactExtId
128
- });
129
- }
130
- return out;
131
- }
132
- function isPickable(c) {
133
- return c.status === "ring" || c.status === "invite";
134
- }
135
- function matchesQuery(c, query) {
136
- const q = query.trim().toLowerCase();
137
- if (!q) return true;
138
- return c.name.toLowerCase().includes(q) || (c.email ?? "").toLowerCase().includes(q);
139
- }
140
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
141
- var COPY = {
142
- workspace: {
143
- title: "Call thread members",
144
- description: "Only members of this thread\u2019s workspace can join. Pick who to ring \u2014 or nobody, and share the call from the thread.",
145
- action: "Start call"
146
- },
147
- chat: {
148
- title: "Call the group",
149
- description: "The people you pick are rung.",
150
- action: "Start call"
151
- },
152
- contacts: {
153
- title: "New call",
154
- description: "Your contacts with an ARBI account are rung. Others get an invitation to create an account and join.",
155
- action: "Start call"
156
- }
157
- };
158
- function CandidateRow({
159
- candidate,
160
- checked,
161
- onToggle,
162
- onAddContact,
163
- adding
20
+ function ChatCallCard({
21
+ callExtId,
22
+ senderName,
23
+ latest,
24
+ name,
25
+ time
164
26
  }) {
165
- const pickable = isPickable(candidate);
166
- const hint = candidate.status === "invite" ? candidate.reason : candidate.status === "add-contact" ? "Not in your contacts" : void 0;
167
- const row = /* @__PURE__ */ jsxs(
168
- "li",
27
+ const arbi = useArbi();
28
+ const { data: call } = useCall(latest ? callExtId : null);
29
+ const liveNow = useCallSession((s) => callExtId in s.liveCalls);
30
+ const inThisCall = useCallSession((s) => s.active?.callExtId === callExtId);
31
+ const inAnotherCall = useCallSession((s) => !!s.active && s.active.callExtId !== callExtId);
32
+ const [busy, setBusy] = useState(false);
33
+ const live = latest && isCallLive(call, liveNow);
34
+ let summary;
35
+ if (live) summary = senderName ? `${senderName} started a call` : "Call in progress";
36
+ else if (latest && call?.started_at && call.ended_at) {
37
+ const seconds = (Date.parse(call.ended_at) - Date.parse(call.started_at)) / 1e3;
38
+ const joined = (call.last_participants ?? []).map(name);
39
+ summary = [
40
+ "Call",
41
+ formatCallDuration(seconds),
42
+ joined.length ? `joined ${joined.join(", ")}` : null
43
+ ].filter(Boolean).join(" \xB7 ");
44
+ } else summary = senderName ? `Call started by ${senderName}` : "Call";
45
+ return /* @__PURE__ */ jsxs(
46
+ "div",
169
47
  {
170
- className: cn(
171
- "flex items-center gap-3 rounded-md px-2 py-1.5",
172
- pickable ? "hover:bg-muted" : "opacity-60"
173
- ),
174
- "data-testid": `call-picker-row-${candidate.id}`,
175
- "data-status": candidate.status,
176
- "aria-disabled": !pickable || void 0,
48
+ className: "mx-auto my-2 flex w-fit max-w-full flex-wrap items-center justify-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-xs",
49
+ "data-testid": "chat-call-card",
50
+ "data-live": live || void 0,
177
51
  children: [
178
- /* @__PURE__ */ jsx(
179
- Checkbox,
180
- {
181
- checked,
182
- disabled: !pickable,
183
- onCheckedChange: onToggle,
184
- "aria-label": `Invite ${candidate.name}`,
185
- "data-testid": `call-picker-check-${candidate.id}`
186
- }
187
- ),
188
- /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
189
- /* @__PURE__ */ jsx("span", { className: "block truncate text-sm", children: candidate.name }),
190
- /* @__PURE__ */ jsxs("span", { className: "block truncate text-xs text-muted-foreground", children: [
191
- candidate.email,
192
- hint && /* @__PURE__ */ jsxs("span", { className: "ml-1 inline-flex items-center gap-1", children: [
193
- candidate.status === "invite" && /* @__PURE__ */ jsx(Mail, { className: "size-3", "aria-hidden": true }),
194
- candidate.email ? `\xB7 ${hint}` : hint
195
- ] })
196
- ] })
197
- ] }),
198
- candidate.status === "add-contact" && /* @__PURE__ */ jsxs(
52
+ live ? /* @__PURE__ */ jsx(Phone, { className: "size-4 animate-pulse text-green-text", "aria-hidden": true }) : /* @__PURE__ */ jsx(PhoneOff, { className: "size-4 text-muted-foreground", "aria-hidden": true }),
53
+ /* @__PURE__ */ jsx("span", { "data-testid": "chat-call-card-summary", children: summary }),
54
+ time && /* @__PURE__ */ jsx("time", { className: "text-muted-foreground", children: time }),
55
+ live && /* @__PURE__ */ jsx(
199
56
  Button,
200
57
  {
201
58
  type: "button",
202
59
  size: "sm",
203
- variant: "outline",
204
- disabled: adding,
205
- onClick: onAddContact,
206
- "data-testid": `call-picker-add-${candidate.id}`,
207
- children: [
208
- /* @__PURE__ */ jsx(UserPlus, { className: "mr-1 size-3.5" }),
209
- "Add contact"
210
- ]
60
+ className: "h-7 bg-green-600 text-white hover:bg-green-700",
61
+ disabled: busy || inThisCall || inAnotherCall,
62
+ title: inAnotherCall ? "You are already in a call" : void 0,
63
+ onClick: () => {
64
+ setBusy(true);
65
+ void openCall(arbi, { callExtId }).finally(() => setBusy(false));
66
+ },
67
+ "data-testid": "chat-call-card-join",
68
+ children: inThisCall ? "In call" : "Join"
211
69
  }
212
70
  )
213
71
  ]
214
72
  }
215
73
  );
216
- if (candidate.status !== "unavailable" || !candidate.reason) return row;
217
- return /* @__PURE__ */ jsxs(Tooltip, { children: [
218
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: row }),
219
- /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: candidate.reason }) })
220
- ] });
221
74
  }
222
- function InviteLinks({ callExtId }) {
223
- const { notify } = useBlocksEnvironment();
224
- const revokeLink = useRevokeCallInviteLink(callExtId);
225
- const { data: invites = [] } = useCallInviteLinks(callExtId);
226
- const live = invites.filter((i) => !i.revoked_at);
227
- if (!live.length) return null;
228
- const revoke = async (id) => {
229
- try {
230
- await revokeLink(id);
231
- } catch (err) {
232
- notify(err instanceof Error ? err.message : "Could not revoke the link", "error");
233
- }
234
- };
235
- return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-1", "data-testid": "call-picker-invite-links", children: [
236
- /* @__PURE__ */ jsx("h3", { className: "text-xs font-medium text-muted-foreground", children: "Invitations sent" }),
237
- /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1", children: live.map((invite) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2 text-sm", children: [
238
- /* @__PURE__ */ jsx(Mail, { className: "size-3.5 text-muted-foreground", "aria-hidden": true }),
239
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: invite.email }),
240
- invite.used_at && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "joined" }),
241
- /* @__PURE__ */ jsxs(
242
- Button,
243
- {
244
- type: "button",
245
- size: "sm",
246
- variant: "ghost",
247
- onClick: () => void revoke(invite.external_id),
248
- "data-testid": `call-picker-revoke-${invite.external_id}`,
249
- children: [
250
- /* @__PURE__ */ jsx(X, { className: "mr-1 size-3.5" }),
251
- "Revoke"
252
- ]
253
- }
254
- )
255
- ] }, invite.external_id)) })
256
- ] });
75
+ function useCallPersonName(identity, fallbackName) {
76
+ const arbi = useArbi();
77
+ const { byId } = usePeople();
78
+ const me = arbi.isLoggedIn && arbi.user.identity()?.externalId === identity;
79
+ const profile = me ? arbi.user.profile() : null;
80
+ return useMemo(() => {
81
+ if (profile)
82
+ return getUserDisplayName({ given_name: profile.givenName, family_name: profile.familyName });
83
+ const user = byId(identity);
84
+ if (user) return getUserDisplayName(user);
85
+ return (fallbackName || identity).trim().split(/\s+/).join(" ");
86
+ }, [profile, byId, identity, fallbackName]);
257
87
  }
258
- function CallPeoplePicker({
259
- open,
260
- onOpenChange,
261
- scope,
262
- workspaceExtId,
263
- members: chatMembers = [],
264
- preselected = [],
265
- title,
266
- description,
267
- actionLabel,
268
- callExtId,
269
- onSubmit
270
- }) {
271
- const { notify } = useBlocksEnvironment();
272
- const createContact = useCreateContact();
273
- const { userId: me, workspaceId: selectedWorkspaceId } = useConnectedWorkspace();
274
- const { data: workspaces = [] } = useWorkspaces({ enabled: scope !== "chat" });
275
- const [picked, setPicked] = useState(() => new Set(preselected));
276
- const [query, setQuery] = useState("");
277
- const [newEmail, setNewEmail] = useState(null);
278
- const [newName, setNewName] = useState("");
279
- const [adding, setAdding] = useState(false);
280
- const [busy, setBusy] = useState(false);
281
- const [added, setAdded] = useState([]);
282
- const { byEmail } = usePeople();
283
- const { people: hits } = usePeopleSearch(open && scope === "contacts" ? query : "");
284
- const preselectedKey = preselected.join(",");
285
- useEffect(() => {
286
- if (open) setPicked(new Set(preselectedKey ? preselectedKey.split(",") : []));
287
- }, [open, preselectedKey]);
288
- const workspaceId = workspaceExtId ?? (scope === "contacts" ? selectedWorkspaceId : null);
289
- const workspaceMembers = useMemo(
290
- () => (workspaces.find((w) => w.external_id === workspaceId)?.users ?? []).map((wu) => wu.user),
291
- [workspaces, workspaceId]
292
- );
293
- const candidates = useMemo(() => {
294
- const found = hits.flatMap(
295
- (p) => p.contactId ? [{ contactExtId: p.contactId, user: p.user, name: p.name, email: p.email }] : []
296
- );
297
- return buildCallCandidates({
298
- scope,
299
- members: scope === "chat" ? chatMembers : workspaceMembers,
300
- contacts: [...added, ...found],
301
- contactIdOf: (u) => byEmail(u.email)?.contactId ?? null,
302
- me
303
- });
304
- }, [scope, chatMembers, workspaceMembers, hits, added, byEmail, me]);
305
- const shown = candidates.filter((c) => matchesQuery(c, query));
306
- const copy = COPY[scope];
307
- const toggle = (id) => setPicked((prev) => {
308
- const next = new Set(prev);
309
- if (next.has(id)) next.delete(id);
310
- else next.add(id);
311
- return next;
312
- });
313
- const addContact = async (email, name) => {
314
- setAdding(true);
315
- try {
316
- const cardName = name?.trim();
317
- const contact = await createContact.mutateAsync({
318
- email,
319
- invite: true,
320
- ...cardName ? { card: { name: cardName, emails: [{ email }] } } : {}
321
- });
322
- if (contact.status === "failed") {
323
- throw new Error(contact.outcome_detail || "Could not add the contact");
88
+ function CallConversationEmpty({ callExtId }) {
89
+ const { inCall, transcribing } = useMyCallStatus(callExtId);
90
+ const live = useCallSession((s) => !!s.liveCalls[callExtId]) || inCall;
91
+ if (!live) {
92
+ return /* @__PURE__ */ jsx(
93
+ EmptyState,
94
+ {
95
+ icon: AudioLines,
96
+ title: "No transcript",
97
+ description: "This call was not transcribed. When a call here is transcribed, what people say appears in this conversation.",
98
+ className: "mx-auto my-8 max-w-xl",
99
+ testId: "call-conversation-empty"
324
100
  }
325
- const added2 = callContactOf(contact);
326
- if (added2) setAdded((prev) => [...prev, added2]);
327
- const id = contact.user?.external_id ?? contact.external_id;
328
- const pickable = contact.user ? !!contact.user.encryption_public_key : !!added2?.email;
329
- if (id && pickable) setPicked((prev) => new Set(prev).add(id));
330
- setNewEmail(null);
331
- setNewName("");
332
- } catch (err) {
333
- notify(
334
- err instanceof ArbiApiError ? refusalReason(err, "Could not add the contact") : err instanceof Error ? err.message : "Could not add the contact",
335
- "error"
336
- );
337
- } finally {
338
- setAdding(false);
101
+ );
102
+ }
103
+ return /* @__PURE__ */ jsx(
104
+ EmptyState,
105
+ {
106
+ icon: transcribing ? Captions : CaptionsOff,
107
+ title: "Call in progress \u2014 the transcript will appear here",
108
+ description: transcribing ? "Transcription is on: what people say shows up here as they speak." : "Transcription is off. The host can turn it on with CC in the call.",
109
+ className: "mx-auto my-8 max-w-xl",
110
+ testId: "call-conversation-empty"
339
111
  }
340
- };
341
- const submit = async () => {
342
- if (busy) return;
343
- const chosen = candidates.filter((c) => picked.has(c.id) && isPickable(c));
344
- setBusy(true);
345
- try {
346
- await onSubmit({
347
- ring: chosen.filter((c) => c.status === "ring" && c.user).map((c) => c.user),
348
- invites: chosen.filter((c) => c.status === "invite" && c.contactExtId).map((c) => ({ contactExtId: c.contactExtId, name: c.name }))
349
- });
350
- onOpenChange(false);
351
- } catch (err) {
352
- notify(err instanceof Error ? err.message : "Something went wrong", "error");
353
- } finally {
354
- setBusy(false);
112
+ );
113
+ }
114
+ function ProvisionalRow({ identity }) {
115
+ const name = useCallPersonName(identity);
116
+ return /* @__PURE__ */ jsx(
117
+ ProvisionalTranscriptLine,
118
+ {
119
+ speakerId: identity,
120
+ speakerName: name,
121
+ speaker: /* @__PURE__ */ jsx(CallUserPill, { identity })
122
+ }
123
+ );
124
+ }
125
+ function ProvisionalSpeakers({ ids }) {
126
+ if (!ids.length) return null;
127
+ return /* @__PURE__ */ jsx("div", { className: "pb-2", "data-testid": "transcript-provisional-rows", children: ids.map((id) => /* @__PURE__ */ jsx(ProvisionalRow, { identity: id }, id)) });
128
+ }
129
+ var LINE_WAIT_MS = 1e4;
130
+ var MIN_SPEECH_MS = 800;
131
+ function useProvisionalSpeakers(callExtId, lines) {
132
+ const { inCall, transcribing } = useMyCallStatus(callExtId);
133
+ const speaking = useSpeakingIds();
134
+ const pending = useRef(/* @__PURE__ */ new Map());
135
+ const [, tick] = useState(0);
136
+ const active = inCall && transcribing;
137
+ const now = Date.now();
138
+ if (active) {
139
+ for (const id of speaking) {
140
+ const p = pending.current.get(id);
141
+ if (p) p.lastHeardAt = now;
142
+ else pending.current.set(id, { startedAt: now, lastHeardAt: now });
355
143
  }
144
+ } else if (pending.current.size) {
145
+ pending.current.clear();
146
+ }
147
+ const latest = useMemo(() => {
148
+ const m = /* @__PURE__ */ new Map();
149
+ for (const l of lines) m.set(l.speakerId, Math.max(m.get(l.speakerId) ?? 0, l.at));
150
+ return m;
151
+ }, [lines]);
152
+ for (const [id, p] of [...pending.current]) {
153
+ const arrived = (latest.get(id) ?? 0) >= p.startedAt - 3e3;
154
+ const stale = !speaking.includes(id) && now - p.lastHeardAt > LINE_WAIT_MS;
155
+ if (arrived && !speaking.includes(id) || stale) pending.current.delete(id);
156
+ else if (arrived) p.startedAt = now;
157
+ }
158
+ const waiting = pending.current.size > 0;
159
+ useEffect(() => {
160
+ if (!waiting) return;
161
+ const id = setInterval(() => tick((n) => n + 1), 1e3);
162
+ return () => clearInterval(id);
163
+ }, [waiting]);
164
+ return [...pending.current].filter(([, p]) => p.lastHeardAt - p.startedAt >= MIN_SPEECH_MS).map(([id]) => id);
165
+ }
166
+ var { isTranscriptMessage, parseTranscriptContent } = chats;
167
+ function ConnectedCallChatHistory({
168
+ chat,
169
+ messages,
170
+ items,
171
+ renderItem,
172
+ loadOlder,
173
+ memberById,
174
+ senderName,
175
+ myExtId,
176
+ formatTime,
177
+ formatDay
178
+ }) {
179
+ const arbi = useArbi();
180
+ const [view, setView] = useState("all");
181
+ useEffect(() => setView("all"), [chat?.external_id]);
182
+ const recordingsCallExtId = chat?.call_ext_id ?? null;
183
+ const pendingRecording = usePendingRecording();
184
+ useEffect(() => {
185
+ if (pendingRecording && recordingsCallExtId) setView("recordings");
186
+ }, [pendingRecording, recordingsCallExtId]);
187
+ const latestCallCards = useMemo(() => {
188
+ const latest = /* @__PURE__ */ new Map();
189
+ for (const m of messages)
190
+ if (m.kind === "call" && m.call_ext_id && !m.deleted) latest.set(m.call_ext_id, m.external_id);
191
+ return new Set(latest.values());
192
+ }, [messages]);
193
+ const hasRecTranscript = useMemo(
194
+ () => hasRecordingTranscript(messages, chatTranscriptRef),
195
+ [messages]
196
+ );
197
+ const recordingWindows = useRecordingWindows(recordingsCallExtId, hasRecTranscript);
198
+ const supersededTranscript = useMemo(
199
+ () => supersededTranscriptLines(messages, chatTranscriptRef, recordingWindows),
200
+ [messages, recordingWindows]
201
+ );
202
+ const transcriptMessages = useMemo(
203
+ () => sortTranscriptMessages(
204
+ messages.filter((m) => isTranscriptMessage(m) && !m.deleted && !supersededTranscript.has(m))
205
+ ),
206
+ [messages, supersededTranscript]
207
+ );
208
+ const hasTranscript = transcriptMessages.length > 0;
209
+ const { data: meetingCall } = useCall(hasTranscript ? chat?.call_ext_id : null);
210
+ const isHost = !!meetingCall?.is_creator;
211
+ const [meetingTitle, setMeetingTitle] = useState(null);
212
+ useEffect(() => {
213
+ if (!chat || !isHost && !recordingsCallExtId) return;
214
+ let cancelled = false;
215
+ arbi.chats.title(chat).then((t) => !cancelled && setMeetingTitle(t)).catch(() => void 0);
216
+ return () => {
217
+ cancelled = true;
218
+ };
219
+ }, [arbi, isHost, chat, recordingsCallExtId]);
220
+ const transcriptSpeaker = (msg) => {
221
+ const parsed = msg.decryptFailed ? null : parseTranscriptContent(msg.content);
222
+ return { parsed, speakerId: parsed?.speaker_identity || msg.sender_ext_id || "" };
356
223
  };
357
- const sections = scope === "contacts" ? [
358
- { id: "members", label: "Workspace members" },
359
- { id: "contacts", label: "Contacts" }
360
- ] : [{ id: "members", label: scope === "chat" ? "Members" : "Workspace members" }];
361
- const count = candidates.filter((c) => picked.has(c.id) && isPickable(c)).length;
362
- return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", "data-testid": "call-people-picker", children: [
363
- /* @__PURE__ */ jsxs(DialogHeader, { children: [
364
- /* @__PURE__ */ jsx(DialogTitle, { children: title ?? copy.title }),
365
- /* @__PURE__ */ jsx(DialogDescription, { children: description ?? copy.description })
366
- ] }),
367
- /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-col gap-3", children: [
368
- /* @__PURE__ */ jsx(
369
- Input,
224
+ const transcriptExport = useMemo(
225
+ () => transcriptMessages.filter((m) => !m.decryptFailed).map((m) => {
226
+ const parsed = parseTranscriptContent(m.content);
227
+ const speakerId = parsed?.speaker_identity || m.sender_ext_id || "";
228
+ return {
229
+ speaker: parsed?.speaker_name || (memberById(speakerId) ? senderName(speakerId) : "") || "Former member",
230
+ at: parsed?.started_at ?? m.created_at,
231
+ text: parsed?.text ?? m.content ?? ""
232
+ };
233
+ }),
234
+ [transcriptMessages, memberById, senderName]
235
+ );
236
+ const transcriptLayout = useMemo(() => {
237
+ const shown = view === "transcript" ? transcriptMessages : messages;
238
+ const continued = transcriptRuns(
239
+ shown,
240
+ (m) => isTranscriptMessage(m) && !supersededTranscript.has(m) ? transcriptSpeaker(m).speakerId : null,
241
+ (m) => Date.parse(transcriptSpeaker(m).parsed?.started_at ?? m.created_at ?? "") || 0
242
+ );
243
+ return { continued };
244
+ }, [view, messages, transcriptMessages, supersededTranscript]);
245
+ const transcriptEntry = (msg) => {
246
+ const { parsed, speakerId } = transcriptSpeaker(msg);
247
+ return /* @__PURE__ */ jsx(
248
+ TranscriptEntry,
249
+ {
250
+ speakerId,
251
+ speakerName: speakerId === myExtId ? "Me" : parsed?.speaker_name || (memberById(speakerId) ? senderName(speakerId) : senderName(msg.sender_ext_id)),
252
+ text: parsed?.text ?? msg.content ?? "",
253
+ at: parsed?.started_at ?? msg.created_at,
254
+ continued: transcriptLayout.continued.has(msg)
255
+ },
256
+ msg.external_id
257
+ );
258
+ };
259
+ const chatLines = useMemo(
260
+ () => transcriptMessages.map((m) => {
261
+ const { parsed, speakerId } = transcriptSpeaker(m);
262
+ return { speakerId, at: Date.parse(parsed?.started_at ?? m.created_at ?? "") || 0 };
263
+ }),
264
+ [transcriptMessages]
265
+ );
266
+ const provisionalSpeakers = useProvisionalSpeakers(recordingsCallExtId, chatLines);
267
+ const filterOptions = [
268
+ { value: "all", label: "All" },
269
+ { value: "chat", label: "Chat", testId: "chat-filter" },
270
+ {
271
+ value: "transcript",
272
+ label: "Transcript",
273
+ count: transcriptMessages.length,
274
+ testId: "transcript-filter"
275
+ },
276
+ ...recordingsCallExtId ? [{ value: "recordings", label: "Recordings", testId: "recordings-filter" }] : []
277
+ ];
278
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
279
+ messages.length === 0 && recordingsCallExtId && /* @__PURE__ */ jsx(CallConversationEmpty, { callExtId: recordingsCallExtId }),
280
+ (hasTranscript || recordingsCallExtId) && /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 flex justify-center py-1", children: /* @__PURE__ */ jsx(
281
+ ConversationFilter,
282
+ {
283
+ value: view,
284
+ options: filterOptions,
285
+ onChange: setView
286
+ }
287
+ ) }),
288
+ view === "recordings" && recordingsCallExtId && /* @__PURE__ */ jsx(RecordingsList, { callExtId: recordingsCallExtId, title: meetingTitle, className: "py-1" }),
289
+ loadOlder,
290
+ view === "transcript" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col py-1", "data-testid": "transcript-view", children: [
291
+ isHost && /* @__PURE__ */ jsx(
292
+ SaveTranscriptToThread,
370
293
  {
371
- placeholder: "Search people",
372
- value: query,
373
- onChange: (e) => setQuery(e.target.value),
374
- "aria-label": "Search people",
375
- "data-testid": "call-picker-search"
294
+ title: meetingTitle,
295
+ lines: transcriptExport,
296
+ className: "mx-3 mb-2 self-start"
376
297
  }
377
298
  ),
378
- /* @__PURE__ */ jsxs("div", { className: "max-h-80 overflow-y-auto", children: [
379
- sections.map((section) => {
380
- const rows = shown.filter((c) => c.section === section.id);
381
- if (!rows.length) return null;
382
- return /* @__PURE__ */ jsxs("section", { className: "mb-2", children: [
383
- /* @__PURE__ */ jsx("h3", { className: "px-2 pb-1 text-xs font-medium text-muted-foreground", children: section.label }),
384
- /* @__PURE__ */ jsx("ul", { "data-testid": `call-picker-section-${section.id}`, children: rows.map((c) => /* @__PURE__ */ jsx(
385
- CandidateRow,
386
- {
387
- candidate: c,
388
- checked: picked.has(c.id),
389
- onToggle: () => toggle(c.id),
390
- adding,
391
- onAddContact: () => c.email && void addContact(c.email)
392
- },
393
- c.id
394
- )) })
395
- ] }, section.id);
396
- }),
397
- !shown.length && /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-sm text-muted-foreground", children: scope === "contacts" && !query.trim() ? "Search your contacts by name or e-mail." : candidates.length ? "Nobody matches." : "Nobody to call here yet." })
398
- ] }),
399
- scope === "contacts" && (newEmail === null ? /* @__PURE__ */ jsxs(
400
- Button,
401
- {
402
- type: "button",
403
- variant: "ghost",
404
- size: "sm",
405
- className: "self-start",
406
- onClick: () => setNewEmail(EMAIL_RE.test(query.trim()) ? query.trim() : ""),
407
- "data-testid": "call-picker-add-contact",
408
- children: [
409
- /* @__PURE__ */ jsx(UserPlus, { className: "mr-1 size-4" }),
410
- "Add contact\u2026"
411
- ]
412
- }
413
- ) : /* @__PURE__ */ jsxs(
414
- "form",
415
- {
416
- className: "flex flex-wrap items-center gap-2",
417
- onSubmit: (e) => {
418
- e.preventDefault();
419
- if (EMAIL_RE.test(newEmail.trim())) void addContact(newEmail.trim(), newName);
420
- },
421
- children: [
422
- /* @__PURE__ */ jsx(
423
- Input,
424
- {
425
- className: "min-w-[8rem] flex-1",
426
- placeholder: "Name (optional)",
427
- value: newName,
428
- maxLength: 200,
429
- onChange: (e) => setNewName(e.target.value),
430
- "aria-label": "New contact's name",
431
- autoComplete: "off",
432
- "data-testid": "call-picker-new-name"
433
- }
434
- ),
435
- /* @__PURE__ */ jsx(
436
- Input,
437
- {
438
- className: "min-w-[10rem] flex-1",
439
- type: "email",
440
- autoFocus: true,
441
- placeholder: "name@example.com",
442
- value: newEmail,
443
- onChange: (e) => setNewEmail(e.target.value),
444
- "aria-label": "New contact's e-mail",
445
- "data-testid": "call-picker-new-email"
446
- }
447
- ),
448
- /* @__PURE__ */ jsx(
449
- Button,
450
- {
451
- type: "submit",
452
- size: "sm",
453
- disabled: adding || !EMAIL_RE.test(newEmail.trim()),
454
- "data-testid": "call-picker-new-submit",
455
- children: "Add"
456
- }
457
- ),
458
- /* @__PURE__ */ jsx(
459
- Button,
460
- {
461
- type: "button",
462
- size: "sm",
463
- variant: "ghost",
464
- onClick: () => {
465
- setNewEmail(null);
466
- setNewName("");
467
- },
468
- children: "Cancel"
469
- }
470
- )
471
- ]
472
- }
473
- )),
474
- callExtId && scope === "contacts" && /* @__PURE__ */ jsx(InviteLinks, { callExtId }),
475
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Agents can\u2019t join calls." })
299
+ transcriptMessages.map((msg) => transcriptEntry(msg)),
300
+ /* @__PURE__ */ jsx(ProvisionalSpeakers, { ids: provisionalSpeakers })
476
301
  ] }),
477
- /* @__PURE__ */ jsxs(DialogFooter, { children: [
478
- /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), disabled: busy, children: "Cancel" }),
479
- /* @__PURE__ */ jsx(Button, { onClick: () => void submit(), disabled: busy, "data-testid": "call-picker-submit", children: busy ? "Working\u2026" : `${actionLabel ?? copy.action}${count ? ` (${count})` : ""}` })
480
- ] })
481
- ] }) });
482
- }
483
- function ThreadCallButton({
484
- conversationId
485
- }) {
486
- const arbi = useArbi();
487
- const { data: conversations } = useConnectedConversations();
488
- const { workspaceId: workspaceExtId } = useConnectedWorkspace();
489
- const liveCallId = useCallSession((s) => threadLiveCallId(s, conversationId));
490
- const isLive = liveCallId !== null;
491
- const canHost = useCanHostCalls();
492
- const [picking, setPicking] = useState(false);
493
- const conversation = conversations.find((c) => c.external_id === conversationId);
494
- if (!conversation?.is_shared) return null;
495
- const title = conversation.title ?? null;
496
- return /* @__PURE__ */ jsxs(Fragment, { children: [
497
- /* @__PURE__ */ jsx(
498
- StartCallButton,
499
- {
500
- onStart: async () => {
501
- if (liveCallId && !canHost) return openCall(arbi, { callExtId: liveCallId });
502
- if (isLive) return startThreadCall(arbi, { threadExtId: conversationId, title, ring: [] });
503
- setPicking(true);
504
- return true;
505
- },
506
- isLive,
507
- label: "Call thread members",
508
- testId: "thread-call-button"
302
+ (view === "all" || view === "chat") && items.map((item, i) => {
303
+ const { message: msg, isFirstOfDay } = item;
304
+ const deleted = !!msg.deleted;
305
+ if (deleted && isTranscriptMessage(msg)) return null;
306
+ const separator = isFirstOfDay && /* @__PURE__ */ jsx(DateSeparator, { children: formatDay(msg.created_at) });
307
+ if (isTranscriptMessage(msg)) {
308
+ if (view === "chat") return null;
309
+ const superseded = supersededTranscript.has(msg);
310
+ if (superseded && !isFirstOfDay) return null;
311
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
312
+ separator,
313
+ !superseded && transcriptEntry(msg)
314
+ ] }, msg.external_id);
509
315
  }
510
- ),
511
- picking && /* @__PURE__ */ jsx(
512
- CallPeoplePicker,
513
- {
514
- open: true,
515
- onOpenChange: setPicking,
516
- scope: "workspace",
517
- workspaceExtId,
518
- onSubmit: async ({ ring }) => {
519
- await startThreadCall(arbi, { threadExtId: conversationId, title, ring });
520
- }
316
+ if (msg.kind === "recording" && msg.call_ext_id && !deleted) {
317
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
318
+ separator,
319
+ /* @__PURE__ */ jsx(
320
+ RecordingCard,
321
+ {
322
+ callExtId: msg.call_ext_id,
323
+ recordingExtId: msg.recording_ext_id ?? null,
324
+ title: meetingTitle,
325
+ time: formatTime(msg.created_at)
326
+ }
327
+ )
328
+ ] }, msg.external_id);
521
329
  }
522
- )
523
- ] });
524
- }
525
- function GroupCallButton({
526
- chatExtId,
527
- title,
528
- members
529
- }) {
530
- const arbi = useArbi();
531
- const { userId: me } = useConnectedWorkspace();
532
- const liveCallId = useCallSession((s) => chatLiveCallId(s, chatExtId));
533
- const isLive = liveCallId !== null;
330
+ if (msg.kind === "call" && msg.call_ext_id && !deleted) {
331
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
332
+ separator,
333
+ /* @__PURE__ */ jsx(
334
+ ChatCallCard,
335
+ {
336
+ callExtId: msg.call_ext_id,
337
+ senderName: msg.sender_ext_id ? senderName(msg.sender_ext_id) : null,
338
+ latest: latestCallCards.has(msg.external_id),
339
+ name: senderName,
340
+ time: formatTime(msg.created_at)
341
+ }
342
+ )
343
+ ] }, msg.external_id);
344
+ }
345
+ return renderItem(item, i);
346
+ }),
347
+ view === "all" && /* @__PURE__ */ jsx(ProvisionalSpeakers, { ids: provisionalSpeakers })
348
+ ] });
349
+ }
350
+ function StartCallButton(props) {
351
+ const enabled = useCallsEnabled();
534
352
  const canHost = useCanHostCalls();
535
- const [picking, setPicking] = useState(false);
536
- const others = members.filter((u) => u.external_id !== me && !isAgent(u));
537
- return /* @__PURE__ */ jsxs(Fragment, { children: [
538
- /* @__PURE__ */ jsx(
539
- StartCallButton,
353
+ return enabled && (canHost || props.isLive) ? /* @__PURE__ */ jsx(EnabledStartCallButton, { ...props }) : null;
354
+ }
355
+ function EnabledStartCallButton({
356
+ onStart,
357
+ isLive = false,
358
+ label = "Start call",
359
+ testId = "start-call-button"
360
+ }) {
361
+ const [isStarting, setIsStarting] = useState(false);
362
+ const inCall = useCallSession((s) => s.active !== null);
363
+ const text = inCall ? "Already in a call" : isLive ? "Join call" : label;
364
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
365
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
366
+ Button,
540
367
  {
541
- onStart: async () => {
542
- if (liveCallId && !canHost) return openCall(arbi, { callExtId: liveCallId });
543
- if (isLive) return startChatCall(arbi, { chatExtId, title, ring: [] });
544
- setPicking(true);
545
- return true;
368
+ type: "button",
369
+ variant: "ghost",
370
+ size: "icon",
371
+ "aria-label": isLive ? "Join call" : label,
372
+ "data-testid": testId,
373
+ "data-live": isLive || void 0,
374
+ disabled: isStarting || inCall,
375
+ onClick: () => {
376
+ setIsStarting(true);
377
+ void onStart().finally(() => setIsStarting(false));
546
378
  },
547
- isLive,
548
- label: "Call the group",
549
- testId: "group-call-button"
550
- }
551
- ),
552
- picking && /* @__PURE__ */ jsx(
553
- CallPeoplePicker,
554
- {
555
- open: true,
556
- onOpenChange: setPicking,
557
- scope: "chat",
558
- members: others,
559
- preselected: others.map((u) => u.external_id),
560
- onSubmit: async ({ ring }) => {
561
- await startChatCall(arbi, { chatExtId, title, ring });
562
- }
379
+ className: cn(
380
+ "text-muted-foreground hover:text-foreground",
381
+ isLive && "text-green-text hover:text-green-text"
382
+ ),
383
+ children: /* @__PURE__ */ jsx(Phone, { className: cn("size-5", isLive && "animate-pulse") })
563
384
  }
564
- )
385
+ ) }),
386
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: text }) })
565
387
  ] });
566
388
  }
567
389
  function DmCallButton({ peer }) {
@@ -580,534 +402,712 @@ function DmCallButton({ peer }) {
580
402
  }
581
403
  );
582
404
  }
583
- function NewCallChip() {
584
- return useCanHostCalls() ? /* @__PURE__ */ jsx(EnabledNewCallChip, {}) : null;
405
+ var INVITE_LINK_HINT = "will get an invitation to create an ARBI account and join";
406
+ var NO_KEY_REASON = "Cannot receive encrypted calls yet";
407
+ var NO_EMAIL_REASON = "No e-mail address to send a link to";
408
+ function contactName(contact) {
409
+ return contact.card?.name?.trim() || [contact.card?.given_name, contact.card?.family_name].filter(Boolean).join(" ") || (contact.user ? getUserDisplayName(contact.user) : "") || contactEmail(contact) || "Contact";
585
410
  }
586
- function EnabledNewCallChip() {
587
- const arbi = useArbi();
588
- const inCall = useCallSession((s) => s.active !== null);
589
- const [open, setOpen] = useState(false);
590
- return /* @__PURE__ */ jsxs(Fragment, { children: [
591
- /* @__PURE__ */ jsx(
592
- DmInviteChip,
593
- {
594
- testId: "dm-new-call-chip",
595
- title: "New call",
596
- description: inCall ? "You are already in a call" : "Call people from your contacts",
597
- onClick: () => !inCall && setOpen(true)
598
- }
599
- ),
600
- open && /* @__PURE__ */ jsx(
601
- CallPeoplePicker,
602
- {
603
- open: true,
604
- onOpenChange: setOpen,
605
- scope: "contacts",
606
- onSubmit: async ({ ring, invites }) => {
607
- await startContactsCall(arbi, {
608
- title: contactsCallTitle([
609
- ...ring.map((u) => getUserDisplayName(u)),
610
- ...invites.map((g) => g.name)
611
- ]),
612
- ring,
613
- invites
614
- });
615
- }
616
- }
617
- )
618
- ] });
411
+ function contactEmail(contact) {
412
+ return contact.email ?? contact.user?.email ?? contact.card?.emails?.[0]?.email ?? null;
619
413
  }
620
- function ScheduleCallButton({
621
- conversationId,
622
- peer,
623
- variant = "icon",
624
- className
625
- }) {
626
- const callsEnabled = useCanHostCalls();
627
- const { conversations } = useConversationItems();
628
- const { data: workspaces = [] } = useWorkspaces();
629
- const { workspaceId: selectedWorkspaceId, userId: me } = useConnectedWorkspace();
630
- const { openCalendar } = useBlocksEnvironment();
631
- if (!callsEnabled) return null;
632
- const open = () => {
633
- const conversation = conversationId ? conversations?.find((c) => c.external_id === conversationId) : void 0;
634
- let people = [];
635
- if (peer) people = [peer];
636
- else if (conversation?.is_shared) {
637
- const workspace = workspaces.find((w) => w.external_id === selectedWorkspaceId);
638
- people = (workspace?.users ?? []).map((wu) => wu.user);
639
- }
640
- requestCalendarComposer({
641
- title: peer ? `Call with ${getUserDisplayName(peer) || peer.email}` : conversation?.title ?? "",
642
- conversationId: conversationId ?? null,
643
- attendees: people.filter((u) => !isAgent(u) && u.external_id !== me).map((u) => ({
644
- email: u.email,
645
- user_id: u.external_id,
646
- name: getUserDisplayName(u) || null
647
- })),
648
- addCall: true
649
- });
650
- openCalendar();
414
+ function callContactOf(contact) {
415
+ if (!contact.external_id || contact.status === "failed") return null;
416
+ if (contact.user && isAgent(contact.user)) return null;
417
+ return {
418
+ contactExtId: contact.external_id,
419
+ user: contact.user ?? null,
420
+ name: contactName(contact),
421
+ email: contactEmail(contact)
651
422
  };
652
- return /* @__PURE__ */ jsxs(
653
- Button,
654
- {
655
- type: "button",
656
- variant: "ghost",
657
- size: variant === "icon" ? "icon" : "sm",
658
- className: cn(variant === "icon" && "size-8", className),
659
- onClick: open,
660
- "aria-label": "Schedule call",
661
- title: "Schedule call",
662
- "data-testid": "schedule-call-button",
663
- children: [
664
- /* @__PURE__ */ jsx(CalendarPlus, { className: "size-4" }),
665
- variant === "label" && /* @__PURE__ */ jsx("span", { className: "ml-1.5", children: "Schedule call" })
666
- ]
667
- }
668
- );
669
423
  }
670
- function useMeetingThreadMessages(meetingCallExtId, messageHistory) {
671
- const hasRecTranscript = useMemo(
672
- () => !!meetingCallExtId && hasRecordingTranscript(messageHistory, threadTranscriptRef),
673
- [meetingCallExtId, messageHistory]
674
- );
675
- const recordingWindows = useRecordingWindows(meetingCallExtId, hasRecTranscript);
676
- return useMemo(
677
- () => hasRecTranscript ? supersedeLiveTranscript(messageHistory, threadTranscriptRef, recordingWindows) : messageHistory,
678
- [hasRecTranscript, messageHistory, recordingWindows]
679
- );
424
+ function userCandidate(user, section, status, contactExtId = null) {
425
+ const ringable = status !== "ring" || !!user.encryption_public_key;
426
+ return {
427
+ id: user.external_id,
428
+ name: getUserDisplayName(user) || user.email,
429
+ email: user.email ?? null,
430
+ section,
431
+ status: ringable ? status : "unavailable",
432
+ reason: ringable ? void 0 : NO_KEY_REASON,
433
+ user,
434
+ contactExtId
435
+ };
680
436
  }
681
- function MeetingThreadTranscriptAction({
682
- conversationExtId,
683
- meetingCallExtId,
684
- title,
685
- messages
686
- }) {
687
- const { data: call } = useCall(meetingCallExtId);
688
- const { data: workspaceUsers = [] } = useWorkspaceUsers();
689
- const lines = useMemo(
690
- () => threadTranscriptLines(messages, (id) => {
691
- const user = workspaceUsers.find((wu) => wu.user.external_id === id)?.user;
692
- return user ? getUserDisplayName(user) : null;
693
- }),
694
- [messages, workspaceUsers]
437
+ function buildCallCandidates(opts) {
438
+ const seen = /* @__PURE__ */ new Set();
439
+ const out = [];
440
+ const add = (c) => {
441
+ if (seen.has(c.id)) return;
442
+ seen.add(c.id);
443
+ out.push(c);
444
+ };
445
+ const people = (users) => users.filter((u) => !isAgent(u) && u.external_id !== opts.me);
446
+ if (opts.scope !== "contacts") {
447
+ for (const u of people(opts.members)) add(userCandidate(u, "members", "ring"));
448
+ return out;
449
+ }
450
+ const contacts = (opts.contacts ?? []).filter((c) => !(c.user && isAgent(c.user)));
451
+ const contactOf = new Map(
452
+ contacts.filter((c) => c.user).map((c) => [c.user.external_id, c.contactExtId])
695
453
  );
696
- if (!meetingCallExtId || !call?.is_creator) return null;
697
- return /* @__PURE__ */ jsx(
698
- SaveTranscriptToThread,
699
- {
700
- title,
701
- lines,
702
- excludeConversationExtId: conversationExtId,
703
- iconOnly: true
454
+ for (const u of people(opts.members)) {
455
+ const contactId = contactOf.get(u.external_id) ?? opts.contactIdOf?.(u) ?? null;
456
+ add(userCandidate(u, "members", contactId ? "ring" : "add-contact", contactId));
457
+ }
458
+ for (const c of contacts) {
459
+ if (c.user) {
460
+ if (c.user.external_id === opts.me) continue;
461
+ add(userCandidate(c.user, "contacts", "ring", c.contactExtId));
462
+ continue;
704
463
  }
705
- );
464
+ const status = c.email ? "invite" : "unavailable";
465
+ add({
466
+ id: c.contactExtId,
467
+ name: c.name,
468
+ email: c.email,
469
+ section: "contacts",
470
+ status,
471
+ reason: c.email ? INVITE_LINK_HINT : NO_EMAIL_REASON,
472
+ user: null,
473
+ contactExtId: c.contactExtId
474
+ });
475
+ }
476
+ return out;
706
477
  }
707
- function MeetingThreadRecordingsAction({
708
- meetingCallExtId,
709
- title
710
- }) {
711
- const [open, setOpen] = useState(false);
712
- const pending = usePendingRecording();
713
- useEffect(() => {
714
- if (pending) setOpen(true);
715
- }, [pending]);
716
- return /* @__PURE__ */ jsxs(Fragment, { children: [
717
- /* @__PURE__ */ jsx(
718
- HeaderAction,
719
- {
720
- label: "Recordings",
721
- icon: /* @__PURE__ */ jsx(Disc, { className: "size-3.5", "aria-hidden": true }),
722
- iconOnly: true,
723
- onClick: () => setOpen(true),
724
- testId: "meeting-recordings"
725
- }
726
- ),
727
- open && /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (o) => !o && setOpen(false), children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-2xl", "data-testid": "meeting-recordings-dialog", children: [
728
- /* @__PURE__ */ jsxs(DialogHeader, { children: [
729
- /* @__PURE__ */ jsx(DialogTitle, { children: "Recordings" }),
730
- /* @__PURE__ */ jsx(DialogDescription, { children: "Recordings of this meeting, encrypted under the workspace key. Playing or downloading one decrypts it in this browser." })
731
- ] }),
732
- /* @__PURE__ */ jsx(
733
- RecordingsList,
734
- {
735
- callExtId: meetingCallExtId,
736
- title,
737
- className: "max-h-[60vh] overflow-y-auto"
738
- }
739
- )
740
- ] }) })
741
- ] });
478
+ function isPickable(c) {
479
+ return c.status === "ring" || c.status === "invite";
742
480
  }
743
- function ConnectedConversationCallActions() {
744
- const conversationId = useArbiSessionState((state) => state.conversationId);
745
- const { conversations } = useConversationItems();
746
- const conversation = conversationId ? conversations.find((c) => c.external_id === conversationId) : void 0;
747
- const meetingCallExtId = conversation?.meeting_call_ext_id ?? null;
748
- const { messageHistory } = useConversationData();
749
- const threadMessages = useMeetingThreadMessages(meetingCallExtId, messageHistory);
750
- const meetingMessages = useMemo(
751
- () => meetingCallExtId ? threadMessages.map((m) => ({
752
- content: m.content,
753
- created_at: m.created_at,
754
- tools: m.tools ?? m.meta?.tools
755
- })) : [],
756
- [meetingCallExtId, threadMessages]
757
- );
758
- if (!conversationId) return null;
759
- return /* @__PURE__ */ jsxs(Fragment, { children: [
760
- /* @__PURE__ */ jsx(ThreadCallButton, { conversationId }),
761
- /* @__PURE__ */ jsx(ScheduleCallButton, { conversationId }),
762
- meetingCallExtId && /* @__PURE__ */ jsx(
763
- MeetingThreadTranscriptAction,
764
- {
765
- conversationExtId: conversationId,
766
- meetingCallExtId,
767
- title: conversation?.title,
768
- messages: meetingMessages
769
- }
770
- ),
771
- meetingCallExtId && /* @__PURE__ */ jsx(
772
- MeetingThreadRecordingsAction,
773
- {
774
- meetingCallExtId,
775
- title: conversation?.title
776
- }
777
- )
778
- ] });
481
+ function matchesQuery(c, query) {
482
+ const q = query.trim().toLowerCase();
483
+ if (!q) return true;
484
+ return c.name.toLowerCase().includes(q) || (c.email ?? "").toLowerCase().includes(q);
779
485
  }
780
- function ChatCallCard({
781
- callExtId,
782
- senderName,
783
- latest,
784
- name,
785
- time
486
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
487
+ var COPY = {
488
+ workspace: {
489
+ title: "Call thread members",
490
+ description: "Only members of this thread\u2019s workspace can join. Pick who to ring \u2014 or nobody, and share the call from the thread.",
491
+ action: "Start call"
492
+ },
493
+ chat: {
494
+ title: "Call the group",
495
+ description: "The people you pick are rung.",
496
+ action: "Start call"
497
+ },
498
+ contacts: {
499
+ title: "New call",
500
+ description: "Your contacts with an ARBI account are rung. Others get an invitation to create an account and join.",
501
+ action: "Start call"
502
+ }
503
+ };
504
+ function CandidateRow({
505
+ candidate,
506
+ checked,
507
+ onToggle,
508
+ onAddContact,
509
+ adding
786
510
  }) {
787
- const arbi = useArbi();
788
- const { data: call } = useCall(latest ? callExtId : null);
789
- const liveNow = useCallSession((s) => callExtId in s.liveCalls);
790
- const inThisCall = useCallSession((s) => s.active?.callExtId === callExtId);
791
- const inAnotherCall = useCallSession((s) => !!s.active && s.active.callExtId !== callExtId);
792
- const [busy, setBusy] = useState(false);
793
- const live = latest && isCallLive(call, liveNow);
794
- let summary;
795
- if (live) summary = senderName ? `${senderName} started a call` : "Call in progress";
796
- else if (latest && call?.started_at && call.ended_at) {
797
- const seconds = (Date.parse(call.ended_at) - Date.parse(call.started_at)) / 1e3;
798
- const joined = (call.last_participants ?? []).map(name);
799
- summary = [
800
- "Call",
801
- formatCallDuration(seconds),
802
- joined.length ? `joined ${joined.join(", ")}` : null
803
- ].filter(Boolean).join(" \xB7 ");
804
- } else summary = senderName ? `Call started by ${senderName}` : "Call";
805
- return /* @__PURE__ */ jsxs(
806
- "div",
511
+ const pickable = isPickable(candidate);
512
+ const hint = candidate.status === "invite" ? candidate.reason : candidate.status === "add-contact" ? "Not in your contacts" : void 0;
513
+ const row = /* @__PURE__ */ jsxs(
514
+ "li",
807
515
  {
808
- className: "mx-auto my-2 flex w-fit max-w-full flex-wrap items-center justify-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-xs",
809
- "data-testid": "chat-call-card",
810
- "data-live": live || void 0,
516
+ className: cn(
517
+ "flex items-center gap-3 rounded-md px-2 py-1.5",
518
+ pickable ? "hover:bg-muted" : "opacity-60"
519
+ ),
520
+ "data-testid": `call-picker-row-${candidate.id}`,
521
+ "data-status": candidate.status,
522
+ "aria-disabled": !pickable || void 0,
811
523
  children: [
812
- live ? /* @__PURE__ */ jsx(Phone, { className: "size-4 animate-pulse text-green-text", "aria-hidden": true }) : /* @__PURE__ */ jsx(PhoneOff, { className: "size-4 text-muted-foreground", "aria-hidden": true }),
813
- /* @__PURE__ */ jsx("span", { "data-testid": "chat-call-card-summary", children: summary }),
814
- time && /* @__PURE__ */ jsx("time", { className: "text-muted-foreground", children: time }),
815
- live && /* @__PURE__ */ jsx(
524
+ /* @__PURE__ */ jsx(
525
+ Checkbox,
526
+ {
527
+ checked,
528
+ disabled: !pickable,
529
+ onCheckedChange: onToggle,
530
+ "aria-label": `Invite ${candidate.name}`,
531
+ "data-testid": `call-picker-check-${candidate.id}`
532
+ }
533
+ ),
534
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
535
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-sm", children: candidate.name }),
536
+ /* @__PURE__ */ jsxs("span", { className: "block truncate text-xs text-muted-foreground", children: [
537
+ candidate.email,
538
+ hint && /* @__PURE__ */ jsxs("span", { className: "ml-1 inline-flex items-center gap-1", children: [
539
+ candidate.status === "invite" && /* @__PURE__ */ jsx(Mail, { className: "size-3", "aria-hidden": true }),
540
+ candidate.email ? `\xB7 ${hint}` : hint
541
+ ] })
542
+ ] })
543
+ ] }),
544
+ candidate.status === "add-contact" && /* @__PURE__ */ jsxs(
816
545
  Button,
817
546
  {
818
547
  type: "button",
819
548
  size: "sm",
820
- className: "h-7 bg-green-600 text-white hover:bg-green-700",
821
- disabled: busy || inThisCall || inAnotherCall,
822
- title: inAnotherCall ? "You are already in a call" : void 0,
823
- onClick: () => {
824
- setBusy(true);
825
- void openCall(arbi, { callExtId }).finally(() => setBusy(false));
826
- },
827
- "data-testid": "chat-call-card-join",
828
- children: inThisCall ? "In call" : "Join"
549
+ variant: "outline",
550
+ disabled: adding,
551
+ onClick: onAddContact,
552
+ "data-testid": `call-picker-add-${candidate.id}`,
553
+ children: [
554
+ /* @__PURE__ */ jsx(UserPlus, { className: "mr-1 size-3.5" }),
555
+ "Add contact"
556
+ ]
829
557
  }
830
558
  )
831
559
  ]
832
560
  }
833
561
  );
562
+ if (candidate.status !== "unavailable" || !candidate.reason) return row;
563
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
564
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: row }),
565
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: candidate.reason }) })
566
+ ] });
567
+ }
568
+ function InviteLinks({ callExtId }) {
569
+ const { notify } = useBlocksEnvironment();
570
+ const revokeLink = useRevokeCallInviteLink(callExtId);
571
+ const { data: invites = [] } = useCallInviteLinks(callExtId);
572
+ const live = invites.filter((i) => !i.revoked_at);
573
+ if (!live.length) return null;
574
+ const revoke = async (id) => {
575
+ try {
576
+ await revokeLink(id);
577
+ } catch (err) {
578
+ notify(err instanceof Error ? err.message : "Could not revoke the link", "error");
579
+ }
580
+ };
581
+ return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-1", "data-testid": "call-picker-invite-links", children: [
582
+ /* @__PURE__ */ jsx("h3", { className: "text-xs font-medium text-muted-foreground", children: "Invitations sent" }),
583
+ /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1", children: live.map((invite) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2 text-sm", children: [
584
+ /* @__PURE__ */ jsx(Mail, { className: "size-3.5 text-muted-foreground", "aria-hidden": true }),
585
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: invite.email }),
586
+ invite.used_at && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "joined" }),
587
+ /* @__PURE__ */ jsxs(
588
+ Button,
589
+ {
590
+ type: "button",
591
+ size: "sm",
592
+ variant: "ghost",
593
+ onClick: () => void revoke(invite.external_id),
594
+ "data-testid": `call-picker-revoke-${invite.external_id}`,
595
+ children: [
596
+ /* @__PURE__ */ jsx(X, { className: "mr-1 size-3.5" }),
597
+ "Revoke"
598
+ ]
599
+ }
600
+ )
601
+ ] }, invite.external_id)) })
602
+ ] });
603
+ }
604
+ function CallPeoplePicker({
605
+ open,
606
+ onOpenChange,
607
+ scope,
608
+ workspaceExtId,
609
+ members: chatMembers = [],
610
+ preselected = [],
611
+ title,
612
+ description,
613
+ actionLabel,
614
+ callExtId,
615
+ onSubmit
616
+ }) {
617
+ const { notify } = useBlocksEnvironment();
618
+ const createContact = useCreateContact();
619
+ const { userId: me, workspaceId: selectedWorkspaceId } = useConnectedWorkspace();
620
+ const { data: workspaces = [] } = useWorkspaces({ enabled: scope !== "chat" });
621
+ const [picked, setPicked] = useState(() => new Set(preselected));
622
+ const [query, setQuery] = useState("");
623
+ const [newEmail, setNewEmail] = useState(null);
624
+ const [newName, setNewName] = useState("");
625
+ const [adding, setAdding] = useState(false);
626
+ const [busy, setBusy] = useState(false);
627
+ const [added, setAdded] = useState([]);
628
+ const { byEmail } = usePeople();
629
+ const { people: hits } = usePeopleSearch(open && scope === "contacts" ? query : "");
630
+ const preselectedKey = preselected.join(",");
631
+ useEffect(() => {
632
+ if (open) setPicked(new Set(preselectedKey ? preselectedKey.split(",") : []));
633
+ }, [open, preselectedKey]);
634
+ const workspaceId = workspaceExtId ?? (scope === "contacts" ? selectedWorkspaceId : null);
635
+ const workspaceMembers = useMemo(
636
+ () => (workspaces.find((w) => w.external_id === workspaceId)?.users ?? []).map((wu) => wu.user),
637
+ [workspaces, workspaceId]
638
+ );
639
+ const candidates = useMemo(() => {
640
+ const found = hits.flatMap(
641
+ (p) => p.contactId ? [{ contactExtId: p.contactId, user: p.user, name: p.name, email: p.email }] : []
642
+ );
643
+ return buildCallCandidates({
644
+ scope,
645
+ members: scope === "chat" ? chatMembers : workspaceMembers,
646
+ contacts: [...added, ...found],
647
+ contactIdOf: (u) => byEmail(u.email)?.contactId ?? null,
648
+ me
649
+ });
650
+ }, [scope, chatMembers, workspaceMembers, hits, added, byEmail, me]);
651
+ const shown = candidates.filter((c) => matchesQuery(c, query));
652
+ const copy = COPY[scope];
653
+ const toggle = (id) => setPicked((prev) => {
654
+ const next = new Set(prev);
655
+ if (next.has(id)) next.delete(id);
656
+ else next.add(id);
657
+ return next;
658
+ });
659
+ const addContact = async (email, name) => {
660
+ setAdding(true);
661
+ try {
662
+ const cardName = name?.trim();
663
+ const contact = await createContact.mutateAsync({
664
+ email,
665
+ invite: true,
666
+ ...cardName ? { card: { name: cardName, emails: [{ email }] } } : {}
667
+ });
668
+ if (contact.status === "failed") {
669
+ throw new Error(contact.outcome_detail || "Could not add the contact");
670
+ }
671
+ const added2 = callContactOf(contact);
672
+ if (added2) setAdded((prev) => [...prev, added2]);
673
+ const id = contact.user?.external_id ?? contact.external_id;
674
+ const pickable = contact.user ? !!contact.user.encryption_public_key : !!added2?.email;
675
+ if (id && pickable) setPicked((prev) => new Set(prev).add(id));
676
+ setNewEmail(null);
677
+ setNewName("");
678
+ } catch (err) {
679
+ notify(
680
+ err instanceof ArbiApiError ? refusalReason(err, "Could not add the contact") : err instanceof Error ? err.message : "Could not add the contact",
681
+ "error"
682
+ );
683
+ } finally {
684
+ setAdding(false);
685
+ }
686
+ };
687
+ const submit = async () => {
688
+ if (busy) return;
689
+ const chosen = candidates.filter((c) => picked.has(c.id) && isPickable(c));
690
+ setBusy(true);
691
+ try {
692
+ await onSubmit({
693
+ ring: chosen.filter((c) => c.status === "ring" && c.user).map((c) => c.user),
694
+ invites: chosen.filter((c) => c.status === "invite" && c.contactExtId).map((c) => ({ contactExtId: c.contactExtId, name: c.name }))
695
+ });
696
+ onOpenChange(false);
697
+ } catch (err) {
698
+ notify(err instanceof Error ? err.message : "Something went wrong", "error");
699
+ } finally {
700
+ setBusy(false);
701
+ }
702
+ };
703
+ const sections = scope === "contacts" ? [
704
+ { id: "members", label: "Workspace members" },
705
+ { id: "contacts", label: "Contacts" }
706
+ ] : [{ id: "members", label: scope === "chat" ? "Members" : "Workspace members" }];
707
+ const count = candidates.filter((c) => picked.has(c.id) && isPickable(c)).length;
708
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", "data-testid": "call-people-picker", children: [
709
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
710
+ /* @__PURE__ */ jsx(DialogTitle, { children: title ?? copy.title }),
711
+ /* @__PURE__ */ jsx(DialogDescription, { children: description ?? copy.description })
712
+ ] }),
713
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-col gap-3", children: [
714
+ /* @__PURE__ */ jsx(
715
+ Input,
716
+ {
717
+ placeholder: "Search people",
718
+ value: query,
719
+ onChange: (e) => setQuery(e.target.value),
720
+ "aria-label": "Search people",
721
+ "data-testid": "call-picker-search"
722
+ }
723
+ ),
724
+ /* @__PURE__ */ jsxs("div", { className: "max-h-80 overflow-y-auto", children: [
725
+ sections.map((section) => {
726
+ const rows = shown.filter((c) => c.section === section.id);
727
+ if (!rows.length) return null;
728
+ return /* @__PURE__ */ jsxs("section", { className: "mb-2", children: [
729
+ /* @__PURE__ */ jsx("h3", { className: "px-2 pb-1 text-xs font-medium text-muted-foreground", children: section.label }),
730
+ /* @__PURE__ */ jsx("ul", { "data-testid": `call-picker-section-${section.id}`, children: rows.map((c) => /* @__PURE__ */ jsx(
731
+ CandidateRow,
732
+ {
733
+ candidate: c,
734
+ checked: picked.has(c.id),
735
+ onToggle: () => toggle(c.id),
736
+ adding,
737
+ onAddContact: () => c.email && void addContact(c.email)
738
+ },
739
+ c.id
740
+ )) })
741
+ ] }, section.id);
742
+ }),
743
+ !shown.length && /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-sm text-muted-foreground", children: scope === "contacts" && !query.trim() ? "Search your contacts by name or e-mail." : candidates.length ? "Nobody matches." : "Nobody to call here yet." })
744
+ ] }),
745
+ scope === "contacts" && (newEmail === null ? /* @__PURE__ */ jsxs(
746
+ Button,
747
+ {
748
+ type: "button",
749
+ variant: "ghost",
750
+ size: "sm",
751
+ className: "self-start",
752
+ onClick: () => setNewEmail(EMAIL_RE.test(query.trim()) ? query.trim() : ""),
753
+ "data-testid": "call-picker-add-contact",
754
+ children: [
755
+ /* @__PURE__ */ jsx(UserPlus, { className: "mr-1 size-4" }),
756
+ "Add contact\u2026"
757
+ ]
758
+ }
759
+ ) : /* @__PURE__ */ jsxs(
760
+ "form",
761
+ {
762
+ className: "flex flex-wrap items-center gap-2",
763
+ onSubmit: (e) => {
764
+ e.preventDefault();
765
+ if (EMAIL_RE.test(newEmail.trim())) void addContact(newEmail.trim(), newName);
766
+ },
767
+ children: [
768
+ /* @__PURE__ */ jsx(
769
+ Input,
770
+ {
771
+ className: "min-w-[8rem] flex-1",
772
+ placeholder: "Name (optional)",
773
+ value: newName,
774
+ maxLength: 200,
775
+ onChange: (e) => setNewName(e.target.value),
776
+ "aria-label": "New contact's name",
777
+ autoComplete: "off",
778
+ "data-testid": "call-picker-new-name"
779
+ }
780
+ ),
781
+ /* @__PURE__ */ jsx(
782
+ Input,
783
+ {
784
+ className: "min-w-[10rem] flex-1",
785
+ type: "email",
786
+ autoFocus: true,
787
+ placeholder: "name@example.com",
788
+ value: newEmail,
789
+ onChange: (e) => setNewEmail(e.target.value),
790
+ "aria-label": "New contact's e-mail",
791
+ "data-testid": "call-picker-new-email"
792
+ }
793
+ ),
794
+ /* @__PURE__ */ jsx(
795
+ Button,
796
+ {
797
+ type: "submit",
798
+ size: "sm",
799
+ disabled: adding || !EMAIL_RE.test(newEmail.trim()),
800
+ "data-testid": "call-picker-new-submit",
801
+ children: "Add"
802
+ }
803
+ ),
804
+ /* @__PURE__ */ jsx(
805
+ Button,
806
+ {
807
+ type: "button",
808
+ size: "sm",
809
+ variant: "ghost",
810
+ onClick: () => {
811
+ setNewEmail(null);
812
+ setNewName("");
813
+ },
814
+ children: "Cancel"
815
+ }
816
+ )
817
+ ]
818
+ }
819
+ )),
820
+ callExtId && scope === "contacts" && /* @__PURE__ */ jsx(InviteLinks, { callExtId }),
821
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Agents can\u2019t join calls." })
822
+ ] }),
823
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
824
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), disabled: busy, children: "Cancel" }),
825
+ /* @__PURE__ */ jsx(Button, { onClick: () => void submit(), disabled: busy, "data-testid": "call-picker-submit", children: busy ? "Working\u2026" : `${actionLabel ?? copy.action}${count ? ` (${count})` : ""}` })
826
+ ] })
827
+ ] }) });
834
828
  }
835
- function useCallPersonName(identity, fallbackName) {
829
+ function GroupCallButton({
830
+ chatExtId,
831
+ title,
832
+ members
833
+ }) {
836
834
  const arbi = useArbi();
837
- const { byId } = usePeople();
838
- const me = arbi.isLoggedIn && arbi.user.identity()?.externalId === identity;
839
- const profile = me ? arbi.user.profile() : null;
840
- return useMemo(() => {
841
- if (profile)
842
- return getUserDisplayName({ given_name: profile.givenName, family_name: profile.familyName });
843
- const user = byId(identity);
844
- if (user) return getUserDisplayName(user);
845
- return (fallbackName || identity).trim().split(/\s+/).join(" ");
846
- }, [profile, byId, identity, fallbackName]);
847
- }
848
- function CallConversationEmpty({ callExtId }) {
849
- const { inCall, transcribing } = useMyCallStatus(callExtId);
850
- const live = useCallSession((s) => !!s.liveCalls[callExtId]) || inCall;
851
- if (!live) {
852
- return /* @__PURE__ */ jsx(
853
- EmptyState,
835
+ const { userId: me } = useConnectedWorkspace();
836
+ const liveCallId = useCallSession((s) => chatLiveCallId(s, chatExtId));
837
+ const isLive = liveCallId !== null;
838
+ const canHost = useCanHostCalls();
839
+ const [picking, setPicking] = useState(false);
840
+ const others = members.filter((u) => u.external_id !== me && !isAgent(u));
841
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
842
+ /* @__PURE__ */ jsx(
843
+ StartCallButton,
854
844
  {
855
- icon: AudioLines,
856
- title: "No transcript",
857
- description: "This call was not transcribed. When a call here is transcribed, what people say appears in this conversation.",
858
- className: "mx-auto my-8 max-w-xl",
859
- testId: "call-conversation-empty"
845
+ onStart: async () => {
846
+ if (liveCallId && !canHost) return openCall(arbi, { callExtId: liveCallId });
847
+ if (isLive) return startChatCall(arbi, { chatExtId, title, ring: [] });
848
+ setPicking(true);
849
+ return true;
850
+ },
851
+ isLive,
852
+ label: "Call the group",
853
+ testId: "group-call-button"
860
854
  }
861
- );
862
- }
863
- return /* @__PURE__ */ jsx(
864
- EmptyState,
865
- {
866
- icon: transcribing ? Captions : CaptionsOff,
867
- title: "Call in progress \u2014 the transcript will appear here",
868
- description: transcribing ? "Transcription is on: what people say shows up here as they speak." : "Transcription is off. The host can turn it on with CC in the call.",
869
- className: "mx-auto my-8 max-w-xl",
870
- testId: "call-conversation-empty"
871
- }
872
- );
873
- }
874
- function ProvisionalRow({ identity }) {
875
- const name = useCallPersonName(identity);
876
- return /* @__PURE__ */ jsx(
877
- ProvisionalTranscriptLine,
878
- {
879
- speakerId: identity,
880
- speakerName: name,
881
- speaker: /* @__PURE__ */ jsx(CallUserPill, { identity })
882
- }
883
- );
884
- }
885
- function ProvisionalSpeakers({ ids }) {
886
- if (!ids.length) return null;
887
- return /* @__PURE__ */ jsx("div", { className: "pb-2", "data-testid": "transcript-provisional-rows", children: ids.map((id) => /* @__PURE__ */ jsx(ProvisionalRow, { identity: id }, id)) });
855
+ ),
856
+ picking && /* @__PURE__ */ jsx(
857
+ CallPeoplePicker,
858
+ {
859
+ open: true,
860
+ onOpenChange: setPicking,
861
+ scope: "chat",
862
+ members: others,
863
+ preselected: others.map((u) => u.external_id),
864
+ onSubmit: async ({ ring }) => {
865
+ await startChatCall(arbi, { chatExtId, title, ring });
866
+ }
867
+ }
868
+ )
869
+ ] });
888
870
  }
889
- var LINE_WAIT_MS = 1e4;
890
- var MIN_SPEECH_MS = 800;
891
- function useProvisionalSpeakers(callExtId, lines) {
892
- const { inCall, transcribing } = useMyCallStatus(callExtId);
893
- const speaking = useSpeakingIds();
894
- const pending = useRef(/* @__PURE__ */ new Map());
895
- const [, tick] = useState(0);
896
- const active = inCall && transcribing;
897
- const now = Date.now();
898
- if (active) {
899
- for (const id of speaking) {
900
- const p = pending.current.get(id);
901
- if (p) p.lastHeardAt = now;
902
- else pending.current.set(id, { startedAt: now, lastHeardAt: now });
903
- }
904
- } else if (pending.current.size) {
905
- pending.current.clear();
906
- }
907
- const latest = useMemo(() => {
908
- const m = /* @__PURE__ */ new Map();
909
- for (const l of lines) m.set(l.speakerId, Math.max(m.get(l.speakerId) ?? 0, l.at));
910
- return m;
911
- }, [lines]);
912
- for (const [id, p] of [...pending.current]) {
913
- const arrived = (latest.get(id) ?? 0) >= p.startedAt - 3e3;
914
- const stale = !speaking.includes(id) && now - p.lastHeardAt > LINE_WAIT_MS;
915
- if (arrived && !speaking.includes(id) || stale) pending.current.delete(id);
916
- else if (arrived) p.startedAt = now;
917
- }
918
- const waiting = pending.current.size > 0;
919
- useEffect(() => {
920
- if (!waiting) return;
921
- const id = setInterval(() => tick((n) => n + 1), 1e3);
922
- return () => clearInterval(id);
923
- }, [waiting]);
924
- return [...pending.current].filter(([, p]) => p.lastHeardAt - p.startedAt >= MIN_SPEECH_MS).map(([id]) => id);
871
+ function NewCallChip() {
872
+ return useCanHostCalls() ? /* @__PURE__ */ jsx(EnabledNewCallChip, {}) : null;
925
873
  }
926
- var { isTranscriptMessage, parseTranscriptContent } = chats;
927
- function ConnectedCallChatHistory({
928
- chat,
929
- messages,
930
- items,
931
- renderItem,
932
- loadOlder,
933
- memberById,
934
- senderName,
935
- myExtId,
936
- formatTime,
937
- formatDay
938
- }) {
874
+ function EnabledNewCallChip() {
939
875
  const arbi = useArbi();
940
- const [view, setView] = useState("all");
941
- useEffect(() => setView("all"), [chat?.external_id]);
942
- const recordingsCallExtId = chat?.call_ext_id ?? null;
943
- const pendingRecording = usePendingRecording();
944
- useEffect(() => {
945
- if (pendingRecording && recordingsCallExtId) setView("recordings");
946
- }, [pendingRecording, recordingsCallExtId]);
947
- const latestCallCards = useMemo(() => {
948
- const latest = /* @__PURE__ */ new Map();
949
- for (const m of messages)
950
- if (m.kind === "call" && m.call_ext_id && !m.deleted) latest.set(m.call_ext_id, m.external_id);
951
- return new Set(latest.values());
952
- }, [messages]);
953
- const hasRecTranscript = useMemo(
954
- () => hasRecordingTranscript(messages, chatTranscriptRef),
955
- [messages]
956
- );
957
- const recordingWindows = useRecordingWindows(recordingsCallExtId, hasRecTranscript);
958
- const supersededTranscript = useMemo(
959
- () => supersededTranscriptLines(messages, chatTranscriptRef, recordingWindows),
960
- [messages, recordingWindows]
961
- );
962
- const transcriptMessages = useMemo(
963
- () => sortTranscriptMessages(
964
- messages.filter((m) => isTranscriptMessage(m) && !m.deleted && !supersededTranscript.has(m))
876
+ const inCall = useCallSession((s) => s.active !== null);
877
+ const [open, setOpen] = useState(false);
878
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
879
+ /* @__PURE__ */ jsx(
880
+ DmInviteChip,
881
+ {
882
+ testId: "dm-new-call-chip",
883
+ title: "New call",
884
+ description: inCall ? "You are already in a call" : "Call people from your contacts",
885
+ onClick: () => !inCall && setOpen(true)
886
+ }
965
887
  ),
966
- [messages, supersededTranscript]
967
- );
968
- const hasTranscript = transcriptMessages.length > 0;
969
- const { data: meetingCall } = useCall(hasTranscript ? chat?.call_ext_id : null);
970
- const isHost = !!meetingCall?.is_creator;
971
- const [meetingTitle, setMeetingTitle] = useState(null);
972
- useEffect(() => {
973
- if (!chat || !isHost && !recordingsCallExtId) return;
974
- let cancelled = false;
975
- arbi.chats.title(chat).then((t) => !cancelled && setMeetingTitle(t)).catch(() => void 0);
976
- return () => {
977
- cancelled = true;
978
- };
979
- }, [arbi, isHost, chat, recordingsCallExtId]);
980
- const transcriptSpeaker = (msg) => {
981
- const parsed = msg.decryptFailed ? null : parseTranscriptContent(msg.content);
982
- return { parsed, speakerId: parsed?.speaker_identity || msg.sender_ext_id || "" };
888
+ open && /* @__PURE__ */ jsx(
889
+ CallPeoplePicker,
890
+ {
891
+ open: true,
892
+ onOpenChange: setOpen,
893
+ scope: "contacts",
894
+ onSubmit: async ({ ring, invites }) => {
895
+ await startContactsCall(arbi, {
896
+ title: contactsCallTitle([
897
+ ...ring.map((u) => getUserDisplayName(u)),
898
+ ...invites.map((g) => g.name)
899
+ ]),
900
+ ring,
901
+ invites
902
+ });
903
+ }
904
+ }
905
+ )
906
+ ] });
907
+ }
908
+ function ScheduleCallButton({
909
+ conversationId,
910
+ peer,
911
+ variant = "icon",
912
+ className
913
+ }) {
914
+ const callsEnabled = useCanHostCalls();
915
+ const { conversations } = useConversationItems();
916
+ const { data: workspaces = [] } = useWorkspaces();
917
+ const { workspaceId: selectedWorkspaceId, userId: me } = useConnectedWorkspace();
918
+ const { openCalendar } = useBlocksEnvironment();
919
+ if (!callsEnabled) return null;
920
+ const open = () => {
921
+ const conversation = conversationId ? conversations?.find((c) => c.external_id === conversationId) : void 0;
922
+ let people = [];
923
+ if (peer) people = [peer];
924
+ else if (conversation?.is_shared) {
925
+ const workspace = workspaces.find((w) => w.external_id === selectedWorkspaceId);
926
+ people = (workspace?.users ?? []).map((wu) => wu.user);
927
+ }
928
+ requestCalendarComposer({
929
+ title: peer ? `Call with ${getUserDisplayName(peer) || peer.email}` : conversation?.title ?? "",
930
+ conversationId: conversationId ?? null,
931
+ attendees: people.filter((u) => !isAgent(u) && u.external_id !== me).map((u) => ({
932
+ email: u.email,
933
+ user_id: u.external_id,
934
+ name: getUserDisplayName(u) || null
935
+ })),
936
+ addCall: true
937
+ });
938
+ openCalendar();
983
939
  };
984
- const transcriptExport = useMemo(
985
- () => transcriptMessages.filter((m) => !m.decryptFailed).map((m) => {
986
- const parsed = parseTranscriptContent(m.content);
987
- const speakerId = parsed?.speaker_identity || m.sender_ext_id || "";
988
- return {
989
- speaker: parsed?.speaker_name || (memberById(speakerId) ? senderName(speakerId) : "") || "Former member",
990
- at: parsed?.started_at ?? m.created_at,
991
- text: parsed?.text ?? m.content ?? ""
992
- };
993
- }),
994
- [transcriptMessages, memberById, senderName]
940
+ return /* @__PURE__ */ jsxs(
941
+ Button,
942
+ {
943
+ type: "button",
944
+ variant: "ghost",
945
+ size: variant === "icon" ? "icon" : "sm",
946
+ className: cn(variant === "icon" && "size-8", className),
947
+ onClick: open,
948
+ "aria-label": "Schedule call",
949
+ title: "Schedule call",
950
+ "data-testid": "schedule-call-button",
951
+ children: [
952
+ /* @__PURE__ */ jsx(CalendarPlus, { className: "size-4" }),
953
+ variant === "label" && /* @__PURE__ */ jsx("span", { className: "ml-1.5", children: "Schedule call" })
954
+ ]
955
+ }
995
956
  );
996
- const transcriptLayout = useMemo(() => {
997
- const shown = view === "transcript" ? transcriptMessages : messages;
998
- const continued = transcriptRuns(
999
- shown,
1000
- (m) => isTranscriptMessage(m) && !supersededTranscript.has(m) ? transcriptSpeaker(m).speakerId : null,
1001
- (m) => Date.parse(transcriptSpeaker(m).parsed?.started_at ?? m.created_at ?? "") || 0
1002
- );
1003
- return { continued };
1004
- }, [view, messages, transcriptMessages, supersededTranscript]);
1005
- const transcriptEntry = (msg) => {
1006
- const { parsed, speakerId } = transcriptSpeaker(msg);
1007
- return /* @__PURE__ */ jsx(
1008
- TranscriptEntry,
957
+ }
958
+ function useMeetingThreadMessages(meetingCallExtId, messageHistory) {
959
+ const hasRecTranscript = useMemo(
960
+ () => !!meetingCallExtId && hasRecordingTranscript(messageHistory, threadTranscriptRef),
961
+ [meetingCallExtId, messageHistory]
962
+ );
963
+ const recordingWindows = useRecordingWindows(meetingCallExtId, hasRecTranscript);
964
+ return useMemo(
965
+ () => hasRecTranscript ? supersedeLiveTranscript(messageHistory, threadTranscriptRef, recordingWindows) : messageHistory,
966
+ [hasRecTranscript, messageHistory, recordingWindows]
967
+ );
968
+ }
969
+ function ThreadCallButton({
970
+ conversationId
971
+ }) {
972
+ const arbi = useArbi();
973
+ const { data: conversations } = useConnectedConversations();
974
+ const { workspaceId: workspaceExtId } = useConnectedWorkspace();
975
+ const liveCallId = useCallSession((s) => threadLiveCallId(s, conversationId));
976
+ const isLive = liveCallId !== null;
977
+ const canHost = useCanHostCalls();
978
+ const [picking, setPicking] = useState(false);
979
+ const conversation = conversations.find((c) => c.external_id === conversationId);
980
+ if (!conversation?.is_shared) return null;
981
+ const title = conversation.title ?? null;
982
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
983
+ /* @__PURE__ */ jsx(
984
+ StartCallButton,
1009
985
  {
1010
- speakerId,
1011
- speakerName: speakerId === myExtId ? "Me" : parsed?.speaker_name || (memberById(speakerId) ? senderName(speakerId) : senderName(msg.sender_ext_id)),
1012
- text: parsed?.text ?? msg.content ?? "",
1013
- at: parsed?.started_at ?? msg.created_at,
1014
- continued: transcriptLayout.continued.has(msg)
1015
- },
1016
- msg.external_id
1017
- );
1018
- };
1019
- const chatLines = useMemo(
1020
- () => transcriptMessages.map((m) => {
1021
- const { parsed, speakerId } = transcriptSpeaker(m);
1022
- return { speakerId, at: Date.parse(parsed?.started_at ?? m.created_at ?? "") || 0 };
986
+ onStart: async () => {
987
+ if (liveCallId && !canHost) return openCall(arbi, { callExtId: liveCallId });
988
+ if (isLive) return startThreadCall(arbi, { threadExtId: conversationId, title, ring: [] });
989
+ setPicking(true);
990
+ return true;
991
+ },
992
+ isLive,
993
+ label: "Call thread members",
994
+ testId: "thread-call-button"
995
+ }
996
+ ),
997
+ picking && /* @__PURE__ */ jsx(
998
+ CallPeoplePicker,
999
+ {
1000
+ open: true,
1001
+ onOpenChange: setPicking,
1002
+ scope: "workspace",
1003
+ workspaceExtId,
1004
+ onSubmit: async ({ ring }) => {
1005
+ await startThreadCall(arbi, { threadExtId: conversationId, title, ring });
1006
+ }
1007
+ }
1008
+ )
1009
+ ] });
1010
+ }
1011
+ function MeetingThreadTranscriptAction({
1012
+ conversationExtId,
1013
+ meetingCallExtId,
1014
+ title,
1015
+ messages
1016
+ }) {
1017
+ const { data: call } = useCall(meetingCallExtId);
1018
+ const { data: workspaceUsers = [] } = useWorkspaceUsers();
1019
+ const lines = useMemo(
1020
+ () => threadTranscriptLines(messages, (id) => {
1021
+ const user = workspaceUsers.find((wu) => wu.user.external_id === id)?.user;
1022
+ return user ? getUserDisplayName(user) : null;
1023
1023
  }),
1024
- [transcriptMessages]
1024
+ [messages, workspaceUsers]
1025
1025
  );
1026
- const provisionalSpeakers = useProvisionalSpeakers(recordingsCallExtId, chatLines);
1027
- const filterOptions = [
1028
- { value: "all", label: "All" },
1029
- { value: "chat", label: "Chat", testId: "chat-filter" },
1026
+ if (!meetingCallExtId || !call?.is_creator) return null;
1027
+ return /* @__PURE__ */ jsx(
1028
+ SaveTranscriptToThread,
1030
1029
  {
1031
- value: "transcript",
1032
- label: "Transcript",
1033
- count: transcriptMessages.length,
1034
- testId: "transcript-filter"
1035
- },
1036
- ...recordingsCallExtId ? [{ value: "recordings", label: "Recordings", testId: "recordings-filter" }] : []
1037
- ];
1030
+ title,
1031
+ lines,
1032
+ excludeConversationExtId: conversationExtId,
1033
+ iconOnly: true
1034
+ }
1035
+ );
1036
+ }
1037
+ function MeetingThreadRecordingsAction({
1038
+ meetingCallExtId,
1039
+ title
1040
+ }) {
1041
+ const [open, setOpen] = useState(false);
1042
+ const pending = usePendingRecording();
1043
+ useEffect(() => {
1044
+ if (pending) setOpen(true);
1045
+ }, [pending]);
1038
1046
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1039
- messages.length === 0 && recordingsCallExtId && /* @__PURE__ */ jsx(CallConversationEmpty, { callExtId: recordingsCallExtId }),
1040
- (hasTranscript || recordingsCallExtId) && /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 flex justify-center py-1", children: /* @__PURE__ */ jsx(
1041
- ConversationFilter,
1047
+ /* @__PURE__ */ jsx(
1048
+ HeaderAction,
1042
1049
  {
1043
- value: view,
1044
- options: filterOptions,
1045
- onChange: setView
1050
+ label: "Recordings",
1051
+ icon: /* @__PURE__ */ jsx(Disc, { className: "size-3.5", "aria-hidden": true }),
1052
+ iconOnly: true,
1053
+ onClick: () => setOpen(true),
1054
+ testId: "meeting-recordings"
1046
1055
  }
1047
- ) }),
1048
- view === "recordings" && recordingsCallExtId && /* @__PURE__ */ jsx(RecordingsList, { callExtId: recordingsCallExtId, title: meetingTitle, className: "py-1" }),
1049
- loadOlder,
1050
- view === "transcript" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col py-1", "data-testid": "transcript-view", children: [
1051
- isHost && /* @__PURE__ */ jsx(
1052
- SaveTranscriptToThread,
1056
+ ),
1057
+ open && /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (o) => !o && setOpen(false), children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-2xl", "data-testid": "meeting-recordings-dialog", children: [
1058
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
1059
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Recordings" }),
1060
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Recordings of this meeting, encrypted under the workspace key. Playing or downloading one decrypts it in this browser." })
1061
+ ] }),
1062
+ /* @__PURE__ */ jsx(
1063
+ RecordingsList,
1053
1064
  {
1054
- title: meetingTitle,
1055
- lines: transcriptExport,
1056
- className: "mx-3 mb-2 self-start"
1065
+ callExtId: meetingCallExtId,
1066
+ title,
1067
+ className: "max-h-[60vh] overflow-y-auto"
1057
1068
  }
1058
- ),
1059
- transcriptMessages.map((msg) => transcriptEntry(msg)),
1060
- /* @__PURE__ */ jsx(ProvisionalSpeakers, { ids: provisionalSpeakers })
1061
- ] }),
1062
- (view === "all" || view === "chat") && items.map((item, i) => {
1063
- const { message: msg, isFirstOfDay } = item;
1064
- const deleted = !!msg.deleted;
1065
- if (deleted && isTranscriptMessage(msg)) return null;
1066
- const separator = isFirstOfDay && /* @__PURE__ */ jsx(DateSeparator, { children: formatDay(msg.created_at) });
1067
- if (isTranscriptMessage(msg)) {
1068
- if (view === "chat") return null;
1069
- const superseded = supersededTranscript.has(msg);
1070
- if (superseded && !isFirstOfDay) return null;
1071
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1072
- separator,
1073
- !superseded && transcriptEntry(msg)
1074
- ] }, msg.external_id);
1075
- }
1076
- if (msg.kind === "recording" && msg.call_ext_id && !deleted) {
1077
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1078
- separator,
1079
- /* @__PURE__ */ jsx(
1080
- RecordingCard,
1081
- {
1082
- callExtId: msg.call_ext_id,
1083
- recordingExtId: msg.recording_ext_id ?? null,
1084
- title: meetingTitle,
1085
- time: formatTime(msg.created_at)
1086
- }
1087
- )
1088
- ] }, msg.external_id);
1069
+ )
1070
+ ] }) })
1071
+ ] });
1072
+ }
1073
+ function ConnectedConversationCallActions() {
1074
+ const conversationId = useArbiSessionState((state) => state.conversationId);
1075
+ const { conversations } = useConversationItems();
1076
+ const conversation = conversationId ? conversations.find((c) => c.external_id === conversationId) : void 0;
1077
+ const meetingCallExtId = conversation?.meeting_call_ext_id ?? null;
1078
+ const { messageHistory } = useConversationData();
1079
+ const threadMessages = useMeetingThreadMessages(meetingCallExtId, messageHistory);
1080
+ const meetingMessages = useMemo(
1081
+ () => meetingCallExtId ? threadMessages.map((m) => ({
1082
+ content: m.content,
1083
+ created_at: m.created_at,
1084
+ tools: m.tools ?? m.meta?.tools
1085
+ })) : [],
1086
+ [meetingCallExtId, threadMessages]
1087
+ );
1088
+ if (!conversationId) return null;
1089
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1090
+ /* @__PURE__ */ jsx(ThreadCallButton, { conversationId }),
1091
+ /* @__PURE__ */ jsx(ScheduleCallButton, { conversationId }),
1092
+ meetingCallExtId && /* @__PURE__ */ jsx(
1093
+ MeetingThreadTranscriptAction,
1094
+ {
1095
+ conversationExtId: conversationId,
1096
+ meetingCallExtId,
1097
+ title: conversation?.title,
1098
+ messages: meetingMessages
1089
1099
  }
1090
- if (msg.kind === "call" && msg.call_ext_id && !deleted) {
1091
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1092
- separator,
1093
- /* @__PURE__ */ jsx(
1094
- ChatCallCard,
1095
- {
1096
- callExtId: msg.call_ext_id,
1097
- senderName: msg.sender_ext_id ? senderName(msg.sender_ext_id) : null,
1098
- latest: latestCallCards.has(msg.external_id),
1099
- name: senderName,
1100
- time: formatTime(msg.created_at)
1101
- }
1102
- )
1103
- ] }, msg.external_id);
1100
+ ),
1101
+ meetingCallExtId && /* @__PURE__ */ jsx(
1102
+ MeetingThreadRecordingsAction,
1103
+ {
1104
+ meetingCallExtId,
1105
+ title: conversation?.title
1104
1106
  }
1105
- return renderItem(item, i);
1106
- }),
1107
- view === "all" && /* @__PURE__ */ jsx(ProvisionalSpeakers, { ids: provisionalSpeakers })
1107
+ )
1108
1108
  ] });
1109
1109
  }
1110
1110
 
1111
1111
  export { CallConversationEmpty, CallPeoplePicker, ChatCallCard, ConnectedCallChatHistory, ConnectedConversationCallActions, DmCallButton, GroupCallButton, MeetingThreadRecordingsAction, MeetingThreadTranscriptAction, NewCallChip, ProvisionalSpeakers, ScheduleCallButton, StartCallButton, ThreadCallButton, buildCallCandidates, callContactOf, isPickable, matchesQuery, useCallPersonName, useMeetingThreadMessages, useProvisionalSpeakers };
1112
- //# sourceMappingURL=chunk-UQ2OBHMV.js.map
1113
- //# sourceMappingURL=chunk-UQ2OBHMV.js.map
1112
+ //# sourceMappingURL=chunk-JUGEFTRC.js.map
1113
+ //# sourceMappingURL=chunk-JUGEFTRC.js.map