@xenon-device-management/xenon 2.14.0 → 2.16.0

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 (335) hide show
  1. package/README.md +44 -29
  2. package/lib/package.json +6 -5
  3. package/lib/public/assets/{AnnotationOverlay-B_VNtKBH.js → AnnotationOverlay-2W4JvEyZ.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-BwuaMjkX.js → ApiKeyGate-GmckX_7r.js} +1 -1
  5. package/lib/public/assets/DeviceMosaicView-DqODzkyo.js +2 -0
  6. package/lib/public/assets/{EmptyState-BSjhk_IV.js → EmptyState-7hETKEWb.js} +1 -1
  7. package/lib/public/assets/{FieldGroup-Cb5QZsE1.js → FieldGroup-GOgssFEP.js} +1 -1
  8. package/lib/public/assets/{FilterMenu-BAaSlWbW.js → FilterMenu-Yvf6RGMd.js} +1 -1
  9. package/lib/public/assets/{Menu-JGGYrSQQ.js → Menu-KrPnfdsf.js} +1 -1
  10. package/lib/public/assets/{Modal-CFCp3n9d.js → Modal-C6WjZFeI.js} +1 -1
  11. package/lib/public/assets/{RecordingPage-CbJj_EBT.js → RecordingPage-CSRIhZEh.js} +1 -1
  12. package/lib/public/assets/{RecordingsPage-BJeYnygR.js → RecordingsPage-CB8ST3nE.js} +1 -1
  13. package/lib/public/assets/{SegmentedControl-Bg42Atjy.js → SegmentedControl-C9o90KId.js} +1 -1
  14. package/lib/public/assets/{SettingCard-Njz1pUIJ.js → SettingCard-CPm3w9l2.js} +1 -1
  15. package/lib/public/assets/{Table-BcTm5xV2.js → Table-DwIgERRt.js} +1 -1
  16. package/lib/public/assets/{activity-DOVdsfMQ.js → activity-DrLbHifn.js} +1 -1
  17. package/lib/public/assets/ai-settings-DU_Ls06P.js +16 -0
  18. package/lib/public/assets/{api-keys-BKnNSTw9.js → api-keys-CWYdEzJ_.js} +1 -1
  19. package/lib/public/assets/{apps-D0SC6uw6.js → apps-BqNVdln7.js} +1 -1
  20. package/lib/public/assets/{arrow-left-MMbTmgoE.js → arrow-left-BtB0_Jg_.js} +1 -1
  21. package/lib/public/assets/{arrow-right-BMfnGmxW.js → arrow-right-C8NhdtyN.js} +1 -1
  22. package/lib/public/assets/{arrow-up-right-DuAftMWB.js → arrow-up-right-gMg0ffvv.js} +1 -1
  23. package/lib/public/assets/{auth-shell-nZC2JNDO.js → auth-shell-CiYZ3N4M.js} +2 -2
  24. package/lib/public/assets/{builds-page-hDCgTwkS.js → builds-page-EaaqDmLN.js} +1 -1
  25. package/lib/public/assets/{button-Dda4irn_.js → button-DDau4Axv.js} +1 -1
  26. package/lib/public/assets/{calendar-BMr0Q62v.js → calendar-DQ83B1ed.js} +1 -1
  27. package/lib/public/assets/{check-CZ9iw9-e.js → check-K1ovTQam.js} +1 -1
  28. package/lib/public/assets/{chevron-right-BRYJJVC9.js → chevron-right-Biq00pMt.js} +1 -1
  29. package/lib/public/assets/{circle-check-CqAJjQ5O.js → circle-check-CBv88cH7.js} +1 -1
  30. package/lib/public/assets/{circle-x-DlDJtnVz.js → circle-x-Bmltailn.js} +1 -1
  31. package/lib/public/assets/{clock-CpuDKysU.js → clock-BPrld-3h.js} +1 -1
  32. package/lib/public/assets/{copy-CAQydNFe.js → copy-t0QB14JI.js} +1 -1
  33. package/lib/public/assets/derive-UnLRp4r3.js +1 -0
  34. package/lib/public/assets/device-explorer-BgGZ8AvV.js +282 -0
  35. package/lib/public/assets/{download-C9XG6lLv.js → download-BroHxNOh.js} +1 -1
  36. package/lib/public/assets/forgot-password-Bjmf49i6.js +1 -0
  37. package/lib/public/assets/{h264Stream-VQBphYhg.js → h264Stream-DeD__6wa.js} +6 -6
  38. package/lib/public/assets/{index-DJGc1M2f.js → index-72oF89LJ.js} +1 -1
  39. package/lib/public/assets/index-B0JeuZbg.js +15 -0
  40. package/lib/public/assets/index-B0gQkEg0.js +219 -0
  41. package/lib/public/assets/{input-Cm0tmSoc.js → input-DIc4MSC7.js} +1 -1
  42. package/lib/public/assets/{line-chart-D2DyJCml.js → line-chart-BcWm2vW8.js} +1 -1
  43. package/lib/public/assets/{list-checks-UWf1GY99.js → list-checks-B06IJJad.js} +1 -1
  44. package/lib/public/assets/{lock-CCJv9m_I.js → lock-pVLvnN30.js} +1 -1
  45. package/lib/public/assets/{login-f8vOVW-U.js → login-CN77vy--.js} +1 -1
  46. package/lib/public/assets/{maintenance-settings-C1PrkuAH.js → maintenance-settings-CSdzQSkY.js} +1 -1
  47. package/lib/public/assets/{monitor-B2jDgwm7.js → monitor-BtrN5IkI.js} +1 -1
  48. package/lib/public/assets/{mouse-pointer-2-DLI1RtzU.js → mouse-pointer-2-CNsQfo6w.js} +1 -1
  49. package/lib/public/assets/{network-ebT86qoi.js → network-BSfA8ZxG.js} +1 -1
  50. package/lib/public/assets/{overview-C0aykVrd.js → overview-BfTebCK5.js} +1 -1
  51. package/lib/public/assets/{page-header-BjzgWz-T.js → page-header-D8KUX6Qg.js} +1 -1
  52. package/lib/public/assets/{play-DLG9t8FU.js → play-Cvc8xzic.js} +1 -1
  53. package/lib/public/assets/{plus-nBFSMu_X.js → plus-C9Zj84UF.js} +1 -1
  54. package/lib/public/assets/{profile-page-CSl0UYlv.js → profile-page-Bi27STgs.js} +1 -1
  55. package/lib/public/assets/{reset-password-LeOuHqmB.js → reset-password-CPsOuwSu.js} +1 -1
  56. package/lib/public/assets/runbook-page-DEQ-DBOX.js +304 -0
  57. package/lib/public/assets/{select-88jHcl-Z.js → select-BWpnhWXj.js} +1 -1
  58. package/lib/public/assets/{selector-detail-redirect-BbXKJjZM.js → selector-detail-redirect-BefSCSpe.js} +1 -1
  59. package/lib/public/assets/{selector-health-page-B4rA1Xkv.js → selector-health-page-MSk96Zz7.js} +1 -1
  60. package/lib/public/assets/session-detail-page-DPyBNef_.js +4 -0
  61. package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
  62. package/lib/public/assets/{settings-GF6ZMSQ6.js → settings-V6x-cXwy.js} +1 -1
  63. package/lib/public/assets/{stat-tile-BV98e_Sv.js → stat-tile-Duex-GZD.js} +1 -1
  64. package/lib/public/assets/{tablet-BiAsnFDj.js → tablet-BBcoqBmL.js} +1 -1
  65. package/lib/public/assets/{teams-B4-jrr-1.js → teams-ts8p5rEh.js} +1 -1
  66. package/lib/public/assets/{trash-2-C5RSuX0e.js → trash-2-CZ2ew6AZ.js} +1 -1
  67. package/lib/public/assets/{upload-BG7_rMXK.js → upload-Dv99y_le.js} +1 -1
  68. package/lib/public/assets/{use-builds-data-J-riQYyC.js → use-builds-data-DrYC8JCF.js} +1 -1
  69. package/lib/public/assets/{use-password-reset-mode-C8-eH-UG.js → use-password-reset-mode-C7QkBNkP.js} +1 -1
  70. package/lib/public/assets/{users-CFba5TSy.js → users-D7tKMESH.js} +1 -1
  71. package/lib/public/assets/{users-Cp0beJm3.js → users-gJ5Y01yo.js} +1 -1
  72. package/lib/public/assets/{video-off-DDsEISjV.js → video-off-CqZVuZGs.js} +1 -1
  73. package/lib/public/assets/{webhook-settings-BGu32vSB.js → webhook-settings-DI5DSW99.js} +1 -1
  74. package/lib/public/assets/{zap-l4VaXzq8.js → zap-ClcHFxfo.js} +1 -1
  75. package/lib/public/index.html +1 -1
  76. package/lib/schema.json +16 -12
  77. package/lib/src/InternalHttpClient.js +27 -5
  78. package/lib/src/XenonCapabilityManager.js +10 -3
  79. package/lib/src/app/dashboardPluginLink.js +117 -0
  80. package/lib/src/app/index.js +13 -34
  81. package/lib/src/app/openapi/control.yaml +6 -4
  82. package/lib/src/app/openapi/grid.yaml +8 -2
  83. package/lib/src/app/openapi/identity.yaml +103 -31
  84. package/lib/src/app/openapi/platform.yaml +33 -15
  85. package/lib/src/app/openapi/sessions.yaml +12 -3
  86. package/lib/src/app/routers/apikeys.js +14 -2
  87. package/lib/src/app/routers/auth.js +31 -6
  88. package/lib/src/app/routers/control.js +13 -5
  89. package/lib/src/app/routers/dashboard.js +42 -27
  90. package/lib/src/app/routers/grid.js +5 -3
  91. package/lib/src/app/routers/profile.js +21 -0
  92. package/lib/src/app/routers/users.js +12 -6
  93. package/lib/src/app/swagger.js +2 -0
  94. package/lib/src/app/ws/h264StreamWs.js +10 -1
  95. package/lib/src/app/ws/nodeSocketRelay.js +77 -11
  96. package/lib/src/config.js +1 -0
  97. package/lib/src/dashboard/commandLogFields.js +42 -0
  98. package/lib/src/dashboard/commands.js +5 -5
  99. package/lib/src/dashboard/event-manager.js +64 -204
  100. package/lib/src/dashboard/services/failure-analysis-service.js +118 -105
  101. package/lib/src/dashboard/services/failureCategories.js +264 -0
  102. package/lib/src/dashboard/sessionCommandSummary.js +39 -0
  103. package/lib/src/data-service/device-store.js +14 -1
  104. package/lib/src/data-service/prisma-store.js +19 -7
  105. package/lib/src/data-service/web-config-service.js +7 -0
  106. package/lib/src/device-managers/AndroidDeviceManager.js +61 -23
  107. package/lib/src/device-managers/IOSDeviceManager.js +10 -0
  108. package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
  109. package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
  110. package/lib/src/device-utils.js +29 -3
  111. package/lib/src/gateway/defaultGateway.js +1 -1
  112. package/lib/src/gateway/forwardToNode.js +6 -11
  113. package/lib/src/gateway/healReport.js +87 -0
  114. package/lib/src/gateway/hubSessionToken.js +27 -4
  115. package/lib/src/gateway/nodeWebDriverUrl.js +3 -5
  116. package/lib/src/gateway/sessionGateway.js +12 -2
  117. package/lib/src/generated/client/edge.js +4 -3
  118. package/lib/src/generated/client/index-browser.js +1 -0
  119. package/lib/src/generated/client/index.d.ts +32 -0
  120. package/lib/src/generated/client/index.js +4 -3
  121. package/lib/src/generated/client/package.json +1 -1
  122. package/lib/src/generated/client/schema.prisma +3 -0
  123. package/lib/src/generated/client/wasm.js +1 -0
  124. package/lib/src/helpers/outboundProxy.js +219 -0
  125. package/lib/src/index.js +21 -0
  126. package/lib/src/interceptors/CommandInterceptor.js +256 -196
  127. package/lib/src/interceptors/xenonScripts.js +28 -7
  128. package/lib/src/logger.js +3 -1
  129. package/lib/src/middleware/authMiddleware.js +15 -3
  130. package/lib/src/middleware/commandCaller.js +3 -3
  131. package/lib/src/middleware/verifyCredential.js +58 -1
  132. package/lib/src/plugin.js +16 -6
  133. package/lib/src/scripts/database-check.js +3 -3
  134. package/lib/src/scripts/initialize-database.js +35 -33
  135. package/lib/src/scripts/run-migrations.js +1079 -27
  136. package/lib/src/services/AICommandService.js +9 -2
  137. package/lib/src/services/AIService.js +192 -42
  138. package/lib/src/services/ApiKeyService.js +16 -5
  139. package/lib/src/services/EmailService.js +29 -0
  140. package/lib/src/services/InterceptorService.js +6 -2
  141. package/lib/src/services/OrphanSweeper.js +8 -0
  142. package/lib/src/services/SelectorStateService.js +11 -7
  143. package/lib/src/services/SelectorVerificationJob.js +21 -6
  144. package/lib/src/services/ServerManager.js +11 -1
  145. package/lib/src/services/SessionLifecycleService.js +118 -44
  146. package/lib/src/services/SocketClient.js +11 -0
  147. package/lib/src/services/SocketServer.js +380 -45
  148. package/lib/src/services/TeamService.js +5 -1
  149. package/lib/src/services/TracingService.js +171 -60
  150. package/lib/src/services/UserService.js +14 -3
  151. package/lib/src/services/UserSessionService.js +16 -4
  152. package/lib/src/services/bug-report/video-slice.js +4 -1
  153. package/lib/src/services/device-access/leaseOverride.js +7 -5
  154. package/lib/src/services/healing/FuzzyXmlHealingProvider.js +51 -0
  155. package/lib/src/services/healing/HealEtalonService.js +3 -2
  156. package/lib/src/services/healing/HealingOrchestrator.js +57 -72
  157. package/lib/src/services/healing/OcrHealingProvider.js +41 -64
  158. package/lib/src/services/healing/ResilioTreeHealingProvider.js +41 -50
  159. package/lib/src/services/healing/VisualAiHealingProvider.js +1 -1
  160. package/lib/src/services/healing/fingerprintIdentity.js +126 -0
  161. package/lib/src/services/healing/resilioPath.js +274 -0
  162. package/lib/src/services/identity/identityChanges.js +41 -0
  163. package/lib/src/services/logcat/SessionDeviceLogs.js +358 -0
  164. package/lib/src/services/logcat/deviceLogBook.js +164 -0
  165. package/lib/src/services/logcat/iosDriverLog.js +178 -0
  166. package/lib/src/services/ocr/ocrData.js +118 -0
  167. package/lib/src/services/ocr/vendor/LICENSE-tessdata.txt +202 -0
  168. package/lib/src/services/ocr/vendor/eng.traineddata.gz +0 -0
  169. package/lib/src/services/omni-vision/OmniVisionService.js +83 -56
  170. package/lib/src/services/passwordResetLink.js +50 -9
  171. package/lib/src/services/passwordResetMode.js +3 -1
  172. package/lib/src/services/publicUrl.js +45 -0
  173. package/lib/src/services/selector-health/SelectorVisibilityResolver.js +121 -0
  174. package/lib/src/services/session/sessionIdentity.js +5 -3
  175. package/lib/src/services/session/shutdownReason.js +26 -0
  176. package/lib/src/services/settings/aiEngineSettings.js +219 -0
  177. package/lib/src/services/token/JwtKeyService.js +8 -2
  178. package/lib/src/services/token/mcpTokenTtl.js +27 -0
  179. package/lib/src/services/token/mintedLifetime.js +51 -0
  180. package/lib/src/sessions/LocalSession.js +29 -1
  181. package/lib/src/sessions/RemoteSession.js +12 -2
  182. package/lib/src/sessions/SessionManager.js +4 -1
  183. package/lib/src/sessions/sessionMemory.js +25 -0
  184. package/lib/test/helpers/driver-log.js +38 -0
  185. package/lib/test/helpers/fake-ai-provider.js +188 -0
  186. package/lib/test/helpers/fake-http-proxy.js +106 -0
  187. package/lib/test/helpers/loki-stores.js +33 -0
  188. package/lib/test/helpers/otlp-probe.js +107 -0
  189. package/lib/test/helpers/page-sources.js +164 -0
  190. package/lib/test/helpers/scratch-database.js +57 -13
  191. package/lib/test/integration/androidDevices.spec.js +1 -1
  192. package/lib/test/integration/audit-ingest.spec.js +10 -10
  193. package/lib/test/integration/auth-flow.spec.js +9 -14
  194. package/lib/test/integration/auth-rate-limit.spec.js +6 -3
  195. package/lib/test/integration/bug-report-route.spec.js +22 -2
  196. package/lib/test/integration/forgot-password-rate-limit.spec.js +6 -3
  197. package/lib/test/integration/forgot-password.spec.js +17 -18
  198. package/lib/test/integration/ios/01iOSSimulator.spec.js +1 -1
  199. package/lib/test/integration/ios/02iOSDevices.spec.js +1 -1
  200. package/lib/test/integration/legacy-deprecation-headers.spec.js +5 -2
  201. package/lib/test/integration/migration.spec.js +6 -1
  202. package/lib/test/integration/node-pair-auth-rest.spec.js +5 -9
  203. package/lib/test/integration/ports-router.spec.js +4 -4
  204. package/lib/test/integration/prisma-device-store-add-devices.spec.js +2 -3
  205. package/lib/test/integration/prisma-device-store-team-filter.spec.js +2 -5
  206. package/lib/test/integration/profile-tokens.spec.js +10 -16
  207. package/lib/test/integration/recordings-router.spec.js +16 -16
  208. package/lib/test/integration/reset-revokes-sessions.spec.js +4 -9
  209. package/lib/test/integration/role-matrix.spec.js +4 -9
  210. package/lib/test/integration/sdk-leases-router.spec.js +9 -9
  211. package/lib/test/integration/sdk-version.spec.js +2 -2
  212. package/lib/test/integration/selector-health-endpoint.spec.js +5 -5
  213. package/lib/test/integration/selector-state-flow.spec.js +5 -5
  214. package/lib/test/integration/team-membership-migration.spec.js +73 -12
  215. package/lib/test/integration/team-visibility-apps.spec.js +15 -22
  216. package/lib/test/integration/team-visibility-control.spec.js +96 -69
  217. package/lib/test/integration/team-visibility-dashboard.spec.js +7 -23
  218. package/lib/test/integration/team-visibility-grid.spec.js +16 -15
  219. package/lib/test/integration/team-visibility-leases.spec.js +8 -11
  220. package/lib/test/integration/team-visibility-recordings.spec.js +6 -19
  221. package/lib/test/integration/team-visibility-sessions.spec.js +6 -19
  222. package/lib/test/integration/users-flow.spec.js +7 -12
  223. package/lib/test/integration/users-lockout.spec.js +5 -3
  224. package/lib/test/unit/EmailService.test.js +66 -0
  225. package/lib/test/unit/ResilioTreeHealingProvider.test.js +46 -19
  226. package/lib/test/unit/XenonCapabilityManager.spec.js +23 -0
  227. package/lib/test/unit/ai-call-time-limit.spec.js +130 -0
  228. package/lib/test/unit/ai-engine-settings.spec.js +338 -0
  229. package/lib/test/unit/ai-provider-switch.spec.js +130 -0
  230. package/lib/test/unit/ai-rate-limit.spec.js +126 -0
  231. package/lib/test/unit/android-adb-retry.spec.js +112 -0
  232. package/lib/test/unit/android-h264-viewers.spec.js +272 -0
  233. package/lib/test/unit/android-preview-hold.spec.js +3 -2
  234. package/lib/test/unit/apikeys-router.spec.js +84 -0
  235. package/lib/test/unit/authMiddleware.bearer.spec.js +9 -0
  236. package/lib/test/unit/authMiddleware.credentialLifetime.spec.js +149 -0
  237. package/lib/test/unit/bug-report/video-slice.spec.js +19 -0
  238. package/lib/test/unit/command-log-network-capture.spec.js +191 -0
  239. package/lib/test/unit/dashboard-events-device-logs.spec.js +255 -0
  240. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  241. package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
  242. package/lib/test/unit/database-check.spec.js +1 -0
  243. package/lib/test/unit/device-log-book.spec.js +180 -0
  244. package/lib/test/unit/device-utils.spec.js +13 -4
  245. package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
  246. package/lib/test/unit/event-log-session-data.spec.js +251 -0
  247. package/lib/test/unit/failure-analysis.spec.js +196 -0
  248. package/lib/test/unit/failure-categories.spec.js +130 -0
  249. package/lib/test/unit/failure-categorization.spec.js +393 -0
  250. package/lib/test/unit/fingerprint-identity.spec.js +433 -0
  251. package/lib/test/unit/h264-multiplexer.spec.js +46 -0
  252. package/lib/test/unit/heal-report.spec.js +71 -0
  253. package/lib/test/unit/healing-instrumentation.spec.js +6 -1
  254. package/lib/test/unit/healing-tier-gate.spec.js +74 -29
  255. package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
  256. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +243 -1
  257. package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
  258. package/lib/test/unit/ios-driver-log.spec.js +168 -0
  259. package/lib/test/unit/ios-simulator-session-readiness.spec.js +187 -0
  260. package/lib/test/unit/ios-simulator-session-teardown.spec.js +158 -0
  261. package/lib/test/unit/lease/lease-override.spec.js +23 -6
  262. package/lib/test/unit/lease/session-lease-proof.spec.js +16 -4
  263. package/lib/test/unit/logcat-ws.spec.js +78 -37
  264. package/lib/test/unit/logger-json-levels.spec.js +44 -0
  265. package/lib/test/unit/mcp-token-ttl.spec.js +38 -0
  266. package/lib/test/unit/observability-example-loki.spec.js +25 -0
  267. package/lib/test/unit/ocr-healing-tier-real-ocr.spec.js +113 -0
  268. package/lib/test/unit/ocr-healing-tier.spec.js +122 -0
  269. package/lib/test/unit/ocr-offline.spec.js +213 -0
  270. package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
  271. package/lib/test/unit/omni-vision-ios-points.spec.js +0 -2
  272. package/lib/test/unit/omni-vision.spec.js +5 -7
  273. package/lib/test/unit/openapi-for-site.spec.js +80 -0
  274. package/lib/test/unit/outbound-proxy.spec.js +320 -0
  275. package/lib/test/unit/package-engines.spec.js +27 -0
  276. package/lib/test/unit/passwordResetMode.test.js +10 -2
  277. package/lib/test/unit/port-exhaustion.spec.js +2 -1
  278. package/lib/test/unit/profile-router.test.js +122 -1
  279. package/lib/test/unit/proxy-option.spec.js +358 -0
  280. package/lib/test/unit/queue-requester.spec.js +1 -1
  281. package/lib/test/unit/reset-link-address.spec.js +266 -0
  282. package/lib/test/unit/reset-link.test.js +10 -4
  283. package/lib/test/unit/resilio-path-healing.spec.js +270 -0
  284. package/lib/test/unit/run-migrations-database.spec.js +807 -0
  285. package/lib/test/unit/run-migrations-fail-fast.test.js +41 -27
  286. package/lib/test/unit/run-migrations.test.js +864 -0
  287. package/lib/test/unit/selector-health-actions.spec.js +3 -1
  288. package/lib/test/unit/selector-learning-every-session.spec.js +179 -0
  289. package/lib/test/unit/selector-state-service.spec.js +72 -23
  290. package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
  291. package/lib/test/unit/selector-verification-job.spec.js +28 -19
  292. package/lib/test/unit/selector-verification-missed-regression.spec.js +2 -2
  293. package/lib/test/unit/selector-visibility-resolver.spec.js +209 -0
  294. package/lib/test/unit/self-healing-boot.spec.js +10 -5
  295. package/lib/test/unit/self-healing-setting.spec.js +7 -6
  296. package/lib/test/unit/session-app-resolution.spec.js +5 -1
  297. package/lib/test/unit/session-attribution-wiring.spec.js +41 -13
  298. package/lib/test/unit/session-create-credential-owner.spec.js +242 -0
  299. package/lib/test/unit/session-credential-strip.spec.js +1 -1
  300. package/lib/test/unit/session-device-logs-ios.spec.js +180 -0
  301. package/lib/test/unit/session-device-logs.spec.js +356 -0
  302. package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
  303. package/lib/test/unit/session-log-indexes.spec.js +64 -3
  304. package/lib/test/unit/socket-team-scope.integration.spec.js +749 -0
  305. package/lib/test/unit/socketServer.scopedEmit.spec.js +237 -5
  306. package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
  307. package/lib/test/unit/stream-start-pipelines.spec.js +72 -18
  308. package/lib/test/unit/token-mint-limits.spec.js +165 -0
  309. package/lib/test/unit/tracing-export-routing.spec.js +137 -0
  310. package/lib/test/unit/tracing-session-spans.spec.js +337 -0
  311. package/lib/test/unit/unexpected-shutdown-reason.spec.js +163 -0
  312. package/lib/test/unit/verify-credential.spec.js +33 -0
  313. package/lib/test/unit/vision-assertion.spec.js +3 -2
  314. package/lib/test/unit/web-config-service.spec.js +19 -0
  315. package/lib/tsconfig.tsbuildinfo +1 -1
  316. package/package.json +6 -5
  317. package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
  318. package/prisma/schema.prisma +3 -0
  319. package/schema.json +16 -12
  320. package/scripts/dev/readme-screenshots.js +276 -23
  321. package/scripts/dev/screenshot-fixtures/logcat.js +157 -0
  322. package/scripts/dev/screenshot-fixtures/phone-screen.html +469 -0
  323. package/scripts/dev/screenshot-fixtures/session.js +242 -0
  324. package/scripts/export-openapi.js +32 -0
  325. package/scripts/lib/openapi-for-site.js +79 -0
  326. package/lib/public/assets/BugReportButton-BAt0_pzq.js +0 -15
  327. package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +0 -2
  328. package/lib/public/assets/ai-settings-CsuJroRf.js +0 -21
  329. package/lib/public/assets/device-explorer-CdO6_nrn.js +0 -282
  330. package/lib/public/assets/forgot-password-CGnkRgE9.js +0 -1
  331. package/lib/public/assets/index-BbKEXUFH.js +0 -219
  332. package/lib/public/assets/runbook-page-CMMeQml9.js +0 -124
  333. package/lib/public/assets/session-detail-page-COL_RZxw.js +0 -4
  334. package/lib/src/scripts/prepare-prisma.js +0 -89
  335. /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Writes the server's OpenAPI document to website/openapi.json, for the
