@ifc-lite/viewer 1.17.3 → 1.17.6

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 (530) hide show
  1. package/.turbo/turbo-build.log +39 -30
  2. package/.turbo/turbo-typecheck.log +1 -1
  3. package/CHANGELOG.md +132 -0
  4. package/DESKTOP_CONTRACT_VERSION +1 -0
  5. package/dist/assets/arrow-CZ5kQ26f.js +20 -0
  6. package/dist/assets/basketViewActivator-86rgogji.js +1 -0
  7. package/dist/assets/{bcf-D5-QWGO9.js → bcf-DOG9_WPX.js} +1 -1
  8. package/dist/assets/{browser-CKs-FY1P.js → browser-C5TFR7sH.js} +1 -1
  9. package/dist/assets/cesium-ADbP7waU.css +1 -0
  10. package/dist/assets/cesium-DUOzBlqv.js +17817 -0
  11. package/dist/assets/drawing-2d-DoxKMqbO.js +257 -0
  12. package/dist/assets/{exporters-C_6J153K.js → exporters-CcPS9MK5.js} +2898 -2380
  13. package/dist/assets/geometry.worker-BFUYA08u.js +1 -0
  14. package/dist/assets/ids-DQ5jY0E8.js +1 -0
  15. package/dist/assets/ifc-lite_bg-BINvzoCP.wasm +0 -0
  16. package/dist/assets/{index-jhBr1wbn.js → index-Bfms9I4A.js} +41036 -34613
  17. package/dist/assets/index-_bfZsDCC.css +1 -0
  18. package/dist/assets/{maplibre-gl-BpvwNKKy.js → maplibre-gl-CGLcoNXc.js} +1 -1
  19. package/dist/assets/native-bridge-DUyLCMZS.js +429 -0
  20. package/dist/assets/{sandbox-B79eavQ3.js → sandbox-C8575tul.js} +4342 -4324
  21. package/dist/assets/{server-client-D3bUPJJc.js → server-client-BuZK7OST.js} +4 -4
  22. package/dist/assets/tauri-core-stub-D8Fa-u43.js +1 -0
  23. package/dist/assets/tauri-dialog-stub-r7Wksg7o.js +1 -0
  24. package/dist/assets/tauri-fs-stub-BdeRC7aK.js +1 -0
  25. package/dist/assets/wasm-bridge-JsqEGDV8.js +1 -0
  26. package/dist/assets/{zip-B-jFFAGa.js → zip-DBEtpeu6.js} +3 -3
  27. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_0.json +1 -0
  28. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_1.json +1 -0
  29. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_10.json +1 -0
  30. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_11.json +1 -0
  31. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_12.json +1 -0
  32. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_13.json +1 -0
  33. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_14.json +1 -0
  34. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_15.json +1 -0
  35. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_16.json +1 -0
  36. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_17.json +1 -0
  37. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_18.json +1 -0
  38. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_19.json +1 -0
  39. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_2.json +1 -0
  40. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_20.json +1 -0
  41. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_21.json +1 -0
  42. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_22.json +1 -0
  43. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_23.json +1 -0
  44. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_24.json +1 -0
  45. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_25.json +1 -0
  46. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_26.json +1 -0
  47. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_27.json +1 -0
  48. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_3.json +1 -0
  49. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_4.json +1 -0
  50. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_5.json +1 -0
  51. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_6.json +1 -0
  52. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_7.json +1 -0
  53. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_8.json +1 -0
  54. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_9.json +1 -0
  55. package/dist/cesium/Assets/Images/bing_maps_credit.png +0 -0
  56. package/dist/cesium/Assets/Images/cesium_credit.png +0 -0
  57. package/dist/cesium/Assets/Images/google_earth_credit.png +0 -0
  58. package/dist/cesium/Assets/Images/ion-credit.png +0 -0
  59. package/dist/cesium/Assets/Textures/LensFlare/DirtMask.jpg +0 -0
  60. package/dist/cesium/Assets/Textures/LensFlare/StarBurst.jpg +0 -0
  61. package/dist/cesium/Assets/Textures/NaturalEarthII/0/0/0.jpg +0 -0
  62. package/dist/cesium/Assets/Textures/NaturalEarthII/0/1/0.jpg +0 -0
  63. package/dist/cesium/Assets/Textures/NaturalEarthII/1/0/0.jpg +0 -0
  64. package/dist/cesium/Assets/Textures/NaturalEarthII/1/0/1.jpg +0 -0
  65. package/dist/cesium/Assets/Textures/NaturalEarthII/1/1/0.jpg +0 -0
  66. package/dist/cesium/Assets/Textures/NaturalEarthII/1/1/1.jpg +0 -0
  67. package/dist/cesium/Assets/Textures/NaturalEarthII/1/2/0.jpg +0 -0
  68. package/dist/cesium/Assets/Textures/NaturalEarthII/1/2/1.jpg +0 -0
  69. package/dist/cesium/Assets/Textures/NaturalEarthII/1/3/0.jpg +0 -0
  70. package/dist/cesium/Assets/Textures/NaturalEarthII/1/3/1.jpg +0 -0
  71. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/0.jpg +0 -0
  72. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/1.jpg +0 -0
  73. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/2.jpg +0 -0
  74. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/3.jpg +0 -0
  75. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/0.jpg +0 -0
  76. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/1.jpg +0 -0
  77. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/2.jpg +0 -0
  78. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/3.jpg +0 -0
  79. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/0.jpg +0 -0
  80. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/1.jpg +0 -0
  81. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/2.jpg +0 -0
  82. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/3.jpg +0 -0
  83. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/0.jpg +0 -0
  84. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/1.jpg +0 -0
  85. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/2.jpg +0 -0
  86. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/3.jpg +0 -0
  87. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/0.jpg +0 -0
  88. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/1.jpg +0 -0
  89. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/2.jpg +0 -0
  90. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/3.jpg +0 -0
  91. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/0.jpg +0 -0
  92. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/1.jpg +0 -0
  93. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/2.jpg +0 -0
  94. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/3.jpg +0 -0
  95. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/0.jpg +0 -0
  96. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/1.jpg +0 -0
  97. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/2.jpg +0 -0
  98. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/3.jpg +0 -0
  99. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/0.jpg +0 -0
  100. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/1.jpg +0 -0
  101. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/2.jpg +0 -0
  102. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/3.jpg +0 -0
  103. package/dist/cesium/Assets/Textures/NaturalEarthII/tilemapresource.xml +14 -0
  104. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_mx.jpg +0 -0
  105. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_my.jpg +0 -0
  106. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_mz.jpg +0 -0
  107. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_px.jpg +0 -0
  108. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_py.jpg +0 -0
  109. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_pz.jpg +0 -0
  110. package/dist/cesium/Assets/Textures/maki/airfield.png +0 -0
  111. package/dist/cesium/Assets/Textures/maki/airport.png +0 -0
  112. package/dist/cesium/Assets/Textures/maki/alcohol-shop.png +0 -0
  113. package/dist/cesium/Assets/Textures/maki/america-football.png +0 -0
  114. package/dist/cesium/Assets/Textures/maki/art-gallery.png +0 -0
  115. package/dist/cesium/Assets/Textures/maki/bakery.png +0 -0
  116. package/dist/cesium/Assets/Textures/maki/bank.png +0 -0
  117. package/dist/cesium/Assets/Textures/maki/bar.png +0 -0
  118. package/dist/cesium/Assets/Textures/maki/baseball.png +0 -0
  119. package/dist/cesium/Assets/Textures/maki/basketball.png +0 -0
  120. package/dist/cesium/Assets/Textures/maki/beer.png +0 -0
  121. package/dist/cesium/Assets/Textures/maki/bicycle.png +0 -0
  122. package/dist/cesium/Assets/Textures/maki/building.png +0 -0
  123. package/dist/cesium/Assets/Textures/maki/bus.png +0 -0
  124. package/dist/cesium/Assets/Textures/maki/cafe.png +0 -0
  125. package/dist/cesium/Assets/Textures/maki/camera.png +0 -0
  126. package/dist/cesium/Assets/Textures/maki/campsite.png +0 -0
  127. package/dist/cesium/Assets/Textures/maki/car.png +0 -0
  128. package/dist/cesium/Assets/Textures/maki/cemetery.png +0 -0
  129. package/dist/cesium/Assets/Textures/maki/cesium.png +0 -0
  130. package/dist/cesium/Assets/Textures/maki/chemist.png +0 -0
  131. package/dist/cesium/Assets/Textures/maki/cinema.png +0 -0
  132. package/dist/cesium/Assets/Textures/maki/circle-stroked.png +0 -0
  133. package/dist/cesium/Assets/Textures/maki/circle.png +0 -0
  134. package/dist/cesium/Assets/Textures/maki/city.png +0 -0
  135. package/dist/cesium/Assets/Textures/maki/clothing-store.png +0 -0
  136. package/dist/cesium/Assets/Textures/maki/college.png +0 -0
  137. package/dist/cesium/Assets/Textures/maki/commercial.png +0 -0
  138. package/dist/cesium/Assets/Textures/maki/cricket.png +0 -0
  139. package/dist/cesium/Assets/Textures/maki/cross.png +0 -0
  140. package/dist/cesium/Assets/Textures/maki/dam.png +0 -0
  141. package/dist/cesium/Assets/Textures/maki/danger.png +0 -0
  142. package/dist/cesium/Assets/Textures/maki/disability.png +0 -0
  143. package/dist/cesium/Assets/Textures/maki/dog-park.png +0 -0
  144. package/dist/cesium/Assets/Textures/maki/embassy.png +0 -0
  145. package/dist/cesium/Assets/Textures/maki/emergency-telephone.png +0 -0
  146. package/dist/cesium/Assets/Textures/maki/entrance.png +0 -0
  147. package/dist/cesium/Assets/Textures/maki/farm.png +0 -0
  148. package/dist/cesium/Assets/Textures/maki/fast-food.png +0 -0
  149. package/dist/cesium/Assets/Textures/maki/ferry.png +0 -0
  150. package/dist/cesium/Assets/Textures/maki/fire-station.png +0 -0
  151. package/dist/cesium/Assets/Textures/maki/fuel.png +0 -0
  152. package/dist/cesium/Assets/Textures/maki/garden.png +0 -0
  153. package/dist/cesium/Assets/Textures/maki/gift.png +0 -0
  154. package/dist/cesium/Assets/Textures/maki/golf.png +0 -0
  155. package/dist/cesium/Assets/Textures/maki/grocery.png +0 -0
  156. package/dist/cesium/Assets/Textures/maki/hairdresser.png +0 -0
  157. package/dist/cesium/Assets/Textures/maki/harbor.png +0 -0
  158. package/dist/cesium/Assets/Textures/maki/heart.png +0 -0
  159. package/dist/cesium/Assets/Textures/maki/heliport.png +0 -0
  160. package/dist/cesium/Assets/Textures/maki/hospital.png +0 -0
  161. package/dist/cesium/Assets/Textures/maki/ice-cream.png +0 -0
  162. package/dist/cesium/Assets/Textures/maki/industrial.png +0 -0
  163. package/dist/cesium/Assets/Textures/maki/land-use.png +0 -0
  164. package/dist/cesium/Assets/Textures/maki/laundry.png +0 -0
  165. package/dist/cesium/Assets/Textures/maki/library.png +0 -0
  166. package/dist/cesium/Assets/Textures/maki/lighthouse.png +0 -0
  167. package/dist/cesium/Assets/Textures/maki/lodging.png +0 -0
  168. package/dist/cesium/Assets/Textures/maki/logging.png +0 -0
  169. package/dist/cesium/Assets/Textures/maki/london-underground.png +0 -0
  170. package/dist/cesium/Assets/Textures/maki/marker-stroked.png +0 -0
  171. package/dist/cesium/Assets/Textures/maki/marker.png +0 -0
  172. package/dist/cesium/Assets/Textures/maki/minefield.png +0 -0
  173. package/dist/cesium/Assets/Textures/maki/mobilephone.png +0 -0
  174. package/dist/cesium/Assets/Textures/maki/monument.png +0 -0
  175. package/dist/cesium/Assets/Textures/maki/museum.png +0 -0
  176. package/dist/cesium/Assets/Textures/maki/music.png +0 -0
  177. package/dist/cesium/Assets/Textures/maki/oil-well.png +0 -0
  178. package/dist/cesium/Assets/Textures/maki/park.png +0 -0
  179. package/dist/cesium/Assets/Textures/maki/park2.png +0 -0
  180. package/dist/cesium/Assets/Textures/maki/parking-garage.png +0 -0
  181. package/dist/cesium/Assets/Textures/maki/parking.png +0 -0
  182. package/dist/cesium/Assets/Textures/maki/pharmacy.png +0 -0
  183. package/dist/cesium/Assets/Textures/maki/pitch.png +0 -0
  184. package/dist/cesium/Assets/Textures/maki/place-of-worship.png +0 -0
  185. package/dist/cesium/Assets/Textures/maki/playground.png +0 -0
  186. package/dist/cesium/Assets/Textures/maki/police.png +0 -0
  187. package/dist/cesium/Assets/Textures/maki/polling-place.png +0 -0
  188. package/dist/cesium/Assets/Textures/maki/post.png +0 -0
  189. package/dist/cesium/Assets/Textures/maki/prison.png +0 -0
  190. package/dist/cesium/Assets/Textures/maki/rail-above.png +0 -0
  191. package/dist/cesium/Assets/Textures/maki/rail-light.png +0 -0
  192. package/dist/cesium/Assets/Textures/maki/rail-metro.png +0 -0
  193. package/dist/cesium/Assets/Textures/maki/rail-underground.png +0 -0
  194. package/dist/cesium/Assets/Textures/maki/rail.png +0 -0
  195. package/dist/cesium/Assets/Textures/maki/religious-christian.png +0 -0
  196. package/dist/cesium/Assets/Textures/maki/religious-jewish.png +0 -0
  197. package/dist/cesium/Assets/Textures/maki/religious-muslim.png +0 -0
  198. package/dist/cesium/Assets/Textures/maki/restaurant.png +0 -0
  199. package/dist/cesium/Assets/Textures/maki/roadblock.png +0 -0
  200. package/dist/cesium/Assets/Textures/maki/rocket.png +0 -0
  201. package/dist/cesium/Assets/Textures/maki/school.png +0 -0
  202. package/dist/cesium/Assets/Textures/maki/scooter.png +0 -0
  203. package/dist/cesium/Assets/Textures/maki/shop.png +0 -0
  204. package/dist/cesium/Assets/Textures/maki/skiing.png +0 -0
  205. package/dist/cesium/Assets/Textures/maki/slaughterhouse.png +0 -0
  206. package/dist/cesium/Assets/Textures/maki/soccer.png +0 -0
  207. package/dist/cesium/Assets/Textures/maki/square-stroked.png +0 -0
  208. package/dist/cesium/Assets/Textures/maki/square.png +0 -0
  209. package/dist/cesium/Assets/Textures/maki/star-stroked.png +0 -0
  210. package/dist/cesium/Assets/Textures/maki/star.png +0 -0
  211. package/dist/cesium/Assets/Textures/maki/suitcase.png +0 -0
  212. package/dist/cesium/Assets/Textures/maki/swimming.png +0 -0
  213. package/dist/cesium/Assets/Textures/maki/telephone.png +0 -0
  214. package/dist/cesium/Assets/Textures/maki/tennis.png +0 -0
  215. package/dist/cesium/Assets/Textures/maki/theatre.png +0 -0
  216. package/dist/cesium/Assets/Textures/maki/toilets.png +0 -0
  217. package/dist/cesium/Assets/Textures/maki/town-hall.png +0 -0
  218. package/dist/cesium/Assets/Textures/maki/town.png +0 -0
  219. package/dist/cesium/Assets/Textures/maki/triangle-stroked.png +0 -0
  220. package/dist/cesium/Assets/Textures/maki/triangle.png +0 -0
  221. package/dist/cesium/Assets/Textures/maki/village.png +0 -0
  222. package/dist/cesium/Assets/Textures/maki/warehouse.png +0 -0
  223. package/dist/cesium/Assets/Textures/maki/waste-basket.png +0 -0
  224. package/dist/cesium/Assets/Textures/maki/water.png +0 -0
  225. package/dist/cesium/Assets/Textures/maki/wetland.png +0 -0
  226. package/dist/cesium/Assets/Textures/maki/zoo.png +0 -0
  227. package/dist/cesium/Assets/Textures/moonSmall.jpg +0 -0
  228. package/dist/cesium/Assets/Textures/pin.svg +1 -0
  229. package/dist/cesium/Assets/Textures/waterNormals.jpg +0 -0
  230. package/dist/cesium/Assets/Textures/waterNormalsSmall.jpg +0 -0
  231. package/dist/cesium/Assets/approximateTerrainHeights.json +1 -0
  232. package/dist/cesium/ThirdParty/Workers/package.json +1 -0
  233. package/dist/cesium/ThirdParty/Workers/zip-web-worker.js +1 -0
  234. package/dist/cesium/ThirdParty/basis_transcoder.wasm +0 -0
  235. package/dist/cesium/ThirdParty/draco_decoder.wasm +0 -0
  236. package/dist/cesium/ThirdParty/google-earth-dbroot-parser.js +1 -0
  237. package/dist/cesium/ThirdParty/wasm_splats_bg.wasm +0 -0
  238. package/dist/cesium/ThirdParty/zip-module.wasm +0 -0
  239. package/dist/cesium/Widgets/Animation/Animation.css +127 -0
  240. package/dist/cesium/Widgets/Animation/lighter.css +70 -0
  241. package/dist/cesium/Widgets/BaseLayerPicker/BaseLayerPicker.css +108 -0
  242. package/dist/cesium/Widgets/BaseLayerPicker/lighter.css +22 -0
  243. package/dist/cesium/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.css +102 -0
  244. package/dist/cesium/Widgets/CesiumInspector/CesiumInspector.css +113 -0
  245. package/dist/cesium/Widgets/CesiumWidget/CesiumWidget.css +119 -0
  246. package/dist/cesium/Widgets/CesiumWidget/lighter.css +14 -0
  247. package/dist/cesium/Widgets/FullscreenButton/FullscreenButton.css +8 -0
  248. package/dist/cesium/Widgets/Geocoder/Geocoder.css +70 -0
  249. package/dist/cesium/Widgets/Geocoder/lighter.css +17 -0
  250. package/dist/cesium/Widgets/I3SBuildingSceneLayerExplorer/I3SBuildingSceneLayerExplorer.css +27 -0
  251. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldHillshade.png +0 -0
  252. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldImagery.png +0 -0
  253. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldOcean.png +0 -0
  254. package/dist/cesium/Widgets/Images/ImageryProviders/azureAerial.png +0 -0
  255. package/dist/cesium/Widgets/Images/ImageryProviders/azureRoads.png +0 -0
  256. package/dist/cesium/Widgets/Images/ImageryProviders/bingAerial.png +0 -0
  257. package/dist/cesium/Widgets/Images/ImageryProviders/bingAerialLabels.png +0 -0
  258. package/dist/cesium/Widgets/Images/ImageryProviders/bingRoads.png +0 -0
  259. package/dist/cesium/Widgets/Images/ImageryProviders/blueMarble.png +0 -0
  260. package/dist/cesium/Widgets/Images/ImageryProviders/earthAtNight.png +0 -0
  261. package/dist/cesium/Widgets/Images/ImageryProviders/googleContour.png +0 -0
  262. package/dist/cesium/Widgets/Images/ImageryProviders/googleRoadmap.png +0 -0
  263. package/dist/cesium/Widgets/Images/ImageryProviders/googleSatellite.png +0 -0
  264. package/dist/cesium/Widgets/Images/ImageryProviders/googleSatelliteLabels.png +0 -0
  265. package/dist/cesium/Widgets/Images/ImageryProviders/mapQuestOpenStreetMap.png +0 -0
  266. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxSatellite.png +0 -0
  267. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxStreets.png +0 -0
  268. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxTerrain.png +0 -0
  269. package/dist/cesium/Widgets/Images/ImageryProviders/naturalEarthII.png +0 -0
  270. package/dist/cesium/Widgets/Images/ImageryProviders/openStreetMap.png +0 -0
  271. package/dist/cesium/Widgets/Images/ImageryProviders/sentinel-2.png +0 -0
  272. package/dist/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmooth.png +0 -0
  273. package/dist/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmoothDark.png +0 -0
  274. package/dist/cesium/Widgets/Images/ImageryProviders/stamenToner.png +0 -0
  275. package/dist/cesium/Widgets/Images/ImageryProviders/stamenWatercolor.png +0 -0
  276. package/dist/cesium/Widgets/Images/NavigationHelp/Mouse.svg +84 -0
  277. package/dist/cesium/Widgets/Images/NavigationHelp/MouseLeft.svg +76 -0
  278. package/dist/cesium/Widgets/Images/NavigationHelp/MouseMiddle.svg +76 -0
  279. package/dist/cesium/Widgets/Images/NavigationHelp/MouseRight.svg +76 -0
  280. package/dist/cesium/Widgets/Images/NavigationHelp/Touch.svg +120 -0
  281. package/dist/cesium/Widgets/Images/NavigationHelp/TouchDrag.svg +129 -0
  282. package/dist/cesium/Widgets/Images/NavigationHelp/TouchRotate.svg +76 -0
  283. package/dist/cesium/Widgets/Images/NavigationHelp/TouchTilt.svg +135 -0
  284. package/dist/cesium/Widgets/Images/NavigationHelp/TouchZoom.svg +74 -0
  285. package/dist/cesium/Widgets/Images/TerrainProviders/CesiumWorldTerrain.png +0 -0
  286. package/dist/cesium/Widgets/Images/TerrainProviders/Ellipsoid.png +0 -0
  287. package/dist/cesium/Widgets/Images/TimelineIcons.png +0 -0
  288. package/dist/cesium/Widgets/Images/info-loading.gif +0 -0
  289. package/dist/cesium/Widgets/InfoBox/InfoBox.css +92 -0
  290. package/dist/cesium/Widgets/InfoBox/InfoBoxDescription.css +178 -0
  291. package/dist/cesium/Widgets/NavigationHelpButton/NavigationHelpButton.css +93 -0
  292. package/dist/cesium/Widgets/NavigationHelpButton/lighter.css +38 -0
  293. package/dist/cesium/Widgets/PerformanceWatchdog/PerformanceWatchdog.css +15 -0
  294. package/dist/cesium/Widgets/ProjectionPicker/ProjectionPicker.css +38 -0
  295. package/dist/cesium/Widgets/SceneModePicker/SceneModePicker.css +56 -0
  296. package/dist/cesium/Widgets/SelectionIndicator/SelectionIndicator.css +20 -0
  297. package/dist/cesium/Widgets/Timeline/Timeline.css +103 -0
  298. package/dist/cesium/Widgets/Timeline/lighter.css +23 -0
  299. package/dist/cesium/Widgets/VRButton/VRButton.css +8 -0
  300. package/dist/cesium/Widgets/Viewer/Viewer.css +107 -0
  301. package/dist/cesium/Widgets/VoxelInspector/VoxelInspector.css +16 -0
  302. package/dist/cesium/Widgets/lighter.css +237 -0
  303. package/dist/cesium/Widgets/lighterShared.css +46 -0
  304. package/dist/cesium/Widgets/shared.css +103 -0
  305. package/dist/cesium/Widgets/widgets.css +1342 -0
  306. package/dist/cesium/Workers/chunk-23ZQ2IVV.js +29 -0
  307. package/dist/cesium/Workers/chunk-2EQO3Q56.js +26 -0
  308. package/dist/cesium/Workers/chunk-2MJIIVP4.js +26 -0
  309. package/dist/cesium/Workers/chunk-2TE5NTVD.js +26 -0
  310. package/dist/cesium/Workers/chunk-2ZBHLJST.js +26 -0
  311. package/dist/cesium/Workers/chunk-5TJMAQVL.js +26 -0
  312. package/dist/cesium/Workers/chunk-6BD4U3VO.js +26 -0
  313. package/dist/cesium/Workers/chunk-7TVGLKQF.js +26 -0
  314. package/dist/cesium/Workers/chunk-BTSYJ5XU.js +26 -0
  315. package/dist/cesium/Workers/chunk-BXMEEOCS.js +63 -0
  316. package/dist/cesium/Workers/chunk-BYLCY7GP.js +29 -0
  317. package/dist/cesium/Workers/chunk-CTHM3W6I.js +26 -0
  318. package/dist/cesium/Workers/chunk-CUUSNIVQ.js +26 -0
  319. package/dist/cesium/Workers/chunk-E3JOOS3S.js +26 -0
  320. package/dist/cesium/Workers/chunk-E7KYDCM5.js +26 -0
  321. package/dist/cesium/Workers/chunk-EDVBB7SS.js +27 -0
  322. package/dist/cesium/Workers/chunk-EFBN7QNX.js +26 -0
  323. package/dist/cesium/Workers/chunk-EQ4YRVWL.js +26 -0
  324. package/dist/cesium/Workers/chunk-F6PRE7D6.js +26 -0
  325. package/dist/cesium/Workers/chunk-FC4ZZ65J.js +26 -0
  326. package/dist/cesium/Workers/chunk-FFBVWF2L.js +26 -0
  327. package/dist/cesium/Workers/chunk-GBAA6GVX.js +26 -0
  328. package/dist/cesium/Workers/chunk-ICALLYLG.js +26 -0
  329. package/dist/cesium/Workers/chunk-ILRYTWTP.js +26 -0
  330. package/dist/cesium/Workers/chunk-IRNLBSEJ.js +26 -0
  331. package/dist/cesium/Workers/chunk-IX4VMHEV.js +26 -0
  332. package/dist/cesium/Workers/chunk-L6QHHACZ.js +26 -0
  333. package/dist/cesium/Workers/chunk-LI2ZSORM.js +26 -0
  334. package/dist/cesium/Workers/chunk-LSLE2RL4.js +26 -0
  335. package/dist/cesium/Workers/chunk-M4HLDBCG.js +26 -0
  336. package/dist/cesium/Workers/chunk-MJHHSGEH.js +26 -0
  337. package/dist/cesium/Workers/chunk-NMVKML6W.js +26 -0
  338. package/dist/cesium/Workers/chunk-OCWJRAXS.js +26 -0
  339. package/dist/cesium/Workers/chunk-OIRKANTH.js +26 -0
  340. package/dist/cesium/Workers/chunk-OIT7J4IC.js +26 -0
  341. package/dist/cesium/Workers/chunk-OLZ3FYUM.js +26 -0
  342. package/dist/cesium/Workers/chunk-Q5BPHJQF.js +26 -0
  343. package/dist/cesium/Workers/chunk-QFM5DCMQ.js +26 -0
  344. package/dist/cesium/Workers/chunk-QKUIYMGC.js +28 -0
  345. package/dist/cesium/Workers/chunk-S44JILQT.js +26 -0
  346. package/dist/cesium/Workers/chunk-SLT4J352.js +26 -0
  347. package/dist/cesium/Workers/chunk-SQMIIXB7.js +26 -0
  348. package/dist/cesium/Workers/chunk-TJ4XLGBQ.js +26 -0
  349. package/dist/cesium/Workers/chunk-TNSUQXWK.js +27 -0
  350. package/dist/cesium/Workers/chunk-UBOGZS7F.js +26 -0
  351. package/dist/cesium/Workers/chunk-V3OSTMM6.js +26 -0
  352. package/dist/cesium/Workers/chunk-V7QEYVP3.js +26 -0
  353. package/dist/cesium/Workers/chunk-VUKYSU4H.js +26 -0
  354. package/dist/cesium/Workers/chunk-W37FE5GR.js +26 -0
  355. package/dist/cesium/Workers/chunk-WBOV35NL.js +26 -0
  356. package/dist/cesium/Workers/chunk-WPMZLB3Y.js +26 -0
  357. package/dist/cesium/Workers/chunk-WWWZVEEH.js +26 -0
  358. package/dist/cesium/Workers/chunk-XFIQ5DEQ.js +28 -0
  359. package/dist/cesium/Workers/chunk-XQHLGIO7.js +26 -0
  360. package/dist/cesium/Workers/chunk-XUSCFAVF.js +26 -0
  361. package/dist/cesium/Workers/chunk-YP7I5QBZ.js +26 -0
  362. package/dist/cesium/Workers/chunk-Z3QF2EHT.js +26 -0
  363. package/dist/cesium/Workers/combineGeometry.js +26 -0
  364. package/dist/cesium/Workers/createBoxGeometry.js +26 -0
  365. package/dist/cesium/Workers/createBoxOutlineGeometry.js +26 -0
  366. package/dist/cesium/Workers/createCircleGeometry.js +26 -0
  367. package/dist/cesium/Workers/createCircleOutlineGeometry.js +26 -0
  368. package/dist/cesium/Workers/createCoplanarPolygonGeometry.js +26 -0
  369. package/dist/cesium/Workers/createCoplanarPolygonOutlineGeometry.js +26 -0
  370. package/dist/cesium/Workers/createCorridorGeometry.js +26 -0
  371. package/dist/cesium/Workers/createCorridorOutlineGeometry.js +26 -0
  372. package/dist/cesium/Workers/createCylinderGeometry.js +26 -0
  373. package/dist/cesium/Workers/createCylinderOutlineGeometry.js +26 -0
  374. package/dist/cesium/Workers/createEllipseGeometry.js +26 -0
  375. package/dist/cesium/Workers/createEllipseOutlineGeometry.js +26 -0
  376. package/dist/cesium/Workers/createEllipsoidGeometry.js +26 -0
  377. package/dist/cesium/Workers/createEllipsoidOutlineGeometry.js +26 -0
  378. package/dist/cesium/Workers/createFrustumGeometry.js +26 -0
  379. package/dist/cesium/Workers/createFrustumOutlineGeometry.js +26 -0
  380. package/dist/cesium/Workers/createGeometry.js +26 -0
  381. package/dist/cesium/Workers/createGroundPolylineGeometry.js +26 -0
  382. package/dist/cesium/Workers/createPlaneGeometry.js +26 -0
  383. package/dist/cesium/Workers/createPlaneOutlineGeometry.js +26 -0
  384. package/dist/cesium/Workers/createPolygonGeometry.js +26 -0
  385. package/dist/cesium/Workers/createPolygonOutlineGeometry.js +26 -0
  386. package/dist/cesium/Workers/createPolylineGeometry.js +26 -0
  387. package/dist/cesium/Workers/createPolylineVolumeGeometry.js +26 -0
  388. package/dist/cesium/Workers/createPolylineVolumeOutlineGeometry.js +26 -0
  389. package/dist/cesium/Workers/createRectangleGeometry.js +26 -0
  390. package/dist/cesium/Workers/createRectangleOutlineGeometry.js +26 -0
  391. package/dist/cesium/Workers/createSimplePolylineGeometry.js +26 -0
  392. package/dist/cesium/Workers/createSphereGeometry.js +26 -0
  393. package/dist/cesium/Workers/createSphereOutlineGeometry.js +26 -0
  394. package/dist/cesium/Workers/createTaskProcessorWorker.js +26 -0
  395. package/dist/cesium/Workers/createVectorTileClampedPolylines.js +26 -0
  396. package/dist/cesium/Workers/createVectorTileGeometries.js +26 -0
  397. package/dist/cesium/Workers/createVectorTilePoints.js +26 -0
  398. package/dist/cesium/Workers/createVectorTilePolygons.js +26 -0
  399. package/dist/cesium/Workers/createVectorTilePolylines.js +26 -0
  400. package/dist/cesium/Workers/createVerticesFromCesium3DTilesTerrain.js +26 -0
  401. package/dist/cesium/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +26 -0
  402. package/dist/cesium/Workers/createVerticesFromHeightmap.js +26 -0
  403. package/dist/cesium/Workers/createVerticesFromQuantizedTerrainMesh.js +26 -0
  404. package/dist/cesium/Workers/createWallGeometry.js +26 -0
  405. package/dist/cesium/Workers/createWallOutlineGeometry.js +26 -0
  406. package/dist/cesium/Workers/decodeDraco.js +26 -0
  407. package/dist/cesium/Workers/decodeGoogleEarthEnterprisePacket.js +26 -0
  408. package/dist/cesium/Workers/decodeI3S.js +26 -0
  409. package/dist/cesium/Workers/gaussianSplatSorter.js +26 -0
  410. package/dist/cesium/Workers/gaussianSplatTextureGenerator.js +26 -0
  411. package/dist/cesium/Workers/incrementallyBuildTerrainPicker.js +26 -0
  412. package/dist/cesium/Workers/transcodeKTX2.js +56 -0
  413. package/dist/cesium/Workers/transferTypedArrayTest.js +26 -0
  414. package/dist/cesium/Workers/upsampleQuantizedTerrainMesh.js +26 -0
  415. package/dist/cesium/Workers/upsampleVerticesFromCesium3DTilesTerrain.js +26 -0
  416. package/dist/index.html +13 -10
  417. package/index.html +1 -0
  418. package/package.json +18 -15
  419. package/src/App.tsx +7 -9
  420. package/src/components/viewer/BCFPanel.tsx +46 -4
  421. package/src/components/viewer/CesiumOverlay.tsx +715 -0
  422. package/src/components/viewer/CesiumSettingsDialog.tsx +100 -0
  423. package/src/components/viewer/ChatPanel.tsx +232 -90
  424. package/src/components/viewer/CommandPalette.tsx +6 -1
  425. package/src/components/viewer/DesktopEntitlementBanner.tsx +74 -0
  426. package/src/components/viewer/ExportChangesButton.tsx +6 -1
  427. package/src/components/viewer/ExportDialog.tsx +22 -6
  428. package/src/components/viewer/HierarchyPanel.tsx +196 -0
  429. package/src/components/viewer/IDSPanel.tsx +52 -3
  430. package/src/components/viewer/KeyboardShortcutsDialog.tsx +1 -1
  431. package/src/components/viewer/MainToolbar.tsx +355 -28
  432. package/src/components/viewer/PropertiesPanel.tsx +234 -81
  433. package/src/components/viewer/ScriptPanel.tsx +34 -8
  434. package/src/components/viewer/SettingsPage.tsx +581 -0
  435. package/src/components/viewer/StatusBar.tsx +17 -1
  436. package/src/components/viewer/ThemeSwitch.tsx +63 -7
  437. package/src/components/viewer/ViewerLayout.tsx +48 -6
  438. package/src/components/viewer/Viewport.tsx +61 -8
  439. package/src/components/viewer/ViewportContainer.tsx +265 -28
  440. package/src/components/viewer/ViewportOverlays.tsx +132 -27
  441. package/src/components/viewer/bcf/BCFTopicDetail.tsx +4 -4
  442. package/src/components/viewer/chat/ModelSelector.tsx +90 -54
  443. package/src/components/viewer/properties/GeoreferencingPanel.tsx +229 -55
  444. package/src/components/viewer/properties/LocationMap.tsx +462 -19
  445. package/src/components/viewer/properties/ModelMetadataPanel.tsx +1 -1
  446. package/src/components/viewer/selectionHandlers.ts +4 -3
  447. package/src/components/viewer/tools/SectionCapControls.tsx +237 -0
  448. package/src/components/viewer/tools/SectionPanel.tsx +39 -18
  449. package/src/components/viewer/useAnimationLoop.ts +13 -1
  450. package/src/components/viewer/useGeometryStreaming.ts +127 -40
  451. package/src/components/viewer/useMouseControls.ts +4 -1
  452. package/src/components/viewer/useRenderUpdates.ts +1 -1
  453. package/src/hooks/ids/idsDataAccessor.ts +60 -24
  454. package/src/hooks/ingest/viewerModelIngest.ts +280 -0
  455. package/src/hooks/useIDS.ts +1 -1
  456. package/src/hooks/useIfc.ts +7 -1
  457. package/src/hooks/useIfcCache.ts +28 -15
  458. package/src/hooks/useIfcFederation.ts +378 -291
  459. package/src/hooks/useIfcLoader.ts +1657 -130
  460. package/src/hooks/useIfcServer.ts +0 -69
  461. package/src/hooks/useViewControls.ts +13 -5
  462. package/src/index.css +484 -10
  463. package/src/lib/desktop/desktopEntitlementEvents.ts +39 -0
  464. package/src/lib/desktop-entitlement.ts +43 -0
  465. package/src/lib/desktop-product.ts +124 -0
  466. package/src/lib/geo/cesium-bridge.ts +318 -0
  467. package/src/lib/geo/effective-georef.test.ts +73 -0
  468. package/src/lib/geo/effective-georef.ts +111 -0
  469. package/src/lib/geo/reproject.ts +249 -37
  470. package/src/lib/llm/byok-guard.test.ts +77 -0
  471. package/src/lib/llm/byok-guard.ts +39 -0
  472. package/src/lib/llm/free-models.test.ts +0 -6
  473. package/src/lib/llm/models.ts +104 -42
  474. package/src/lib/llm/stream-client.ts +74 -110
  475. package/src/lib/llm/stream-direct.test.ts +130 -0
  476. package/src/lib/llm/stream-direct.ts +316 -0
  477. package/src/lib/llm/types.ts +14 -2
  478. package/src/lib/recent-files.ts +2 -1
  479. package/src/main.tsx +1 -10
  480. package/src/services/analysis-extensions.ts +125 -0
  481. package/src/services/api-keys.ts +73 -0
  482. package/src/services/app-navigation.ts +13 -0
  483. package/src/services/bsdd.ts +53 -4
  484. package/src/services/cacheService.ts +1 -1
  485. package/src/services/desktop-cache.ts +43 -0
  486. package/src/services/desktop-export.ts +77 -0
  487. package/src/services/desktop-harness.ts +196 -0
  488. package/src/services/desktop-logger.ts +20 -0
  489. package/src/services/desktop-native-metadata.ts +207 -0
  490. package/src/services/desktop-panel-actions.ts +43 -0
  491. package/src/services/desktop-preferences.ts +44 -0
  492. package/src/services/file-dialog.ts +147 -0
  493. package/src/services/tauri-core-stub.ts +7 -0
  494. package/src/services/tauri-dialog-stub.ts +7 -0
  495. package/src/services/tauri-fs-stub.ts +7 -0
  496. package/src/store/constants.ts +20 -2
  497. package/src/store/index.ts +52 -7
  498. package/src/store/slices/cesiumSlice.ts +127 -0
  499. package/src/store/slices/chatSlice.test.ts +6 -76
  500. package/src/store/slices/chatSlice.ts +21 -58
  501. package/src/store/slices/dataSlice.ts +139 -28
  502. package/src/store/slices/desktopEntitlementSlice.ts +86 -0
  503. package/src/store/slices/loadingSlice.ts +14 -2
  504. package/src/store/slices/modelSlice.ts +58 -3
  505. package/src/store/slices/sectionSlice.test.ts +87 -7
  506. package/src/store/slices/sectionSlice.ts +151 -5
  507. package/src/store/slices/uiSlice.ts +28 -5
  508. package/src/store/types.ts +122 -2
  509. package/src/store.ts +1 -1
  510. package/src/utils/desktopModelSnapshot.ts +358 -0
  511. package/src/utils/ifcConfig.ts +6 -1
  512. package/src/utils/nativeSpatialDataStore.ts +253 -0
  513. package/src/utils/serverDataModel.ts +4 -0
  514. package/src/utils/spatialHierarchy.ts +10 -11
  515. package/src/utils/viewportUtils.ts +7 -2
  516. package/src/vite-env.d.ts +0 -4
  517. package/vite.config.ts +24 -0
  518. package/dist/assets/arrow-DJf2ErbF.js +0 -20
  519. package/dist/assets/basketViewActivator-aojwdomq.js +0 -1
  520. package/dist/assets/desktop-cache-oPzaWXYE.js +0 -1
  521. package/dist/assets/drawing-2d-gWfpdfYe.js +0 -257
  522. package/dist/assets/geometry.worker-Nz9_YIqh.js +0 -1
  523. package/dist/assets/ids-B4jTqB1O.js +0 -1
  524. package/dist/assets/ifc-lite_bg-eSkBTizQ.wasm +0 -0
  525. package/dist/assets/index-pbE7itQS.css +0 -1
  526. package/dist/assets/native-bridge-DSIyEYXG.js +0 -113
  527. package/dist/assets/wasm-bridge-B0J07fZZ.js +0 -1
  528. package/src/components/viewer/UpgradePage.tsx +0 -69
  529. package/src/lib/llm/ClerkChatSync.tsx +0 -74
  530. package/src/lib/llm/clerk-auth.ts +0 -62
@@ -0,0 +1,100 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+
5
+ /**
6
+ * Settings dialog for configuring Cesium 3D overlay options:
7
+ * - Data source selection (OSM Buildings, Bing Aerial, Google Photorealistic)
8
+ * - Terrain toggle
9
+ * - Advanced: custom Cesium ion token override (optional — default is bundled)
10
+ */
11
+
12
+ import { useState } from 'react';
13
+ import {
14
+ Dialog,
15
+ DialogContent,
16
+ DialogHeader,
17
+ DialogTitle,
18
+ DialogTrigger,
19
+ } from '@/components/ui/dialog';
20
+ import { Button } from '@/components/ui/button';
21
+ import { Label } from '@/components/ui/label';
22
+ import { useViewerStore } from '@/store';
23
+ import type { CesiumDataSource } from '@/store/slices/cesiumSlice';
24
+ import { Settings2 } from 'lucide-react';
25
+
26
+ const DATA_SOURCE_OPTIONS: { value: CesiumDataSource; label: string; description: string }[] = [
27
+ { value: 'osm-buildings', label: 'OSM Buildings', description: 'OpenStreetMap 3D buildings worldwide' },
28
+ { value: 'bing-aerial', label: 'Bing Aerial', description: 'Satellite imagery draped on terrain' },
29
+ { value: 'google-photorealistic', label: 'Google Photorealistic', description: 'High-fidelity 3D photogrammetry (where available)' },
30
+ ];
31
+
32
+ export function CesiumSettingsDialog() {
33
+ const dataSource = useViewerStore((s) => s.cesiumDataSource);
34
+ const setCesiumDataSource = useViewerStore((s) => s.setCesiumDataSource);
35
+ const terrainEnabled = useViewerStore((s) => s.cesiumTerrainEnabled);
36
+ const setCesiumTerrainEnabled = useViewerStore((s) => s.setCesiumTerrainEnabled);
37
+ const [open, setOpen] = useState(false);
38
+
39
+ return (
40
+ <Dialog open={open} onOpenChange={setOpen}>
41
+ <DialogTrigger asChild>
42
+ <Button variant="ghost" size="icon-sm" className="h-6 w-6" aria-label="Cesium settings">
43
+ <Settings2 className="h-3 w-3" />
44
+ </Button>
45
+ </DialogTrigger>
46
+ <DialogContent className="sm:max-w-md">
47
+ <DialogHeader>
48
+ <DialogTitle className="text-sm font-bold">3D World Context</DialogTitle>
49
+ </DialogHeader>
50
+
51
+ <div className="space-y-4 mt-2">
52
+ {/* Data Source */}
53
+ <div className="space-y-1.5">
54
+ <Label className="text-xs font-medium">3D Data Source</Label>
55
+ <div className="space-y-1">
56
+ {DATA_SOURCE_OPTIONS.map((opt) => (
57
+ <label
58
+ key={opt.value}
59
+ className={`flex items-start gap-2 p-2 rounded border cursor-pointer transition-colors ${
60
+ dataSource === opt.value
61
+ ? 'border-teal-500 bg-teal-50 dark:bg-teal-950/30'
62
+ : 'border-zinc-200 dark:border-zinc-700 hover:border-zinc-300 dark:hover:border-zinc-600'
63
+ }`}
64
+ >
65
+ <input
66
+ type="radio"
67
+ name="cesium-data-source"
68
+ checked={dataSource === opt.value}
69
+ onChange={() => setCesiumDataSource(opt.value)}
70
+ className="mt-0.5 accent-teal-500"
71
+ />
72
+ <div>
73
+ <div className="text-xs font-medium">{opt.label}</div>
74
+ <div className="text-[10px] text-zinc-400">{opt.description}</div>
75
+ </div>
76
+ </label>
77
+ ))}
78
+ </div>
79
+ </div>
80
+
81
+ {/* Terrain Toggle */}
82
+ <label className="flex items-center gap-2 cursor-pointer">
83
+ <input
84
+ type="checkbox"
85
+ checked={terrainEnabled}
86
+ onChange={(e) => setCesiumTerrainEnabled(e.target.checked)}
87
+ className="accent-teal-500"
88
+ />
89
+ <span className="text-xs">Enable 3D terrain (Cesium World Terrain)</span>
90
+ </label>
91
+
92
+ <p className="text-[10px] text-zinc-400 border-t border-zinc-100 dark:border-zinc-800 pt-3">
93
+ Powered by Cesium ion. Real-world 3D tiles are overlaid on your georeferenced
94
+ IFC model. Only available when the model contains IfcMapConversion data.
95
+ </p>
96
+ </div>
97
+ </DialogContent>
98
+ </Dialog>
99
+ );
100
+ }
@@ -29,14 +29,15 @@ import {
29
29
  ArrowDown,
30
30
  Zap,
31
31
  } from 'lucide-react';