5
+ * documentation site's API reference. Run it with `npm run build:openapi`
6
+ * after changing src/app/swagger.ts or anything in src/app/openapi/.
7
+ *
8
+ * The committed file carries no version (see lib/openapi-for-site.js), so a
9
+ * release's version bump leaves it as it is. CI regenerates it and fails when
10
+ * it differs from what is committed (Schema Drift Check).
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ require('ts-node').register({ transpileOnly: true });
17
+
18
+ const { swaggerSpec } = require('../src/app/swagger');
19
+ const { forSite, assertNoRawDocumentLink } = require('./lib/openapi-for-site');
20
+ const { version } = require('../package.json');
21
+
22
+ const target = path.join(__dirname, '..', 'website', 'openapi.json');
23
+
24
+ const text = `${JSON.stringify(forSite(swaggerSpec, version), null, 2)}\n`;
25
+
26
+ // forSite rewrites the introduction's link to the raw document by its exact
27
+ // text. If the introduction is reworded, the link would stay and lead to the
28
+ // documentation site, so refuse to write the file.
29
+ assertNoRawDocumentLink(text);
30
+
31
+ fs.writeFileSync(target, text);
32
+ console.log(`wrote ${path.relative(process.cwd(), target)}`);
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * The OpenAPI document as the documentation site publishes it.
5
+ *
6
+ * The server's spec (src/app/swagger.ts) names the version it was built from,
7
+ * in `info.version` and again in the introduction. Committed as it is, the
8
+ * copy under website/ would change with every release and fail the drift
9
+ * check on a version bump alone. So the version is swapped for a token, and
10
+ * the site's build (website/scripts/generate.mjs) puts the current one back.
11
+ *
12
+ * The introduction also links to the raw document on the server it describes.
13
+ * On the documentation site that link would lead to the docs host, so it
14
+ * becomes a plain path, "on your server".
15
+ *
16
+ * Only `info` is touched: an example elsewhere that happens to show a version
17
+ * is an example, not this server's version.
18
+ */
19
+
20
+ const VERSION_TOKEN = '__XENON_VERSION__';
21
+
22
+ const RAW_DOCUMENT_LINK = '[`/xenon/api-docs.json`](/xenon/api-docs.json)';
23
+ const RAW_DOCUMENT_ON_YOUR_SERVER = '`/xenon/api-docs.json` on your server';
24
+
25
+ // Any Markdown link whose target is the raw document, however its text is
26
+ // worded: forSite rewrites only the one exact sentence above.
27
+ const RAW_DOCUMENT_LINK_PATTERN = /\]\(\s*<?\/xenon\/api-docs\.json[^)]*\)/;
28
+
29
+ function replaceInStrings(value, from, to) {
30
+ if (typeof value === 'string') return value.split(from).join(to);
31
+ if (Array.isArray(value)) return value.map((item) => replaceInStrings(item, from, to));
32
+ if (value && typeof value === 'object') {
33
+ const out = {};
34
+ for (const [key, item] of Object.entries(value)) out[key] = replaceInStrings(item, from, to);
35
+ return out;
36
+ }
37
+ return value;
38
+ }
39
+
40
+ /**
41
+ * @param {object} spec the OpenAPI document, as swaggerSpec holds it
42
+ * @param {string} version the version `spec.info` was built from
43
+ * @returns {object} a copy for the site; `spec` is not changed
44
+ */
45
+ function forSite(spec, version) {
46
+ if (typeof version !== 'string' || version === '') {
47
+ throw new Error(
48
+ `forSite needs the version spec.info was built from, got ${JSON.stringify(version)}`,
49
+ );
50
+ }
51
+ const copy = JSON.parse(JSON.stringify(spec));
52
+ if (copy.info) {
53
+ copy.info = replaceInStrings(copy.info, RAW_DOCUMENT_LINK, RAW_DOCUMENT_ON_YOUR_SERVER);
54
+ copy.info = replaceInStrings(copy.info, version, VERSION_TOKEN);
55
+ }
56
+ return copy;
57
+ }
58
+
59
+ /**
60
+ * Throws if the document still links to the raw document on the server it
61
+ * describes. On the documentation site that link leads to the docs host,
62
+ * where there is no such file. forSite rewrites the introduction's one known
63
+ * wording; this catches the introduction (or any description) being reworded.
64
+ *
65
+ * @param {string} text the document as JSON text, after forSite
66
+ */
67
+ function assertNoRawDocumentLink(text) {
68
+ const match = RAW_DOCUMENT_LINK_PATTERN.exec(text);
69
+ if (!match) return;
70
+ const around = text.slice(Math.max(0, match.index - 40), match.index + match[0].length);
71
+ throw new Error(
72
+ `The OpenAPI document still links to /xenon/api-docs.json (…${around}), which would lead to ` +
73
+ 'the documentation site, where there is no such file. In src/app/swagger.ts, write ' +
74
+ 'the sentence as plain code, or change RAW_DOCUMENT_LINK in ' +
75
+ 'scripts/lib/openapi-for-site.js to the new wording so forSite rewrites it.',
76
+ );
77
+ }
78
+
79
+ module.exports = { forSite, VERSION_TOKEN, assertNoRawDocumentLink };
@@ -1,15 +0,0 @@
1
- import{c as g,j as o,r as f,t as m,f as b}from"./index-BbKEXUFH.js";/**
2
- * @license lucide-react v0.555.0 - ISC
3
- *
4
- * This source code is licensed under the ISC license.
5
- * See the LICENSE file in the root directory of this source tree.
6
- */const y=[["path",{d:"M12 20v-9",key:"1qisl0"}],["path",{d:"M14 7a4 4 0 0 1 4 4v3a6 6 0 0 1-12 0v-3a4 4 0 0 1 4-4z",key:"uouzyp"}],["path",{d:"M14.12 3.88 16 2",key:"qol33r"}],["path",{d:"M21 21a4 4 0 0 0-3.81-4",key:"1b0z45"}],["path",{d:"M21 5a4 4 0 0 1-3.55 3.97",key:"5cxbf6"}],["path",{d:"M22 13h-4",key:"1jl80f"}],["path",{d:"M3 21a4 4 0 0 1 3.81-4",key:"1fjd4g"}],["path",{d:"M3 5a4 4 0 0 0 3.55 3.97",key:"1d7oge"}],["path",{d:"M6 13H2",key:"82j7cp"}],["path",{d:"m8 2 1.88 1.88",key:"fmnt4t"}],["path",{d:"M9 7.13V6a3 3 0 1 1 6 0v1.13",key:"1vgav8"}]],h=g("bug",y);/**
7
- * @license lucide-react v0.555.0 - ISC
8
- *
9
- * This source code is licensed under the ISC license.
10
- * See the LICENSE file in the root directory of this source tree.
11
- */const x=[["path",{d:"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",key:"1s2grr"}],["path",{d:"M20 2v4",key:"1rf3ol"}],["path",{d:"M22 4h-4",key:"gwowj6"}],["circle",{cx:"4",cy:"20",r:"2",key:"6kqj1y"}]],v=g("sparkles",x),j=({tone:e="neutral",children:r,title:s,className:t})=>o.jsx("span",{className:`pill pill-${e}${t?` ${t}`:""}`,title:s,children:r});async function k(e){const r=new URLSearchParams({mode:e.mode});e.mode==="slice"&&e.windowSec&&r.set("windowSec",String(e.windowSec));const s=`/xenon/api/sessions/${encodeURIComponent(e.sessionId)}/bug-report?${r.toString()}`,t=await fetch(s,{method:"POST"});if(!t.ok){let u=`HTTP ${t.status}`;try{const p=await t.json();p&&p.error&&(u=p.error)}catch{}throw new Error(u)}const a=t.headers.get("content-disposition")||"",n=/filename="([^"]+)"/.exec(a),d=n?n[1]:`bugreport-${e.sessionId}.zip`,i=await t.blob(),l=URL.createObjectURL(i),c=document.createElement("a");c.href=l,c.download=d,document.body.appendChild(c),c.click(),document.body.removeChild(c),URL.revokeObjectURL(l)}const M=({sessionId:e,mode:r,windowSec:s,variant:t})=>{const[a,n]=f.useState(!1),{toast:d}=m();async function i(){if(!a){n(!0);try{await k({sessionId:e,mode:r,windowSec:s}),d("Bug report downloaded","success")}catch(l){d(`Bug report failed: ${l.message}`,"error")}finally{n(!1)}}}return t==="floating"?o.jsxs("button",{type:"button",onClick:i,disabled:a,title:`Capture last ${s??60}s as bug report`,"aria-label":"Generate bug report",className:`fixed bottom-4 right-4 z-30 inline-flex items-center gap-2 px-3 py-2 rounded-full
12
- bg-[var(--surface-2)] border border-[var(--border)] text-xs text-[var(--text)]
13
- shadow-lg hover:bg-[var(--surface-raised)] disabled:opacity-50`,children:[a?o.jsx(b,{className:"h-3.5 w-3.5 animate-spin"}):o.jsx(h,{className:"h-3.5 w-3.5"}),"Bug report"]}):o.jsxs("button",{type:"button",onClick:i,disabled:a,title:"Download a zip of this session for ticketing",className:`inline-flex items-center gap-1.5 px-3 py-1.5 rounded text-xs
14
- bg-[var(--surface-2)] hover:bg-[var(--surface-raised)] border border-[var(--border)]
15
- text-[var(--text)] disabled:opacity-50`,children:[a?o.jsx(b,{className:"h-3.5 w-3.5 animate-spin"}):o.jsx(h,{className:"h-3.5 w-3.5"}),"Download bug report"]})};export{M as B,j as P,v as S};
@@ -1,2 +0,0 @@
1
- import{r as l,j as t,z as Oe,w as $e,X as T,F as Me,v as Pe,e as Le,$ as Be,T as He,a0 as ze}from"./index-BbKEXUFH.js";import{C as xe,p as We,c as Ue,W as Fe,M as Ke,A as Q,H as Ge,b as Ve,S as be,a as Ye,I as Xe,e as qe,u as Je,f as Ze,h as ve,d as Qe,g as et}from"./h264Stream-VQBphYhg.js";import{p as tt}from"./labels-C21SA4JG.js";import{C as rt}from"./circle-check-CqAJjQ5O.js";import{f as nt,A as st}from"./AnnotationOverlay-B_VNtKBH.js";import{V as ot}from"./video-off-DDsEISjV.js";import{P as at}from"./plus-nBFSMu_X.js";import{a as it}from"./SegmentedControl-Bg42Atjy.js";import{v as ct,a as lt,c as dt,s as ut,R as ft,b as ht,g as pt,d as le,e as mt}from"./recordings-BWe1ydXm.js";import{D as ne}from"./download-C9XG6lLv.js";import{B as de}from"./button-Dda4irn_.js";import{C as gt}from"./clock-CpuDKysU.js";import{a as xt}from"./page-header-BjzgWz-T.js";import"./utils-CDN07tui.js";function bt(e){switch(e){case"automation":return"In automation";case"manual_other":return"Manual control by another user";case"manual_self":return"On your grid";case"recording_other_group":return"Recording in another group";default:return e?"Busy":null}}const ue=[{id:"ios",label:"iOS",match:e=>e==="ios"},{id:"android",label:"Android",match:e=>e==="android"},{id:"tvos",label:"tvOS",match:e=>e==="tvos"},{id:"androidtv",label:"Android TV",match:e=>e==="androidtv"||e==="android-tv"},{id:"other",label:"Other",match:()=>!0}],Y={available:"bg-[var(--color-success)]",inUse:"bg-[var(--color-warning)]",offline:"bg-[rgb(var(--rgb-offline))]"},vt=[["available",Y.available],["in use",Y.inUse],["offline",Y.offline]];function fe(e){return tt(e)}function yt({devices:e,inMosaic:n,onToggle:r,status:a="ready"}){const[g,N]=l.useState(""),[x,w]=l.useState({}),m=l.useMemo(()=>{const c=g.trim().toLowerCase();return c?e.filter(b=>b.udid.toLowerCase().includes(c)||(b.name??"").toLowerCase().includes(c)||(b.altName??"").toLowerCase().includes(c)||(b.platform??"").toLowerCase().includes(c)):e},[e,g]),y=l.useMemo(()=>{const c=ue.map(d=>({id:d.id,label:d.label,rows:[]})),b=d=>c.find((R,v)=>ue[v].match(d))??c[c.length-1];for(const d of m)b((d.platform??"").toLowerCase()).rows.push(d);return c.filter(d=>d.rows.length>0)},[m]);if(e.length===0){const c=a==="loading"?"Loading devices…":a==="error"?"Couldn’t load devices. Retrying…":"No devices online.";return t.jsx("div",{role:"status",className:"text-xs text-[var(--text-dim)] p-3",children:c})}return t.jsxs("div",{className:"flex flex-col gap-2",children:[t.jsxs("div",{className:"relative",children:[t.jsx(Oe,{"aria-hidden":!0,size:12,className:"absolute left-2 top-1/2 -translate-y-1/2 text-[var(--text-dim)]"}),t.jsx("input",{type:"text",value:g,placeholder:"Filter devices...",onChange:c=>N(c.target.value),className:"w-full pl-7 pr-2 py-1.5 text-xs rounded border border-[var(--border)] bg-transparent placeholder:text-[var(--text-dim)] focus:outline-none focus:border-[var(--color-info)]"})]}),y.map(c=>{const b=x[c.id];return t.jsxs("section",{className:"flex flex-col gap-1",children:[t.jsxs("button",{type:"button","aria-expanded":!b,onClick:()=>w(d=>({...d,[c.id]:!d[c.id]})),className:"flex items-center justify-between w-full text-[11px] text-[var(--text-dim)] hover:text-[rgb(var(--rgb-fg))] px-1 py-0.5",children:[t.jsxs("span",{className:"flex items-center gap-1.5",children:[t.jsx($e,{"aria-hidden":!0,size:12,className:`transition-transform ${b?"-rotate-90":"rotate-0"}`}),c.label]}),t.jsx("span",{children:c.rows.length})]}),!b&&t.jsx("ul",{className:"flex flex-col gap-1 text-sm",children:c.rows.map(d=>{const R=d.busyReason==="manual_self",v=n.has(d.udid),p=!!d.busy&&!R&&!v||!!d.offline,E=d.offline?"Offline":bt(d.busyReason),z=!p,C=[d.name??d.udid,d.platform&&fe(d.platform)].concat(p&&E?[E]:[]).filter(Boolean).join(", ");return t.jsx("li",{children:t.jsxs("button",{type:"button",disabled:p,draggable:!p,onDragStart:A=>{A.dataTransfer.effectAllowed="copy",A.dataTransfer.setData("application/x-xenon-udid",d.udid),A.dataTransfer.setData("text/plain",d.udid)},onClick:()=>r(d.udid),"aria-label":C,"aria-pressed":v,className:`w-full flex items-center gap-2 px-2 py-1.5 rounded text-left border transition-colors ${p?"opacity-60 cursor-not-allowed border-transparent":v?"border-[rgb(var(--rgb-accent)/0.4)] bg-[rgb(var(--rgb-accent)/0.05)] hover:bg-[rgb(var(--rgb-accent)/0.1)]":"border-transparent hover:border-[var(--border)] hover:bg-[var(--surface-2)]"}`,title:p?`${E} — release first`:v?"Click to remove from the grid":"Click to add to the grid (or drag to a cell)",children:[v?t.jsx(rt,{"aria-hidden":!0,size:14,className:"shrink-0 text-[var(--color-accent-soft)]"}):t.jsx(xe,{"aria-hidden":!0,size:14,className:"shrink-0 text-[var(--text-dim)]"}),t.jsx("span",{"aria-hidden":!0,className:`inline-block w-1.5 h-1.5 rounded-full ${d.offline?Y.offline:z?Y.available:Y.inUse}`}),t.jsx("span",{className:"font-medium flex-1 truncate text-[13px]",children:d.name??d.udid}),d.platform&&t.jsx("span",{className:"text-[11px] px-1.5 py-0.5 rounded bg-[var(--surface-2)] text-[var(--text-dim)]",children:fe(d.platform)})]})},d.udid)})})]},c.id)}),g&&y.length===0&&t.jsxs("div",{className:"text-xs text-[var(--text-dim)] px-1 py-2",children:['No devices match "',g,'".']}),t.jsx("div",{className:"text-[10px] text-[var(--text-dim)] px-1 pt-2 flex items-center gap-3",children:vt.map(([c,b])=>t.jsxs("span",{className:"inline-flex items-center gap-1",children:[t.jsx("span",{className:`w-1.5 h-1.5 rounded-full inline-block ${b}`}),c]},c))})]})}const wt=[{id:"1",label:"1",icon:t.jsx(jt,{})},{id:"2x1",label:"2×1",icon:t.jsx(Et,{})},{id:"2x2",label:"2×2",icon:t.jsx(Rt,{})},{id:"3x2",label:"3×2",icon:t.jsx(St,{})},{id:"auto",label:"Auto",icon:t.jsx(Tt,{})}];function Nt({value:e,onChange:n}){return t.jsx("div",{role:"tablist",className:"inline-flex border border-[var(--border)] rounded-md overflow-hidden text-xs bg-[rgb(var(--rgb-white)/0.02)]",children:wt.map(r=>{const a=e===r.id;return t.jsxs("button",{role:"tab","aria-selected":a,onClick:()=>n(r.id),className:`flex items-center gap-1.5 px-2.5 py-1.5 border-r border-[var(--border)] last:border-r-0 transition-colors ${a?"bg-[var(--surface-2)] text-[rgb(var(--rgb-fg))]":"text-[var(--text-dim)] hover:bg-[var(--surface-2)] hover:text-[rgb(var(--rgb-fg))]"}`,title:`Layout: ${r.label}`,children:[t.jsx("span",{className:"w-3.5 h-3.5 inline-flex items-center justify-center",children:r.icon}),t.jsx("span",{children:r.label})]},r.id)})})}function M(e){return t.jsx("rect",{rx:1.2,ry:1.2,fill:"currentColor",...e})}function jt(){return t.jsx("svg",{viewBox:"0 0 14 14",className:"w-3.5 h-3.5",children:M({x:2,y:2,width:10,height:10})})}function Et(){return t.jsxs("svg",{viewBox:"0 0 14 14",className:"w-3.5 h-3.5",children:[M({x:1.5,y:3,width:5,height:8}),M({x:7.5,y:3,width:5,height:8})]})}function Rt(){return t.jsxs("svg",{viewBox:"0 0 14 14",className:"w-3.5 h-3.5",children:[M({x:1.5,y:1.5,width:5,height:5}),M({x:7.5,y:1.5,width:5,height:5}),M({x:1.5,y:7.5,width:5,height:5}),M({x:7.5,y:7.5,width:5,height:5})]})}function St(){return t.jsxs("svg",{viewBox:"0 0 14 14",className:"w-3.5 h-3.5",children:[M({x:1,y:1.5,width:3.3,height:5}),M({x:5.35,y:1.5,width:3.3,height:5}),M({x:9.7,y:1.5,width:3.3,height:5}),M({x:1,y:7.5,width:3.3,height:5}),M({x:5.35,y:7.5,width:3.3,height:5}),M({x:9.7,y:7.5,width:3.3,height:5})]})}function Tt(){return t.jsxs("svg",{viewBox:"0 0 14 14",className:"w-3.5 h-3.5",fill:"none",stroke:"currentColor",strokeWidth:"1.4",children:[t.jsx("rect",{x:"2",y:"2",width:"10",height:"10",rx:"1.5"}),t.jsx("circle",{cx:"7",cy:"7",r:"1.5",fill:"currentColor",stroke:"none"})]})}function Ct(e,n,r,a,g,N,x=!1){const w=nt(r.width,r.height,N);let m=(e-r.left-w.left)/w.width,y=(n-r.top-w.top)/w.height;return!(m>=0&&m<=1&&y>=0&&y<=1)&&!x?null:(m=Math.min(1,Math.max(0,m)),y=Math.min(1,Math.max(0,y)),{x:Math.round(m*a),y:Math.round(y*g)})}const Dt=5e3;function At(e,n){if(e.httpStatus===404)return{ended:!0,startedAt:n};if(e.httpStatus!==200)return{ended:!1,startedAt:n};const r=e.body??{};return r.status!=="running"?{ended:!0,startedAt:n}:n&&r.startedAt&&r.startedAt!==n?{ended:!0,startedAt:n}:{ended:!1,startedAt:n??r.startedAt}}function kt(e,n,r,a=Dt){const g=l.useRef(r);g.current=r,l.useEffect(()=>{if(!n)return;let N=!1,x=!1,w;const m=async()=>{if(!(x||N)){x=!0;try{const c=await fetch(`/xenon/api/control/${encodeURIComponent(e)}/stream/status`),b=c.ok?await c.json().catch(()=>{}):void 0;if(N)return;const d=At({httpStatus:c.status,body:b},w);w=d.startedAt,d.ended&&(N=!0,g.current())}catch{}finally{x=!1}}};m();const y=setInterval(m,a);return()=>{N=!0,clearInterval(y)}},[e,n,a])}const It=3,_t=45e3,he=[2e3,5e3,1e4];function Ot(e){const n=Math.max(0,Math.min(e,he.length-1));return he[n]}function $t(e){return e<It}function pe(e){var r;const n=(e.lastError||"").toLowerCase();if(/did not find test app|not installed|webdriveragentrunner|applicationverification/.test(n))return"WebDriverAgent is not installed on this device. Install and trust WDA, then retry.";if(/unsupported ios version|manualpairingtunnelstart/.test(n))return"This device’s iOS version is not supported for live streaming on this host.";if(/port range for purpose 'tunnel' is exhausted/.test(n))return"No free tunnel ports on this host (12100–12199 are in use). Stop another iPhone’s preview, then retry.";if(/go-ios tunnel .* exited before it was ready/.test(n)){const a=(r=/exited before it was ready \([^)]*\): (.+)$/.exec(e.lastError||""))==null?void 0:r[1];return`The device tunnel stopped while starting${a?` (go-ios: ${a.trim()})`:""}. Retry; if it keeps failing, unlock the iPhone and check it trusts this Mac.`}return/go-ios tunnel .* came up on port/.test(n)?"The device tunnel started on an unexpected port and was stopped. Retry.":/econnrefused|econnreset|connection reset|tunnel/.test(n)?"Lost the connection to the device tunnel. Reconnect the device, then retry.":e.lastError&&e.lastError.trim()?e.lastError.trim():"We couldn’t start the live stream for this device."}const Mt=10,Pt=500;function Lt({udid:e,name:n,recordingId:r,annotateMode:a,shape:g,color:N,aspect:x="9 / 16",screenWidth:w,screenHeight:m,platform:y,onAnnotation:c,overlayAnnotations:b,onOverlayAnnotationsChange:d,onRemove:R}){const[v,p]=l.useState("connecting"),[E,z]=l.useState(),C=l.useCallback((o,u)=>{if(!(o>0)||!(u>0))return;const _=o/u;z(k=>k!==void 0&&Math.abs(k-_)<.001?k:_)},[]),[A,O]=l.useState(0),[W,G]=l.useState(null),h=l.useRef(0),s=l.useRef(null),[i,f]=l.useState(""),[j,P]=l.useState([]),[U,I]=l.useState(!1),B=l.useRef(null),L=l.useRef(null),$=y==="android"||y==="androidtv"||y==="android-tv",S=y==="ios"||y==="tvos";l.useEffect(()=>{let o=!1;if(r){G(null),p("connecting"),O(Date.now()),T.startStream(e).catch(()=>{});return}const u=Me();return(async()=>{try{const _=encodeURIComponent(e),k=await fetch(`/xenon/api/control/${_}/stream/status`);if(!k.ok)return;const D=await k.json();if(We(y||"",D==null?void 0:D.type,u)!=="h264"||!(D!=null&&D.h264Path))return;const K=await Ue(e,D.h264Path);if(o||!K)return;G(K)}catch{}})(),()=>{o=!0}},[e,y,r]);const F=!a&&!!w&&!!m,te=(o,u,_,k=!1)=>Ct(o,u,_,w,m,E,k),je=(o,u,_)=>{const k=Date.now()+Math.random(),D=o-_.left,K=u-_.top;P(H=>[...H,{id:k,x:D,y:K}]),window.setTimeout(()=>{P(H=>H.filter(J=>J.id!==k))},500)},Ee=o=>{if(!F||v==="unavailable"||o.button!==0)return;const u=o.currentTarget;u.focus(),te(o.clientX,o.clientY,u.getBoundingClientRect())&&(u.setPointerCapture(o.pointerId),L.current={startX:o.clientX,startY:o.clientY,startTime:Date.now(),rect:u.getBoundingClientRect()},je(o.clientX,o.clientY,L.current.rect))},Re=async o=>{var ie,ce;const u=L.current;if(L.current=null,!u||!F)return;try{o.currentTarget.releasePointerCapture(o.pointerId)}catch{}const _=o.clientX-u.startX,k=o.clientY-u.startY,D=Math.hypot(_,k),K=Date.now()-u.startTime,H=te(u.startX,u.startY,u.rect,!0),J=D<Mt;try{if(J&&K<Pt)await T.tap(e,H.x,H.y);else if(J)await((ce=(ie=T).touchAndHold)==null?void 0:ce.call(ie,e,H.x,H.y,K));else{const Z=te(o.clientX,o.clientY,u.rect,!0);await T.swipe(e,H.x,H.y,Z.x,Z.y)}}catch(Z){console.error(`[DeviceTile] gesture failed for ${e}`,Z)}},Se=()=>{L.current=null},Te=async o=>{if(!F||v==="unavailable"||o.metaKey||o.ctrlKey||o.altKey)return;const u=o.key;try{if(u.length===1){o.preventDefault(),await T.typeText(e,u);return}if(u==="Backspace"){o.preventDefault(),$?await T.pressKey(e,67):await T.typeText(e,"\b");return}if(u==="Enter"){o.preventDefault(),$?await T.pressKey(e,66):await T.typeText(e,`
2
- `);return}if(u==="Tab"){o.preventDefault(),await T.typeText(e," ");return}u==="Escape"&&$&&(o.preventDefault(),await T.pressKey(e,Q.BACK))}catch(_){console.error(`[DeviceTile] keystroke "${u}" failed for ${e}`,_)}},Ce=()=>T.pressKey(e,$?Q.HOME:Xe.HOME).catch(o=>console.error(`[DeviceTile] home failed for ${e}`,o)),De=()=>T.pressKey(e,Q.BACK).catch(o=>console.error(`[DeviceTile] back failed for ${e}`,o)),Ae=()=>T.pressKey(e,Q.APP_SWITCH).catch(o=>console.error(`[DeviceTile] app-switch failed for ${e}`,o)),ke=async()=>{try{const o=await T.getScreenshot(e),u=(o==null?void 0:o.screenshot)??(o==null?void 0:o.value)??o;if(!u)return;const _=await fetch(`data:image/png;base64,${u}`).then(K=>K.blob()),k=URL.createObjectURL(_),D=document.createElement("a");D.href=k,D.download=`${n||e}-${Date.now()}.png`,document.body.appendChild(D),D.click(),D.remove(),setTimeout(()=>URL.revokeObjectURL(k),1e3)}catch(o){console.error(`[DeviceTile] screenshot failed for ${e}`,o)}};l.useEffect(()=>{console.log(`[DeviceTile] Rendering ${e} in state: ${v}`)},[e,v]);const Ie=`/xenon/api/control/${encodeURIComponent(e)}/stream?t=${A}`,ae=l.useCallback(async()=>{try{const o=await fetch(`/xenon/api/control/${encodeURIComponent(e)}/stream/status`);if(!o.ok)throw new Error(String(o.status));const u=await o.json();f(pe({lastError:u==null?void 0:u.lastError,status:u==null?void 0:u.status}))}catch{f(pe({}))}},[e]),re=l.useCallback(()=>{const o=h.current;$t(o)?(h.current=o+1,s.current&&clearTimeout(s.current),s.current=setTimeout(()=>{p("connecting"),O(Date.now())},Ot(o))):(p("unavailable"),ae())},[ae]);l.useEffect(()=>{if(v!=="connecting")return;const o=setTimeout(re,_t);return()=>clearTimeout(o)},[v,A,re]),kt(e,v==="live"&&!W,()=>{console.warn(`[DeviceTile] MJPEG stream ended for ${e}; reconnecting`),p("connecting"),O(Date.now())}),l.useEffect(()=>()=>{s.current&&clearTimeout(s.current)},[]);const _e=()=>{s.current&&clearTimeout(s.current),h.current=0,f(""),p("connecting"),O(Date.now())},X=!!r,q=n||e.slice(0,16)+"…";return t.jsxs("div",{className:"flex items-stretch justify-center w-full h-full min-h-0 min-w-0 gap-2",children:[t.jsxs("div",{className:"theme-dark relative bg-[var(--black)] rounded-lg overflow-hidden border border-[var(--border)] group max-h-full max-w-full self-center",style:{aspectRatio:x,height:"100%"},children:[v==="connecting"&&t.jsxs("div",{role:"status","aria-live":"polite",className:"flex flex-col items-center justify-center gap-1 px-4 text-center bg-[rgb(var(--rgb-well)/0.8)] text-[var(--text-dim)] absolute inset-0 z-20 pointer-events-none",children:[t.jsx("div",{"aria-hidden":!0,className:"w-10 h-10 mb-3 border-4 border-[var(--border)] border-t-[var(--text-dim)] rounded-full animate-spin"}),t.jsx("div",{className:"font-semibold text-[var(--text)]",children:"Starting stream…"}),t.jsx("div",{className:"text-xs text-[var(--text-dim)] truncate max-w-full",children:q}),t.jsx("div",{className:"text-[11px] mt-3 text-[var(--text-dim)] leading-snug",children:S?"iPhone streams can take up to 20 seconds to start.":"Connecting to the device…"})]}),v==="unavailable"&&t.jsx("div",{role:"alert",className:"flex items-center justify-center bg-[var(--black)] text-[var(--text-dim)] absolute inset-0 z-20",children:t.jsxs("div",{className:"text-center p-6",children:[t.jsx(ot,{"aria-hidden":!0,size:36,className:"mx-auto mb-3 text-[var(--text-dim)]"}),t.jsx("div",{className:"font-semibold text-[var(--text)]",children:"Connection failed"}),t.jsx("p",{className:"text-xs mt-2 text-[var(--text-dim)] leading-relaxed max-w-[240px] mx-auto",children:i||"We couldn’t start the live stream for this device."}),t.jsx("button",{type:"button",className:"mt-5 text-sm font-semibold px-4 py-2 rounded bg-[var(--color-accent)] hover:bg-[var(--color-accent-strong)] text-[var(--color-on-accent)] transition-colors",onClick:_e,children:"Retry"})]})}),W?t.jsx(Fe,{wsUrl:W,className:"absolute inset-0 w-full h-full object-contain bg-black select-none pointer-events-none",onReady:()=>p("live"),onFrameSize:C,onFatal:()=>{console.warn(`[DeviceTile] H.264 fatal for ${e}; falling back to MJPEG`),G(null),p("connecting"),O(Date.now())}}):t.jsx(Ke,{src:Ie,alt:q,className:"absolute inset-0 w-full h-full object-contain bg-black select-none pointer-events-none",draggable:!1,style:{opacity:v==="live"?1:0,transition:"opacity 0.3s ease-in-out"},onLoad:o=>{console.log(`[DeviceTile] Image LOADED for ${e}`),C(o.currentTarget.naturalWidth,o.currentTarget.naturalHeight),h.current=0,s.current&&clearTimeout(s.current),p("live")},onError:o=>{console.error(`[DeviceTile] Image ERROR for ${e}`,o),re()}},A),F&&v!=="unavailable"&&t.jsx("div",{ref:B,className:`absolute inset-0 z-[35] touch-none outline-none ${U?"ring-2 ring-[rgb(var(--rgb-accent)/0.6)] ring-inset rounded-lg":""}`,tabIndex:0,onPointerDown:Ee,onPointerUp:Re,onPointerCancel:Se,onKeyDown:Te,onFocus:()=>I(!0),onBlur:()=>I(!1)}),t.jsx("div",{className:"absolute inset-0 z-20 pointer-events-none overflow-hidden",children:j.map(o=>t.jsx("span",{className:"absolute block w-10 h-10 rounded-full border-2 border-[var(--color-accent-soft)] animate-ping",style:{left:o.x-20,top:o.y-20}},o.id))}),X&&t.jsx(st,{enabled:a,shape:g,color:N,committed:b,mediaAspect:E,onCommittedChange:d,onCommit:(o,u)=>c(r,o,u)}),X&&a&&v==="live"&&t.jsx("div",{className:"absolute top-3 left-14 right-3 z-30 flex justify-end pointer-events-none",children:t.jsx("span",{className:"px-2 py-0.5 rounded-sm bg-[var(--color-info)] text-white text-[10px] font-semibold shadow-lg whitespace-nowrap overflow-hidden text-ellipsis",children:"Annotating · Esc to stop"})}),X&&t.jsx("div",{className:"absolute top-3 left-3 z-30 pointer-events-none",children:t.jsx("span",{className:"px-2 py-0.5 rounded-sm bg-[var(--red-600)] text-white text-[9px] font-black shadow-lg",children:"REC"})}),t.jsxs("div",{className:"absolute top-3 left-3 z-30 flex flex-col gap-1.5 pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity duration-150",children:[v==="live"&&t.jsx("span",{className:"px-2 py-0.5 rounded-sm bg-[var(--color-success)] text-[var(--color-on-success)] text-[11px] font-semibold shadow-lg w-fit",children:"Live"}),t.jsx("span",{className:"px-2 py-1 rounded bg-[rgb(var(--rgb-well)/0.8)] text-[var(--text)] text-[11px] font-medium backdrop-blur-md border border-[rgb(var(--rgb-fg)/0.1)] shadow-xl truncate max-w-[180px]",children:q})]})]}),t.jsx(Bt,{streamState:v,recording:X,isIOS:S,isAndroid:$,onClose:!X&&R?()=>R(e):void 0,sendHome:Ce,sendBack:De,sendAppSwitch:Ae,captureScreenshot:ke,displayName:q})]})}const ee="w-7 h-7 flex items-center justify-center rounded-md hover:bg-[var(--surface-2)] hover:text-[var(--text)] transition-colors";function Bt({streamState:e,recording:n,isIOS:r,isAndroid:a,onClose:g,sendHome:N,sendBack:x,sendAppSwitch:w,captureScreenshot:m,displayName:y}){const c=e==="live";return!g&&!c?null:t.jsxs("div",{className:"self-stretch w-9 flex flex-col items-center justify-between gap-1 py-2 rounded-lg bg-[var(--surface)] border border-[var(--border)] text-[var(--text-dim)]",children:[t.jsx("div",{className:"flex flex-col items-center gap-1",children:g&&t.jsx("button",{type:"button","aria-label":`Remove ${y}`,title:"Remove from the grid",onClick:g,className:"w-7 h-7 flex items-center justify-center rounded-md hover:bg-[rgb(var(--rgb-red)/0.15)] hover:text-[var(--color-danger)] transition-colors",children:t.jsx(Pe,{"aria-hidden":!0,size:16})})}),c&&t.jsxs("div",{className:"flex flex-col items-center gap-1.5 my-auto",children:[(a||r)&&t.jsx("button",{type:"button","aria-label":"Home",title:"Home",onClick:N,className:ee,children:t.jsx(Ge,{"aria-hidden":!0,size:15})}),a&&t.jsx("button",{type:"button","aria-label":"Back",title:"Back",onClick:x,className:ee,children:t.jsx(Ve,{"aria-hidden":!0,size:16})}),a&&t.jsx("button",{type:"button","aria-label":"Recent apps",title:"Recent apps",onClick:w,className:ee,children:t.jsx(be,{"aria-hidden":!0,size:13})})]}),c&&t.jsx("div",{className:"flex flex-col items-center gap-1",children:t.jsx("button",{type:"button","aria-label":"Screenshot",title:"Screenshot",onClick:m,className:ee,children:t.jsx(Ye,{"aria-hidden":!0,size:15})})})]})}const Ht={1:"1fr","2x1":"1fr 1fr","2x2":"1fr 1fr","3x2":"1fr 1fr 1fr"},zt={1:"1fr","2x1":"1fr","2x2":"1fr 1fr","3x2":"1fr 1fr"},Wt={1:1,"2x1":2,"2x2":4,"3x2":6};function Ut({layout:e,tiles:n,annotateMode:r,shape:a,color:g,onAnnotation:N,overlayAnnotationsByRecording:x,onOverlayAnnotationsChange:w,onRemove:m,onDropDevice:y}){const c=qe(e,n.length),b=Wt[c];n.length;const d=n.length,R=e==="auto"?Math.max(d,1):b,v=Math.max(0,R-d);return t.jsxs("div",{style:{display:"grid",gap:8,gridTemplateColumns:Ht[c],gridTemplateRows:zt[c]},className:"h-full",children:[n.map(p=>t.jsx(Lt,{udid:p.udid,name:p.name,mjpegPort:p.mjpegPort,recordingId:p.recordingId,aspect:p.aspect,screenWidth:p.screenWidth,screenHeight:p.screenHeight,platform:p.platform,annotateMode:r,shape:a,color:g,overlayAnnotations:p.recordingId?x==null?void 0:x[p.recordingId]:void 0,onOverlayAnnotationsChange:p.recordingId&&w?E=>w(p.recordingId,E):void 0,onAnnotation:N,onRemove:m},p.udid)),Array.from({length:v},(p,E)=>t.jsx(Ft,{onDropDevice:y},`empty-${E}`))]})}function Ft({onDropDevice:e}){const[n,r]=l.useState(!1);return t.jsxs("div",{onDragOver:a=>{e&&(a.preventDefault(),a.dataTransfer.dropEffect="copy",n||r(!0))},onDragLeave:()=>r(!1),onDrop:a=>{if(r(!1),!e)return;a.preventDefault();const g=a.dataTransfer.getData("application/x-xenon-udid")||a.dataTransfer.getData("text/plain");g&&e(g)},className:`flex flex-col items-center justify-center gap-2 rounded-lg border border-dashed transition-colors ${n?"border-[rgb(var(--rgb-accent)/0.6)] bg-[rgb(var(--rgb-accent)/0.05)]":"border-[var(--border)] bg-[rgb(var(--rgb-fg)/0.02)]"} text-[var(--text-dim)] min-h-[120px]`,children:[t.jsx("div",{"aria-hidden":"true",className:`w-8 h-8 rounded-full flex items-center justify-center ${n?"bg-[rgb(var(--rgb-accent)/0.15)] text-[var(--color-accent-soft)]":"bg-[rgb(var(--rgb-fg)/0.05)] text-[rgb(var(--rgb-fg)/0.6)]"}`,children:t.jsx(at,{size:16})}),t.jsx("div",{className:"text-xs font-medium",children:"Drag a device here"}),t.jsx("div",{className:"text-[10px] opacity-70",children:"or click one in the list"})]})}function Kt(e,n){return n==="starting"?"Starting…":e<=1?"Record":`Record ${e} devices`}const V={"aria-hidden":!0,size:14,className:"shrink-0"};function Gt({selectedUdids:e,onClearMarks:n}){const{state:r,dispatch:a}=Je(),[g,N]=l.useState(()=>Date.now());l.useEffect(()=>{if(r.recordingPhase!=="recording"&&r.recordingPhase!=="stopping")return;N(Date.now());const h=window.setInterval(()=>N(Date.now()),250);return()=>window.clearInterval(h)},[r.recordingPhase]);const x=r.startedAt&&(r.recordingPhase==="recording"||r.recordingPhase==="stopping")?g-r.startedAt:0,w=r.recordingPhase==="starting"||r.recordingPhase==="stopping",m=r.recordingPhase==="idle"&&e.length>0&&!w,y=r.recordingPhase==="recording"||r.recording&&r.tiles.some(h=>!!h.recordingId),c=y&&!w,b=c,d=b&&r.annotateMode,R=!!r.groupId&&r.recordingPhase==="idle",v=e.length>1||r.downloadableVideoCount>1,p=R&&r.compositeEnabled,E=R&&!r.compositeEnabled&&(r.downloadableVideoCount===1||r.downloadableVideoCount===0&&e.length<=1),z=async()=>{var h;if(m){a({type:"SET_RECORDING_PHASE",phase:"starting"}),a({type:"SET_BANNER",banner:null});try{const s=await ut(e);if(!((h=s.recordings)!=null&&h.length)){a({type:"SET_RECORDING_PHASE",phase:"idle"}),a({type:"SET_BANNER",banner:{tone:"error",message:"Recording did not start. Check that live preview is working, then try again."}});return}const i={};for(const j of s.recordings)i[j.udid]=j.id;const f=e.length-s.recordings.length;a({type:"START_RECORDING",groupId:s.groupId,startedAt:Date.now(),tileIds:i,compositeEnabled:s.compositeEnabled===!0}),f>0?a({type:"SET_BANNER",banner:{tone:"error",message:`Recording started on ${s.recordings.length} of ${e.length} devices (${f} failed).`}}):a({type:"SET_BANNER",banner:null})}catch(s){if(a({type:"SET_RECORDING_PHASE",phase:"idle"}),s instanceof ft)if(s.body.error==="device_busy"){const i=(s.body.busyDevices??[]).map(f=>`${f.udid} (${f.reason})`).join(", ");a({type:"SET_BANNER",banner:{tone:"error",message:`Cannot start — these devices are busy: ${i}`}})}else a({type:"SET_BANNER",banner:{tone:"error",message:`Server-wide recording cap reached (${s.body.active}/${s.body.limit}).`}});else a({type:"SET_BANNER",banner:{tone:"error",message:`Recording failed: ${s.message}`}})}}},C=async()=>{if(!(!r.groupId||r.recordingPhase!=="recording")){a({type:"SET_RECORDING_PHASE",phase:"stopping"});try{const h=await ht(r.groupId),s=(h.recordings??[]).filter(f=>f.status==="STOPPED"&&(f.sizeBytes??0)>=1024),i=(h.recordings??[]).filter(f=>f.status!=="STOPPED");a({type:"STOP_RECORDING",downloadableVideoCount:s.length,compositeEnabled:r.compositeEnabled}),s.length===0?a({type:"SET_BANNER",banner:{tone:"error",message:i.length>0?`Recording failed on ${i.length} device(s). Nothing to download.`:"Recording stopped but no playable video was produced."}}):i.length>0?a({type:"SET_BANNER",banner:{tone:"info",message:`${s.length} ready, ${i.length} failed. Download with the button above.`}}):a({type:"SET_BANNER",banner:{tone:"info",message:s.length===1?"Video ready. Download it with the button above.":`${s.length} videos ready. Download them with the button above.`}})}catch(h){a({type:"STOP_RECORDING"}),a({type:"SET_BANNER",banner:{tone:"error",message:`Stop failed: ${h.message}`}})}}},A=h=>{const s=h==="annotate";s&&!b||s!==r.annotateMode&&a({type:"SET_ANNOTATE_MODE",enabled:s})};l.useEffect(()=>{if(!r.annotateMode)return;const h=s=>{var f;if(s.key!=="Escape"||s.repeat)return;const i=s.target;i&&((f=i.closest)!=null&&f.call(i,"input, textarea, select")||i.isContentEditable)||a({type:"SET_ANNOTATE_MODE",enabled:!1})};return window.addEventListener("keydown",h),()=>window.removeEventListener("keydown",h)},[r.annotateMode,a]);const O=()=>{b&&n()},W=Object.values(r.overlayAnnotations).some(h=>((h==null?void 0:h.length)??0)>0),G=[{id:"RECT",label:"Rect"},{id:"CIRCLE",label:"Circle"},{id:"ARROW",label:"Arrow"},{id:"FREEHAND",label:"Draw"}];return t.jsxs("div",{className:"flex items-center gap-2 flex-wrap justify-end",children:[r.recordingPhase==="recording"||r.recordingPhase==="stopping"?t.jsxs("span",{className:"inline-flex items-center gap-1.5 px-2 py-1 rounded text-xs font-mono tabular-nums bg-[rgb(var(--rgb-red-600)/0.2)] text-[var(--red-200)] border border-[rgb(var(--rgb-red)/0.4)]",title:"Recording elapsed time",children:[t.jsx("span",{className:"inline-block w-1.5 h-1.5 rounded-full bg-[var(--red)] animate-pulse"}),"REC ",Ze(x),r.recordingPhase==="stopping"?" · Stopping…":""]}):t.jsxs("button",{type:"button",onClick:z,disabled:!m,title:e.length===0?"Add a device to the grid first":"Record every device on the grid",className:"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded bg-[var(--red-600)] text-white disabled:opacity-40 disabled:cursor-not-allowed light:disabled:bg-transparent light:disabled:text-[var(--text)] light:disabled:ring-1 light:disabled:ring-inset light:disabled:ring-[color:var(--border)]",children:[t.jsx(xe,{...V,size:10,fill:"currentColor"}),Kt(e.length,r.recordingPhase)]}),t.jsxs("button",{type:"button",onClick:C,disabled:!c,className:"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded border border-[var(--border)] disabled:opacity-40 disabled:cursor-not-allowed",children:[t.jsx(be,{...V,size:12}),r.recordingPhase==="stopping"?"Stopping…":"Stop"]}),t.jsx(it,{size:"sm",value:r.annotateMode?"annotate":"interact",onChange:A,segments:[{value:"interact",label:"Interact",title:"Tap, swipe and type on the devices (Esc)",keyShortcuts:"Escape"},{value:"annotate",label:"Annotate",title:b?"Draw marks on the recording":"Start recording to annotate",disabled:!b}]}),y&&t.jsxs("div",{className:"flex items-center gap-1 ml-1 pl-2 border-l border-[var(--border)]",children:[G.map(h=>t.jsx("button",{type:"button",disabled:!d,"aria-pressed":r.shape===h.id,onClick:()=>a({type:"SET_SHAPE",shape:h.id}),className:`px-2 py-1 text-xs rounded border disabled:opacity-40 disabled:cursor-not-allowed ${r.shape===h.id?"bg-[var(--surface-2)] border-[var(--color-info)] text-[rgb(var(--rgb-fg))]":"border-[var(--border)] opacity-80 hover:opacity-100"}`,children:h.label},h.id)),t.jsx("input",{type:"color",value:r.color,disabled:!d,onChange:h=>a({type:"SET_COLOR",color:h.target.value}),title:"Annotation color","aria-label":"Annotation color",className:"w-7 h-7 rounded border border-[var(--border)] bg-transparent cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"}),t.jsx("button",{type:"button",onClick:O,disabled:!b||!W,title:"Clear marks from the preview and the recording",className:"px-2 py-1 text-xs rounded border border-[var(--border)] hover:bg-[var(--surface-2)] disabled:opacity-40 disabled:cursor-not-allowed",children:"Clear marks"})]}),R&&E&&t.jsxs("a",{href:ct(r.groupId),className:"ml-1 inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded border border-[var(--border)] hover:bg-[var(--surface-2)]",download:!0,children:[t.jsx(ne,{...V}),"Download video"]}),R&&!E&&t.jsxs("a",{href:lt(r.groupId),className:"ml-1 inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded border border-[var(--border)] hover:bg-[var(--surface-2)]",download:`videos-${r.groupId}.zip`,title:"ZIP of mp4 files only (no JSON extras)",children:[t.jsx(ne,{...V}),"Download ",v?"videos":"video"]}),p&&t.jsxs("a",{href:dt(r.groupId),className:"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded border border-[var(--border)] hover:bg-[var(--surface-2)]",download:`side-by-side-${r.groupId}.mp4`,title:"All devices in one side-by-side video",children:[t.jsx(ne,{...V}),"Side-by-side"]}),R&&r.groupId&&t.jsxs(Le,{to:`/recordings/${encodeURIComponent(r.groupId)}`,className:"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded border border-[var(--border)] hover:bg-[var(--surface-2)]",children:[t.jsx(Be,{...V}),"Open in Recordings"]})]})}function Vt({remainingSec:e,onContinue:n,onReleaseNow:r}){return t.jsx("div",{role:"dialog","aria-modal":"true","aria-labelledby":"idle-warning-title",className:"fixed inset-0 z-40 bg-black/60 flex items-center justify-center",children:t.jsxs("div",{className:"bg-[var(--bg)] border border-[var(--border)] rounded-lg w-full max-w-md p-6",children:[t.jsx("h3",{id:"idle-warning-title",className:"text-base font-semibold mb-2",children:"Are you still there?"}),t.jsxs("p",{className:"text-sm text-[var(--text-dim)] mb-4",children:["Your devices will be released in"," ",t.jsxs("span",{className:"text-[var(--text)] font-mono font-semibold",children:[e,"s"]})," due to inactivity. Click ",t.jsx("strong",{children:"Continue Session"})," to keep them locked."]}),t.jsxs("div",{className:"flex justify-end gap-2",children:[t.jsx("button",{type:"button",onClick:r,className:"h-9 px-3 rounded-md border border-[var(--border)] text-sm text-[var(--text-dim)] hover:text-[var(--text)]",children:"Release now"}),t.jsx("button",{type:"button",autoFocus:!0,onClick:n,className:"h-9 px-4 rounded-md bg-[var(--color-accent)] text-[var(--color-on-accent)] text-sm font-medium",children:"Continue session"})]})]})})}function Yt({message:e,onRestore:n,onDismiss:r}){return t.jsxs("div",{role:"status",className:"text-sm rounded border border-[var(--border)] bg-[var(--surface-2)] text-[var(--text)] px-3 py-2 flex items-center gap-2",children:[t.jsx(gt,{"aria-hidden":!0,size:14,className:"shrink-0 text-[var(--text-dim)]"}),t.jsx("span",{className:"flex-1",children:e}),n&&t.jsx(de,{variant:"tonal",size:"sm",onClick:n,children:"Restore devices"}),t.jsx(de,{variant:"ghost",size:"sm",onClick:r,children:"Dismiss"})]})}function ye(e){var n;return((n=e.marketingName)==null?void 0:n.trim())||e.name||e.udid}function we(e){if(!e)return"9 / 16";const n=Number(e.screenWidth),r=Number(e.screenHeight);if(Number.isFinite(n)&&Number.isFinite(r)&&n>0&&r>0)return`${n} / ${r}`;const a=(e.platform||"").toLowerCase();return a==="tvos"||a==="androidtv"||a==="android-tv"?"16 / 9":"9 / 16"}function se(e){const n=Number(e.screenWidth),r=Number(e.screenHeight);return{udid:e.udid,name:ye(e),mjpegPort:0,aspect:we(e),screenWidth:Number.isFinite(n)&&n>0?n:void 0,screenHeight:Number.isFinite(r)&&r>0?r:void 0,platform:e.platform}}function Xt(e,n){return n?"blocked":e?"remove":"add"}function oe(e,n,r){return!r||!e?!1:e===`manual_${r}_${n}`}function qt(e,n){if(!e.busy)return;const r=ve(e);return r?r.startsWith("manual_")?oe(r,e.udid,n)?"manual_self":"manual_other":"automation":"unknown"}function Jt(e,n){return e.busy?oe(ve(e),e.udid,n):!1}function Zt(e,n){return e>0&&n==="idle"}function Qt(e,n,r){const a=[],g=[];for(const N of e){const x=n.find(m=>m.udid===N.udid),w=(x==null?void 0:x.name)||N.name||N.udid;x?x.offline?g.push({name:w,reason:"offline"}):x.busy&&!oe(x.session_id,x.udid,r)?g.push({name:w,reason:"in_use"}):a.push(x):g.push({name:w,reason:"gone"})}return{restore:a,skipped:g}}const Ne=(e,n)=>`${e} ${n}${e===1?"":"s"}`;function er(e,n){return`Released ${Ne(e,"device")} after ${n} minutes without activity.`}const tr={in_use:"is now in use by someone else.",offline:"is offline.",gone:"is no longer connected."};function rr(e,n){return n.length===0?null:[e>0?`Restored ${Ne(e,"device")}.`:"No devices restored.",...n.map(a=>`${a.name} ${tr[a.reason]}`)].join(" ")}function nr(){let e=Promise.resolve();return{enqueue(n){const r=e.then(n,n);return e=r.then(()=>{},()=>{}),r}}}const me=["mousemove","mousedown","keydown","touchstart","wheel"];function sr({idleAfterMs:e,warningSec:n,onWarning:r,onTimeout:a,enabled:g}){const[N,x]=l.useState(!1),[w,m]=l.useState(null),y=l.useRef(null),c=l.useRef(null),b=l.useRef(!1),d=l.useRef(r),R=l.useRef(a);d.current=r,R.current=a;function v(){y.current&&(clearTimeout(y.current),y.current=null),c.current&&(clearInterval(c.current),c.current=null)}function p(){b.current=!0,x(!0),m(n),d.current(n);let C=n;c.current=setInterval(()=>{C-=1,C<=0?(v(),b.current=!1,x(!1),m(null),R.current()):m(C)},1e3)}function E(){v(),b.current=!1,x(!1),m(null);const C=e-n*1e3;y.current=setTimeout(p,Math.max(0,C))}function z(){g&&E()}return l.useEffect(()=>{if(!g){v(),b.current=!1,x(!1),m(null);return}function C(){b.current||E()}return me.forEach(A=>window.addEventListener(A,C,{passive:!0})),E(),()=>{me.forEach(A=>window.removeEventListener(A,C)),v()}},[g,e,n]),{warning:N,remainingSec:w,reset:z}}const ge=5*60*1e3,or=30;function ar(e,n){return{udid:e.udid,name:ye(e),altName:e.name,platform:e.platform,busy:e.busy,busyReason:qt(e,n),mjpegServerPort:e.mjpegServerPort,offline:!!e.offline}}function wr(){const[e,n]=Qe(),[r,a]=l.useState([]),[g,N]=l.useState("loading"),[x,w]=l.useState(0),[m,y]=l.useState(null),c=l.useRef(!1),b=l.useRef(nr());l.useEffect(()=>{let s=!1;return fetch("/xenon/api/auth/me",{credentials:"include"}).then(i=>i.ok?i.json():null).then(i=>{!s&&(i!=null&&i.userId)&&y(i.userId)}).catch(()=>{}),()=>{s=!0}},[]),l.useEffect(()=>{let s=!1;const i=async()=>{try{const j=await T.getDevices();if(s)return;a(Array.isArray(j)?j:[]),N("ready")}catch{s||N("error")}};i();const f=window.setInterval(i,5e3);return()=>{s=!0,window.clearInterval(f)}},[x]),l.useEffect(()=>{let s=!1;if(!(!m||c.current))return c.current=!0,(async()=>{var j;let i=[],f=[];try{const P=await T.getDevices();i=Array.isArray(P)?P:[];const U=i.filter(B=>Jt(B,m));if(f=(await Promise.all(U.map(async B=>{try{const L=await fetch(`/xenon/api/control/${encodeURIComponent(B.udid)}/stream/status`);if(!L.ok)return null;const $=await L.json();return($==null?void 0:$.status)==="running"?B:null}catch{return null}}))).filter(B=>B!==null).map(se),s)return;f.length>0&&n({type:"SET_TILES",tiles:f})}catch{}try{const{serverNow:P,groups:U}=await pt(),I=U[0];if(s||!I)return;const B=new Set(f.map(S=>S.udid)),L=I.recordings.filter(S=>!B.has(S.udid)).map(S=>i.find(F=>F.udid===S.udid)).filter(S=>!!S).map(se);L.length>0&&n({type:"SET_TILES",tiles:[...f,...L]});const $={};for(const S of I.annotations){let F;try{F=JSON.parse(S.geometry)}catch{continue}($[j=S.recordingId]??($[j]=[])).push({shape:S.shape,color:S.color,geometry:F,text:S.text??void 0})}n({type:"REHYDRATE_RECORDING",groupId:I.groupId,startedAt:Date.now()-(P-Date.parse(I.startedAt)),tileIds:Object.fromEntries(I.recordings.map(S=>[S.udid,S.id])),compositeEnabled:I.compositeEnabled,overlayAnnotations:$})}catch{}})(),()=>{s=!0}},[m,n]),l.useEffect(()=>{for(const s of e.tiles){if(s.screenWidth&&s.screenHeight)continue;const i=r.find(P=>P.udid===s.udid);if(!i)continue;const f=Number(i.screenWidth),j=Number(i.screenHeight);Number.isFinite(f)&&f>0&&Number.isFinite(j)&&j>0&&n({type:"PATCH_TILE_DIMS",udid:s.udid,screenWidth:f,screenHeight:j,aspect:we(i)})}},[r,e.tiles,n]);const d=l.useMemo(()=>new Set(e.tiles.map(s=>s.udid)),[e.tiles]),R=l.useMemo(()=>r.map(s=>ar(s,m)),[r,m]),v=async s=>{n({type:"ADD_TILE",tile:se(s)});const i=await T.startStream(s.udid).catch(()=>null);ze(i)&&n({type:"REMOVE_TILE",udid:s.udid})},p=async s=>{const i=Xt(e.tiles.some(j=>j.udid===s),e.recording);if(i==="blocked"){n({type:"SET_ERROR_BANNER",message:"Stop recording before changing the devices on the grid."});return}if(i==="remove"){await E(s);return}const f=r.find(j=>j.udid===s);f&&await v(f)},E=async s=>{n({type:"REMOVE_TILE",udid:s});try{await fetch(`/xenon/api/control/${encodeURIComponent(s)}/stream/leave`,{method:"POST"})}catch{}w(i=>i+1)},z=(s,i,f)=>{if(!e.groupId)return;const j=e.groupId,P=e.startedAt?Date.now()-e.startedAt:0,U={recordingId:s,timecodeMs:P,shape:i.shape,geometry:JSON.stringify(i.geometry),color:i.color,text:i.text};b.current.enqueue(async()=>{try{return await le(j,f?{...U,image:f}:U)}catch(I){if(!f)throw I;return le(j,U)}}).catch(I=>n({type:"SET_ERROR_BANNER",message:`Annotation failed: ${I.message}`}))},C=()=>{if(!e.groupId)return;const s=e.groupId,i=e.startedAt?Date.now()-e.startedAt:0;n({type:"CLEAR_OVERLAY_ANNOTATIONS"}),b.current.enqueue(()=>mt(s,i)).catch(()=>n({type:"SET_ERROR_BANNER",message:"Couldn't clear marks from the recording. They will still appear in the video."}))},A=()=>{for(const s of e.tiles){const i=`/xenon/api/control/${encodeURIComponent(s.udid)}/stream/leave`;try{navigator.sendBeacon?navigator.sendBeacon(i):fetch(i,{method:"POST",keepalive:!0}).catch(()=>{})}catch{}}e.tiles.forEach(s=>n({type:"REMOVE_TILE",udid:s.udid}))},[O,W]=l.useState(null),G=async()=>{const s=O==null?void 0:O.tiles;if(!s)return;const i=Qt(s,r,m);for(const j of i.restore)await v(j);const f=rr(i.restore.length,i.skipped);W(f?{message:f,tiles:null}:null),w(j=>j+1)},h=sr({idleAfterMs:ge,warningSec:or,enabled:Zt(e.tiles.length,e.recordingPhase),onWarning:()=>{},onTimeout:()=>{const s=e.tiles;A(),W({message:er(s.length,ge/6e4),tiles:s})}});return t.jsxs(et.Provider,{value:{state:e,dispatch:n},children:[t.jsxs("div",{className:"flex flex-col h-full p-4 gap-3",children:[t.jsxs("header",{className:"flex items-center justify-between gap-3",children:[t.jsxs("div",{className:"flex items-center gap-3",children:[t.jsx(xt,{icon:He,children:"Live devices"}),t.jsx(Nt,{value:e.layout,onChange:s=>n({type:"SET_LAYOUT",layout:s})})]}),t.jsx(Gt,{selectedUdids:e.tiles.map(s=>s.udid),onClearMarks:C})]}),e.banner&&t.jsxs("div",{role:"alert",className:`text-sm rounded border px-3 py-2 flex items-center gap-2 ${e.banner.tone==="error"?"border-[rgb(var(--rgb-red)/0.4)] bg-[rgb(var(--rgb-red)/0.1)] text-[var(--red-100)]":"border-[rgb(var(--rgb-success)/0.4)] bg-[rgb(var(--rgb-success)/0.1)] text-[var(--text)]"}`,children:[t.jsx("span",{className:"flex-1",children:e.banner.message}),t.jsx("button",{type:"button",className:"text-xs opacity-80 hover:opacity-100",onClick:()=>n({type:"SET_BANNER",banner:null}),children:"Dismiss"})]}),O&&t.jsx(Yt,{message:O.message,onRestore:O.tiles&&!e.recording?G:void 0,onDismiss:()=>W(null)}),t.jsxs("div",{className:"grid grid-cols-[260px_1fr] gap-3 flex-1 min-h-0",children:[t.jsxs("aside",{className:"flex flex-col gap-2 border border-[var(--border)] rounded p-3 overflow-y-auto min-h-0",children:[t.jsxs("div",{className:"flex items-center justify-between",children:[t.jsx("span",{className:"text-xs text-[var(--text-dim)]",children:"Devices"}),t.jsx("button",{className:"text-xs underline opacity-70 hover:opacity-100",onClick:()=>w(s=>s+1),children:"Refresh"})]}),t.jsx(yt,{devices:R,inMosaic:d,onToggle:p,status:g}),t.jsx("p",{className:"mt-2 text-[11px] text-[var(--text-dim)] leading-relaxed",children:"Click a device to add it to the grid. Record captures every device on the grid; after Stop, download the video."})]}),t.jsx("main",{className:"border border-[var(--border)] rounded p-2 overflow-y-auto h-full min-h-0",children:t.jsx(Ut,{layout:e.layout,tiles:e.tiles,annotateMode:e.annotateMode,shape:e.shape,color:e.color,overlayAnnotationsByRecording:e.overlayAnnotations,onOverlayAnnotationsChange:(s,i)=>n({type:"SET_OVERLAY_ANNOTATIONS",recordingId:s,annotations:i}),onAnnotation:z,onRemove:E,onDropDevice:p})})]})]}),h.warning&&h.remainingSec!==null&&t.jsx(Vt,{remainingSec:h.remainingSec,onContinue:h.reset,onReleaseNow:A})]})}export{wr as default};
@@ -1,21 +0,0 @@
1
- import{c as p,t as z,r as c,X as m,j as e,S as k,A as N,W as I,Y as $}from"./index-BbKEXUFH.js";/* empty css */import{S,A as F}from"./SettingCard-Njz1pUIJ.js";import{P as B}from"./page-header-BjzgWz-T.js";import{L as T}from"./lock-CCJv9m_I.js";import{C as b}from"./circle-check-CqAJjQ5O.js";import{A as E}from"./activity-DOVdsfMQ.js";import"./button-Dda4irn_.js";import"./utils-CDN07tui.js";/**
2
- * @license lucide-react v0.555.0 - ISC
3
- *
4
- * This source code is licensed under the ISC license.
5
- * See the LICENSE file in the root directory of this source tree.
6
- */const q=[["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M17 20v2",key:"1rnc9c"}],["path",{d:"M17 2v2",key:"11trls"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M2 17h2",key:"7oei6x"}],["path",{d:"M2 7h2",key:"asdhe0"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"M20 17h2",key:"1fpfkl"}],["path",{d:"M20 7h2",key:"1o8tra"}],["path",{d:"M7 20v2",key:"4gnj0m"}],["path",{d:"M7 2v2",key:"1i4yhu"}],["rect",{x:"4",y:"4",width:"16",height:"16",rx:"2",key:"1vbyd7"}],["rect",{x:"8",y:"8",width:"8",height:"8",rx:"1",key:"z9xiuo"}]],D=p("cpu",q);/**
7
- * @license lucide-react v0.555.0 - ISC
8
- *
9
- * This source code is licensed under the ISC license.
10
- * See the LICENSE file in the root directory of this source tree.
11
- */const U=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]],L=p("globe",U);/**
12
- * @license lucide-react v0.555.0 - ISC
13
- *
14
- * This source code is licensed under the ISC license.
15
- * See the LICENSE file in the root directory of this source tree.
16
- */const G=[["rect",{width:"20",height:"8",x:"2",y:"2",rx:"2",ry:"2",key:"ngkwjq"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2",ry:"2",key:"iecqi9"}],["line",{x1:"6",x2:"6.01",y1:"6",y2:"6",key:"16zg32"}],["line",{x1:"6",x2:"6.01",y1:"18",y2:"18",key:"nzw8ys"}]],R=p("server",G);/**
17
- * @license lucide-react v0.555.0 - ISC
18
- *
19
- * This source code is licensed under the ISC license.
20
- * See the LICENSE file in the root directory of this source tree.
21
- */const O=[["path",{d:"M10 8h4",key:"1sr2af"}],["path",{d:"M12 21v-9",key:"17s77i"}],["path",{d:"M12 8V3",key:"13r4qs"}],["path",{d:"M17 16h4",key:"h1uq16"}],["path",{d:"M19 12V3",key:"o1uvq1"}],["path",{d:"M19 21v-5",key:"qua636"}],["path",{d:"M3 14h4",key:"bcjad9"}],["path",{d:"M5 10V3",key:"cb8scm"}],["path",{d:"M5 21v-7",key:"1w1uti"}]],V=p("sliders-vertical",O),_={aiProvider:"gemini",aiModel:"",aiBaseUrl:"",geminiModel:"",openaiModel:"",anthropicModel:"",ollamaModel:"",geminiSet:!1,openaiSet:!1,anthropicSet:!1,aiTemperature:1,aiMaxTokens:4096,aiTopP:1},X=(s,i)=>s.aiProvider===i.aiProvider&&s.aiTemperature===i.aiTemperature&&s.aiMaxTokens===i.aiMaxTokens&&s.aiTopP===i.aiTopP,o=s=>{switch(s){case"gemini":return"gemini-3-flash-preview";case"openai":return"gpt-4o";case"anthropic":return"claude-sonnet-4-6";case"ollama":return"llama3";default:return"—"}},Y=s=>{switch(s){case"ollama":return"http://localhost:11434";default:return"Provider default"}},se=()=>{const{toast:s}=z(),[i,r]=c.useState(_),[h,u]=c.useState(_),[w,g]=c.useState(!0),[x,v]=c.useState(!1),[f,j]=c.useState(!1);c.useEffect(()=>{P()},[]);const P=async()=>{g(!0);try{const a=await m.getGlobalConfig(),n={aiProvider:a.aiProvider||"gemini",aiModel:a.aiModel||"",aiBaseUrl:a.aiBaseUrl||"",geminiModel:a.geminiModel||"",openaiModel:a.openaiModel||"",anthropicModel:a.anthropicModel||"",ollamaModel:a.ollamaModel||"",geminiSet:!!a.geminiSet,openaiSet:!!a.openaiSet,anthropicSet:!!a.anthropicSet,aiTemperature:typeof a.aiTemperature=="number"?a.aiTemperature:1,aiMaxTokens:typeof a.aiMaxTokens=="number"?a.aiMaxTokens:4096,aiTopP:typeof a.aiTopP=="number"?a.aiTopP:1};r(n),u(n)}catch(a){console.error("Failed to load AISettings",a),s("Failed to access AI configuration.","error")}finally{g(!1)}},C=async()=>{v(!0);try{await m.updateGlobalConfig({aiProvider:i.aiProvider,aiTemperature:i.aiTemperature,aiMaxTokens:i.aiMaxTokens,aiTopP:i.aiTopP}),u(i),s("AI engine configuration saved.","success")}catch(a){console.error("Failed to save AISettings",a),$(s,a)}finally{v(!1)}},A=async()=>{j(!0);const a=Date.now();try{const n=await m.testAIConfig({aiProvider:i.aiProvider}),l=Date.now()-a;n.success?s(`${n.message} (${l}ms)`,"success"):s(n.message,"error")}catch(n){s(`Connection failed: ${(n==null?void 0:n.message)||"unknown error"}`,"error")}finally{j(!1)}},d=[{id:"gemini",name:"Google Gemini",description:`${o("gemini")} — Multimodal reasoning`,icon:e.jsx("span",{className:"ai-provider-glyph",children:"G"}),isConfigured:!!i.geminiSet},{id:"openai",name:"OpenAI",description:`${o("openai")} — OpenAI v1 compatible`,icon:e.jsx(D,{size:18}),isConfigured:!!i.openaiSet},{id:"anthropic",name:"Anthropic",description:`${o("anthropic")} — Advanced analysis`,icon:e.jsx(k,{size:18}),isConfigured:!!i.anthropicSet},{id:"ollama",name:"Ollama",description:"Local / self-hosted — no API key required",icon:e.jsx(R,{size:18}),isConfigured:!!i.ollamaModel||!!i.aiModel||!!i.aiBaseUrl}],t=d.find(a=>a.id===i.aiProvider),y=d.filter(a=>a.isConfigured).length,M=!X(i,h);return w?e.jsxs("div",{className:"settings-loading",children:[e.jsx(N,{className:"animate-spin",size:32}),e.jsx("span",{children:"Synchronizing Global State..."})]}):e.jsxs("div",{className:"settings-container",children:[e.jsx(B,{icon:I,title:"AI engine",subtitle:e.jsxs(e.Fragment,{children:["All credentials and endpoints are managed via environment variables.",e.jsxs("span",{className:"page-header-subnote",children:[e.jsx(T,{size:12}),"Select the active provider from configured options below."]})]})}),e.jsx("div",{className:"settings-content",children:e.jsxs("div",{className:"settings-grid settings-grid--two-equal",children:[e.jsx(S,{icon:e.jsx(k,{size:16}),title:"Provider registry",titleExtra:e.jsxs("span",{className:`provider-count-pill ${y>0?"is-ok":"is-empty"}`,children:[y," / ",d.length," configured"]}),description:"Providers are activated via environment variables. Select a configured engine to activate.",children:e.jsx("div",{className:"provider-list",children:d.map(a=>{const n=i.aiProvider===a.id,l=a.isConfigured;return e.jsxs("button",{type:"button",className:`provider-row ${n?"is-active":""} ${l?"":"is-disabled"}`,onClick:()=>l&&r({...i,aiProvider:a.id}),disabled:!l,title:l?`Activate ${a.name}`:`Set XENON_${a.id.toUpperCase()}_API_KEY to enable`,children:[e.jsx("div",{className:"provider-row__icon",children:a.icon}),e.jsxs("div",{className:"provider-row__body",children:[e.jsx("div",{className:"provider-row__name",children:a.name}),e.jsx("div",{className:"provider-row__desc",children:a.description}),e.jsx("div",{className:"provider-row__status",children:n?e.jsxs("span",{className:"provider-status provider-status--active",children:[e.jsx(b,{size:11}),a.isConfigured?"Active":"Active — no key"]}):a.isConfigured?e.jsxs("span",{className:"provider-status provider-status--ready",children:[e.jsx(b,{size:11}),"READY"]}):e.jsxs("span",{className:"provider-status provider-status--off",children:[e.jsx(T,{size:10}),"Not set"]})})]})]},a.id)})})}),e.jsxs(S,{icon:e.jsx(L,{size:16}),title:"Runtime configuration",description:"Environmental overrides for AI model endpoints and identifiers.",children:[e.jsxs("div",{className:"ai-config-display",children:[e.jsxs("div",{className:"ai-config-row",children:[e.jsx("span",{className:"ai-config-label",children:"Active provider"}),e.jsxs("span",{className:"ai-config-value",children:[t==null?void 0:t.icon,(t==null?void 0:t.name)||"—"]})]}),e.jsxs("div",{className:"ai-config-row",children:[e.jsx("span",{className:"ai-config-label",children:"Model"}),e.jsxs("span",{className:"ai-config-value mono",children:[e.jsxs("span",{children:[i.aiProvider==="gemini"&&(i.geminiModel||i.aiModel||o("gemini")),i.aiProvider==="openai"&&(i.openaiModel||i.aiModel||o("openai")),i.aiProvider==="anthropic"&&(i.anthropicModel||i.aiModel||o("anthropic")),i.aiProvider==="ollama"&&(i.ollamaModel||i.aiModel||o("ollama"))]}),e.jsx("span",{className:"ai-config-default",children:"Default"})]})]}),e.jsxs("div",{className:"ai-config-row",children:[e.jsx("span",{className:"ai-config-label",children:"Base URL"}),e.jsxs("span",{className:"ai-config-value mono",children:[e.jsx("span",{children:i.aiBaseUrl||Y(i.aiProvider)}),!i.aiBaseUrl&&e.jsx("span",{className:"ai-config-default",children:"Default"})]})]})]}),e.jsxs("div",{className:"model-params",children:[e.jsxs("div",{className:"model-params__header",children:[e.jsx(V,{size:14}),e.jsx("span",{children:"Model Parameters"})]}),e.jsxs("div",{className:"model-param",children:[e.jsxs("div",{className:"model-param__label-row",children:[e.jsx("label",{htmlFor:"ai-temp",children:"Temperature"}),e.jsx("span",{className:"model-param__value",children:i.aiTemperature.toFixed(1)})]}),e.jsx("input",{id:"ai-temp",type:"range",min:0,max:2,step:.1,value:i.aiTemperature,onChange:a=>r({...i,aiTemperature:parseFloat(a.target.value)}),className:"model-param__slider"})]}),e.jsxs("div",{className:"model-param",children:[e.jsx("div",{className:"model-param__label-row",children:e.jsx("label",{htmlFor:"ai-max-tokens",children:"Max tokens"})}),e.jsx("div",{className:"setting-input-wrapper",children:e.jsx("input",{id:"ai-max-tokens",type:"number",min:256,step:128,value:i.aiMaxTokens,onChange:a=>r({...i,aiMaxTokens:parseInt(a.target.value,10)||0})})})]}),e.jsxs("div",{className:"model-param",children:[e.jsxs("div",{className:"model-param__label-row",children:[e.jsx("label",{htmlFor:"ai-top-p",children:"Top P"}),e.jsx("span",{className:"model-param__value",children:i.aiTopP.toFixed(2)})]}),e.jsx("input",{id:"ai-top-p",type:"range",min:0,max:1,step:.01,value:i.aiTopP,onChange:a=>r({...i,aiTopP:parseFloat(a.target.value)}),className:"model-param__slider"})]})]}),e.jsxs("button",{type:"button",className:"test-connection-btn",onClick:A,disabled:f||!(t!=null&&t.isConfigured),children:[f?e.jsx(N,{size:14,className:"animate-spin"}):e.jsx(E,{size:14}),e.jsx("span",{children:"Test Connection"})]})]})]})}),(M||x)&&e.jsx(F,{onSave:C,onDiscard:()=>r(h),isSaving:x,isDirty:M,saveLabel:"Save configuration"})]})};export{se as AISettings};