32
- import { SignInButton, SignedIn, SignedOut, UserButton } from '@clerk/clerk-react';
33
32
  import { Button } from '@/components/ui/button';
33
+ import { toast } from '@/components/ui/toast';
34
34
  import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
35
35
  import { useViewerStore } from '@/store';
36
36
  import { buildErrorFeedbackContent } from '@/store/slices/chatSlice';
37
37
  import { ChatMessageComponent } from './chat/ChatMessage';
38
38
  import { ModelSelector } from './chat/ModelSelector';
39
39
  import { fetchUsageSnapshot, streamChat, type StreamMessage, type TextContentPart, type ImageContentPart, type UsageInfo } from '@/lib/llm/stream-client';
40
+ import { streamAnthropicChat, streamOpenAiChat } from '@/lib/llm/stream-direct';
40
41
  import { buildStreamMessagesForModel, filterAttachmentsForModel } from '@/lib/llm/message-capabilities';
41
42
  import { buildSystemPrompt } from '@/lib/llm/system-prompt';
42
43
  import { getModelContext, parseCSV } from '@/lib/llm/context-builder';
@@ -48,9 +49,11 @@ import type { ScriptDiagnostic } from '@/lib/llm/script-diagnostics';
48
49
  import { buildRepairSessionKey, getEscalatedRepairScope, pruneMessagesForRepair } from '@/lib/llm/repair-loop';
49
50
  import type { ChatMessage, ChatRepairRequest, FileAttachment } from '@/lib/llm/types';
50
51
  import { canUsePlainCodeBlockFallback, type ScriptMutationIntent } from '@/lib/llm/script-preservation';
51
- import { Image as ImageIcon } from 'lucide-react';
52
- import { isClerkConfigured } from '@/lib/llm/clerk-auth';
52
+ import { Check, Image as ImageIcon, Key, Eye, EyeOff, ExternalLink } from 'lucide-react';
53
+ import { hasDesktopFeatureAccess } from '@/lib/desktop-product';
53
54
  import { getModelById } from '@/lib/llm/models';
55
+ import { resolveStreamRoute } from '@/lib/llm/byok-guard';
56
+ import { getApiKeys, updateApiKeys, hasAnthropicKey, hasOpenaiKey, subscribeApiKeys } from '@/services/api-keys';
54
57
  import { useSandbox } from '@/hooks/useSandbox';
55
58
 
56
59
  // Environment variable for the proxy URL
@@ -64,7 +67,6 @@ const EXAMPLE_PROMPTS = [
64
67
  ];
65
68
 
66
69
  const CONTINUE_PROMPT = 'Continue from exactly where your last response stopped. Do not repeat previously generated text.';
67
- const DEFAULT_PRO_MONTHLY_CREDIT_LIMIT = 1000;
68
70
  const USAGE_REFRESH_INTERVAL_MS = 15_000;
69
71
  const EST_CHARS_PER_TOKEN = 4;
70
72
  const IMAGE_TOKEN_COST_EST = 850;
@@ -208,21 +210,30 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
208
210
  const consumePendingPrompt = useViewerStore((s) => s.consumeChatPendingPrompt);
209
211
  const pendingRepairRequest = useViewerStore((s) => s.chatPendingRepairRequest);
210
212
  const consumePendingRepairRequest = useViewerStore((s) => s.consumeChatPendingRepairRequest);
211
- const authToken = useViewerStore((s) => s.chatAuthToken);
212
- const hasPro = useViewerStore((s) => s.chatHasPro);
213
+ const hasByokKey = useViewerStore((s) => s.chatHasByokKey);
214
+ const setChatHasByokKey = useViewerStore((s) => s.setChatHasByokKey);
213
215
  const usage = useViewerStore((s) => s.chatUsage);
214
216
  const setChatUsage = useViewerStore((s) => s.setChatUsage);
217
+ const desktopEntitlement = useViewerStore((s) => s.desktopEntitlement);
215
218
  const { execute } = useSandbox();
216
- const displayUsage: UsageInfo | null = usage ?? (hasPro
217
- ? {
218
- type: 'credits',
219
- used: 0,
220
- limit: DEFAULT_PRO_MONTHLY_CREDIT_LIMIT,
221
- pct: 0,
222
- resetAt: 0,
223
- billable: false,
224
- }
225
- : null);
219
+ const canUseAiAssistant = hasDesktopFeatureAccess(desktopEntitlement, 'ai_assistant');
220
+
221
+ // Sync BYOK key availability into the store and track per-provider state
222
+ const [keyStateAnthropic, setKeyStateAnthropic] = useState(hasAnthropicKey);
223
+ const [keyStateOpenai, setKeyStateOpenai] = useState(hasOpenaiKey);
224
+ useEffect(() => {
225
+ const refresh = () => {
226
+ const a = hasAnthropicKey();
227
+ const o = hasOpenaiKey();
228
+ setKeyStateAnthropic(a);
229
+ setKeyStateOpenai(o);
230
+ setChatHasByokKey(a || o);
231
+ };
232
+ refresh();
233
+ return subscribeApiKeys(refresh);
234
+ }, [setChatHasByokKey]);
235
+
236
+ const displayUsage: UsageInfo | null = usage;
226
237
  const usageResetLabel = displayUsage?.resetAt && displayUsage.resetAt > 0
227
238
  ? new Date(displayUsage.resetAt * 1000).toLocaleDateString()
228
239
  : '—';
@@ -233,6 +244,10 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
233
244
  const [showScrollBtn, setShowScrollBtn] = useState(false);
234
245
  const [userScrolledUp, setUserScrolledUp] = useState(false);
235
246
  const [lastFinishReason, setLastFinishReason] = useState<string | null>(null);
247
+ const promptAiUpgrade = useCallback(() => {
248
+ setChatError('AI assistant is available with Desktop Pro.');
249
+ toast.info('AI assistant is available with Desktop Pro');
250
+ }, [setChatError]);
236
251
 
237
252
  const inputRef = useRef<HTMLTextAreaElement>(null);
238
253
  const scrollRef = useRef<HTMLDivElement>(null);
@@ -294,7 +309,7 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
294
309
  useEffect(() => {
295
310
  let cancelled = false;
296
311
  const refreshUsage = async () => {
297
- const snapshot = await fetchUsageSnapshot(PROXY_URL, authToken);
312
+ const snapshot = await fetchUsageSnapshot(PROXY_URL);
298
313
  if (!cancelled && snapshot) {
299
314
  setChatUsage(snapshot);
300
315
  }
@@ -309,7 +324,7 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
309
324
  cancelled = true;
310
325
  window.clearInterval(timer);
311
326
  };
312
- }, [authToken, setChatUsage]);
327
+ }, [setChatUsage]);
313
328
 
314
329
  // ── Keyboard shortcuts ──
315
330
  useEffect(() => {
@@ -385,6 +400,24 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
385
400
  // ── Core send logic ──
386
401
  const doSend = useCallback(async (text: string, options?: ChatSendOptions) => {
387
402
  if (!text.trim() || status === 'streaming' || status === 'sending') return;
403
+ if (!canUseAiAssistant) {
404
+ setChatError('AI assistant is available with Desktop Pro.');
405
+ return;
406
+ }
407
+
408
+ // Resolve the stream route BEFORE any user-visible side effects (adding
409
+ // the user message, clearing attachments, setting sending state). If the
410
+ // selected BYOK model has no key, bail out now so the chat transcript
411
+ // doesn't stack orphaned user messages on repeated sends.
412
+ const route = resolveStreamRoute(activeModel, getApiKeys());
413
+ if (route.kind === 'missing-key') {
414
+ setChatError(
415
+ route.provider === 'anthropic'
416
+ ? 'Enter your Anthropic API key above to use this model.'
417
+ : 'Enter your OpenAI API key above to use this model.',
418
+ );
419
+ return;
420
+ }
388
421
 
389
422
  const continuationBase = options?.continuationBase;
390
423
  const responseIntent = options?.intent ?? 'create';
@@ -571,14 +604,8 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
571
604
  }
572
605
  };
573
606
 
574
- await streamChat({
575
- proxyUrl: PROXY_URL,
576
- model: activeModel,
577
- messages: streamMessages,
578
- system: systemPrompt,
579
- authToken,
580
- signal: abortController.signal,
581
- onChunk: (chunk) => {
607
+ // ── Shared stream callbacks ──
608
+ const handleChunk = (chunk: string) => {
582
609
  clearPendingAttachmentsOnce();
583
610
  accumulated += chunk;
584
611
  if (!responseEditState.applyFailed && responseEditState.intent !== 'repair') {
@@ -612,8 +639,8 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
612
639
  }
613
640
  setChatStatus('streaming');
614
641
  updateStreaming(accumulated);
615
- },
616
- onComplete: (fullText) => {
642
+ };
643
+ const handleComplete = (fullText: string) => {
617
644
  clearPendingAttachmentsOnce();
618
645
  const normalizedText = continuationBase
619
646
  ? stripContinuationOverlap(continuationBase, fullText)
@@ -755,22 +782,62 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
755
782
  }
756
783
 
757
784
  commitAssistantTurn();
758
- },
759
- onUsageInfo: (info: UsageInfo) => {
785
+ };
786
+ const handleUsageInfo = (info: UsageInfo) => {
760
787
  setChatUsage(info);
761
- },
762
- onFinishReason: (reason) => {
788
+ };
789
+ const handleFinishReason = (reason: string | null) => {
763
790
  setLastFinishReason(reason);
764
791
  if (reason === 'length') {
765
792
  setChatError('Response reached output limit. Click Continue to resume.');
766
793
  }
767
- },
768
- onError: (err) => {
794
+ };
795
+ const handleError = (err: Error) => {
769
796
  setChatError(err.message);
770
797
  setChatAbortController(null);
771
798
  commitAssistantTurn();
772
- },
773
- });
799
+ };
800
+
801
+ // Route to direct provider streaming for BYOK models, or through the proxy
802
+ // for free models. The route was already resolved (and the missing-key
803
+ // case handled) at the top of doSend, so this dispatch is total.
804
+ if (route.kind === 'anthropic') {
805
+ await streamAnthropicChat(route.apiKey, {
806
+ model: activeModel,
807
+ messages: streamMessages,
808
+ system: systemPrompt,
809
+ signal: abortController.signal,
810
+ onChunk: handleChunk,
811
+ onComplete: handleComplete,
812
+ onFinishReason: handleFinishReason,
813
+ onError: handleError,
814
+ });
815
+ } else if (route.kind === 'openai') {
816
+ await streamOpenAiChat(route.apiKey, {
817
+ model: activeModel,
818
+ messages: streamMessages,
819
+ system: systemPrompt,
820
+ signal: abortController.signal,
821
+ onChunk: handleChunk,
822
+ onComplete: handleComplete,
823
+ onFinishReason: handleFinishReason,
824
+ onError: handleError,
825
+ });
826
+ } else {
827
+ await streamChat({
828
+ proxyUrl: PROXY_URL,
829
+ model: activeModel,
830
+ messages: streamMessages,
831
+ system: systemPrompt,
832
+ signal: abortController.signal,
833
+ onChunk: handleChunk,
834
+ onComplete: handleComplete,
835
+ onFinishReason: handleFinishReason,
836
+ onError: handleError,
837
+ onUsageInfo: handleUsageInfo,
838
+ });
839
+ }
840
+
774
841
  if (abortController.signal.aborted) {
775
842
  commitAssistantTurn();
776
843
  const currentState = useViewerStore.getState();
@@ -780,15 +847,19 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
780
847
  }
781
848
  }
782
849
  }, [
783
- status, activeModel, attachments, authToken,
850
+ canUseAiAssistant, status, activeModel, attachments,
784
851
  addMessage, setChatStatus, updateStreaming, finalizeAssistant,
785
852
  setChatError, setChatAbortController, clearAttachments, setChatUsage, resizeInput,
786
853
  buildRepairPromptFromLiveState, triggerAutoRepair, execute,
787
854
  ]);
788
855
 
789
856
  const handleSend = useCallback(() => {
857
+ if (!canUseAiAssistant) {
858
+ promptAiUpgrade();
859
+ return;
860
+ }
790
861
  doSend(inputText);
791
- }, [inputText, doSend]);
862
+ }, [canUseAiAssistant, doSend, inputText, promptAiUpgrade]);
792
863
 
793
864
  // Allow other panels (e.g. ScriptPanel errors) to trigger a chat repair turn.
794
865
  useEffect(() => {
@@ -817,6 +888,10 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
817
888
  }, [pendingRepairRequest, status, consumePendingRepairRequest, buildRepairPromptFromLiveState, doSend]);
818
889
 
819
890
  const handleContinue = useCallback(() => {
891
+ if (!canUseAiAssistant) {
892
+ promptAiUpgrade();
893
+ return;
894
+ }
820
895
  const state = useViewerStore.getState();
821
896
  const partial = state.chatStreamingContent.trim();
822
897
  const lastAssistant = [...state.chatMessages].reverse().find((m) => m.role === 'assistant');
@@ -829,7 +904,7 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
829
904
  }
830
905
  setChatError(null);
831
906
  doSend(CONTINUE_PROMPT, { continuationBase });
832
- }, [doSend, finalizeAssistant, setChatError]);
907
+ }, [canUseAiAssistant, doSend, finalizeAssistant, promptAiUpgrade, setChatError]);
833
908
 
834
909
  const handleStop = useCallback(() => {
835
910
  const controller = useViewerStore.getState().chatAbortController;
@@ -854,6 +929,10 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
854
929
 
855
930
  // ── Error feedback (Fix this) ──
856
931
  const handleFixError = useCallback((code: string, errorMsg: string) => {
932
+ if (!canUseAiAssistant) {
933
+ promptAiUpgrade();
934
+ return;
935
+ }
857
936
  const diagnostics = useViewerStore.getState().scriptLastDiagnostics;
858
937
  const liveCode = useViewerStore.getState().scriptEditorContent;
859
938
  const staleCode = code.trim() !== liveCode.trim() ? code : undefined;
@@ -868,7 +947,7 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
868
947
  requestedRepairScope: getPrimaryRootCause(diagnostics)?.repairScope,
869
948
  rootCauseKey: getPrimaryRootCause(diagnostics)?.rootCauseKey,
870
949
  });
871
- }, [buildRepairPromptFromLiveState, doSend]);
950
+ }, [buildRepairPromptFromLiveState, canUseAiAssistant, doSend, promptAiUpgrade]);
872
951
 
873
952
  // ── Clickable example prompts ──
874
953
  const handleExampleClick = useCallback((prompt: string) => {
@@ -898,6 +977,10 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
898
977
 
899
978
  // ── File upload (button + drag-drop + paste) ──
900
979
  const processFiles = useCallback(async (files: FileList | File[]) => {
980
+ if (!canUseAiAssistant) {
981
+ promptAiUpgrade();
982
+ return;
983
+ }
901
984
  const model = getModelById(activeModel);
902
985
  const supportsImages = model?.supportsImages ?? false;
903
986
  const supportsFileAttachments = model?.supportsFileAttachments ?? true;
@@ -965,7 +1048,7 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
965
1048
  setChatError(`Could not read ${file.name}: ${error instanceof Error ? error.message : String(error)}`);
966
1049
  }
967
1050
  }
968
- }, [activeModel, addAttachment, attachments.length, setChatError]);
1051
+ }, [activeModel, addAttachment, attachments.length, canUseAiAssistant, promptAiUpgrade, setChatError]);
969
1052
 
970
1053
  const handleFileUpload = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
971
1054
  const files = e.target.files;
@@ -1038,19 +1121,15 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
1038
1121
  modelSupportsImages ? 'image/*' : '',
1039
1122
  ].filter(Boolean).join(',');
1040
1123
  const canAttachInput = modelSupportsFiles || modelSupportsImages;
1041
- const clerkEnabled = isClerkConfigured();
1042
- const showUpgradeNudge = Boolean(error && (error.includes('Upgrade to Pro') || error.includes('daily limit')));
1124
+ // Detect when selected model needs a missing BYOK key (reactive state, not raw reads)
1125
+ const modelSource = modelForUi?.source ?? 'proxy';
1126
+ const needsAnthropicKey = modelSource === 'anthropic' && !keyStateAnthropic;
1127
+ const needsOpenaiKey = modelSource === 'openai' && !keyStateOpenai;
1128
+ const needsByokKey = needsAnthropicKey || needsOpenaiKey;
1043
1129
  const showSupportEmail = Boolean(error && error.includes('louis@ltplus.com'));
1044
1130
  const canContinue = Boolean(
1045
1131
  !isActive && (streamingContent.trim().length > 0 || lastFinishReason === 'length'),
1046
1132
  );
1047
- const openUpgradePage = useCallback(() => {
1048
- const currentPath = `${window.location.pathname}${window.location.search}`;
1049
- const target = `/upgrade?returnTo=${encodeURIComponent(currentPath)}`;
1050
- window.history.pushState({}, '', target);
1051
- window.dispatchEvent(new PopStateEvent('popstate'));
1052
- }, []);
1053
-
1054
1133
  return (
1055
1134
  <div
1056
1135
  className="h-full flex flex-col bg-background relative"
@@ -1085,7 +1164,7 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
1085
1164
  <TooltipContent>Clear</TooltipContent>
1086
1165
  </Tooltip>
1087
1166
 
1088
- <ModelSelector hasPro={hasPro} />
1167
+ <ModelSelector />
1089
1168
  <div className="flex-1" />
1090
1169
 
1091
1170
  <Tooltip>
@@ -1102,31 +1181,6 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
1102
1181
  <TooltipContent>Auto-run: {autoExecute ? 'ON' : 'OFF'}</TooltipContent>
1103
1182
  </Tooltip>
1104
1183
 
1105
- {clerkEnabled && (
1106
- <>
1107
- <SignedOut>
1108
- <SignInButton mode="modal">
1109
- <Button variant="ghost" size="sm" className="h-6 px-2 text-xs text-muted-foreground">
1110
- Sign in
1111
- </Button>
1112
- </SignInButton>
1113
- </SignedOut>
1114
- {!hasPro && (
1115
- <Button
1116
- variant="ghost"
1117
- size="sm"
1118
- className="h-6 px-2 text-xs text-muted-foreground"
1119
- onClick={openUpgradePage}
1120
- >
1121
- Pro
1122
- </Button>
1123
- )}
1124
- <SignedIn>
1125
- <UserButton afterSignOutUrl="/" />
1126
- </SignedIn>
1127
- </>
1128
- )}
1129
-
1130
1184
  {onClose && (
1131
1185
  <Button variant="ghost" size="icon-xs" onClick={onClose}>
1132
1186
  <X className="h-3.5 w-3.5" />
@@ -1134,6 +1188,17 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
1134
1188
  )}
1135
1189
  </div>
1136
1190
 
1191
+ {!canUseAiAssistant && (
1192
+ <div className="border-b bg-muted/40 px-3 py-2 text-xs text-muted-foreground">
1193
+ AI assistant requires Desktop Pro. Core viewing and scripting stay available without it.
1194
+ </div>
1195
+ )}
1196
+
1197
+ {/* Inline BYOK key prompt — shown when user picks a model without the matching key */}
1198
+ {needsByokKey && canUseAiAssistant && (
1199
+ <InlineKeyPrompt provider={needsAnthropicKey ? 'anthropic' : 'openai'} />
1200
+ )}
1201
+
1137
1202
  {/* Clear confirmation */}
1138
1203
  {showClearConfirm && (
1139
1204
  <div className="px-3 py-2 bg-destructive/5 border-b flex items-center gap-2 text-xs">
@@ -1238,16 +1303,6 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
1238
1303
  Continue
1239
1304
  </Button>
1240
1305
  )}
1241
- {showUpgradeNudge && clerkEnabled && (
1242
- <Button
1243
- variant="outline"
1244
- size="sm"
1245
- className="h-5 px-2 text-[10px]"
1246
- onClick={openUpgradePage}
1247
- >
1248
- Upgrade
1249
- </Button>
1250
- )}
1251
1306
  {showSupportEmail && (
1252
1307
  <a className="underline text-[10px]" href="mailto:louis@ltplus.com">
1253
1308
  Contact support
@@ -1308,14 +1363,16 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
1308
1363
  variant="ghost"
1309
1364
  size="icon-xs"
1310
1365
  onClick={() => fileInputRef.current?.click()}
1311
- disabled={!canAttachInput}
1366
+ disabled={!canAttachInput || !canUseAiAssistant}
1312
1367
  className="shrink-0 mb-0.5"
1313
1368
  >
1314
1369
  <Paperclip className="h-3.5 w-3.5" />
1315
1370
  </Button>
1316
1371
  </TooltipTrigger>
1317
1372
  <TooltipContent>
1318
- {canAttachInput
1373
+ {!canUseAiAssistant
1374
+ ? 'AI assistant not available'
1375
+ : canAttachInput
1319
1376
  ? 'Attach file or image (paste, drag & drop)'
1320
1377
  : 'Selected model does not support attachments'}
1321
1378
  </TooltipContent>
@@ -1330,10 +1387,11 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
1330
1387
  }}
1331
1388
  onKeyDown={handleKeyDown}
1332
1389
  onPaste={handlePaste}
1333
- placeholder="Ask anything..."
1390
+ placeholder={!canUseAiAssistant ? 'AI assistant not available' : needsByokKey ? `Enter your ${needsAnthropicKey ? 'Anthropic' : 'OpenAI'} API key above` : 'Ask anything...'}
1334
1391
  rows={1}
1335
1392
  className="flex-1 resize-none rounded-md border border-input bg-background text-foreground placeholder:text-muted-foreground px-3 py-1.5 text-sm min-h-[32px] max-h-[120px] focus:outline-none focus:ring-1 focus:ring-ring"
1336
1393
  style={{ height: 'auto', overflow: 'hidden' }}
1394
+ disabled={!canUseAiAssistant || needsByokKey}
1337
1395
  />
1338
1396
 
1339
1397
  {isActive ? (
@@ -1357,7 +1415,7 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
1357
1415
  variant="default"
1358
1416
  size="icon-xs"
1359
1417
  onClick={handleSend}
1360
- disabled={!inputText.trim()}
1418
+ disabled={!inputText.trim() || !canUseAiAssistant || needsByokKey}
1361
1419
  className="shrink-0 mb-0.5"
1362
1420
  >
1363
1421
  <Send className="h-3.5 w-3.5" />
@@ -1400,3 +1458,87 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
1400
1458
  </div>
1401
1459
  );
1402
1460
  }
1461
+
1462
+ // ── Inline BYOK key prompt (shown inside chat panel when key is missing) ──
1463
+
1464
+ const PROVIDER_INFO = {
1465
+ anthropic: {
1466
+ label: 'Anthropic',
1467
+ placeholder: 'sk-ant-api03-...',
1468
+ url: 'https://console.anthropic.com/settings/keys',
1469
+ urlLabel: 'console.anthropic.com',
1470
+ },
1471
+ openai: {
1472
+ label: 'OpenAI',
1473
+ placeholder: 'sk-...',
1474
+ url: 'https://platform.openai.com/api-keys',
1475
+ urlLabel: 'platform.openai.com',
1476
+ },
1477
+ } as const;
1478
+
1479
+ function InlineKeyPrompt({ provider }: { provider: 'anthropic' | 'openai' }) {
1480
+ const [value, setValue] = useState('');
1481
+ const [show, setShow] = useState(false);
1482
+ const [saved, setSaved] = useState(false);
1483
+ const info = PROVIDER_INFO[provider];
1484
+
1485
+ const handleSave = useCallback(() => {
1486
+ const trimmed = value.trim();
1487
+ if (!trimmed) return;
1488
+ if (provider === 'anthropic') {
1489
+ updateApiKeys({ anthropicKey: trimmed });
1490
+ } else {
1491
+ updateApiKeys({ openaiKey: trimmed });
1492
+ }
1493
+ setSaved(true);
1494
+ }, [value, provider]);
1495
+
1496
+ // Brief success state before the parent unmounts this component
1497
+ if (saved) {
1498
+ return (
1499
+ <div className="border-b bg-emerald-500/10 px-3 py-2 flex items-center gap-2 text-xs text-emerald-700 dark:text-emerald-400">
1500
+ <Check className="h-3.5 w-3.5" />
1501
+ <span>{info.label} key saved — ready to chat</span>
1502
+ </div>
1503
+ );
1504
+ }
1505
+
1506
+ return (
1507
+ <div className="border-b bg-muted/30 px-3 py-2.5 space-y-1.5">
1508
+ <div className="flex items-center gap-1.5 text-xs font-medium">
1509
+ <Key className="h-3.5 w-3.5" />
1510
+ {info.label} API key required
1511
+ </div>
1512
+ <p className="text-[11px] text-muted-foreground">
1513
+ Paste your key below — stored in your browser only, sent directly to {info.label}.{' '}
1514
+ <a href={info.url} target="_blank" rel="noopener noreferrer" className="underline inline-flex items-center gap-0.5">
1515
+ Get a key <ExternalLink className="h-2.5 w-2.5" />
1516
+ </a>
1517
+ </p>
1518
+ <div className="flex gap-1.5">
1519
+ <div className="relative flex-1">
1520
+ <input
1521
+ type={show ? 'text' : 'password'}
1522
+ value={value}
1523
+ onChange={(e) => setValue(e.target.value)}
1524
+ onKeyDown={(e) => { if (e.key === 'Enter') handleSave(); }}
1525
+ placeholder={info.placeholder}
1526
+ autoComplete="off"
1527
+ spellCheck={false}
1528
+ className="w-full rounded border border-input bg-background px-2 py-1 text-xs font-mono focus:outline-none focus:ring-1 focus:ring-ring pr-7"
1529
+ />
1530
+ <button
1531
+ type="button"
1532
+ onClick={() => setShow(!show)}
1533
+ className="absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
1534
+ >
1535
+ {show ? <EyeOff className="h-3 w-3" /> : <Eye className="h-3 w-3" />}
1536
+ </button>
1537
+ </div>
1538
+ <Button size="sm" className="h-7 px-3 text-xs" onClick={handleSave} disabled={!value.trim()}>
1539
+ Save
1540
+ </Button>
1541
+ </div>
1542
+ </div>
1543
+ );
1544
+ }
@@ -70,6 +70,7 @@ import { SCRIPT_TEMPLATES } from '@/lib/scripts/templates';
70
70
  import { GLTFExporter, CSVExporter } from '@ifc-lite/export';
71
71
  import { getRecentFiles, formatFileSize, getCachedFile } from '@/lib/recent-files';
72
72
  import type { RecentFileEntry } from '@/lib/recent-files';
73
+ import { closeActiveAnalysisExtension } from '@/services/analysis-extensions';
73
74
 
74
75
  // ── Types ──────────────────────────────────────────────────────────────
75
76
 
@@ -189,6 +190,8 @@ function activateRightPanel(panel: 'bcf' | 'ids' | 'lens') {
189
190
  panel === 'ids' ? s.idsPanelVisible :
190
191
  s.lensPanelVisible;
191
192
 
193
+ closeActiveAnalysisExtension();
194
+
192
195
  // Close all content panels
193
196
  s.setBcfPanelVisible(false);
194
197
  s.setIdsPanelVisible(false);
@@ -211,6 +214,8 @@ function activateBottomPanel(panel: 'script' | 'list') {
211
214
  const s = useViewerStore.getState();
212
215
  const isActive = panel === 'script' ? s.scriptPanelVisible : s.listPanelVisible;
213
216
 
217
+ closeActiveAnalysisExtension();
218
+
214
219
  // Close all bottom panels
215
220
  s.setScriptPanelVisible(false);
216
221
  s.setListPanelVisible(false);
@@ -270,7 +275,7 @@ export function CommandPalette({ open, onOpenChange }: CommandPaletteProps) {
270
275
  detail: formatFileSize(rf.size),
271
276
  action: () => {
272
277
  // Try loading from IndexedDB blob cache → dispatches to MainToolbar's loadFile
273
- getCachedFile(fileName).then(file => {
278
+ getCachedFile(rf).then(file => {
274
279
  if (file) {
275
280
  window.dispatchEvent(new CustomEvent('ifc-lite:load-file', { detail: file }));
276
281
  } else {