comfyui-frontend-package 1.37.9__py3-none-any.whl → 1.37.10__py3-none-any.whl

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 (174) hide show
  1. comfyui_frontend_package/static/assets/{AboutPanel-BudnQURa.js → AboutPanel-C5DQxPC9.js} +2 -2
  2. comfyui_frontend_package/static/assets/{AboutPanel-BudnQURa.js.map → AboutPanel-C5DQxPC9.js.map} +1 -1
  3. comfyui_frontend_package/static/assets/{AudioPreviewPlayer-CHjtFZRu.js → AudioPreviewPlayer-BNO9ZjOM.js} +2 -2
  4. comfyui_frontend_package/static/assets/{AudioPreviewPlayer-CHjtFZRu.js.map → AudioPreviewPlayer-BNO9ZjOM.js.map} +1 -1
  5. comfyui_frontend_package/static/assets/{ComfyQueueButton-QhN0FmOY.js → ComfyQueueButton-D0Y-jFOd.js} +2 -2
  6. comfyui_frontend_package/static/assets/{ComfyQueueButton-QhN0FmOY.js.map → ComfyQueueButton-D0Y-jFOd.js.map} +1 -1
  7. comfyui_frontend_package/static/assets/{ExtensionPanel-D2sITMri.js → ExtensionPanel-CbCxE4jg.js} +2 -2
  8. comfyui_frontend_package/static/assets/{ExtensionPanel-D2sITMri.js.map → ExtensionPanel-CbCxE4jg.js.map} +1 -1
  9. comfyui_frontend_package/static/assets/GraphView-CepV6iZo.css +1 -0
  10. comfyui_frontend_package/static/assets/GraphView-_ZGu5uNf.js +15 -0
  11. comfyui_frontend_package/static/assets/GraphView-_ZGu5uNf.js.map +1 -0
  12. comfyui_frontend_package/static/assets/{KeybindingPanel-Dm6H1RN2.js → KeybindingPanel-Ceb51ttS.js} +2 -2
  13. comfyui_frontend_package/static/assets/{KeybindingPanel-Dm6H1RN2.js.map → KeybindingPanel-Ceb51ttS.js.map} +1 -1
  14. comfyui_frontend_package/static/assets/{LazyImage.vue_vue_type_script_setup_true_lang-C58MdJKR.js → LazyImage.vue_vue_type_script_setup_true_lang-BHcWsrBj.js} +2 -2
  15. comfyui_frontend_package/static/assets/{LazyImage.vue_vue_type_script_setup_true_lang-C58MdJKR.js.map → LazyImage.vue_vue_type_script_setup_true_lang-BHcWsrBj.js.map} +1 -1
  16. comfyui_frontend_package/static/assets/{LegacyCreditsPanel-Be7L1Af3.js → LegacyCreditsPanel-K_GDtrt_.js} +2 -2
  17. comfyui_frontend_package/static/assets/{LegacyCreditsPanel-Be7L1Af3.js.map → LegacyCreditsPanel-K_GDtrt_.js.map} +1 -1
  18. comfyui_frontend_package/static/assets/{Load3D-BAJ3yM4D.js → Load3D-CbCbkSys.js} +2 -2
  19. comfyui_frontend_package/static/assets/Load3D-CbCbkSys.js.map +1 -0
  20. comfyui_frontend_package/static/assets/{Load3D.vue_vue_type_script_setup_true_lang-DMHpkO9N.js → Load3D.vue_vue_type_script_setup_true_lang-3QcwinlP.js} +2 -2
  21. comfyui_frontend_package/static/assets/{Load3D.vue_vue_type_script_setup_true_lang-DMHpkO9N.js.map → Load3D.vue_vue_type_script_setup_true_lang-3QcwinlP.js.map} +1 -1
  22. comfyui_frontend_package/static/assets/{Media3DTop-DdNgRlQW.js → Media3DTop--kp0iekJ.js} +2 -2
  23. comfyui_frontend_package/static/assets/{Media3DTop-DdNgRlQW.js.map → Media3DTop--kp0iekJ.js.map} +1 -1
  24. comfyui_frontend_package/static/assets/{ServerConfigPanel-LtRqEOzu.js → ServerConfigPanel-BGnrWtif.js} +2 -2
  25. comfyui_frontend_package/static/assets/{ServerConfigPanel-LtRqEOzu.js.map → ServerConfigPanel-BGnrWtif.js.map} +1 -1
  26. comfyui_frontend_package/static/assets/{SubscriptionRequiredDialogContent-CDaF1jns.js → SubscriptionRequiredDialogContent-BMLw-0ti.js} +2 -2
  27. comfyui_frontend_package/static/assets/{SubscriptionRequiredDialogContent-CDaF1jns.js.map → SubscriptionRequiredDialogContent-BMLw-0ti.js.map} +1 -1
  28. comfyui_frontend_package/static/assets/{UserPanel-_xK7KdbS.js → UserPanel-BfOBqtyE.js} +2 -2
  29. comfyui_frontend_package/static/assets/{UserPanel-_xK7KdbS.js.map → UserPanel-BfOBqtyE.js.map} +1 -1
  30. comfyui_frontend_package/static/assets/{UserSelectView-B_VOz5XU.js → UserSelectView-hAFmUbm0.js} +2 -2
  31. comfyui_frontend_package/static/assets/{UserSelectView-B_VOz5XU.js.map → UserSelectView-hAFmUbm0.js.map} +1 -1
  32. comfyui_frontend_package/static/assets/{ValueControlPopover-BPJIx3_y.js → ValueControlPopover-BEbJxP9Z.js} +2 -2
  33. comfyui_frontend_package/static/assets/{ValueControlPopover-BPJIx3_y.js.map → ValueControlPopover-BEbJxP9Z.js.map} +1 -1
  34. comfyui_frontend_package/static/assets/{WidgetAudioUI-5nP5sugf.js → WidgetAudioUI-BuXP7XBQ.js} +2 -2
  35. comfyui_frontend_package/static/assets/{WidgetAudioUI-5nP5sugf.js.map → WidgetAudioUI-BuXP7XBQ.js.map} +1 -1
  36. comfyui_frontend_package/static/assets/{WidgetButton-Cd9G_fIM.js → WidgetButton-vKBzo4q0.js} +2 -2
  37. comfyui_frontend_package/static/assets/{WidgetButton-Cd9G_fIM.js.map → WidgetButton-vKBzo4q0.js.map} +1 -1
  38. comfyui_frontend_package/static/assets/{WidgetColorPicker-x6uZPjSf.js → WidgetColorPicker-FoC3o0Lp.js} +2 -2
  39. comfyui_frontend_package/static/assets/{WidgetColorPicker-x6uZPjSf.js.map → WidgetColorPicker-FoC3o0Lp.js.map} +1 -1
  40. comfyui_frontend_package/static/assets/{WidgetGalleria-DHPc3hwg.js → WidgetGalleria-CV4Nv2wv.js} +2 -2
  41. comfyui_frontend_package/static/assets/{WidgetGalleria-DHPc3hwg.js.map → WidgetGalleria-CV4Nv2wv.js.map} +1 -1
  42. comfyui_frontend_package/static/assets/WidgetInputNumber-zsVb0Vit.js +2 -0
  43. comfyui_frontend_package/static/assets/WidgetInputNumber-zsVb0Vit.js.map +1 -0
  44. comfyui_frontend_package/static/assets/{WidgetInputNumber.vue_vue_type_script_setup_true_lang-vY96bQKz.js → WidgetInputNumber.vue_vue_type_script_setup_true_lang-D-IwEPPr.js} +2 -2
  45. comfyui_frontend_package/static/assets/{WidgetInputNumber.vue_vue_type_script_setup_true_lang-vY96bQKz.js.map → WidgetInputNumber.vue_vue_type_script_setup_true_lang-D-IwEPPr.js.map} +1 -1
  46. comfyui_frontend_package/static/assets/{WidgetInputText-BbfUME5C.js → WidgetInputText-Cv8Z1_b9.js} +2 -2
  47. comfyui_frontend_package/static/assets/{WidgetInputText-BbfUME5C.js.map → WidgetInputText-Cv8Z1_b9.js.map} +1 -1
  48. comfyui_frontend_package/static/assets/{WidgetLayoutField.vue_vue_type_script_setup_true_lang-7bIn_5fI.js → WidgetLayoutField.vue_vue_type_script_setup_true_lang-DFWVBJH-.js} +2 -2
  49. comfyui_frontend_package/static/assets/{WidgetLayoutField.vue_vue_type_script_setup_true_lang-7bIn_5fI.js.map → WidgetLayoutField.vue_vue_type_script_setup_true_lang-DFWVBJH-.js.map} +1 -1
  50. comfyui_frontend_package/static/assets/{WidgetLegacy-AkIJRd8v.js → WidgetLegacy-Dh4xE798.js} +2 -2
  51. comfyui_frontend_package/static/assets/WidgetLegacy-Dh4xE798.js.map +1 -0
  52. comfyui_frontend_package/static/assets/WidgetMarkdown-C4CnxNoM.js +2 -0
  53. comfyui_frontend_package/static/assets/{WidgetMarkdown-voa7Ohgp.js.map → WidgetMarkdown-C4CnxNoM.js.map} +1 -1
  54. comfyui_frontend_package/static/assets/{WidgetRecordAudio-DelweXYm.js → WidgetRecordAudio-BQ9yATXo.js} +2 -2
  55. comfyui_frontend_package/static/assets/{WidgetRecordAudio-DelweXYm.js.map → WidgetRecordAudio-BQ9yATXo.js.map} +1 -1
  56. comfyui_frontend_package/static/assets/WidgetSelect-B9DwAKiE.js +2 -0
  57. comfyui_frontend_package/static/assets/WidgetSelect-B9DwAKiE.js.map +1 -0
  58. comfyui_frontend_package/static/assets/{WidgetSelect.vue_vue_type_script_setup_true_lang-CT0J0vij.js → WidgetSelect.vue_vue_type_script_setup_true_lang-EU2Y4eXi.js} +2 -2
  59. comfyui_frontend_package/static/assets/{WidgetSelect.vue_vue_type_script_setup_true_lang-CT0J0vij.js.map → WidgetSelect.vue_vue_type_script_setup_true_lang-EU2Y4eXi.js.map} +1 -1
  60. comfyui_frontend_package/static/assets/{WidgetTextarea-SHMM72Cj.js → WidgetTextarea-C3Wo0ULT.js} +2 -2
  61. comfyui_frontend_package/static/assets/{WidgetTextarea-SHMM72Cj.js.map → WidgetTextarea-C3Wo0ULT.js.map} +1 -1
  62. comfyui_frontend_package/static/assets/{WidgetToggleSwitch-BmT1MQCn.js → WidgetToggleSwitch-BZUubsU5.js} +2 -2
  63. comfyui_frontend_package/static/assets/{WidgetToggleSwitch-BmT1MQCn.js.map → WidgetToggleSwitch-BZUubsU5.js.map} +1 -1
  64. comfyui_frontend_package/static/assets/{WidgetWithControl.vue_vue_type_script_setup_true_lang-CTEGWC4h.js → WidgetWithControl.vue_vue_type_script_setup_true_lang-B7cz0rwK.js} +3 -3
  65. comfyui_frontend_package/static/assets/{WidgetWithControl.vue_vue_type_script_setup_true_lang-CTEGWC4h.js.map → WidgetWithControl.vue_vue_type_script_setup_true_lang-B7cz0rwK.js.map} +1 -1
  66. comfyui_frontend_package/static/assets/{audioService-BcbrJBnn.js → audioService-CNb54hvw.js} +2 -2
  67. comfyui_frontend_package/static/assets/{audioService-BcbrJBnn.js.map → audioService-CNb54hvw.js.map} +1 -1
  68. comfyui_frontend_package/static/assets/{audioUtils-CJCA0wCG.js → audioUtils-ClyG3xvN.js} +2 -2
  69. comfyui_frontend_package/static/assets/{audioUtils-CJCA0wCG.js.map → audioUtils-ClyG3xvN.js.map} +1 -1
  70. comfyui_frontend_package/static/assets/commands-DHxq3nWN.js +2 -0
  71. comfyui_frontend_package/static/assets/commands-DHxq3nWN.js.map +1 -0
  72. comfyui_frontend_package/static/assets/{index-DvCHS_f2.js → index-B3VEOed6.js} +20 -20
  73. comfyui_frontend_package/static/assets/index-B3VEOed6.js.map +1 -0
  74. comfyui_frontend_package/static/assets/index-B5S_Le6r.js +5 -0
  75. comfyui_frontend_package/static/assets/index-B5S_Le6r.js.map +1 -0
  76. comfyui_frontend_package/static/assets/index-CPtP45Cb.css +1 -0
  77. comfyui_frontend_package/static/assets/{index-kQCr6fgp.js → index-CtDW77xi.js} +2 -2
  78. comfyui_frontend_package/static/assets/{index-kQCr6fgp.js.map → index-CtDW77xi.js.map} +1 -1
  79. comfyui_frontend_package/static/assets/{keybindingService-D3pQ4cJG.js → keybindingService-50G4NbCc.js} +2 -2
  80. comfyui_frontend_package/static/assets/{keybindingService-D3pQ4cJG.js.map → keybindingService-50G4NbCc.js.map} +1 -1
  81. comfyui_frontend_package/static/assets/{main-BFC5CmrY.js → main-B3vd7GL1.js} +3 -3
  82. comfyui_frontend_package/static/assets/main-B3vd7GL1.js.map +1 -0
  83. comfyui_frontend_package/static/assets/{main-M4YqFlAB.js → main-BKCc2TkD.js} +3 -3
  84. comfyui_frontend_package/static/assets/main-BKCc2TkD.js.map +1 -0
  85. comfyui_frontend_package/static/assets/{main-C0iYASPd.js → main-BNvzUx74.js} +3 -3
  86. comfyui_frontend_package/static/assets/main-BNvzUx74.js.map +1 -0
  87. comfyui_frontend_package/static/assets/main-BPu7av80.js +17 -0
  88. comfyui_frontend_package/static/assets/main-BPu7av80.js.map +1 -0
  89. comfyui_frontend_package/static/assets/{main-Dd3BY3MO.js → main-BQ4z0Xxc.js} +2 -2
  90. comfyui_frontend_package/static/assets/main-BQ4z0Xxc.js.map +1 -0
  91. comfyui_frontend_package/static/assets/{main-BI-3llij.js → main-BoyaoV4J.js} +2 -2
  92. comfyui_frontend_package/static/assets/main-BoyaoV4J.js.map +1 -0
  93. comfyui_frontend_package/static/assets/{main-DB30B6rY.js → main-CJ1ZmTbz.js} +3 -3
  94. comfyui_frontend_package/static/assets/main-CJ1ZmTbz.js.map +1 -0
  95. comfyui_frontend_package/static/assets/{main-D8hMMX5u.js → main-DxByB8u3.js} +2 -2
  96. comfyui_frontend_package/static/assets/main-DxByB8u3.js.map +1 -0
  97. comfyui_frontend_package/static/assets/{main-BdVq5c-Q.js → main-Dyk43SJi.js} +2 -2
  98. comfyui_frontend_package/static/assets/main-Dyk43SJi.js.map +1 -0
  99. comfyui_frontend_package/static/assets/{main-Cte3FX_T.js → main-QMLeyFJQ.js} +3 -3
  100. comfyui_frontend_package/static/assets/main-QMLeyFJQ.js.map +1 -0
  101. comfyui_frontend_package/static/assets/{main-BPVbVwRz.js → main-f7MqrMkx.js} +3 -3
  102. comfyui_frontend_package/static/assets/main-f7MqrMkx.js.map +1 -0
  103. comfyui_frontend_package/static/assets/nodeDefs-CqPqVYqF.js +54 -0
  104. comfyui_frontend_package/static/assets/nodeDefs-CqPqVYqF.js.map +1 -0
  105. comfyui_frontend_package/static/assets/settings-BDcsAddA.js +6 -0
  106. comfyui_frontend_package/static/assets/settings-BDcsAddA.js.map +1 -0
  107. comfyui_frontend_package/static/assets/settings-BscQZApb.js +6 -0
  108. comfyui_frontend_package/static/assets/settings-BscQZApb.js.map +1 -0
  109. comfyui_frontend_package/static/assets/settings-BxGvSZQt.js +6 -0
  110. comfyui_frontend_package/static/assets/settings-BxGvSZQt.js.map +1 -0
  111. comfyui_frontend_package/static/assets/settings-CRbIyGOx.js +6 -0
  112. comfyui_frontend_package/static/assets/settings-CRbIyGOx.js.map +1 -0
  113. comfyui_frontend_package/static/assets/settings-C_M95LcR.js +6 -0
  114. comfyui_frontend_package/static/assets/settings-C_M95LcR.js.map +1 -0
  115. comfyui_frontend_package/static/assets/{settings-C83z84hb.js → settings-CfAtcjYG.js} +3 -3
  116. comfyui_frontend_package/static/assets/settings-CfAtcjYG.js.map +1 -0
  117. comfyui_frontend_package/static/assets/settings-D1FGfDLO.js +6 -0
  118. comfyui_frontend_package/static/assets/settings-D1FGfDLO.js.map +1 -0
  119. comfyui_frontend_package/static/assets/settings-DIhoSYUg.js +6 -0
  120. comfyui_frontend_package/static/assets/settings-DIhoSYUg.js.map +1 -0
  121. comfyui_frontend_package/static/assets/settings-DR935Ify.js +6 -0
  122. comfyui_frontend_package/static/assets/settings-DR935Ify.js.map +1 -0
  123. comfyui_frontend_package/static/assets/settings-DwzAhg99.js +6 -0
  124. comfyui_frontend_package/static/assets/settings-DwzAhg99.js.map +1 -0
  125. comfyui_frontend_package/static/assets/settings-ydiuzS6H.js +6 -0
  126. comfyui_frontend_package/static/assets/settings-ydiuzS6H.js.map +1 -0
  127. comfyui_frontend_package/static/index.html +1 -1
  128. {comfyui_frontend_package-1.37.9.dist-info → comfyui_frontend_package-1.37.10.dist-info}/METADATA +1 -1
  129. {comfyui_frontend_package-1.37.9.dist-info → comfyui_frontend_package-1.37.10.dist-info}/RECORD +131 -123
  130. comfyui_frontend_package/static/assets/GraphView-BZLpyVMl.css +0 -1
  131. comfyui_frontend_package/static/assets/GraphView-C35zkdNN.js +0 -15
  132. comfyui_frontend_package/static/assets/GraphView-C35zkdNN.js.map +0 -1
  133. comfyui_frontend_package/static/assets/Load3D-BAJ3yM4D.js.map +0 -1
  134. comfyui_frontend_package/static/assets/WidgetInputNumber-Cnhf4Ann.js +0 -2
  135. comfyui_frontend_package/static/assets/WidgetInputNumber-Cnhf4Ann.js.map +0 -1
  136. comfyui_frontend_package/static/assets/WidgetLegacy-AkIJRd8v.js.map +0 -1
  137. comfyui_frontend_package/static/assets/WidgetMarkdown-voa7Ohgp.js +0 -2
  138. comfyui_frontend_package/static/assets/WidgetSelect-XG4R8CnZ.js +0 -2
  139. comfyui_frontend_package/static/assets/WidgetSelect-XG4R8CnZ.js.map +0 -1
  140. comfyui_frontend_package/static/assets/index-BB_8e12C.css +0 -1
  141. comfyui_frontend_package/static/assets/index-Cm0D-ExL.js +0 -5
  142. comfyui_frontend_package/static/assets/index-Cm0D-ExL.js.map +0 -1
  143. comfyui_frontend_package/static/assets/index-DvCHS_f2.js.map +0 -1
  144. comfyui_frontend_package/static/assets/main-BFC5CmrY.js.map +0 -1
  145. comfyui_frontend_package/static/assets/main-BI-3llij.js.map +0 -1
  146. comfyui_frontend_package/static/assets/main-BPVbVwRz.js.map +0 -1
  147. comfyui_frontend_package/static/assets/main-BdVq5c-Q.js.map +0 -1
  148. comfyui_frontend_package/static/assets/main-C0iYASPd.js.map +0 -1
  149. comfyui_frontend_package/static/assets/main-Cte3FX_T.js.map +0 -1
  150. comfyui_frontend_package/static/assets/main-D8hMMX5u.js.map +0 -1
  151. comfyui_frontend_package/static/assets/main-DB30B6rY.js.map +0 -1
  152. comfyui_frontend_package/static/assets/main-Dd3BY3MO.js.map +0 -1
  153. comfyui_frontend_package/static/assets/main-M4YqFlAB.js.map +0 -1
  154. comfyui_frontend_package/static/assets/settings-2GCqRrJG.js +0 -6
  155. comfyui_frontend_package/static/assets/settings-2GCqRrJG.js.map +0 -1
  156. comfyui_frontend_package/static/assets/settings-BwT6YspU.js +0 -6
  157. comfyui_frontend_package/static/assets/settings-BwT6YspU.js.map +0 -1
  158. comfyui_frontend_package/static/assets/settings-C83z84hb.js.map +0 -1
  159. comfyui_frontend_package/static/assets/settings-CpGAolL8.js +0 -6
  160. comfyui_frontend_package/static/assets/settings-CpGAolL8.js.map +0 -1
  161. comfyui_frontend_package/static/assets/settings-DHYEklkf.js +0 -6
  162. comfyui_frontend_package/static/assets/settings-DHYEklkf.js.map +0 -1
  163. comfyui_frontend_package/static/assets/settings-DLFPCNJr.js +0 -6
  164. comfyui_frontend_package/static/assets/settings-DLFPCNJr.js.map +0 -1
  165. comfyui_frontend_package/static/assets/settings-JVJwzdtf.js +0 -6
  166. comfyui_frontend_package/static/assets/settings-JVJwzdtf.js.map +0 -1
  167. comfyui_frontend_package/static/assets/settings-Tg5NBf0t.js +0 -6
  168. comfyui_frontend_package/static/assets/settings-Tg5NBf0t.js.map +0 -1
  169. comfyui_frontend_package/static/assets/settings-_tes1LYa.js +0 -6
  170. comfyui_frontend_package/static/assets/settings-_tes1LYa.js.map +0 -1
  171. comfyui_frontend_package/static/assets/settings-cav_bQJj.js +0 -6
  172. comfyui_frontend_package/static/assets/settings-cav_bQJj.js.map +0 -1
  173. {comfyui_frontend_package-1.37.9.dist-info → comfyui_frontend_package-1.37.10.dist-info}/WHEEL +0 -0
  174. {comfyui_frontend_package-1.37.9.dist-info → comfyui_frontend_package-1.37.10.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"LazyImage.vue_vue_type_script_setup_true_lang-C58MdJKR.js","sources":["../../src/composables/useFeatureFlags.ts","../../src/components/input/SingleSelect.vue","../../src/platform/assets/composables/useModelTypes.ts","../../src/platform/assets/components/UploadModelConfirmation.vue","../../src/platform/assets/components/VideoHelpDialog.vue","../../src/platform/assets/components/UploadModelFooter.vue","../../src/platform/assets/importSources/civitaiImportSource.ts","../../src/platform/assets/importSources/huggingfaceImportSource.ts","../../src/platform/assets/utils/importSourceUtil.ts","../../src/platform/assets/components/UploadModelUrlInput.vue","../../src/platform/assets/components/UploadModelUrlInputCivitai.vue","../../src/platform/assets/composables/useUploadModelWizard.ts","../../src/platform/assets/components/UploadModelDialog.vue","../../../../../../../assets/images/civitai.svg","../../src/platform/assets/components/UploadModelDialogHeader.vue","../../src/platform/assets/components/UploadModelUpgradeModalBody.vue","../../src/platform/assets/components/UploadModelUpgradeModalFooter.vue","../../src/platform/assets/components/UploadModelUpgradeModal.vue","../../src/platform/assets/components/UploadModelUpgradeModalHeader.vue","../../src/platform/assets/composables/useModelUpload.ts","../../../../../../../assets/images/default-template.png","../../src/composables/useIntersectionObserver.ts","../../src/services/mediaCacheService.ts","../../src/components/common/LazyImage.vue"],"sourcesContent":["import { computed, reactive, readonly } from 'vue'\n\nimport { remoteConfig } from '@/platform/remoteConfig/remoteConfig'\nimport { api } from '@/scripts/api'\n\n/**\n * Known server feature flags (top-level, not extensions)\n */\nexport enum ServerFeatureFlag {\n SUPPORTS_PREVIEW_METADATA = 'supports_preview_metadata',\n MAX_UPLOAD_SIZE = 'max_upload_size',\n MANAGER_SUPPORTS_V4 = 'extension.manager.supports_v4',\n MODEL_UPLOAD_BUTTON_ENABLED = 'model_upload_button_enabled',\n ASSET_DELETION_ENABLED = 'asset_deletion_enabled',\n ASSET_RENAME_ENABLED = 'asset_rename_enabled',\n PRIVATE_MODELS_ENABLED = 'private_models_enabled',\n ONBOARDING_SURVEY_ENABLED = 'onboarding_survey_enabled',\n HUGGINGFACE_MODEL_IMPORT_ENABLED = 'huggingface_model_import_enabled',\n ASYNC_MODEL_UPLOAD_ENABLED = 'async_model_upload_enabled'\n}\n\n/**\n * Composable for reactive access to server-side feature flags\n */\nexport function useFeatureFlags() {\n const flags = reactive({\n get supportsPreviewMetadata() {\n return api.getServerFeature(ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA)\n },\n get maxUploadSize() {\n return api.getServerFeature(ServerFeatureFlag.MAX_UPLOAD_SIZE)\n },\n get supportsManagerV4() {\n return api.getServerFeature(ServerFeatureFlag.MANAGER_SUPPORTS_V4)\n },\n get modelUploadButtonEnabled() {\n // Check remote config first (from /api/features), fall back to websocket feature flags\n return (\n remoteConfig.value.model_upload_button_enabled ??\n api.getServerFeature(\n ServerFeatureFlag.MODEL_UPLOAD_BUTTON_ENABLED,\n false\n )\n )\n },\n get assetDeletionEnabled() {\n return (\n remoteConfig.value.asset_deletion_enabled ??\n api.getServerFeature(ServerFeatureFlag.ASSET_DELETION_ENABLED, false)\n )\n },\n get assetRenameEnabled() {\n return (\n remoteConfig.value.asset_rename_enabled ??\n api.getServerFeature(ServerFeatureFlag.ASSET_RENAME_ENABLED, false)\n )\n },\n get privateModelsEnabled() {\n // Check remote config first (from /api/features), fall back to websocket feature flags\n return (\n remoteConfig.value.private_models_enabled ??\n api.getServerFeature(ServerFeatureFlag.PRIVATE_MODELS_ENABLED, false)\n )\n },\n get onboardingSurveyEnabled() {\n return (\n remoteConfig.value.onboarding_survey_enabled ??\n api.getServerFeature(ServerFeatureFlag.ONBOARDING_SURVEY_ENABLED, true)\n )\n },\n get huggingfaceModelImportEnabled() {\n return (\n remoteConfig.value.huggingface_model_import_enabled ??\n api.getServerFeature(\n ServerFeatureFlag.HUGGINGFACE_MODEL_IMPORT_ENABLED,\n false\n )\n )\n },\n get asyncModelUploadEnabled() {\n return (\n remoteConfig.value.async_model_upload_enabled ??\n api.getServerFeature(\n ServerFeatureFlag.ASYNC_MODEL_UPLOAD_ENABLED,\n false\n )\n )\n }\n })\n\n const featureFlag = <T = unknown>(featurePath: string, defaultValue?: T) =>\n computed(() => api.getServerFeature(featurePath, defaultValue))\n\n return {\n flags: readonly(flags),\n featureFlag\n }\n}\n","<template>\n <!--\n Note: We explicitly pass options here (not just via $attrs) because:\n 1. Our custom value template needs options to look up labels from values\n 2. PrimeVue's value slot only provides 'value' and 'placeholder', not the selected item's label\n 3. We need to maintain the icon slot functionality in the value template\n option-label=\"name\" is required because our option template directly accesses option.name\n -->\n <Select\n v-model=\"selectedItem\"\n v-bind=\"$attrs\"\n :options=\"options\"\n option-label=\"name\"\n option-value=\"value\"\n unstyled\n :pt=\"{\n root: ({ props }: SelectPassThroughMethodOptions<SelectOption>) => ({\n class: [\n // container\n 'h-10 relative inline-flex cursor-pointer select-none items-center',\n // trigger surface\n 'rounded-lg',\n 'bg-secondary-background text-base-foreground',\n 'border-[2.5px] border-solid border-transparent',\n 'transition-all duration-200 ease-in-out',\n 'focus-within:border-node-component-border',\n // disabled\n { 'opacity-60 cursor-default': props.disabled }\n ]\n }),\n label: {\n class:\n // Align with MultiSelect labelContainer spacing\n 'flex-1 flex items-center whitespace-nowrap pl-4 py-2 outline-hidden'\n },\n dropdown: {\n class:\n // Right chevron touch area\n 'flex shrink-0 items-center justify-center px-3 py-2'\n },\n overlay: {\n class: cn(\n 'mt-2 p-2 rounded-lg',\n 'bg-base-background text-base-foreground',\n 'border border-solid border-border-default'\n )\n },\n listContainer: () => ({\n style: `max-height: min(${listMaxHeight}, 50vh)`,\n class: 'scrollbar-custom'\n }),\n list: {\n class:\n // Same list tone/size as MultiSelect\n 'flex flex-col gap-0 p-0 m-0 list-none border-none text-sm'\n },\n option: ({ context }: SelectPassThroughMethodOptions<SelectOption>) => ({\n class: cn(\n // Row layout\n 'flex items-center justify-between gap-3 px-2 py-3 rounded',\n 'hover:bg-secondary-background-hover',\n // Add focus state for keyboard navigation\n context.focused && 'bg-secondary-background-hover',\n // Selected state + check icon\n context.selected &&\n 'bg-secondary-background-selected hover:bg-secondary-background-selected'\n )\n }),\n optionLabel: {\n class: 'truncate'\n },\n optionGroupLabel: {\n class: 'px-3 py-2 text-xs uppercase tracking-wide text-muted-foreground'\n },\n emptyMessage: {\n class: 'px-3 py-2 text-sm text-muted-foreground'\n }\n }\"\n :aria-label=\"label || t('g.singleSelectDropdown')\"\n role=\"combobox\"\n :aria-expanded=\"false\"\n aria-haspopup=\"listbox\"\n :tabindex=\"0\"\n >\n <!-- Trigger value -->\n <template #value=\"slotProps\">\n <div class=\"flex items-center gap-2 text-sm\">\n <slot name=\"icon\" />\n <span\n v-if=\"slotProps.value !== null && slotProps.value !== undefined\"\n class=\"text-base-foreground\"\n >\n {{ getLabel(slotProps.value) }}\n </span>\n <span v-else class=\"text-base-foreground\">\n {{ label }}\n </span>\n </div>\n </template>\n\n <!-- Trigger caret -->\n <template #dropdownicon>\n <i class=\"icon-[lucide--chevron-down] text-muted-foreground\" />\n </template>\n\n <!-- Option row -->\n <template #option=\"{ option, selected }\">\n <div\n class=\"flex w-full items-center justify-between gap-3\"\n :style=\"optionStyle\"\n >\n <span class=\"truncate\">{{ option.name }}</span>\n <i v-if=\"selected\" class=\"icon-[lucide--check] text-base-foreground\" />\n </div>\n </template>\n </Select>\n</template>\n\n<script setup lang=\"ts\">\nimport type { SelectPassThroughMethodOptions } from 'primevue/select'\nimport Select from 'primevue/select'\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { cn } from '@/utils/tailwindUtil'\n\nimport type { SelectOption } from './types'\n\ndefineOptions({\n inheritAttrs: false\n})\n\nconst {\n label,\n options,\n listMaxHeight = '28rem',\n popoverMinWidth,\n popoverMaxWidth\n} = defineProps<{\n label?: string\n /**\n * Required for displaying the selected item's label.\n * Cannot rely on $attrs alone because we need to access options\n * in getLabel() to map values to their display names.\n */\n options?: SelectOption[]\n /** Maximum height of the dropdown panel (default: 28rem) */\n listMaxHeight?: string\n /** Minimum width of the popover (default: auto) */\n popoverMinWidth?: string\n /** Maximum width of the popover (default: auto) */\n popoverMaxWidth?: string\n}>()\n\nconst selectedItem = defineModel<string | undefined>({ required: true })\n\nconst { t } = useI18n()\n\n/**\n * Maps a value to its display label.\n * Necessary because PrimeVue's value slot doesn't provide the selected item's label,\n * only the raw value. We need this to show the correct text when an item is selected.\n */\nconst getLabel = (val: string | null | undefined) => {\n if (val == null) return label ?? ''\n if (!options) return label ?? ''\n const found = options.find((o) => o.value === val)\n return found ? found.name : (label ?? '')\n}\n\n// Extract complex style logic from template\nconst optionStyle = computed(() => {\n if (!popoverMinWidth && !popoverMaxWidth) return undefined\n\n const styles: string[] = []\n if (popoverMinWidth) styles.push(`min-width: ${popoverMinWidth}`)\n if (popoverMaxWidth) styles.push(`max-width: ${popoverMaxWidth}`)\n\n return styles.join('; ')\n})\n</script>\n","import { createSharedComposable, useAsyncState } from '@vueuse/core'\n\nimport { api } from '@/scripts/api'\n\n/**\n * Format folder name to display name\n * Converts \"upscale_models\" -> \"Upscale Model\"\n * Converts \"loras\" -> \"LoRA\"\n */\nfunction formatDisplayName(folderName: string): string {\n // Special cases for acronyms and proper nouns\n const specialCases: Record<string, string> = {\n loras: 'LoRA',\n ipadapter: 'IP-Adapter',\n sams: 'SAM',\n clip_vision: 'CLIP Vision',\n animatediff_motion_lora: 'AnimateDiff Motion LoRA',\n animatediff_models: 'AnimateDiff Model',\n vae: 'VAE',\n sam2: 'SAM 2',\n controlnet: 'ControlNet',\n gligen: 'GLIGEN'\n }\n\n if (specialCases[folderName]) {\n return specialCases[folderName]\n }\n\n return folderName\n .split('_')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n}\n\ninterface ModelTypeOption {\n name: string // Display name\n value: string // Actual tag value\n}\n\nconst DISALLOWED_MODEL_TYPES = ['nlf'] as const\n\n/**\n * Composable for fetching and managing model types from the API\n * Uses shared state to ensure data is only fetched once\n */\nexport const useModelTypes = createSharedComposable(() => {\n const {\n state: modelTypes,\n isLoading,\n error,\n execute: fetchModelTypes\n } = useAsyncState(\n async (): Promise<ModelTypeOption[]> => {\n const response = await api.getModelFolders()\n return response\n .filter(\n (folder) =>\n !DISALLOWED_MODEL_TYPES.includes(\n folder.name as (typeof DISALLOWED_MODEL_TYPES)[number]\n )\n )\n .map((folder) => ({\n name: formatDisplayName(folder.name),\n value: folder.name\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n },\n [] as ModelTypeOption[],\n {\n immediate: false,\n onError: (err) => {\n console.error('Failed to fetch model types:', err)\n }\n }\n )\n\n return {\n modelTypes,\n isLoading,\n error,\n fetchModelTypes\n }\n})\n","<template>\n <div class=\"flex flex-col gap-4 text-sm text-muted-foreground\">\n <div class=\"flex flex-col gap-2\">\n <p class=\"m-0\">\n {{ $t('assetBrowser.modelAssociatedWithLink') }}\n </p>\n <div\n class=\"flex items-center gap-3 rounded-lg bg-secondary-background p-3\"\n >\n <img\n v-if=\"previewImage\"\n :src=\"previewImage\"\n :alt=\"metadata?.filename || metadata?.name || 'Model preview'\"\n class=\"size-14 flex-shrink-0 rounded object-cover\"\n />\n <p class=\"m-0 min-w-0 flex-1 truncate text-base-foreground\">\n {{ metadata?.filename || metadata?.name }}\n </p>\n </div>\n </div>\n\n <!-- Model Type Selection -->\n <div class=\"flex flex-col gap-2\">\n <label class=\"\">\n {{ $t('assetBrowser.modelTypeSelectorLabel') }}\n </label>\n <SingleSelect\n v-model=\"modelValue\"\n :label=\"\n isLoading\n ? $t('g.loading')\n : $t('assetBrowser.modelTypeSelectorPlaceholder')\n \"\n :options=\"modelTypes\"\n :disabled=\"isLoading\"\n data-attr=\"upload-model-step2-type-selector\"\n />\n <div class=\"flex items-center gap-2\">\n <i class=\"icon-[lucide--circle-question-mark]\" />\n <span>{{ $t('assetBrowser.notSureLeaveAsIs') }}</span>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport SingleSelect from '@/components/input/SingleSelect.vue'\nimport { useModelTypes } from '@/platform/assets/composables/useModelTypes'\nimport type { AssetMetadata } from '@/platform/assets/schemas/assetSchema'\n\ndefineProps<{\n metadata?: AssetMetadata\n previewImage?: string\n}>()\n\nconst modelValue = defineModel<string | undefined>()\n\nconst { modelTypes, isLoading } = useModelTypes()\n</script>\n","<template>\n <Dialog\n v-model:visible=\"isVisible\"\n modal\n :closable=\"false\"\n :close-on-escape=\"false\"\n :dismissable-mask=\"true\"\n :pt=\"{\n root: { class: 'video-help-dialog' },\n header: { class: '!hidden' },\n content: { class: '!p-0' },\n mask: { class: '!bg-black/70' }\n }\"\n :style=\"{ width: '90vw' }\"\n >\n <div class=\"relative\">\n <Button\n variant=\"textonly\"\n size=\"icon\"\n class=\"absolute top-4 right-6 z-10\"\n :aria-label=\"$t('g.close')\"\n @click=\"isVisible = false\"\n >\n <i class=\"pi pi-times text-sm\" />\n </Button>\n <video\n autoplay\n muted\n loop\n :aria-label=\"ariaLabel\"\n class=\"w-full rounded-lg\"\n :src=\"videoUrl\"\n >\n {{ $t('g.videoFailedToLoad') }}\n </video>\n </div>\n </Dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { useEventListener } from '@vueuse/core'\nimport Dialog from 'primevue/dialog'\nimport { onWatcherCleanup, watch } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\n\nconst isVisible = defineModel<boolean>({ required: true })\n\nconst { videoUrl, ariaLabel = 'Help video' } = defineProps<{\n videoUrl: string\n ariaLabel?: string\n}>()\n\nconst handleEscapeKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.stopImmediatePropagation()\n event.stopPropagation()\n event.preventDefault()\n isVisible.value = false\n }\n}\n\n// Add listener with capture phase to intercept before parent dialogs\n// Only active when dialog is visible\nwatch(\n isVisible,\n (visible) => {\n if (visible) {\n const stop = useEventListener(document, 'keydown', handleEscapeKey, {\n capture: true\n })\n onWatcherCleanup(stop)\n }\n },\n { immediate: true }\n)\n</script>\n","<template>\n <div class=\"flex justify-end gap-2 w-full\">\n <div\n v-if=\"currentStep === 1 && flags.huggingfaceModelImportEnabled\"\n class=\"mr-auto flex items-center gap-2\"\n >\n <i class=\"icon-[lucide--circle-question-mark] text-muted-foreground\" />\n <Button\n variant=\"muted-textonly\"\n size=\"sm\"\n data-attr=\"upload-model-step1-help-civitai\"\n @click=\"showCivitaiHelp = true\"\n >\n {{ $t('assetBrowser.providerCivitai') }}\n </Button>\n <Button\n variant=\"muted-textonly\"\n size=\"sm\"\n data-attr=\"upload-model-step1-help-huggingface\"\n @click=\"showHuggingFaceHelp = true\"\n >\n {{ $t('assetBrowser.providerHuggingFace') }}\n </Button>\n </div>\n <Button\n v-else-if=\"currentStep === 1\"\n variant=\"muted-textonly\"\n size=\"lg\"\n class=\"mr-auto underline\"\n data-attr=\"upload-model-step1-help-link\"\n @click=\"showCivitaiHelp = true\"\n >\n <i class=\"icon-[lucide--circle-question-mark]\" />\n <span>{{ $t('assetBrowser.uploadModelHowDoIFindThis') }}</span>\n </Button>\n <Button\n v-if=\"currentStep === 1\"\n variant=\"muted-textonly\"\n size=\"lg\"\n data-attr=\"upload-model-step1-cancel-button\"\n :disabled=\"isFetchingMetadata || isUploading\"\n @click=\"emit('close')\"\n >\n {{ $t('g.cancel') }}\n </Button>\n <Button\n v-if=\"currentStep !== 1 && currentStep !== 3\"\n variant=\"muted-textonly\"\n size=\"lg\"\n :data-attr=\"`upload-model-step${currentStep}-back-button`\"\n :disabled=\"isFetchingMetadata || isUploading\"\n @click=\"emit('back')\"\n >\n {{ $t('g.back') }}\n </Button>\n <span v-else />\n\n <Button\n v-if=\"currentStep === 1\"\n variant=\"secondary\"\n size=\"lg\"\n data-attr=\"upload-model-step1-continue-button\"\n :disabled=\"!canFetchMetadata || isFetchingMetadata\"\n @click=\"emit('fetchMetadata')\"\n >\n <i\n v-if=\"isFetchingMetadata\"\n class=\"icon-[lucide--loader-circle] animate-spin\"\n />\n <span>{{ $t('g.continue') }}</span>\n </Button>\n <Button\n v-else-if=\"currentStep === 2\"\n variant=\"secondary\"\n size=\"lg\"\n data-attr=\"upload-model-step2-confirm-button\"\n :disabled=\"!canUploadModel || isUploading\"\n @click=\"emit('upload')\"\n >\n <i v-if=\"isUploading\" class=\"icon-[lucide--loader-circle] animate-spin\" />\n <span>{{ $t('assetBrowser.upload') }}</span>\n </Button>\n <Button\n v-else-if=\"\n currentStep === 3 &&\n (uploadStatus === 'success' || uploadStatus === 'processing')\n \"\n variant=\"secondary\"\n data-attr=\"upload-model-step3-finish-button\"\n @click=\"emit('close')\"\n >\n {{\n uploadStatus === 'processing'\n ? $t('g.close')\n : $t('assetBrowser.finish')\n }}\n </Button>\n <VideoHelpDialog\n v-model=\"showCivitaiHelp\"\n video-url=\"https://media.comfy.org/compressed_768/civitai_howto.webm\"\n :aria-label=\"$t('assetBrowser.uploadModelHelpVideo')\"\n />\n <VideoHelpDialog\n v-model=\"showHuggingFaceHelp\"\n video-url=\"https://media.comfy.org/byom/huggingfacehowto.mp4\"\n :aria-label=\"$t('assetBrowser.uploadModelHelpVideo')\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport VideoHelpDialog from '@/platform/assets/components/VideoHelpDialog.vue'\n\nconst { flags } = useFeatureFlags()\n\nconst showCivitaiHelp = ref(false)\nconst showHuggingFaceHelp = ref(false)\n\ndefineProps<{\n currentStep: number\n isFetchingMetadata: boolean\n isUploading: boolean\n canFetchMetadata: boolean\n canUploadModel: boolean\n uploadStatus?: 'processing' | 'success' | 'error'\n}>()\n\nconst emit = defineEmits<{\n (e: 'back'): void\n (e: 'fetchMetadata'): void\n (e: 'upload'): void\n (e: 'close'): void\n}>()\n</script>\n","import type { ImportSource } from '@/platform/assets/types/importSource'\n\n/**\n * Civitai model import source configuration\n */\nexport const civitaiImportSource: ImportSource = {\n type: 'civitai',\n name: 'Civitai',\n hostnames: ['civitai.com']\n}\n","import type { ImportSource } from '@/platform/assets/types/importSource'\n\n/**\n * Hugging Face model import source configuration\n */\nexport const huggingfaceImportSource: ImportSource = {\n type: 'huggingface',\n name: 'Hugging Face',\n hostnames: ['huggingface.co']\n}\n","import type { ImportSource } from '@/platform/assets/types/importSource'\n\n/**\n * Check if a URL belongs to a specific import source\n */\nexport function validateSourceUrl(url: string, source: ImportSource): boolean {\n try {\n const hostname = new URL(url).hostname.toLowerCase()\n return source.hostnames.some(\n (h) => hostname === h || hostname.endsWith(`.${h}`)\n )\n } catch {\n return false\n }\n}\n","<template>\n <div class=\"flex flex-col justify-between h-full gap-6 text-sm\">\n <div class=\"flex flex-col gap-6\">\n <div class=\"flex flex-col gap-2\">\n <p class=\"m-0 text-foreground\">\n {{ $t('assetBrowser.uploadModelDescription1Generic') }}\n </p>\n <div class=\"m-0\">\n <p class=\"m-0 text-muted-foreground\">\n {{ $t('assetBrowser.uploadModelDescription2Generic') }}\n </p>\n <span class=\"inline-flex items-center gap-1 flex-wrap mt-2\">\n <span class=\"inline-flex items-center gap-1\">\n <img\n :src=\"civitaiIcon\"\n :alt=\"$t('assetBrowser.providerCivitai')\"\n class=\"w-4 h-4\"\n />\n <a\n :href=\"civitaiUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-muted underline\"\n >\n {{ $t('assetBrowser.providerCivitai') }}</a\n ><span>,</span>\n </span>\n <span class=\"inline-flex items-center gap-1\">\n <img\n :src=\"huggingFaceIcon\"\n :alt=\"$t('assetBrowser.providerHuggingFace')\"\n class=\"w-4 h-4\"\n />\n <a\n :href=\"huggingFaceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-muted underline\"\n >\n {{ $t('assetBrowser.providerHuggingFace') }}\n </a>\n </span>\n </span>\n </div>\n </div>\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"relative\">\n <InputText\n v-model=\"url\"\n autofocus\n :placeholder=\"$t('assetBrowser.genericLinkPlaceholder')\"\n class=\"w-full border-0 bg-secondary-background p-4 pr-10\"\n data-attr=\"upload-model-step1-url-input\"\n />\n <i\n v-if=\"isValidUrl\"\n class=\"icon-[lucide--circle-check-big] absolute top-1/2 right-3 size-5 -translate-y-1/2 text-green-500\"\n />\n </div>\n <p v-if=\"error\" class=\"text-xs text-error\">\n {{ error }}\n </p>\n <p v-else-if=\"!flags.asyncModelUploadEnabled\" class=\"text-foreground\">\n <i18n-t keypath=\"assetBrowser.maxFileSize\" tag=\"span\">\n <template #size>\n <span class=\"font-bold italic\">{{\n $t('assetBrowser.maxFileSizeValue')\n }}</span>\n </template>\n </i18n-t>\n </p>\n </div>\n </div>\n\n <div class=\"text-sm text-muted\">\n {{ $t('assetBrowser.uploadModelHelpFooterText') }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport InputText from 'primevue/inputtext'\nimport { computed } from 'vue'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport { civitaiImportSource } from '@/platform/assets/importSources/civitaiImportSource'\nimport { huggingfaceImportSource } from '@/platform/assets/importSources/huggingfaceImportSource'\nimport { validateSourceUrl } from '@/platform/assets/utils/importSourceUtil'\n\nconst { flags } = useFeatureFlags()\n\nconst props = defineProps<{\n modelValue: string\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n}>()\n\nconst url = computed({\n get: () => props.modelValue,\n set: (value: string) => emit('update:modelValue', value)\n})\n\nconst importSources = [civitaiImportSource, huggingfaceImportSource]\n\nconst isValidUrl = computed(() => {\n const trimmedUrl = url.value.trim()\n if (!trimmedUrl) return false\n return importSources.some((source) => validateSourceUrl(trimmedUrl, source))\n})\n\nconst civitaiIcon = '/assets/images/civitai.svg'\nconst civitaiUrl = 'https://civitai.com/models'\nconst huggingFaceIcon = '/assets/images/hf-logo.svg'\nconst huggingFaceUrl = 'https://huggingface.co'\n</script>\n","<template>\n <div class=\"flex flex-col gap-6 text-sm text-muted-foreground\">\n <div class=\"flex flex-col gap-2\">\n <p class=\"m-0\">\n {{ $t('assetBrowser.uploadModelDescription1') }}\n </p>\n <ul class=\"list-disc space-y-1 pl-5 mt-0\">\n <li>\n <i18n-t keypath=\"assetBrowser.uploadModelDescription2\" tag=\"span\">\n <template #link>\n <a\n href=\"https://civitai.com/models\"\n target=\"_blank\"\n class=\"text-muted-foreground\"\n >\n {{ $t('assetBrowser.uploadModelDescription2Link') }}\n </a>\n </template>\n </i18n-t>\n </li>\n <li v-if=\"!flags.asyncModelUploadEnabled\">\n <i18n-t keypath=\"assetBrowser.uploadModelDescription3\" tag=\"span\">\n <template #size>\n <span class=\"font-bold italic\">{{\n $t('assetBrowser.maxFileSizeValue')\n }}</span>\n </template>\n </i18n-t>\n </li>\n </ul>\n </div>\n\n <div class=\"flex flex-col gap-2\">\n <i18n-t keypath=\"assetBrowser.civitaiLinkLabel\" tag=\"label\" class=\"mb-0\">\n <template #download>\n <span class=\"font-bold italic\">{{\n $t('assetBrowser.civitaiLinkLabelDownload')\n }}</span>\n </template>\n </i18n-t>\n <div class=\"relative\">\n <InputText\n v-model=\"url\"\n autofocus\n :placeholder=\"$t('assetBrowser.civitaiLinkPlaceholder')\"\n class=\"w-full border-0 bg-secondary-background p-4 pr-10\"\n data-attr=\"upload-model-step1-url-input\"\n />\n <i\n v-if=\"isValidUrl\"\n class=\"icon-[lucide--circle-check-big] absolute top-1/2 right-3 size-5 -translate-y-1/2 text-green-500\"\n />\n </div>\n <p v-if=\"error\" class=\"text-xs text-error\">\n {{ error }}\n </p>\n <i18n-t\n v-else\n keypath=\"assetBrowser.civitaiLinkExample\"\n tag=\"p\"\n class=\"text-xs\"\n >\n <template #example>\n <strong>{{ $t('assetBrowser.civitaiLinkExampleStrong') }}</strong>\n </template>\n <template #link>\n <a\n href=\"https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295\"\n target=\"_blank\"\n class=\"text-muted-foreground\"\n >\n {{ $t('assetBrowser.civitaiLinkExampleUrl') }}\n </a>\n </template>\n </i18n-t>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport InputText from 'primevue/inputtext'\nimport { computed } from 'vue'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport { civitaiImportSource } from '@/platform/assets/importSources/civitaiImportSource'\nimport { validateSourceUrl } from '@/platform/assets/utils/importSourceUtil'\n\nconst { flags } = useFeatureFlags()\n\ndefineProps<{\n error?: string\n}>()\n\nconst url = defineModel<string>({ required: true })\n\nconst isValidUrl = computed(() => {\n const trimmedUrl = url.value.trim()\n if (!trimmedUrl) return false\n return validateSourceUrl(trimmedUrl, civitaiImportSource)\n})\n</script>\n","import type { Ref } from 'vue'\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport { st } from '@/i18n'\nimport { civitaiImportSource } from '@/platform/assets/importSources/civitaiImportSource'\nimport { huggingfaceImportSource } from '@/platform/assets/importSources/huggingfaceImportSource'\nimport type { AssetMetadata } from '@/platform/assets/schemas/assetSchema'\nimport { assetService } from '@/platform/assets/services/assetService'\nimport type { ImportSource } from '@/platform/assets/types/importSource'\nimport { validateSourceUrl } from '@/platform/assets/utils/importSourceUtil'\nimport { useAssetDownloadStore } from '@/stores/assetDownloadStore'\nimport { useAssetsStore } from '@/stores/assetsStore'\nimport { useModelToNodeStore } from '@/stores/modelToNodeStore'\n\ninterface WizardData {\n url: string\n metadata?: AssetMetadata\n name: string\n tags: string[]\n previewImage?: string\n}\n\ninterface ModelTypeOption {\n name: string\n value: string\n}\n\nexport function useUploadModelWizard(modelTypes: Ref<ModelTypeOption[]>) {\n const { t } = useI18n()\n const assetsStore = useAssetsStore()\n const assetDownloadStore = useAssetDownloadStore()\n const modelToNodeStore = useModelToNodeStore()\n const { flags } = useFeatureFlags()\n const currentStep = ref(1)\n const isFetchingMetadata = ref(false)\n const isUploading = ref(false)\n const uploadStatus = ref<'processing' | 'success' | 'error'>()\n const uploadError = ref('')\n\n const wizardData = ref<WizardData>({\n url: '',\n name: '',\n tags: []\n })\n\n const selectedModelType = ref<string>()\n\n // Available import sources\n const importSources: ImportSource[] = flags.huggingfaceModelImportEnabled\n ? [civitaiImportSource, huggingfaceImportSource]\n : [civitaiImportSource]\n\n // Detected import source based on URL\n const detectedSource = computed(() => {\n const url = wizardData.value.url.trim()\n if (!url) return null\n return (\n importSources.find((source) => validateSourceUrl(url, source)) ?? null\n )\n })\n\n // Clear error when URL changes\n watch(\n () => wizardData.value.url,\n () => {\n uploadError.value = ''\n }\n )\n\n // Validation\n const canFetchMetadata = computed(() => {\n return wizardData.value.url.trim().length > 0\n })\n\n const canUploadModel = computed(() => {\n return !!selectedModelType.value\n })\n\n async function fetchMetadata() {\n if (!canFetchMetadata.value) return\n\n // Clean and normalize URL\n let cleanedUrl = wizardData.value.url.trim()\n try {\n cleanedUrl = new URL(encodeURI(cleanedUrl)).toString()\n } catch {\n // If URL parsing fails, just use the trimmed input\n }\n wizardData.value.url = cleanedUrl\n\n // Validate URL belongs to a supported import source\n const source = detectedSource.value\n if (!source) {\n const supportedSources = importSources.map((s) => s.name).join(', ')\n uploadError.value = t('assetBrowser.unsupportedUrlSource', {\n sources: supportedSources\n })\n return\n }\n\n isFetchingMetadata.value = true\n try {\n const metadata = await assetService.getAssetMetadata(wizardData.value.url)\n\n // Decode URL-encoded filenames (e.g., Chinese characters)\n if (metadata.filename) {\n try {\n metadata.filename = decodeURIComponent(metadata.filename)\n } catch {\n // Keep original if decoding fails\n }\n }\n if (metadata.name) {\n try {\n metadata.name = decodeURIComponent(metadata.name)\n } catch {\n // Keep original if decoding fails\n }\n }\n\n wizardData.value.metadata = metadata\n\n // Pre-fill name from metadata\n wizardData.value.name = metadata.filename || metadata.name || ''\n\n // Store preview image if available\n wizardData.value.previewImage = metadata.preview_image\n\n // Pre-fill model type from metadata tags if available\n if (metadata.tags && metadata.tags.length > 0) {\n wizardData.value.tags = metadata.tags\n // Try to detect model type from tags\n const typeTag = metadata.tags.find((tag) =>\n modelTypes.value.some((type) => type.value === tag)\n )\n if (typeTag) {\n selectedModelType.value = typeTag\n }\n }\n\n currentStep.value = 2\n } catch (error) {\n console.error('Failed to retrieve metadata:', error)\n uploadError.value =\n error instanceof Error\n ? error.message\n : st(\n 'assetBrowser.uploadModelFailedToRetrieveMetadata',\n 'Failed to retrieve metadata. Please check the link and try again.'\n )\n currentStep.value = 1\n } finally {\n isFetchingMetadata.value = false\n }\n }\n\n async function uploadPreviewImage(\n filename: string\n ): Promise<string | undefined> {\n if (!wizardData.value.previewImage) return undefined\n\n try {\n const baseFilename = filename.split('.')[0]\n let extension = 'png'\n const mimeMatch = wizardData.value.previewImage.match(\n /^data:image\\/([^;]+);/\n )\n if (mimeMatch) {\n extension = mimeMatch[1] === 'jpeg' ? 'jpg' : mimeMatch[1]\n }\n\n const previewAsset = await assetService.uploadAssetFromBase64({\n data: wizardData.value.previewImage,\n name: `${baseFilename}_preview.${extension}`,\n tags: ['preview']\n })\n return previewAsset.id\n } catch (error) {\n console.error('Failed to upload preview image:', error)\n return undefined\n }\n }\n\n async function refreshModelCaches() {\n if (!selectedModelType.value) return\n\n const providers = modelToNodeStore.getAllNodeProviders(\n selectedModelType.value\n )\n const results = await Promise.allSettled(\n providers.map((provider) =>\n assetsStore.updateModelsForNodeType(provider.nodeDef.name)\n )\n )\n results.forEach((result, index) => {\n if (result.status === 'rejected') {\n console.error(\n `Failed to refresh ${providers[index].nodeDef.name}:`,\n result.reason\n )\n }\n })\n }\n\n async function uploadModel(): Promise<boolean> {\n if (!canUploadModel.value) {\n return false\n }\n\n const source = detectedSource.value\n if (!source) {\n uploadError.value = t('assetBrowser.noValidSourceDetected')\n return false\n }\n\n isUploading.value = true\n\n try {\n const tags = selectedModelType.value\n ? ['models', selectedModelType.value]\n : ['models']\n const filename =\n wizardData.value.metadata?.filename ||\n wizardData.value.metadata?.name ||\n 'model'\n\n const previewId = await uploadPreviewImage(filename)\n const userMetadata = {\n source: source.type,\n source_url: wizardData.value.url,\n model_type: selectedModelType.value\n }\n\n if (flags.asyncModelUploadEnabled) {\n const result = await assetService.uploadAssetAsync({\n source_url: wizardData.value.url,\n tags,\n user_metadata: userMetadata,\n preview_id: previewId\n })\n\n if (result.type === 'async' && result.task.status !== 'completed') {\n if (selectedModelType.value) {\n assetDownloadStore.trackDownload(\n result.task.task_id,\n selectedModelType.value\n )\n }\n uploadStatus.value = 'processing'\n } else {\n uploadStatus.value = 'success'\n await refreshModelCaches()\n }\n currentStep.value = 3\n } else {\n await assetService.uploadAssetFromUrl({\n url: wizardData.value.url,\n name: filename,\n tags,\n user_metadata: userMetadata,\n preview_id: previewId\n })\n uploadStatus.value = 'success'\n await refreshModelCaches()\n currentStep.value = 3\n }\n } catch (error) {\n console.error('Failed to upload asset:', error)\n uploadStatus.value = 'error'\n uploadError.value =\n error instanceof Error ? error.message : 'Failed to upload model'\n currentStep.value = 3\n } finally {\n isUploading.value = false\n }\n return uploadStatus.value !== 'error'\n }\n\n function goToPreviousStep() {\n if (currentStep.value > 1) {\n currentStep.value = currentStep.value - 1\n }\n }\n\n return {\n // State\n currentStep,\n isFetchingMetadata,\n isUploading,\n uploadStatus,\n uploadError,\n wizardData,\n selectedModelType,\n\n // Computed\n canFetchMetadata,\n canUploadModel,\n detectedSource,\n\n // Actions\n fetchMetadata,\n uploadModel,\n goToPreviousStep\n }\n}\n","<template>\n <div\n class=\"upload-model-dialog flex flex-col gap-6 border-t border-border-default p-4 pt-6\"\n >\n <!-- Scrollable content area -->\n <div class=\"min-h-0 flex-auto basis-0 overflow-y-auto\">\n <!-- Step 1: Enter URL -->\n <UploadModelUrlInput\n v-if=\"currentStep === 1 && flags.huggingfaceModelImportEnabled\"\n v-model=\"wizardData.url\"\n :error=\"uploadError\"\n />\n <UploadModelUrlInputCivitai\n v-else-if=\"currentStep === 1\"\n v-model=\"wizardData.url\"\n :error=\"uploadError\"\n />\n\n <!-- Step 2: Confirm Metadata -->\n <UploadModelConfirmation\n v-else-if=\"currentStep === 2\"\n v-model=\"selectedModelType\"\n :metadata=\"wizardData.metadata\"\n :preview-image=\"wizardData.previewImage\"\n />\n\n <!-- Step 3: Upload Progress -->\n <UploadModelProgress\n v-else-if=\"currentStep === 3 && uploadStatus != null\"\n :result=\"uploadStatus\"\n :error=\"uploadError\"\n :metadata=\"wizardData.metadata\"\n :model-type=\"selectedModelType\"\n :preview-image=\"wizardData.previewImage\"\n />\n </div>\n\n <!-- Navigation Footer - always visible -->\n <UploadModelFooter\n class=\"flex-shrink-0\"\n :current-step=\"currentStep\"\n :is-fetching-metadata=\"isFetchingMetadata\"\n :is-uploading=\"isUploading\"\n :can-fetch-metadata=\"canFetchMetadata\"\n :can-upload-model=\"canUploadModel\"\n :upload-status=\"uploadStatus\"\n @back=\"goToPreviousStep\"\n @fetch-metadata=\"handleFetchMetadata\"\n @upload=\"handleUploadModel\"\n @close=\"handleClose\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted } from 'vue'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport UploadModelConfirmation from '@/platform/assets/components/UploadModelConfirmation.vue'\nimport UploadModelFooter from '@/platform/assets/components/UploadModelFooter.vue'\nimport UploadModelProgress from '@/platform/assets/components/UploadModelProgress.vue'\nimport UploadModelUrlInput from '@/platform/assets/components/UploadModelUrlInput.vue'\nimport UploadModelUrlInputCivitai from '@/platform/assets/components/UploadModelUrlInputCivitai.vue'\nimport { useModelTypes } from '@/platform/assets/composables/useModelTypes'\nimport { useUploadModelWizard } from '@/platform/assets/composables/useUploadModelWizard'\nimport { useDialogStore } from '@/stores/dialogStore'\n\nconst { flags } = useFeatureFlags()\nconst dialogStore = useDialogStore()\nconst { modelTypes, fetchModelTypes } = useModelTypes()\n\nconst emit = defineEmits<{\n 'upload-success': []\n}>()\n\nconst {\n currentStep,\n isFetchingMetadata,\n isUploading,\n uploadStatus,\n uploadError,\n wizardData,\n selectedModelType,\n canFetchMetadata,\n canUploadModel,\n fetchMetadata,\n uploadModel,\n goToPreviousStep\n} = useUploadModelWizard(modelTypes)\n\nasync function handleFetchMetadata() {\n await fetchMetadata()\n}\n\nasync function handleUploadModel() {\n const success = await uploadModel()\n if (success) {\n emit('upload-success')\n }\n}\n\nfunction handleClose() {\n dialogStore.closeDialog({ key: 'upload-model' })\n}\n\nonMounted(() => {\n fetchModelTypes()\n})\n</script>\n\n<style scoped>\n.upload-model-dialog {\n width: 90vw;\n max-width: 800px;\n min-height: min(400px, 80vh);\n max-height: 90vh;\n}\n\n@media (min-width: 640px) {\n .upload-model-dialog {\n width: auto;\n min-width: 600px;\n }\n}\n</style>\n","export default \"__VITE_PUBLIC_ASSET__53f47e52__\"","<template>\n <div class=\"flex items-center gap-2 p-4 font-bold\">\n <img\n v-if=\"!flags.huggingfaceModelImportEnabled\"\n src=\"/assets/images/civitai.svg\"\n class=\"size-4\"\n />\n <span>{{ $t(titleKey) }}</span>\n <span\n class=\"rounded-full bg-white px-1.5 py-0 text-xxs font-inter font-semibold uppercase text-black\"\n >\n {{ $t('g.beta') }}\n </span>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\n\nconst { flags } = useFeatureFlags()\n\nconst titleKey = computed(() => {\n return flags.huggingfaceModelImportEnabled\n ? 'assetBrowser.uploadModelGeneric'\n : 'assetBrowser.uploadModelFromCivitai'\n})\n</script>\n","<template>\n <div\n class=\"flex flex-1 flex-col items-center justify-center text-base text-muted-foreground\"\n >\n <p class=\"m-0 max-w-md\">\n {{ $t('assetBrowser.upgradeFeatureDescription') }}\n </p>\n </div>\n</template>\n","<template>\n <div class=\"flex flex-wrap justify-end gap-2 w-full\">\n <a\n href=\"https://blog.comfy.org/p/comfy-cloud-new-features-and-pricing\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-muted-foreground mr-auto underline flex items-center gap-2\"\n >\n <i class=\"icon-[lucide--external-link]\" />\n <span>{{ $t('g.learnMore') }}</span>\n </a>\n <Button variant=\"textonly\" @click=\"emit('close')\">{{\n $t('g.close')\n }}</Button>\n <Button variant=\"secondary\" @click=\"emit('subscribe')\">\n {{ $t('subscription.required.subscribe') }}\n </Button>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\n\nconst emit = defineEmits<{\n close: []\n subscribe: []\n}>()\n</script>\n","<template>\n <div\n class=\"flex flex-col justify-between gap-10 p-4 border-t border-border-default w-auto max-w-[min(500px,90vw)]\"\n >\n <UploadModelUpgradeModalBody />\n\n <UploadModelUpgradeModalFooter\n @close=\"handleClose\"\n @subscribe=\"handleSubscribe\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport UploadModelUpgradeModalBody from '@/platform/assets/components/UploadModelUpgradeModalBody.vue'\nimport UploadModelUpgradeModalFooter from '@/platform/assets/components/UploadModelUpgradeModalFooter.vue'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport { useDialogStore } from '@/stores/dialogStore'\n\nconst dialogStore = useDialogStore()\nconst { showSubscriptionDialog } = useSubscription()\n\nfunction handleClose() {\n dialogStore.closeDialog({ key: 'upload-model-upgrade' })\n}\n\nfunction handleSubscribe() {\n showSubscriptionDialog()\n}\n</script>\n","<template>\n <div class=\"flex items-center gap-2 p-4 font-bold\">\n <span>{{ $t('assetBrowser.upgradeToUnlockFeature') }}</span>\n </div>\n</template>\n","import { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport UploadModelDialog from '@/platform/assets/components/UploadModelDialog.vue'\nimport UploadModelDialogHeader from '@/platform/assets/components/UploadModelDialogHeader.vue'\nimport UploadModelUpgradeModal from '@/platform/assets/components/UploadModelUpgradeModal.vue'\nimport UploadModelUpgradeModalHeader from '@/platform/assets/components/UploadModelUpgradeModalHeader.vue'\nimport { useDialogStore } from '@/stores/dialogStore'\nimport { computed } from 'vue'\n\nexport function useModelUpload(\n onUploadSuccess?: () => Promise<unknown> | void\n) {\n const dialogStore = useDialogStore()\n const { flags } = useFeatureFlags()\n const isUploadButtonEnabled = computed(() => flags.modelUploadButtonEnabled)\n\n function showUploadDialog() {\n if (!flags.privateModelsEnabled) {\n // Show upgrade modal if private models are disabled\n dialogStore.showDialog({\n key: 'upload-model-upgrade',\n headerComponent: UploadModelUpgradeModalHeader,\n component: UploadModelUpgradeModal,\n dialogComponentProps: {\n pt: {\n header: 'py-0! pl-0!',\n content: 'p-0! overflow-y-hidden!'\n }\n }\n })\n } else {\n // Show regular upload modal\n dialogStore.showDialog({\n key: 'upload-model',\n headerComponent: UploadModelDialogHeader,\n component: UploadModelDialog,\n props: {\n onUploadSuccess: async () => {\n await onUploadSuccess?.()\n }\n },\n dialogComponentProps: {\n pt: {\n header: 'py-0! pl-0!',\n content: 'p-0! overflow-y-hidden!'\n }\n }\n })\n }\n }\n return { isUploadButtonEnabled, showUploadDialog }\n}\n","export default \"__VITE_PUBLIC_ASSET__c8cdd13f__\"","import { onBeforeUnmount, ref, watch } from 'vue'\nimport type { Ref } from 'vue'\n\ninterface UseIntersectionObserverOptions extends IntersectionObserverInit {\n immediate?: boolean\n}\n\nexport function useIntersectionObserver(\n target: Ref<Element | null>,\n callback: IntersectionObserverCallback,\n options: UseIntersectionObserverOptions = {}\n) {\n const { immediate = true, ...observerOptions } = options\n\n const isSupported =\n typeof window !== 'undefined' && 'IntersectionObserver' in window\n const isIntersecting = ref(false)\n\n let observer: IntersectionObserver | null = null\n\n const cleanup = () => {\n if (observer) {\n observer.disconnect()\n observer = null\n }\n }\n\n const observe = () => {\n cleanup()\n\n if (!isSupported || !target.value) return\n\n observer = new IntersectionObserver((entries) => {\n isIntersecting.value = entries.some((entry) => entry.isIntersecting)\n callback(entries, observer!)\n }, observerOptions)\n\n observer.observe(target.value)\n }\n\n const unobserve = () => {\n if (observer && target.value) {\n observer.unobserve(target.value)\n }\n }\n\n if (immediate) {\n watch(target, observe, { immediate: true, flush: 'post' })\n }\n\n onBeforeUnmount(cleanup)\n\n return {\n isSupported,\n isIntersecting,\n observe,\n unobserve,\n cleanup\n }\n}\n","import { reactive } from 'vue'\n\ninterface CachedMedia {\n src: string\n blob?: Blob\n objectUrl?: string\n error?: boolean\n isLoading: boolean\n lastAccessed: number\n}\n\ninterface MediaCacheOptions {\n maxSize?: number\n maxAge?: number // in milliseconds\n preloadDistance?: number // pixels from viewport\n}\n\nclass MediaCacheService {\n public cache = reactive(new Map<string, CachedMedia>())\n private readonly maxSize: number\n private readonly maxAge: number\n private cleanupInterval: number | null = null\n private urlRefCount = new Map<string, number>()\n\n constructor(options: MediaCacheOptions = {}) {\n this.maxSize = options.maxSize ?? 100\n this.maxAge = options.maxAge ?? 30 * 60 * 1000 // 30 minutes\n\n // Start cleanup interval\n this.startCleanupInterval()\n }\n\n private startCleanupInterval() {\n // Clean up every 5 minutes\n this.cleanupInterval = window.setInterval(\n () => {\n this.cleanup()\n },\n 5 * 60 * 1000\n )\n }\n\n private cleanup() {\n const now = Date.now()\n const keysToDelete: string[] = []\n\n // Find expired entries\n for (const [key, entry] of Array.from(this.cache.entries())) {\n if (now - entry.lastAccessed > this.maxAge) {\n // Only revoke object URL if no components are using it\n if (entry.objectUrl) {\n const refCount = this.urlRefCount.get(entry.objectUrl) || 0\n if (refCount === 0) {\n URL.revokeObjectURL(entry.objectUrl)\n this.urlRefCount.delete(entry.objectUrl)\n keysToDelete.push(key)\n }\n // Don't delete cache entry if URL is still in use\n } else {\n keysToDelete.push(key)\n }\n }\n }\n\n // Remove expired entries\n keysToDelete.forEach((key) => this.cache.delete(key))\n\n // If still over size limit, remove oldest entries that aren't in use\n if (this.cache.size > this.maxSize) {\n const entries = Array.from(this.cache.entries())\n entries.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed)\n\n let removedCount = 0\n const targetRemoveCount = this.cache.size - this.maxSize\n\n for (const [key, entry] of entries) {\n if (removedCount >= targetRemoveCount) break\n\n if (entry.objectUrl) {\n const refCount = this.urlRefCount.get(entry.objectUrl) || 0\n if (refCount === 0) {\n URL.revokeObjectURL(entry.objectUrl)\n this.urlRefCount.delete(entry.objectUrl)\n this.cache.delete(key)\n removedCount++\n }\n } else {\n this.cache.delete(key)\n removedCount++\n }\n }\n }\n }\n\n async getCachedMedia(src: string): Promise<CachedMedia> {\n let entry = this.cache.get(src)\n\n if (entry) {\n // Update last accessed time\n entry.lastAccessed = Date.now()\n return entry\n }\n\n // Create new entry\n entry = {\n src,\n isLoading: true,\n lastAccessed: Date.now()\n }\n\n // Update cache with loading entry\n this.cache.set(src, entry)\n\n try {\n // Fetch the media\n const response = await fetch(src, { cache: 'force-cache' })\n if (!response.ok) {\n throw new Error(`Failed to fetch: ${response.status}`)\n }\n\n const blob = await response.blob()\n const objectUrl = URL.createObjectURL(blob)\n\n // Update entry with successful result\n const updatedEntry: CachedMedia = {\n src,\n blob,\n objectUrl,\n isLoading: false,\n lastAccessed: Date.now()\n }\n\n this.cache.set(src, updatedEntry)\n return updatedEntry\n } catch (error) {\n console.warn('Failed to cache media:', src, error)\n\n // Update entry with error\n const errorEntry: CachedMedia = {\n src,\n error: true,\n isLoading: false,\n lastAccessed: Date.now()\n }\n\n this.cache.set(src, errorEntry)\n return errorEntry\n }\n }\n\n acquireUrl(src: string): string | undefined {\n const entry = this.cache.get(src)\n if (entry?.objectUrl) {\n const currentCount = this.urlRefCount.get(entry.objectUrl) || 0\n this.urlRefCount.set(entry.objectUrl, currentCount + 1)\n return entry.objectUrl\n }\n return undefined\n }\n\n releaseUrl(src: string): void {\n const entry = this.cache.get(src)\n if (entry?.objectUrl) {\n const count = (this.urlRefCount.get(entry.objectUrl) || 1) - 1\n if (count <= 0) {\n URL.revokeObjectURL(entry.objectUrl)\n this.urlRefCount.delete(entry.objectUrl)\n // Remove from cache as well\n this.cache.delete(src)\n } else {\n this.urlRefCount.set(entry.objectUrl, count)\n }\n }\n }\n\n clearCache() {\n // Revoke all object URLs\n for (const entry of Array.from(this.cache.values())) {\n if (entry.objectUrl) {\n URL.revokeObjectURL(entry.objectUrl)\n }\n }\n this.cache.clear()\n this.urlRefCount.clear()\n }\n\n destroy() {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval)\n this.cleanupInterval = null\n }\n this.clearCache()\n }\n}\n\n// Global instance\nlet mediaCacheInstance: MediaCacheService | null = null\n\nexport function useMediaCache(options?: MediaCacheOptions) {\n if (!mediaCacheInstance) {\n mediaCacheInstance = new MediaCacheService(options)\n }\n\n const getCachedMedia = (src: string) =>\n mediaCacheInstance!.getCachedMedia(src)\n const clearCache = () => mediaCacheInstance!.clearCache()\n const acquireUrl = (src: string) => mediaCacheInstance!.acquireUrl(src)\n const releaseUrl = (src: string) => mediaCacheInstance!.releaseUrl(src)\n\n return {\n getCachedMedia,\n clearCache,\n acquireUrl,\n releaseUrl,\n cache: mediaCacheInstance.cache\n }\n}\n\n// Cleanup on page unload\nif (typeof window !== 'undefined') {\n window.addEventListener('beforeunload', () => {\n if (mediaCacheInstance) {\n mediaCacheInstance.destroy()\n }\n })\n}\n","<template>\n <div\n ref=\"containerRef\"\n class=\"relative flex h-full w-full items-center justify-center overflow-hidden\"\n :class=\"containerClass\"\n >\n <Skeleton\n v-if=\"!isImageLoaded\"\n width=\"100%\"\n height=\"100%\"\n class=\"absolute inset-0\"\n />\n <img\n v-if=\"cachedSrc\"\n :src=\"cachedSrc\"\n :alt=\"alt\"\n draggable=\"false\"\n :class=\"imageClass\"\n :style=\"imageStyle\"\n @load=\"onImageLoad\"\n @error=\"onImageError\"\n />\n <div\n v-if=\"hasError\"\n class=\"absolute inset-0 flex items-center justify-center\"\n >\n <img\n src=\"/assets/images/default-template.png\"\n :alt=\"alt\"\n draggable=\"false\"\n :class=\"imageClass\"\n :style=\"imageStyle\"\n />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Skeleton from 'primevue/skeleton'\nimport { computed, onUnmounted, ref, watch } from 'vue'\n\nimport { useIntersectionObserver } from '@/composables/useIntersectionObserver'\nimport { useMediaCache } from '@/services/mediaCacheService'\nimport type { ClassValue } from '@/utils/tailwindUtil'\n\nconst {\n src,\n alt = '',\n containerClass = '',\n imageClass = '',\n imageStyle,\n rootMargin = '300px'\n} = defineProps<{\n src: string\n alt?: string\n containerClass?: ClassValue\n imageClass?: ClassValue\n imageStyle?: Record<string, any>\n rootMargin?: string\n}>()\n\nconst containerRef = ref<HTMLElement | null>(null)\nconst isIntersecting = ref(false)\nconst isImageLoaded = ref(false)\nconst hasError = ref(false)\nconst cachedSrc = ref<string | undefined>(undefined)\n\nconst { getCachedMedia, acquireUrl, releaseUrl } = useMediaCache()\n\n// Use intersection observer to detect when the image container comes into view\nuseIntersectionObserver(\n containerRef,\n (entries) => {\n const entry = entries[0]\n isIntersecting.value = entry?.isIntersecting ?? false\n },\n {\n rootMargin,\n threshold: 0.1\n }\n)\n\n// Only start loading the image when it's in view\nconst shouldLoad = computed(() => isIntersecting.value)\n\nwatch(\n shouldLoad,\n async (shouldLoadVal) => {\n if (shouldLoadVal && src && !cachedSrc.value && !hasError.value) {\n try {\n const cachedMedia = await getCachedMedia(src)\n if (cachedMedia.error) {\n hasError.value = true\n } else if (cachedMedia.objectUrl) {\n const acquiredUrl = acquireUrl(src)\n cachedSrc.value = acquiredUrl || cachedMedia.objectUrl\n } else {\n cachedSrc.value = src\n }\n } catch (error) {\n console.warn('Failed to load cached media:', error)\n cachedSrc.value = src\n }\n } else if (!shouldLoadVal) {\n if (cachedSrc.value?.startsWith('blob:')) {\n releaseUrl(src)\n }\n // Hide image when out of view\n isImageLoaded.value = false\n cachedSrc.value = undefined\n hasError.value = false\n }\n },\n { immediate: true }\n)\n\nconst onImageLoad = () => {\n isImageLoaded.value = true\n hasError.value = false\n}\n\nconst onImageError = () => {\n hasError.value = true\n isImageLoaded.value = false\n}\n\nonUnmounted(() => {\n if (cachedSrc.value?.startsWith('blob:')) {\n releaseUrl(src)\n }\n})\n</script>\n"],"names":["useFeatureFlags","flags","reactive","api","remoteConfig","featureFlag","__name","featurePath","defaultValue","computed","readonly","selectedItem","_useModel","__props","t","useI18n","getLabel","val","found","o","optionStyle","styles","formatDisplayName","folderName","specialCases","word","DISALLOWED_MODEL_TYPES","useModelTypes","createSharedComposable","modelTypes","isLoading","error","fetchModelTypes","useAsyncState","folder","a","b","err","modelValue","isVisible","handleEscapeKey","event","watch","visible","stop","useEventListener","onWatcherCleanup","showCivitaiHelp","ref","showHuggingFaceHelp","emit","__emit","civitaiImportSource","huggingfaceImportSource","validateSourceUrl","url","source","hostname","h","civitaiIcon","civitaiUrl","huggingFaceIcon","huggingFaceUrl","props","value","importSources","isValidUrl","trimmedUrl","useUploadModelWizard","assetsStore","useAssetsStore","assetDownloadStore","useAssetDownloadStore","modelToNodeStore","useModelToNodeStore","currentStep","isFetchingMetadata","isUploading","uploadStatus","uploadError","wizardData","selectedModelType","detectedSource","canFetchMetadata","canUploadModel","fetchMetadata","cleanedUrl","supportedSources","s","metadata","assetService","typeTag","tag","type","st","uploadPreviewImage","filename","baseFilename","extension","mimeMatch","refreshModelCaches","providers","provider","result","index","uploadModel","tags","previewId","userMetadata","goToPreviousStep","dialogStore","useDialogStore","handleFetchMetadata","handleUploadModel","handleClose","onMounted","_imports_0$1","titleKey","_hoisted_1","_hoisted_2","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_ctx","showSubscriptionDialog","useSubscription","handleSubscribe","useModelUpload","onUploadSuccess","isUploadButtonEnabled","showUploadDialog","UploadModelDialogHeader","UploadModelDialog","UploadModelUpgradeModalHeader","UploadModelUpgradeModal","_imports_0","useIntersectionObserver","target","callback","options","immediate","observerOptions","isSupported","isIntersecting","observer","cleanup","observe","entries","entry","unobserve","onBeforeUnmount","MediaCacheService","now","keysToDelete","key","removedCount","targetRemoveCount","src","response","blob","objectUrl","updatedEntry","errorEntry","currentCount","count","mediaCacheInstance","useMediaCache","containerRef","isImageLoaded","hasError","cachedSrc","getCachedMedia","acquireUrl","releaseUrl","shouldLoad","shouldLoadVal","cachedMedia","acquiredUrl","onImageLoad","onImageError","onUnmounted"],"mappings":"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"}
1
+ {"version":3,"file":"LazyImage.vue_vue_type_script_setup_true_lang-BHcWsrBj.js","sources":["../../src/composables/useFeatureFlags.ts","../../src/components/input/SingleSelect.vue","../../src/platform/assets/composables/useModelTypes.ts","../../src/platform/assets/components/UploadModelConfirmation.vue","../../src/platform/assets/components/VideoHelpDialog.vue","../../src/platform/assets/components/UploadModelFooter.vue","../../src/platform/assets/importSources/civitaiImportSource.ts","../../src/platform/assets/importSources/huggingfaceImportSource.ts","../../src/platform/assets/utils/importSourceUtil.ts","../../src/platform/assets/components/UploadModelUrlInput.vue","../../src/platform/assets/components/UploadModelUrlInputCivitai.vue","../../src/platform/assets/composables/useUploadModelWizard.ts","../../src/platform/assets/components/UploadModelDialog.vue","../../../../../../../assets/images/civitai.svg","../../src/platform/assets/components/UploadModelDialogHeader.vue","../../src/platform/assets/components/UploadModelUpgradeModalBody.vue","../../src/platform/assets/components/UploadModelUpgradeModalFooter.vue","../../src/platform/assets/components/UploadModelUpgradeModal.vue","../../src/platform/assets/components/UploadModelUpgradeModalHeader.vue","../../src/platform/assets/composables/useModelUpload.ts","../../../../../../../assets/images/default-template.png","../../src/composables/useIntersectionObserver.ts","../../src/services/mediaCacheService.ts","../../src/components/common/LazyImage.vue"],"sourcesContent":["import { computed, reactive, readonly } from 'vue'\n\nimport { remoteConfig } from '@/platform/remoteConfig/remoteConfig'\nimport { api } from '@/scripts/api'\n\n/**\n * Known server feature flags (top-level, not extensions)\n */\nexport enum ServerFeatureFlag {\n SUPPORTS_PREVIEW_METADATA = 'supports_preview_metadata',\n MAX_UPLOAD_SIZE = 'max_upload_size',\n MANAGER_SUPPORTS_V4 = 'extension.manager.supports_v4',\n MODEL_UPLOAD_BUTTON_ENABLED = 'model_upload_button_enabled',\n ASSET_DELETION_ENABLED = 'asset_deletion_enabled',\n ASSET_RENAME_ENABLED = 'asset_rename_enabled',\n PRIVATE_MODELS_ENABLED = 'private_models_enabled',\n ONBOARDING_SURVEY_ENABLED = 'onboarding_survey_enabled',\n HUGGINGFACE_MODEL_IMPORT_ENABLED = 'huggingface_model_import_enabled',\n ASYNC_MODEL_UPLOAD_ENABLED = 'async_model_upload_enabled'\n}\n\n/**\n * Composable for reactive access to server-side feature flags\n */\nexport function useFeatureFlags() {\n const flags = reactive({\n get supportsPreviewMetadata() {\n return api.getServerFeature(ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA)\n },\n get maxUploadSize() {\n return api.getServerFeature(ServerFeatureFlag.MAX_UPLOAD_SIZE)\n },\n get supportsManagerV4() {\n return api.getServerFeature(ServerFeatureFlag.MANAGER_SUPPORTS_V4)\n },\n get modelUploadButtonEnabled() {\n // Check remote config first (from /api/features), fall back to websocket feature flags\n return (\n remoteConfig.value.model_upload_button_enabled ??\n api.getServerFeature(\n ServerFeatureFlag.MODEL_UPLOAD_BUTTON_ENABLED,\n false\n )\n )\n },\n get assetDeletionEnabled() {\n return (\n remoteConfig.value.asset_deletion_enabled ??\n api.getServerFeature(ServerFeatureFlag.ASSET_DELETION_ENABLED, false)\n )\n },\n get assetRenameEnabled() {\n return (\n remoteConfig.value.asset_rename_enabled ??\n api.getServerFeature(ServerFeatureFlag.ASSET_RENAME_ENABLED, false)\n )\n },\n get privateModelsEnabled() {\n // Check remote config first (from /api/features), fall back to websocket feature flags\n return (\n remoteConfig.value.private_models_enabled ??\n api.getServerFeature(ServerFeatureFlag.PRIVATE_MODELS_ENABLED, false)\n )\n },\n get onboardingSurveyEnabled() {\n return (\n remoteConfig.value.onboarding_survey_enabled ??\n api.getServerFeature(ServerFeatureFlag.ONBOARDING_SURVEY_ENABLED, true)\n )\n },\n get huggingfaceModelImportEnabled() {\n return (\n remoteConfig.value.huggingface_model_import_enabled ??\n api.getServerFeature(\n ServerFeatureFlag.HUGGINGFACE_MODEL_IMPORT_ENABLED,\n false\n )\n )\n },\n get asyncModelUploadEnabled() {\n return (\n remoteConfig.value.async_model_upload_enabled ??\n api.getServerFeature(\n ServerFeatureFlag.ASYNC_MODEL_UPLOAD_ENABLED,\n false\n )\n )\n }\n })\n\n const featureFlag = <T = unknown>(featurePath: string, defaultValue?: T) =>\n computed(() => api.getServerFeature(featurePath, defaultValue))\n\n return {\n flags: readonly(flags),\n featureFlag\n }\n}\n","<template>\n <!--\n Note: We explicitly pass options here (not just via $attrs) because:\n 1. Our custom value template needs options to look up labels from values\n 2. PrimeVue's value slot only provides 'value' and 'placeholder', not the selected item's label\n 3. We need to maintain the icon slot functionality in the value template\n option-label=\"name\" is required because our option template directly accesses option.name\n -->\n <Select\n v-model=\"selectedItem\"\n v-bind=\"$attrs\"\n :options=\"options\"\n option-label=\"name\"\n option-value=\"value\"\n unstyled\n :pt=\"{\n root: ({ props }: SelectPassThroughMethodOptions<SelectOption>) => ({\n class: [\n // container\n 'h-10 relative inline-flex cursor-pointer select-none items-center',\n // trigger surface\n 'rounded-lg',\n 'bg-secondary-background text-base-foreground',\n 'border-[2.5px] border-solid border-transparent',\n 'transition-all duration-200 ease-in-out',\n 'focus-within:border-node-component-border',\n // disabled\n { 'opacity-60 cursor-default': props.disabled }\n ]\n }),\n label: {\n class:\n // Align with MultiSelect labelContainer spacing\n 'flex-1 flex items-center whitespace-nowrap pl-4 py-2 outline-hidden'\n },\n dropdown: {\n class:\n // Right chevron touch area\n 'flex shrink-0 items-center justify-center px-3 py-2'\n },\n overlay: {\n class: cn(\n 'mt-2 p-2 rounded-lg',\n 'bg-base-background text-base-foreground',\n 'border border-solid border-border-default'\n )\n },\n listContainer: () => ({\n style: `max-height: min(${listMaxHeight}, 50vh)`,\n class: 'scrollbar-custom'\n }),\n list: {\n class:\n // Same list tone/size as MultiSelect\n 'flex flex-col gap-0 p-0 m-0 list-none border-none text-sm'\n },\n option: ({ context }: SelectPassThroughMethodOptions<SelectOption>) => ({\n class: cn(\n // Row layout\n 'flex items-center justify-between gap-3 px-2 py-3 rounded',\n 'hover:bg-secondary-background-hover',\n // Add focus state for keyboard navigation\n context.focused && 'bg-secondary-background-hover',\n // Selected state + check icon\n context.selected &&\n 'bg-secondary-background-selected hover:bg-secondary-background-selected'\n )\n }),\n optionLabel: {\n class: 'truncate'\n },\n optionGroupLabel: {\n class: 'px-3 py-2 text-xs uppercase tracking-wide text-muted-foreground'\n },\n emptyMessage: {\n class: 'px-3 py-2 text-sm text-muted-foreground'\n }\n }\"\n :aria-label=\"label || t('g.singleSelectDropdown')\"\n role=\"combobox\"\n :aria-expanded=\"false\"\n aria-haspopup=\"listbox\"\n :tabindex=\"0\"\n >\n <!-- Trigger value -->\n <template #value=\"slotProps\">\n <div class=\"flex items-center gap-2 text-sm\">\n <slot name=\"icon\" />\n <span\n v-if=\"slotProps.value !== null && slotProps.value !== undefined\"\n class=\"text-base-foreground\"\n >\n {{ getLabel(slotProps.value) }}\n </span>\n <span v-else class=\"text-base-foreground\">\n {{ label }}\n </span>\n </div>\n </template>\n\n <!-- Trigger caret -->\n <template #dropdownicon>\n <i class=\"icon-[lucide--chevron-down] text-muted-foreground\" />\n </template>\n\n <!-- Option row -->\n <template #option=\"{ option, selected }\">\n <div\n class=\"flex w-full items-center justify-between gap-3\"\n :style=\"optionStyle\"\n >\n <span class=\"truncate\">{{ option.name }}</span>\n <i v-if=\"selected\" class=\"icon-[lucide--check] text-base-foreground\" />\n </div>\n </template>\n </Select>\n</template>\n\n<script setup lang=\"ts\">\nimport type { SelectPassThroughMethodOptions } from 'primevue/select'\nimport Select from 'primevue/select'\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { cn } from '@/utils/tailwindUtil'\n\nimport type { SelectOption } from './types'\n\ndefineOptions({\n inheritAttrs: false\n})\n\nconst {\n label,\n options,\n listMaxHeight = '28rem',\n popoverMinWidth,\n popoverMaxWidth\n} = defineProps<{\n label?: string\n /**\n * Required for displaying the selected item's label.\n * Cannot rely on $attrs alone because we need to access options\n * in getLabel() to map values to their display names.\n */\n options?: SelectOption[]\n /** Maximum height of the dropdown panel (default: 28rem) */\n listMaxHeight?: string\n /** Minimum width of the popover (default: auto) */\n popoverMinWidth?: string\n /** Maximum width of the popover (default: auto) */\n popoverMaxWidth?: string\n}>()\n\nconst selectedItem = defineModel<string | undefined>({ required: true })\n\nconst { t } = useI18n()\n\n/**\n * Maps a value to its display label.\n * Necessary because PrimeVue's value slot doesn't provide the selected item's label,\n * only the raw value. We need this to show the correct text when an item is selected.\n */\nconst getLabel = (val: string | null | undefined) => {\n if (val == null) return label ?? ''\n if (!options) return label ?? ''\n const found = options.find((o) => o.value === val)\n return found ? found.name : (label ?? '')\n}\n\n// Extract complex style logic from template\nconst optionStyle = computed(() => {\n if (!popoverMinWidth && !popoverMaxWidth) return undefined\n\n const styles: string[] = []\n if (popoverMinWidth) styles.push(`min-width: ${popoverMinWidth}`)\n if (popoverMaxWidth) styles.push(`max-width: ${popoverMaxWidth}`)\n\n return styles.join('; ')\n})\n</script>\n","import { createSharedComposable, useAsyncState } from '@vueuse/core'\n\nimport { api } from '@/scripts/api'\n\n/**\n * Format folder name to display name\n * Converts \"upscale_models\" -> \"Upscale Model\"\n * Converts \"loras\" -> \"LoRA\"\n */\nfunction formatDisplayName(folderName: string): string {\n // Special cases for acronyms and proper nouns\n const specialCases: Record<string, string> = {\n loras: 'LoRA',\n ipadapter: 'IP-Adapter',\n sams: 'SAM',\n clip_vision: 'CLIP Vision',\n animatediff_motion_lora: 'AnimateDiff Motion LoRA',\n animatediff_models: 'AnimateDiff Model',\n vae: 'VAE',\n sam2: 'SAM 2',\n controlnet: 'ControlNet',\n gligen: 'GLIGEN'\n }\n\n if (specialCases[folderName]) {\n return specialCases[folderName]\n }\n\n return folderName\n .split('_')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n}\n\ninterface ModelTypeOption {\n name: string // Display name\n value: string // Actual tag value\n}\n\nconst DISALLOWED_MODEL_TYPES = ['nlf'] as const\n\n/**\n * Composable for fetching and managing model types from the API\n * Uses shared state to ensure data is only fetched once\n */\nexport const useModelTypes = createSharedComposable(() => {\n const {\n state: modelTypes,\n isLoading,\n error,\n execute: fetchModelTypes\n } = useAsyncState(\n async (): Promise<ModelTypeOption[]> => {\n const response = await api.getModelFolders()\n return response\n .filter(\n (folder) =>\n !DISALLOWED_MODEL_TYPES.includes(\n folder.name as (typeof DISALLOWED_MODEL_TYPES)[number]\n )\n )\n .map((folder) => ({\n name: formatDisplayName(folder.name),\n value: folder.name\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n },\n [] as ModelTypeOption[],\n {\n immediate: false,\n onError: (err) => {\n console.error('Failed to fetch model types:', err)\n }\n }\n )\n\n return {\n modelTypes,\n isLoading,\n error,\n fetchModelTypes\n }\n})\n","<template>\n <div class=\"flex flex-col gap-4 text-sm text-muted-foreground\">\n <div class=\"flex flex-col gap-2\">\n <p class=\"m-0\">\n {{ $t('assetBrowser.modelAssociatedWithLink') }}\n </p>\n <div\n class=\"flex items-center gap-3 rounded-lg bg-secondary-background p-3\"\n >\n <img\n v-if=\"previewImage\"\n :src=\"previewImage\"\n :alt=\"metadata?.filename || metadata?.name || 'Model preview'\"\n class=\"size-14 flex-shrink-0 rounded object-cover\"\n />\n <p class=\"m-0 min-w-0 flex-1 truncate text-base-foreground\">\n {{ metadata?.filename || metadata?.name }}\n </p>\n </div>\n </div>\n\n <!-- Model Type Selection -->\n <div class=\"flex flex-col gap-2\">\n <label class=\"\">\n {{ $t('assetBrowser.modelTypeSelectorLabel') }}\n </label>\n <SingleSelect\n v-model=\"modelValue\"\n :label=\"\n isLoading\n ? $t('g.loading')\n : $t('assetBrowser.modelTypeSelectorPlaceholder')\n \"\n :options=\"modelTypes\"\n :disabled=\"isLoading\"\n data-attr=\"upload-model-step2-type-selector\"\n />\n <div class=\"flex items-center gap-2\">\n <i class=\"icon-[lucide--circle-question-mark]\" />\n <span>{{ $t('assetBrowser.notSureLeaveAsIs') }}</span>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport SingleSelect from '@/components/input/SingleSelect.vue'\nimport { useModelTypes } from '@/platform/assets/composables/useModelTypes'\nimport type { AssetMetadata } from '@/platform/assets/schemas/assetSchema'\n\ndefineProps<{\n metadata?: AssetMetadata\n previewImage?: string\n}>()\n\nconst modelValue = defineModel<string | undefined>()\n\nconst { modelTypes, isLoading } = useModelTypes()\n</script>\n","<template>\n <Dialog\n v-model:visible=\"isVisible\"\n modal\n :closable=\"false\"\n :close-on-escape=\"false\"\n :dismissable-mask=\"true\"\n :pt=\"{\n root: { class: 'video-help-dialog' },\n header: { class: '!hidden' },\n content: { class: '!p-0' },\n mask: { class: '!bg-black/70' }\n }\"\n :style=\"{ width: '90vw' }\"\n >\n <div class=\"relative\">\n <Button\n variant=\"textonly\"\n size=\"icon\"\n class=\"absolute top-4 right-6 z-10\"\n :aria-label=\"$t('g.close')\"\n @click=\"isVisible = false\"\n >\n <i class=\"pi pi-times text-sm\" />\n </Button>\n <video\n autoplay\n muted\n loop\n :aria-label=\"ariaLabel\"\n class=\"w-full rounded-lg\"\n :src=\"videoUrl\"\n >\n {{ $t('g.videoFailedToLoad') }}\n </video>\n </div>\n </Dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { useEventListener } from '@vueuse/core'\nimport Dialog from 'primevue/dialog'\nimport { onWatcherCleanup, watch } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\n\nconst isVisible = defineModel<boolean>({ required: true })\n\nconst { videoUrl, ariaLabel = 'Help video' } = defineProps<{\n videoUrl: string\n ariaLabel?: string\n}>()\n\nconst handleEscapeKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.stopImmediatePropagation()\n event.stopPropagation()\n event.preventDefault()\n isVisible.value = false\n }\n}\n\n// Add listener with capture phase to intercept before parent dialogs\n// Only active when dialog is visible\nwatch(\n isVisible,\n (visible) => {\n if (visible) {\n const stop = useEventListener(document, 'keydown', handleEscapeKey, {\n capture: true\n })\n onWatcherCleanup(stop)\n }\n },\n { immediate: true }\n)\n</script>\n","<template>\n <div class=\"flex justify-end gap-2 w-full\">\n <div\n v-if=\"currentStep === 1 && flags.huggingfaceModelImportEnabled\"\n class=\"mr-auto flex items-center gap-2\"\n >\n <i class=\"icon-[lucide--circle-question-mark] text-muted-foreground\" />\n <Button\n variant=\"muted-textonly\"\n size=\"sm\"\n data-attr=\"upload-model-step1-help-civitai\"\n @click=\"showCivitaiHelp = true\"\n >\n {{ $t('assetBrowser.providerCivitai') }}\n </Button>\n <Button\n variant=\"muted-textonly\"\n size=\"sm\"\n data-attr=\"upload-model-step1-help-huggingface\"\n @click=\"showHuggingFaceHelp = true\"\n >\n {{ $t('assetBrowser.providerHuggingFace') }}\n </Button>\n </div>\n <Button\n v-else-if=\"currentStep === 1\"\n variant=\"muted-textonly\"\n size=\"lg\"\n class=\"mr-auto underline\"\n data-attr=\"upload-model-step1-help-link\"\n @click=\"showCivitaiHelp = true\"\n >\n <i class=\"icon-[lucide--circle-question-mark]\" />\n <span>{{ $t('assetBrowser.uploadModelHowDoIFindThis') }}</span>\n </Button>\n <Button\n v-if=\"currentStep === 1\"\n variant=\"muted-textonly\"\n size=\"lg\"\n data-attr=\"upload-model-step1-cancel-button\"\n :disabled=\"isFetchingMetadata || isUploading\"\n @click=\"emit('close')\"\n >\n {{ $t('g.cancel') }}\n </Button>\n <Button\n v-if=\"currentStep !== 1 && currentStep !== 3\"\n variant=\"muted-textonly\"\n size=\"lg\"\n :data-attr=\"`upload-model-step${currentStep}-back-button`\"\n :disabled=\"isFetchingMetadata || isUploading\"\n @click=\"emit('back')\"\n >\n {{ $t('g.back') }}\n </Button>\n <span v-else />\n\n <Button\n v-if=\"currentStep === 1\"\n variant=\"secondary\"\n size=\"lg\"\n data-attr=\"upload-model-step1-continue-button\"\n :disabled=\"!canFetchMetadata || isFetchingMetadata\"\n @click=\"emit('fetchMetadata')\"\n >\n <i\n v-if=\"isFetchingMetadata\"\n class=\"icon-[lucide--loader-circle] animate-spin\"\n />\n <span>{{ $t('g.continue') }}</span>\n </Button>\n <Button\n v-else-if=\"currentStep === 2\"\n variant=\"secondary\"\n size=\"lg\"\n data-attr=\"upload-model-step2-confirm-button\"\n :disabled=\"!canUploadModel || isUploading\"\n @click=\"emit('upload')\"\n >\n <i v-if=\"isUploading\" class=\"icon-[lucide--loader-circle] animate-spin\" />\n <span>{{ $t('assetBrowser.upload') }}</span>\n </Button>\n <Button\n v-else-if=\"\n currentStep === 3 &&\n (uploadStatus === 'success' || uploadStatus === 'processing')\n \"\n variant=\"secondary\"\n data-attr=\"upload-model-step3-finish-button\"\n @click=\"emit('close')\"\n >\n {{\n uploadStatus === 'processing'\n ? $t('g.close')\n : $t('assetBrowser.finish')\n }}\n </Button>\n <VideoHelpDialog\n v-model=\"showCivitaiHelp\"\n video-url=\"https://media.comfy.org/compressed_768/civitai_howto.webm\"\n :aria-label=\"$t('assetBrowser.uploadModelHelpVideo')\"\n />\n <VideoHelpDialog\n v-model=\"showHuggingFaceHelp\"\n video-url=\"https://media.comfy.org/byom/huggingfacehowto.mp4\"\n :aria-label=\"$t('assetBrowser.uploadModelHelpVideo')\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport VideoHelpDialog from '@/platform/assets/components/VideoHelpDialog.vue'\n\nconst { flags } = useFeatureFlags()\n\nconst showCivitaiHelp = ref(false)\nconst showHuggingFaceHelp = ref(false)\n\ndefineProps<{\n currentStep: number\n isFetchingMetadata: boolean\n isUploading: boolean\n canFetchMetadata: boolean\n canUploadModel: boolean\n uploadStatus?: 'processing' | 'success' | 'error'\n}>()\n\nconst emit = defineEmits<{\n (e: 'back'): void\n (e: 'fetchMetadata'): void\n (e: 'upload'): void\n (e: 'close'): void\n}>()\n</script>\n","import type { ImportSource } from '@/platform/assets/types/importSource'\n\n/**\n * Civitai model import source configuration\n */\nexport const civitaiImportSource: ImportSource = {\n type: 'civitai',\n name: 'Civitai',\n hostnames: ['civitai.com']\n}\n","import type { ImportSource } from '@/platform/assets/types/importSource'\n\n/**\n * Hugging Face model import source configuration\n */\nexport const huggingfaceImportSource: ImportSource = {\n type: 'huggingface',\n name: 'Hugging Face',\n hostnames: ['huggingface.co']\n}\n","import type { ImportSource } from '@/platform/assets/types/importSource'\n\n/**\n * Check if a URL belongs to a specific import source\n */\nexport function validateSourceUrl(url: string, source: ImportSource): boolean {\n try {\n const hostname = new URL(url).hostname.toLowerCase()\n return source.hostnames.some(\n (h) => hostname === h || hostname.endsWith(`.${h}`)\n )\n } catch {\n return false\n }\n}\n","<template>\n <div class=\"flex flex-col justify-between h-full gap-6 text-sm\">\n <div class=\"flex flex-col gap-6\">\n <div class=\"flex flex-col gap-2\">\n <p class=\"m-0 text-foreground\">\n {{ $t('assetBrowser.uploadModelDescription1Generic') }}\n </p>\n <div class=\"m-0\">\n <p class=\"m-0 text-muted-foreground\">\n {{ $t('assetBrowser.uploadModelDescription2Generic') }}\n </p>\n <span class=\"inline-flex items-center gap-1 flex-wrap mt-2\">\n <span class=\"inline-flex items-center gap-1\">\n <img\n :src=\"civitaiIcon\"\n :alt=\"$t('assetBrowser.providerCivitai')\"\n class=\"w-4 h-4\"\n />\n <a\n :href=\"civitaiUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-muted underline\"\n >\n {{ $t('assetBrowser.providerCivitai') }}</a\n ><span>,</span>\n </span>\n <span class=\"inline-flex items-center gap-1\">\n <img\n :src=\"huggingFaceIcon\"\n :alt=\"$t('assetBrowser.providerHuggingFace')\"\n class=\"w-4 h-4\"\n />\n <a\n :href=\"huggingFaceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-muted underline\"\n >\n {{ $t('assetBrowser.providerHuggingFace') }}\n </a>\n </span>\n </span>\n </div>\n </div>\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"relative\">\n <InputText\n v-model=\"url\"\n autofocus\n :placeholder=\"$t('assetBrowser.genericLinkPlaceholder')\"\n class=\"w-full border-0 bg-secondary-background p-4 pr-10\"\n data-attr=\"upload-model-step1-url-input\"\n />\n <i\n v-if=\"isValidUrl\"\n class=\"icon-[lucide--circle-check-big] absolute top-1/2 right-3 size-5 -translate-y-1/2 text-green-500\"\n />\n </div>\n <p v-if=\"error\" class=\"text-xs text-error\">\n {{ error }}\n </p>\n <p v-else-if=\"!flags.asyncModelUploadEnabled\" class=\"text-foreground\">\n <i18n-t keypath=\"assetBrowser.maxFileSize\" tag=\"span\">\n <template #size>\n <span class=\"font-bold italic\">{{\n $t('assetBrowser.maxFileSizeValue')\n }}</span>\n </template>\n </i18n-t>\n </p>\n </div>\n </div>\n\n <div class=\"text-sm text-muted\">\n {{ $t('assetBrowser.uploadModelHelpFooterText') }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport InputText from 'primevue/inputtext'\nimport { computed } from 'vue'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport { civitaiImportSource } from '@/platform/assets/importSources/civitaiImportSource'\nimport { huggingfaceImportSource } from '@/platform/assets/importSources/huggingfaceImportSource'\nimport { validateSourceUrl } from '@/platform/assets/utils/importSourceUtil'\n\nconst { flags } = useFeatureFlags()\n\nconst props = defineProps<{\n modelValue: string\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n}>()\n\nconst url = computed({\n get: () => props.modelValue,\n set: (value: string) => emit('update:modelValue', value)\n})\n\nconst importSources = [civitaiImportSource, huggingfaceImportSource]\n\nconst isValidUrl = computed(() => {\n const trimmedUrl = url.value.trim()\n if (!trimmedUrl) return false\n return importSources.some((source) => validateSourceUrl(trimmedUrl, source))\n})\n\nconst civitaiIcon = '/assets/images/civitai.svg'\nconst civitaiUrl = 'https://civitai.com/models'\nconst huggingFaceIcon = '/assets/images/hf-logo.svg'\nconst huggingFaceUrl = 'https://huggingface.co'\n</script>\n","<template>\n <div class=\"flex flex-col gap-6 text-sm text-muted-foreground\">\n <div class=\"flex flex-col gap-2\">\n <p class=\"m-0\">\n {{ $t('assetBrowser.uploadModelDescription1') }}\n </p>\n <ul class=\"list-disc space-y-1 pl-5 mt-0\">\n <li>\n <i18n-t keypath=\"assetBrowser.uploadModelDescription2\" tag=\"span\">\n <template #link>\n <a\n href=\"https://civitai.com/models\"\n target=\"_blank\"\n class=\"text-muted-foreground\"\n >\n {{ $t('assetBrowser.uploadModelDescription2Link') }}\n </a>\n </template>\n </i18n-t>\n </li>\n <li v-if=\"!flags.asyncModelUploadEnabled\">\n <i18n-t keypath=\"assetBrowser.uploadModelDescription3\" tag=\"span\">\n <template #size>\n <span class=\"font-bold italic\">{{\n $t('assetBrowser.maxFileSizeValue')\n }}</span>\n </template>\n </i18n-t>\n </li>\n </ul>\n </div>\n\n <div class=\"flex flex-col gap-2\">\n <i18n-t keypath=\"assetBrowser.civitaiLinkLabel\" tag=\"label\" class=\"mb-0\">\n <template #download>\n <span class=\"font-bold italic\">{{\n $t('assetBrowser.civitaiLinkLabelDownload')\n }}</span>\n </template>\n </i18n-t>\n <div class=\"relative\">\n <InputText\n v-model=\"url\"\n autofocus\n :placeholder=\"$t('assetBrowser.civitaiLinkPlaceholder')\"\n class=\"w-full border-0 bg-secondary-background p-4 pr-10\"\n data-attr=\"upload-model-step1-url-input\"\n />\n <i\n v-if=\"isValidUrl\"\n class=\"icon-[lucide--circle-check-big] absolute top-1/2 right-3 size-5 -translate-y-1/2 text-green-500\"\n />\n </div>\n <p v-if=\"error\" class=\"text-xs text-error\">\n {{ error }}\n </p>\n <i18n-t\n v-else\n keypath=\"assetBrowser.civitaiLinkExample\"\n tag=\"p\"\n class=\"text-xs\"\n >\n <template #example>\n <strong>{{ $t('assetBrowser.civitaiLinkExampleStrong') }}</strong>\n </template>\n <template #link>\n <a\n href=\"https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295\"\n target=\"_blank\"\n class=\"text-muted-foreground\"\n >\n {{ $t('assetBrowser.civitaiLinkExampleUrl') }}\n </a>\n </template>\n </i18n-t>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport InputText from 'primevue/inputtext'\nimport { computed } from 'vue'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport { civitaiImportSource } from '@/platform/assets/importSources/civitaiImportSource'\nimport { validateSourceUrl } from '@/platform/assets/utils/importSourceUtil'\n\nconst { flags } = useFeatureFlags()\n\ndefineProps<{\n error?: string\n}>()\n\nconst url = defineModel<string>({ required: true })\n\nconst isValidUrl = computed(() => {\n const trimmedUrl = url.value.trim()\n if (!trimmedUrl) return false\n return validateSourceUrl(trimmedUrl, civitaiImportSource)\n})\n</script>\n","import type { Ref } from 'vue'\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport { st } from '@/i18n'\nimport { civitaiImportSource } from '@/platform/assets/importSources/civitaiImportSource'\nimport { huggingfaceImportSource } from '@/platform/assets/importSources/huggingfaceImportSource'\nimport type { AssetMetadata } from '@/platform/assets/schemas/assetSchema'\nimport { assetService } from '@/platform/assets/services/assetService'\nimport type { ImportSource } from '@/platform/assets/types/importSource'\nimport { validateSourceUrl } from '@/platform/assets/utils/importSourceUtil'\nimport { useAssetDownloadStore } from '@/stores/assetDownloadStore'\nimport { useAssetsStore } from '@/stores/assetsStore'\nimport { useModelToNodeStore } from '@/stores/modelToNodeStore'\n\ninterface WizardData {\n url: string\n metadata?: AssetMetadata\n name: string\n tags: string[]\n previewImage?: string\n}\n\ninterface ModelTypeOption {\n name: string\n value: string\n}\n\nexport function useUploadModelWizard(modelTypes: Ref<ModelTypeOption[]>) {\n const { t } = useI18n()\n const assetsStore = useAssetsStore()\n const assetDownloadStore = useAssetDownloadStore()\n const modelToNodeStore = useModelToNodeStore()\n const { flags } = useFeatureFlags()\n const currentStep = ref(1)\n const isFetchingMetadata = ref(false)\n const isUploading = ref(false)\n const uploadStatus = ref<'processing' | 'success' | 'error'>()\n const uploadError = ref('')\n\n const wizardData = ref<WizardData>({\n url: '',\n name: '',\n tags: []\n })\n\n const selectedModelType = ref<string>()\n\n // Available import sources\n const importSources: ImportSource[] = flags.huggingfaceModelImportEnabled\n ? [civitaiImportSource, huggingfaceImportSource]\n : [civitaiImportSource]\n\n // Detected import source based on URL\n const detectedSource = computed(() => {\n const url = wizardData.value.url.trim()\n if (!url) return null\n return (\n importSources.find((source) => validateSourceUrl(url, source)) ?? null\n )\n })\n\n // Clear error when URL changes\n watch(\n () => wizardData.value.url,\n () => {\n uploadError.value = ''\n }\n )\n\n // Validation\n const canFetchMetadata = computed(() => {\n return wizardData.value.url.trim().length > 0\n })\n\n const canUploadModel = computed(() => {\n return !!selectedModelType.value\n })\n\n async function fetchMetadata() {\n if (!canFetchMetadata.value) return\n\n // Clean and normalize URL\n let cleanedUrl = wizardData.value.url.trim()\n try {\n cleanedUrl = new URL(encodeURI(cleanedUrl)).toString()\n } catch {\n // If URL parsing fails, just use the trimmed input\n }\n wizardData.value.url = cleanedUrl\n\n // Validate URL belongs to a supported import source\n const source = detectedSource.value\n if (!source) {\n const supportedSources = importSources.map((s) => s.name).join(', ')\n uploadError.value = t('assetBrowser.unsupportedUrlSource', {\n sources: supportedSources\n })\n return\n }\n\n isFetchingMetadata.value = true\n try {\n const metadata = await assetService.getAssetMetadata(wizardData.value.url)\n\n // Decode URL-encoded filenames (e.g., Chinese characters)\n if (metadata.filename) {\n try {\n metadata.filename = decodeURIComponent(metadata.filename)\n } catch {\n // Keep original if decoding fails\n }\n }\n if (metadata.name) {\n try {\n metadata.name = decodeURIComponent(metadata.name)\n } catch {\n // Keep original if decoding fails\n }\n }\n\n wizardData.value.metadata = metadata\n\n // Pre-fill name from metadata\n wizardData.value.name = metadata.filename || metadata.name || ''\n\n // Store preview image if available\n wizardData.value.previewImage = metadata.preview_image\n\n // Pre-fill model type from metadata tags if available\n if (metadata.tags && metadata.tags.length > 0) {\n wizardData.value.tags = metadata.tags\n // Try to detect model type from tags\n const typeTag = metadata.tags.find((tag) =>\n modelTypes.value.some((type) => type.value === tag)\n )\n if (typeTag) {\n selectedModelType.value = typeTag\n }\n }\n\n currentStep.value = 2\n } catch (error) {\n console.error('Failed to retrieve metadata:', error)\n uploadError.value =\n error instanceof Error\n ? error.message\n : st(\n 'assetBrowser.uploadModelFailedToRetrieveMetadata',\n 'Failed to retrieve metadata. Please check the link and try again.'\n )\n currentStep.value = 1\n } finally {\n isFetchingMetadata.value = false\n }\n }\n\n async function uploadPreviewImage(\n filename: string\n ): Promise<string | undefined> {\n if (!wizardData.value.previewImage) return undefined\n\n try {\n const baseFilename = filename.split('.')[0]\n let extension = 'png'\n const mimeMatch = wizardData.value.previewImage.match(\n /^data:image\\/([^;]+);/\n )\n if (mimeMatch) {\n extension = mimeMatch[1] === 'jpeg' ? 'jpg' : mimeMatch[1]\n }\n\n const previewAsset = await assetService.uploadAssetFromBase64({\n data: wizardData.value.previewImage,\n name: `${baseFilename}_preview.${extension}`,\n tags: ['preview']\n })\n return previewAsset.id\n } catch (error) {\n console.error('Failed to upload preview image:', error)\n return undefined\n }\n }\n\n async function refreshModelCaches() {\n if (!selectedModelType.value) return\n\n const providers = modelToNodeStore.getAllNodeProviders(\n selectedModelType.value\n )\n const results = await Promise.allSettled(\n providers.map((provider) =>\n assetsStore.updateModelsForNodeType(provider.nodeDef.name)\n )\n )\n results.forEach((result, index) => {\n if (result.status === 'rejected') {\n console.error(\n `Failed to refresh ${providers[index].nodeDef.name}:`,\n result.reason\n )\n }\n })\n }\n\n async function uploadModel(): Promise<boolean> {\n if (!canUploadModel.value) {\n return false\n }\n\n const source = detectedSource.value\n if (!source) {\n uploadError.value = t('assetBrowser.noValidSourceDetected')\n return false\n }\n\n isUploading.value = true\n\n try {\n const tags = selectedModelType.value\n ? ['models', selectedModelType.value]\n : ['models']\n const filename =\n wizardData.value.metadata?.filename ||\n wizardData.value.metadata?.name ||\n 'model'\n\n const previewId = await uploadPreviewImage(filename)\n const userMetadata = {\n source: source.type,\n source_url: wizardData.value.url,\n model_type: selectedModelType.value\n }\n\n if (flags.asyncModelUploadEnabled) {\n const result = await assetService.uploadAssetAsync({\n source_url: wizardData.value.url,\n tags,\n user_metadata: userMetadata,\n preview_id: previewId\n })\n\n if (result.type === 'async' && result.task.status !== 'completed') {\n if (selectedModelType.value) {\n assetDownloadStore.trackDownload(\n result.task.task_id,\n selectedModelType.value\n )\n }\n uploadStatus.value = 'processing'\n } else {\n uploadStatus.value = 'success'\n await refreshModelCaches()\n }\n currentStep.value = 3\n } else {\n await assetService.uploadAssetFromUrl({\n url: wizardData.value.url,\n name: filename,\n tags,\n user_metadata: userMetadata,\n preview_id: previewId\n })\n uploadStatus.value = 'success'\n await refreshModelCaches()\n currentStep.value = 3\n }\n } catch (error) {\n console.error('Failed to upload asset:', error)\n uploadStatus.value = 'error'\n uploadError.value =\n error instanceof Error ? error.message : 'Failed to upload model'\n currentStep.value = 3\n } finally {\n isUploading.value = false\n }\n return uploadStatus.value !== 'error'\n }\n\n function goToPreviousStep() {\n if (currentStep.value > 1) {\n currentStep.value = currentStep.value - 1\n }\n }\n\n return {\n // State\n currentStep,\n isFetchingMetadata,\n isUploading,\n uploadStatus,\n uploadError,\n wizardData,\n selectedModelType,\n\n // Computed\n canFetchMetadata,\n canUploadModel,\n detectedSource,\n\n // Actions\n fetchMetadata,\n uploadModel,\n goToPreviousStep\n }\n}\n","<template>\n <div\n class=\"upload-model-dialog flex flex-col gap-6 border-t border-border-default p-4 pt-6\"\n >\n <!-- Scrollable content area -->\n <div class=\"min-h-0 flex-auto basis-0 overflow-y-auto\">\n <!-- Step 1: Enter URL -->\n <UploadModelUrlInput\n v-if=\"currentStep === 1 && flags.huggingfaceModelImportEnabled\"\n v-model=\"wizardData.url\"\n :error=\"uploadError\"\n />\n <UploadModelUrlInputCivitai\n v-else-if=\"currentStep === 1\"\n v-model=\"wizardData.url\"\n :error=\"uploadError\"\n />\n\n <!-- Step 2: Confirm Metadata -->\n <UploadModelConfirmation\n v-else-if=\"currentStep === 2\"\n v-model=\"selectedModelType\"\n :metadata=\"wizardData.metadata\"\n :preview-image=\"wizardData.previewImage\"\n />\n\n <!-- Step 3: Upload Progress -->\n <UploadModelProgress\n v-else-if=\"currentStep === 3 && uploadStatus != null\"\n :result=\"uploadStatus\"\n :error=\"uploadError\"\n :metadata=\"wizardData.metadata\"\n :model-type=\"selectedModelType\"\n :preview-image=\"wizardData.previewImage\"\n />\n </div>\n\n <!-- Navigation Footer - always visible -->\n <UploadModelFooter\n class=\"flex-shrink-0\"\n :current-step=\"currentStep\"\n :is-fetching-metadata=\"isFetchingMetadata\"\n :is-uploading=\"isUploading\"\n :can-fetch-metadata=\"canFetchMetadata\"\n :can-upload-model=\"canUploadModel\"\n :upload-status=\"uploadStatus\"\n @back=\"goToPreviousStep\"\n @fetch-metadata=\"handleFetchMetadata\"\n @upload=\"handleUploadModel\"\n @close=\"handleClose\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted } from 'vue'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport UploadModelConfirmation from '@/platform/assets/components/UploadModelConfirmation.vue'\nimport UploadModelFooter from '@/platform/assets/components/UploadModelFooter.vue'\nimport UploadModelProgress from '@/platform/assets/components/UploadModelProgress.vue'\nimport UploadModelUrlInput from '@/platform/assets/components/UploadModelUrlInput.vue'\nimport UploadModelUrlInputCivitai from '@/platform/assets/components/UploadModelUrlInputCivitai.vue'\nimport { useModelTypes } from '@/platform/assets/composables/useModelTypes'\nimport { useUploadModelWizard } from '@/platform/assets/composables/useUploadModelWizard'\nimport { useDialogStore } from '@/stores/dialogStore'\n\nconst { flags } = useFeatureFlags()\nconst dialogStore = useDialogStore()\nconst { modelTypes, fetchModelTypes } = useModelTypes()\n\nconst emit = defineEmits<{\n 'upload-success': []\n}>()\n\nconst {\n currentStep,\n isFetchingMetadata,\n isUploading,\n uploadStatus,\n uploadError,\n wizardData,\n selectedModelType,\n canFetchMetadata,\n canUploadModel,\n fetchMetadata,\n uploadModel,\n goToPreviousStep\n} = useUploadModelWizard(modelTypes)\n\nasync function handleFetchMetadata() {\n await fetchMetadata()\n}\n\nasync function handleUploadModel() {\n const success = await uploadModel()\n if (success) {\n emit('upload-success')\n }\n}\n\nfunction handleClose() {\n dialogStore.closeDialog({ key: 'upload-model' })\n}\n\nonMounted(() => {\n fetchModelTypes()\n})\n</script>\n\n<style scoped>\n.upload-model-dialog {\n width: 90vw;\n max-width: 800px;\n min-height: min(400px, 80vh);\n max-height: 90vh;\n}\n\n@media (min-width: 640px) {\n .upload-model-dialog {\n width: auto;\n min-width: 600px;\n }\n}\n</style>\n","export default \"__VITE_PUBLIC_ASSET__53f47e52__\"","<template>\n <div class=\"flex items-center gap-2 p-4 font-bold\">\n <img\n v-if=\"!flags.huggingfaceModelImportEnabled\"\n src=\"/assets/images/civitai.svg\"\n class=\"size-4\"\n />\n <span>{{ $t(titleKey) }}</span>\n <span\n class=\"rounded-full bg-white px-1.5 py-0 text-xxs font-inter font-semibold uppercase text-black\"\n >\n {{ $t('g.beta') }}\n </span>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\n\nconst { flags } = useFeatureFlags()\n\nconst titleKey = computed(() => {\n return flags.huggingfaceModelImportEnabled\n ? 'assetBrowser.uploadModelGeneric'\n : 'assetBrowser.uploadModelFromCivitai'\n})\n</script>\n","<template>\n <div\n class=\"flex flex-1 flex-col items-center justify-center text-base text-muted-foreground\"\n >\n <p class=\"m-0 max-w-md\">\n {{ $t('assetBrowser.upgradeFeatureDescription') }}\n </p>\n </div>\n</template>\n","<template>\n <div class=\"flex flex-wrap justify-end gap-2 w-full\">\n <a\n href=\"https://blog.comfy.org/p/comfy-cloud-new-features-and-pricing\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-muted-foreground mr-auto underline flex items-center gap-2\"\n >\n <i class=\"icon-[lucide--external-link]\" />\n <span>{{ $t('g.learnMore') }}</span>\n </a>\n <Button variant=\"textonly\" @click=\"emit('close')\">{{\n $t('g.close')\n }}</Button>\n <Button variant=\"secondary\" @click=\"emit('subscribe')\">\n {{ $t('subscription.required.subscribe') }}\n </Button>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\n\nconst emit = defineEmits<{\n close: []\n subscribe: []\n}>()\n</script>\n","<template>\n <div\n class=\"flex flex-col justify-between gap-10 p-4 border-t border-border-default w-auto max-w-[min(500px,90vw)]\"\n >\n <UploadModelUpgradeModalBody />\n\n <UploadModelUpgradeModalFooter\n @close=\"handleClose\"\n @subscribe=\"handleSubscribe\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport UploadModelUpgradeModalBody from '@/platform/assets/components/UploadModelUpgradeModalBody.vue'\nimport UploadModelUpgradeModalFooter from '@/platform/assets/components/UploadModelUpgradeModalFooter.vue'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport { useDialogStore } from '@/stores/dialogStore'\n\nconst dialogStore = useDialogStore()\nconst { showSubscriptionDialog } = useSubscription()\n\nfunction handleClose() {\n dialogStore.closeDialog({ key: 'upload-model-upgrade' })\n}\n\nfunction handleSubscribe() {\n showSubscriptionDialog()\n}\n</script>\n","<template>\n <div class=\"flex items-center gap-2 p-4 font-bold\">\n <span>{{ $t('assetBrowser.upgradeToUnlockFeature') }}</span>\n </div>\n</template>\n","import { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport UploadModelDialog from '@/platform/assets/components/UploadModelDialog.vue'\nimport UploadModelDialogHeader from '@/platform/assets/components/UploadModelDialogHeader.vue'\nimport UploadModelUpgradeModal from '@/platform/assets/components/UploadModelUpgradeModal.vue'\nimport UploadModelUpgradeModalHeader from '@/platform/assets/components/UploadModelUpgradeModalHeader.vue'\nimport { useDialogStore } from '@/stores/dialogStore'\nimport { computed } from 'vue'\n\nexport function useModelUpload(\n onUploadSuccess?: () => Promise<unknown> | void\n) {\n const dialogStore = useDialogStore()\n const { flags } = useFeatureFlags()\n const isUploadButtonEnabled = computed(() => flags.modelUploadButtonEnabled)\n\n function showUploadDialog() {\n if (!flags.privateModelsEnabled) {\n // Show upgrade modal if private models are disabled\n dialogStore.showDialog({\n key: 'upload-model-upgrade',\n headerComponent: UploadModelUpgradeModalHeader,\n component: UploadModelUpgradeModal,\n dialogComponentProps: {\n pt: {\n header: 'py-0! pl-0!',\n content: 'p-0! overflow-y-hidden!'\n }\n }\n })\n } else {\n // Show regular upload modal\n dialogStore.showDialog({\n key: 'upload-model',\n headerComponent: UploadModelDialogHeader,\n component: UploadModelDialog,\n props: {\n onUploadSuccess: async () => {\n await onUploadSuccess?.()\n }\n },\n dialogComponentProps: {\n pt: {\n header: 'py-0! pl-0!',\n content: 'p-0! overflow-y-hidden!'\n }\n }\n })\n }\n }\n return { isUploadButtonEnabled, showUploadDialog }\n}\n","export default \"__VITE_PUBLIC_ASSET__c8cdd13f__\"","import { onBeforeUnmount, ref, watch } from 'vue'\nimport type { Ref } from 'vue'\n\ninterface UseIntersectionObserverOptions extends IntersectionObserverInit {\n immediate?: boolean\n}\n\nexport function useIntersectionObserver(\n target: Ref<Element | null>,\n callback: IntersectionObserverCallback,\n options: UseIntersectionObserverOptions = {}\n) {\n const { immediate = true, ...observerOptions } = options\n\n const isSupported =\n typeof window !== 'undefined' && 'IntersectionObserver' in window\n const isIntersecting = ref(false)\n\n let observer: IntersectionObserver | null = null\n\n const cleanup = () => {\n if (observer) {\n observer.disconnect()\n observer = null\n }\n }\n\n const observe = () => {\n cleanup()\n\n if (!isSupported || !target.value) return\n\n observer = new IntersectionObserver((entries) => {\n isIntersecting.value = entries.some((entry) => entry.isIntersecting)\n callback(entries, observer!)\n }, observerOptions)\n\n observer.observe(target.value)\n }\n\n const unobserve = () => {\n if (observer && target.value) {\n observer.unobserve(target.value)\n }\n }\n\n if (immediate) {\n watch(target, observe, { immediate: true, flush: 'post' })\n }\n\n onBeforeUnmount(cleanup)\n\n return {\n isSupported,\n isIntersecting,\n observe,\n unobserve,\n cleanup\n }\n}\n","import { reactive } from 'vue'\n\ninterface CachedMedia {\n src: string\n blob?: Blob\n objectUrl?: string\n error?: boolean\n isLoading: boolean\n lastAccessed: number\n}\n\ninterface MediaCacheOptions {\n maxSize?: number\n maxAge?: number // in milliseconds\n preloadDistance?: number // pixels from viewport\n}\n\nclass MediaCacheService {\n public cache = reactive(new Map<string, CachedMedia>())\n private readonly maxSize: number\n private readonly maxAge: number\n private cleanupInterval: number | null = null\n private urlRefCount = new Map<string, number>()\n\n constructor(options: MediaCacheOptions = {}) {\n this.maxSize = options.maxSize ?? 100\n this.maxAge = options.maxAge ?? 30 * 60 * 1000 // 30 minutes\n\n // Start cleanup interval\n this.startCleanupInterval()\n }\n\n private startCleanupInterval() {\n // Clean up every 5 minutes\n this.cleanupInterval = window.setInterval(\n () => {\n this.cleanup()\n },\n 5 * 60 * 1000\n )\n }\n\n private cleanup() {\n const now = Date.now()\n const keysToDelete: string[] = []\n\n // Find expired entries\n for (const [key, entry] of Array.from(this.cache.entries())) {\n if (now - entry.lastAccessed > this.maxAge) {\n // Only revoke object URL if no components are using it\n if (entry.objectUrl) {\n const refCount = this.urlRefCount.get(entry.objectUrl) || 0\n if (refCount === 0) {\n URL.revokeObjectURL(entry.objectUrl)\n this.urlRefCount.delete(entry.objectUrl)\n keysToDelete.push(key)\n }\n // Don't delete cache entry if URL is still in use\n } else {\n keysToDelete.push(key)\n }\n }\n }\n\n // Remove expired entries\n keysToDelete.forEach((key) => this.cache.delete(key))\n\n // If still over size limit, remove oldest entries that aren't in use\n if (this.cache.size > this.maxSize) {\n const entries = Array.from(this.cache.entries())\n entries.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed)\n\n let removedCount = 0\n const targetRemoveCount = this.cache.size - this.maxSize\n\n for (const [key, entry] of entries) {\n if (removedCount >= targetRemoveCount) break\n\n if (entry.objectUrl) {\n const refCount = this.urlRefCount.get(entry.objectUrl) || 0\n if (refCount === 0) {\n URL.revokeObjectURL(entry.objectUrl)\n this.urlRefCount.delete(entry.objectUrl)\n this.cache.delete(key)\n removedCount++\n }\n } else {\n this.cache.delete(key)\n removedCount++\n }\n }\n }\n }\n\n async getCachedMedia(src: string): Promise<CachedMedia> {\n let entry = this.cache.get(src)\n\n if (entry) {\n // Update last accessed time\n entry.lastAccessed = Date.now()\n return entry\n }\n\n // Create new entry\n entry = {\n src,\n isLoading: true,\n lastAccessed: Date.now()\n }\n\n // Update cache with loading entry\n this.cache.set(src, entry)\n\n try {\n // Fetch the media\n const response = await fetch(src, { cache: 'force-cache' })\n if (!response.ok) {\n throw new Error(`Failed to fetch: ${response.status}`)\n }\n\n const blob = await response.blob()\n const objectUrl = URL.createObjectURL(blob)\n\n // Update entry with successful result\n const updatedEntry: CachedMedia = {\n src,\n blob,\n objectUrl,\n isLoading: false,\n lastAccessed: Date.now()\n }\n\n this.cache.set(src, updatedEntry)\n return updatedEntry\n } catch (error) {\n console.warn('Failed to cache media:', src, error)\n\n // Update entry with error\n const errorEntry: CachedMedia = {\n src,\n error: true,\n isLoading: false,\n lastAccessed: Date.now()\n }\n\n this.cache.set(src, errorEntry)\n return errorEntry\n }\n }\n\n acquireUrl(src: string): string | undefined {\n const entry = this.cache.get(src)\n if (entry?.objectUrl) {\n const currentCount = this.urlRefCount.get(entry.objectUrl) || 0\n this.urlRefCount.set(entry.objectUrl, currentCount + 1)\n return entry.objectUrl\n }\n return undefined\n }\n\n releaseUrl(src: string): void {\n const entry = this.cache.get(src)\n if (entry?.objectUrl) {\n const count = (this.urlRefCount.get(entry.objectUrl) || 1) - 1\n if (count <= 0) {\n URL.revokeObjectURL(entry.objectUrl)\n this.urlRefCount.delete(entry.objectUrl)\n // Remove from cache as well\n this.cache.delete(src)\n } else {\n this.urlRefCount.set(entry.objectUrl, count)\n }\n }\n }\n\n clearCache() {\n // Revoke all object URLs\n for (const entry of Array.from(this.cache.values())) {\n if (entry.objectUrl) {\n URL.revokeObjectURL(entry.objectUrl)\n }\n }\n this.cache.clear()\n this.urlRefCount.clear()\n }\n\n destroy() {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval)\n this.cleanupInterval = null\n }\n this.clearCache()\n }\n}\n\n// Global instance\nlet mediaCacheInstance: MediaCacheService | null = null\n\nexport function useMediaCache(options?: MediaCacheOptions) {\n if (!mediaCacheInstance) {\n mediaCacheInstance = new MediaCacheService(options)\n }\n\n const getCachedMedia = (src: string) =>\n mediaCacheInstance!.getCachedMedia(src)\n const clearCache = () => mediaCacheInstance!.clearCache()\n const acquireUrl = (src: string) => mediaCacheInstance!.acquireUrl(src)\n const releaseUrl = (src: string) => mediaCacheInstance!.releaseUrl(src)\n\n return {\n getCachedMedia,\n clearCache,\n acquireUrl,\n releaseUrl,\n cache: mediaCacheInstance.cache\n }\n}\n\n// Cleanup on page unload\nif (typeof window !== 'undefined') {\n window.addEventListener('beforeunload', () => {\n if (mediaCacheInstance) {\n mediaCacheInstance.destroy()\n }\n })\n}\n","<template>\n <div\n ref=\"containerRef\"\n class=\"relative flex h-full w-full items-center justify-center overflow-hidden\"\n :class=\"containerClass\"\n >\n <Skeleton\n v-if=\"!isImageLoaded\"\n width=\"100%\"\n height=\"100%\"\n class=\"absolute inset-0\"\n />\n <img\n v-if=\"cachedSrc\"\n :src=\"cachedSrc\"\n :alt=\"alt\"\n draggable=\"false\"\n :class=\"imageClass\"\n :style=\"imageStyle\"\n @load=\"onImageLoad\"\n @error=\"onImageError\"\n />\n <div\n v-if=\"hasError\"\n class=\"absolute inset-0 flex items-center justify-center\"\n >\n <img\n src=\"/assets/images/default-template.png\"\n :alt=\"alt\"\n draggable=\"false\"\n :class=\"imageClass\"\n :style=\"imageStyle\"\n />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Skeleton from 'primevue/skeleton'\nimport { computed, onUnmounted, ref, watch } from 'vue'\n\nimport { useIntersectionObserver } from '@/composables/useIntersectionObserver'\nimport { useMediaCache } from '@/services/mediaCacheService'\nimport type { ClassValue } from '@/utils/tailwindUtil'\n\nconst {\n src,\n alt = '',\n containerClass = '',\n imageClass = '',\n imageStyle,\n rootMargin = '300px'\n} = defineProps<{\n src: string\n alt?: string\n containerClass?: ClassValue\n imageClass?: ClassValue\n imageStyle?: Record<string, any>\n rootMargin?: string\n}>()\n\nconst containerRef = ref<HTMLElement | null>(null)\nconst isIntersecting = ref(false)\nconst isImageLoaded = ref(false)\nconst hasError = ref(false)\nconst cachedSrc = ref<string | undefined>(undefined)\n\nconst { getCachedMedia, acquireUrl, releaseUrl } = useMediaCache()\n\n// Use intersection observer to detect when the image container comes into view\nuseIntersectionObserver(\n containerRef,\n (entries) => {\n const entry = entries[0]\n isIntersecting.value = entry?.isIntersecting ?? false\n },\n {\n rootMargin,\n threshold: 0.1\n }\n)\n\n// Only start loading the image when it's in view\nconst shouldLoad = computed(() => isIntersecting.value)\n\nwatch(\n shouldLoad,\n async (shouldLoadVal) => {\n if (shouldLoadVal && src && !cachedSrc.value && !hasError.value) {\n try {\n const cachedMedia = await getCachedMedia(src)\n if (cachedMedia.error) {\n hasError.value = true\n } else if (cachedMedia.objectUrl) {\n const acquiredUrl = acquireUrl(src)\n cachedSrc.value = acquiredUrl || cachedMedia.objectUrl\n } else {\n cachedSrc.value = src\n }\n } catch (error) {\n console.warn('Failed to load cached media:', error)\n cachedSrc.value = src\n }\n } else if (!shouldLoadVal) {\n if (cachedSrc.value?.startsWith('blob:')) {\n releaseUrl(src)\n }\n // Hide image when out of view\n isImageLoaded.value = false\n cachedSrc.value = undefined\n hasError.value = false\n }\n },\n { immediate: true }\n)\n\nconst onImageLoad = () => {\n isImageLoaded.value = true\n hasError.value = false\n}\n\nconst onImageError = () => {\n hasError.value = true\n isImageLoaded.value = false\n}\n\nonUnmounted(() => {\n if (cachedSrc.value?.startsWith('blob:')) {\n releaseUrl(src)\n }\n})\n</script>\n"],"names":["useFeatureFlags","flags","reactive","api","remoteConfig","featureFlag","__name","featurePath","defaultValue","computed","readonly","selectedItem","_useModel","__props","t","useI18n","getLabel","val","found","o","optionStyle","styles","formatDisplayName","folderName","specialCases","word","DISALLOWED_MODEL_TYPES","useModelTypes","createSharedComposable","modelTypes","isLoading","error","fetchModelTypes","useAsyncState","folder","a","b","err","modelValue","isVisible","handleEscapeKey","event","watch","visible","stop","useEventListener","onWatcherCleanup","showCivitaiHelp","ref","showHuggingFaceHelp","emit","__emit","civitaiImportSource","huggingfaceImportSource","validateSourceUrl","url","source","hostname","h","civitaiIcon","civitaiUrl","huggingFaceIcon","huggingFaceUrl","props","value","importSources","isValidUrl","trimmedUrl","useUploadModelWizard","assetsStore","useAssetsStore","assetDownloadStore","useAssetDownloadStore","modelToNodeStore","useModelToNodeStore","currentStep","isFetchingMetadata","isUploading","uploadStatus","uploadError","wizardData","selectedModelType","detectedSource","canFetchMetadata","canUploadModel","fetchMetadata","cleanedUrl","supportedSources","s","metadata","assetService","typeTag","tag","type","st","uploadPreviewImage","filename","baseFilename","extension","mimeMatch","refreshModelCaches","providers","provider","result","index","uploadModel","tags","previewId","userMetadata","goToPreviousStep","dialogStore","useDialogStore","handleFetchMetadata","handleUploadModel","handleClose","onMounted","_imports_0$1","titleKey","_hoisted_1","_hoisted_2","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_ctx","showSubscriptionDialog","useSubscription","handleSubscribe","useModelUpload","onUploadSuccess","isUploadButtonEnabled","showUploadDialog","UploadModelDialogHeader","UploadModelDialog","UploadModelUpgradeModalHeader","UploadModelUpgradeModal","_imports_0","useIntersectionObserver","target","callback","options","immediate","observerOptions","isSupported","isIntersecting","observer","cleanup","observe","entries","entry","unobserve","onBeforeUnmount","MediaCacheService","now","keysToDelete","key","removedCount","targetRemoveCount","src","response","blob","objectUrl","updatedEntry","errorEntry","currentCount","count","mediaCacheInstance","useMediaCache","containerRef","isImageLoaded","hasError","cachedSrc","getCachedMedia","acquireUrl","releaseUrl","shouldLoad","shouldLoadVal","cachedMedia","acquiredUrl","onImageLoad","onImageError","onUnmounted"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- var Q=Object.defineProperty;var l=(h,m)=>Q(h,"name",{value:m,configurable:!0});import{a as O,w as W,g as X,s as Y,ad as z,d as Z,ae as L,q as N,G as ee}from"./vendor-primevue-Bp7MMXLP.js";import{u as V}from"./vendor-vue-DaLZjKA7.js";import{a5 as R,a6 as q,a7 as te,a8 as H,_ as S,o as se,a2 as ae,p as oe,a4 as ne,A as ie,m as re,dv as le}from"./index-DvCHS_f2.js";import{bx as j,E as k,c as $,d as p,e as n,z as c,by as a,j as T,q as I,k as u,u as i,s as G,r as A,w as J,cp as K,h as ce,A as D,l as de}from"./vendor-other-B8t_Wf2K.js";import"./vendor-xterm-CWYFmgbN.js";import"./vendor-three-DH0o8VQ7.js";import"./vendor-tiptap-VxK0yxkd.js";const ue={key:0,class:"flex items-center gap-1"},me={class:"flex items-center gap-2"},pe={key:1,class:"flex items-center gap-1"},fe=j({__name:"UserCredit",props:{textClass:{},showCreditsOnly:{type:Boolean}},setup(h){const m=R(),x=k(()=>m.isFetchingBalance),{t:w,locale:f}=V(),v=k(()=>{const _=m.balance?.effective_balance_micros??m.balance?.amount_micros??0;return`${q({cents:_,locale:f.value})} ${w("credits.credits")}`}),d=k(()=>{const _=m.balance?.effective_balance_micros??m.balance?.amount_micros??0;return q({cents:_,locale:f.value,numberOptions:{minimumFractionDigits:0,maximumFractionDigits:0}})});return(_,g)=>x.value?(p(),$("div",ue,[n("div",me,[c(a(O),{shape:"circle",width:"1.5rem",height:"1.5rem"})]),g[0]||(g[0]=n("div",{class:"flex-1"},null,-1)),c(a(O),{width:"8rem",height:"2rem"})])):(p(),$("div",pe,[_.showCreditsOnly?I("",!0):(p(),T(a(W),{key:0,severity:"secondary",rounded:"",class:"p-1 text-amber-400"},{icon:u(()=>g[1]||(g[1]=[n("i",{class:"icon-[lucide--component]"},null,-1)])),_:1})),n("div",{class:G(_.textClass)},i(_.showCreditsOnly?d.value:v.value),3)]))}});var F=(h=>(h.CREDIT_ADDED="credit_added",h.ACCOUNT_CREATED="account_created",h.API_USAGE_STARTED="api_usage_started",h.API_USAGE_COMPLETED="api_usage_completed",h))(F||{});const M=K.create({baseURL:H(),headers:{"Content-Type":"application/json"}}),ve=l(()=>{const h=A(!1),m=A(null),{d:x}=V();J(()=>H(),e=>{M.defaults.baseURL=e});const w=l((e,s,b)=>{if(te(e))return;let y;if(!K.isAxiosError(e))y=`${s} failed: ${e instanceof Error?e.message:String(e)}`;else{const o=e,r=o.response?.status;r&&b?.[r]?y=b[r]:y=o.response?.data?.message??`${s} failed with status ${r}`}m.value=y},"handleRequestError"),f=l(async(e,s)=>{const{errorContext:b,routeSpecificErrors:y}=s;h.value=!0,m.value=null;try{return(await e()).data}catch(o){return w(o,b,y),null}finally{h.value=!1}},"executeRequest");function v(e){switch(e){case"credit_added":return"Credits Added";case"account_created":return"Account Created";case"api_usage_completed":return"API Usage";default:return e}}l(v,"formatEventType");function d(e){const s=new Date(e);return x(s,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}l(d,"formatDate");function _(e){return e.split("_").map(s=>s.charAt(0).toUpperCase()+s.slice(1)).join(" ")}l(_,"formatJsonKey");function g(e){return typeof e=="number"?e.toLocaleString():typeof e=="string"&&e.match(/^\d{4}-\d{2}-\d{2}/)?new Date(e).toLocaleString():e}l(g,"formatJsonValue");function P(e){switch(e){case"credit_added":return"success";case"account_created":return"info";case"api_usage_completed":return"warning";default:return"info"}}l(P,"getEventSeverity");function E(e){const{amount:s,api_name:b,model:y,...o}=e.params||{};return Object.keys(o).length>0}l(E,"hasAdditionalInfo");function B(e){const{...s}=e.params||{};return Object.entries(s).map(([b,y])=>{const o=_(b),r=g(y);return`<strong>${o}:</strong> ${r}`}).join("<br>")}l(B,"getTooltipContent");function t(e){return e?(e/100).toFixed(2):"0.00"}l(t,"formatAmount");async function C({page:e=1,limit:s=10}={}){const b="Fetching customer events",y={400:"Invalid input, object invalid",404:"Not found"},o=await R().getAuthHeader();return o?await f(()=>M.get("/customers/events",{params:{page:e,limit:s},headers:o}),{errorContext:b,routeSpecificErrors:y}):(m.value="Authentication header is missing",null)}return l(C,"getMyEvents"),{isLoading:h,error:m,getMyEvents:C,formatEventType:v,getEventSeverity:P,formatAmount:t,hasAdditionalInfo:E,formatDate:d,formatJsonKey:_,formatJsonValue:g,getTooltipContent:B}},"useCustomerEventsService"),ge={key:0,class:"flex items-center justify-center p-8"},he={key:1,class:"p-4"},_e={class:"event-details"},ye={key:0,class:"font-semibold text-green-500"},Ce={key:1},be={key:2,class:"flex flex-col gap-1"},$e={class:"font-semibold"},xe={class:"text-sm text-smoke-400"},we=j({__name:"UsageLogsTable",setup(h,{expose:m}){const x=A([]),w=A(!0),f=A(null),v=ve(),d=A({page:1,limit:7,total:0,totalPages:0}),_=k(()=>(d.value.page-1)*d.value.limit),g=k(()=>{const t=new Map;return x.value.forEach(C=>{v.hasAdditionalInfo(C)&&C.event_id&&t.set(C.event_id,v.getTooltipContent(C))}),t}),P=l(async()=>{w.value=!0,f.value=null;try{const t=await v.getMyEvents({page:d.value.page,limit:d.value.limit});t?(t.events&&(x.value=t.events),t.page&&(d.value.page=t.page),t.limit&&(d.value.limit=t.limit),t.total&&(d.value.total=t.total),t.totalPages&&(d.value.totalPages=t.totalPages),se()?.checkForCompletedTopup(t.events)):f.value=v.error.value||"Failed to load events"}catch(t){f.value=t instanceof Error?t.message:"Unknown error",console.error("Error loading events:",t)}finally{w.value=!1}},"loadEvents"),E=l(t=>{d.value.page=t.page+1,P().catch(C=>{console.error("Error loading events:",C)})},"onPageChange");return m({refresh:l(async()=>{d.value.page=1,await P()},"refresh")}),(t,C)=>{const e=ce("tooltip");return p(),$("div",null,[w.value?(p(),$("div",ge,[c(a(X))])):f.value?(p(),$("div",he,[c(a(Y),{severity:"error",closable:!1},{default:u(()=>[D(i(f.value),1)]),_:1})])):(p(),T(a(z),{key:2,value:x.value,paginator:!0,rows:d.value.limit,"total-records":d.value.total,first:_.value,lazy:!0,class:"p-datatable-sm custom-datatable",onPage:E},{default:u(()=>[c(a(L),{field:"event_type",header:t.$t("credits.eventType")},{body:u(({data:s})=>[c(a(Z),{value:a(v).formatEventType(s.event_type),severity:a(v).getEventSeverity(s.event_type)},null,8,["value","severity"])]),_:1},8,["header"]),c(a(L),{field:"details",header:t.$t("credits.details")},{body:u(({data:s})=>[n("div",_e,[s.event_type===a(F).CREDIT_ADDED?(p(),$("div",ye,i(t.$t("credits.added"))+" $"+i(a(v).formatAmount(s.params?.amount)),1)):s.event_type===a(F).ACCOUNT_CREATED?(p(),$("div",Ce,i(t.$t("credits.accountInitialized")),1)):s.event_type===a(F).API_USAGE_COMPLETED?(p(),$("div",be,[n("div",$e,i(s.params?.api_name||"API"),1),n("div",xe,i(t.$t("credits.model"))+": "+i(s.params?.model||"-"),1)])):I("",!0)])]),_:1},8,["header"]),c(a(L),{field:"createdAt",header:t.$t("credits.time")},{body:u(({data:s})=>[D(i(a(v).formatDate(s.createdAt)),1)]),_:1},8,["header"]),c(a(L),{field:"params",header:t.$t("credits.additionalInfo")},{body:u(({data:s})=>[a(v).hasAdditionalInfo(s)?de((p(),T(S,{key:0,variant:"textonly",size:"icon-sm","aria-label":t.$t("credits.additionalInfo")},{default:u(()=>C[0]||(C[0]=[n("i",{class:"pi pi-info-circle"},null,-1)])),_:2},1032,["aria-label"])),[[e,{escape:!1,value:g.value.get(s.event_id)||"",pt:{text:{style:{width:"max-content !important"}}}},void 0,{top:!0}]]):I("",!0)]),_:1},8,["header"])]),_:1},8,["value","rows","total-records","first"]))])}}}),Ae={class:"flex h-full flex-col"},ke={class:"mb-2 text-2xl font-bold"},Ee={class:"flex flex-col gap-2"},Se={class:"text-sm font-medium text-muted"},De={class:"flex items-center justify-between"},Te={class:"flex flex-row items-center"},Pe={key:1,class:"text-xs text-muted"},Ue={class:"flex items-center justify-between"},Le={key:0,class:"grow"},Ie={class:"text-sm font-medium"},Be={class:"text-xs text-muted"},Fe={class:"flex flex-row gap-2"},He=j({__name:"LegacyCreditsPanel",setup(h){const{buildDocsUrl:m,docsPaths:x}=ae(),w=oe(),f=R(),v=ne(),d=ie(),{isActiveSubscription:_}=re(),g=k(()=>f.loading),P=k(()=>f.isFetchingBalance),E=A(null),B=k(()=>f.lastBalanceUpdateTime?f.lastBalanceUpdateTime.toLocaleString():"");J(()=>f.lastBalanceUpdateTime,(o,r)=>{o&&o!==r&&E.value&&E.value.refresh()});const t=l(()=>{w.showTopUpCreditsDialog()},"handlePurchaseCreditsClick"),C=l(async()=>{await v.accessBillingPortal()},"handleCreditsHistoryClick"),e=l(async()=>{await d.execute("Comfy.ContactSupport")},"handleMessageSupport"),s=l(()=>{window.open(m("/tutorials/api-nodes/faq",{includeLocale:!0}),"_blank")},"handleFaqClick"),b=l(()=>{window.open(m(x.partnerNodesPricing,{includeLocale:!0}),"_blank")},"handleOpenPartnerNodesInfo"),y=A([]);return(o,r)=>(p(),T(a(ee),{value:"Credits",class:"credits-container h-full"},{default:u(()=>[n("div",Ae,[n("h2",ke,i(o.$t("credits.credits")),1),c(a(N)),n("div",Ee,[n("h3",Se,i(o.$t("credits.yourCreditBalance")),1),n("div",De,[c(fe,{"text-class":"text-3xl font-bold"}),g.value?(p(),T(a(O),{key:0,width:"2rem",height:"2rem"})):a(_)?(p(),T(S,{key:1,loading:g.value,onClick:t},{default:u(()=>[D(i(o.$t("credits.purchaseCredits")),1)]),_:1},8,["loading"])):I("",!0)]),n("div",Te,[P.value?(p(),T(a(O),{key:0,width:"12rem",height:"1rem",class:"text-xs"})):B.value?(p(),$("div",Pe,i(o.$t("credits.lastUpdated"))+": "+i(B.value),1)):I("",!0),c(S,{variant:"muted-textonly",size:"icon-sm","aria-label":o.$t("g.refresh"),onClick:r[0]||(r[0]=()=>a(v).fetchBalance())},{default:u(()=>r[1]||(r[1]=[n("i",{class:"pi pi-refresh"},null,-1)])),_:1},8,["aria-label"])])]),n("div",Ue,[n("h3",null,i(o.$t("credits.activity")),1),c(S,{variant:"muted-textonly",loading:g.value,onClick:C},{default:u(()=>[r[2]||(r[2]=n("i",{class:"pi pi-arrow-up-right"},null,-1)),D(" "+i(o.$t("credits.invoiceHistory")),1)]),_:1},8,["loading"])]),y.value.length>0?(p(),$("div",Le,[c(a(z),{value:y.value,"show-headers":!1},{default:u(()=>[c(a(L),{field:"title",header:o.$t("g.name")},{body:u(({data:U})=>[n("div",Ie,i(U.title),1),n("div",Be,i(U.timestamp),1)]),_:1},8,["header"]),c(a(L),{field:"amount",header:o.$t("g.amount")},{body:u(({data:U})=>[n("div",{class:G(["text-center text-base font-medium",U.isPositive?"text-sky-500":"text-red-400"])},i(U.isPositive?"+":"-")+"$"+i(a(le)(U.amount,"usd")),3)]),_:1},8,["header"])]),_:1},8,["value"])])):I("",!0),c(a(N)),c(we,{ref_key:"usageLogsTableRef",ref:E},null,512),n("div",Fe,[c(S,{variant:"muted-textonly",onClick:s},{default:u(()=>[r[3]||(r[3]=n("i",{class:"pi pi-question-circle"},null,-1)),D(" "+i(o.$t("credits.faqs")),1)]),_:1}),c(S,{variant:"muted-textonly",onClick:b},{default:u(()=>[r[4]||(r[4]=n("i",{class:"pi pi-question-circle"},null,-1)),D(" "+i(o.$t("subscription.partnerNodesCredits")),1)]),_:1}),c(S,{variant:"muted-textonly",onClick:e},{default:u(()=>[r[5]||(r[5]=n("i",{class:"pi pi-comments"},null,-1)),D(" "+i(o.$t("credits.messageSupport")),1)]),_:1})])])]),_:1}))}});export{He as default};
2
- //# sourceMappingURL=LegacyCreditsPanel-Be7L1Af3.js.map
1
+ var Q=Object.defineProperty;var l=(h,m)=>Q(h,"name",{value:m,configurable:!0});import{a as O,w as W,g as X,s as Y,ad as z,d as Z,ae as L,q as N,G as ee}from"./vendor-primevue-Bp7MMXLP.js";import{u as V}from"./vendor-vue-DaLZjKA7.js";import{a5 as R,a6 as q,a7 as te,a8 as H,_ as S,o as se,a2 as ae,p as oe,a4 as ne,A as ie,m as re,dv as le}from"./index-B3VEOed6.js";import{bx as j,E as k,c as $,d as p,e as n,z as c,by as a,j as T,q as I,k as u,u as i,s as G,r as A,w as J,cp as K,h as ce,A as D,l as de}from"./vendor-other-B8t_Wf2K.js";import"./vendor-xterm-CWYFmgbN.js";import"./vendor-three-DH0o8VQ7.js";import"./vendor-tiptap-VxK0yxkd.js";const ue={key:0,class:"flex items-center gap-1"},me={class:"flex items-center gap-2"},pe={key:1,class:"flex items-center gap-1"},fe=j({__name:"UserCredit",props:{textClass:{},showCreditsOnly:{type:Boolean}},setup(h){const m=R(),x=k(()=>m.isFetchingBalance),{t:w,locale:f}=V(),v=k(()=>{const _=m.balance?.effective_balance_micros??m.balance?.amount_micros??0;return`${q({cents:_,locale:f.value})} ${w("credits.credits")}`}),d=k(()=>{const _=m.balance?.effective_balance_micros??m.balance?.amount_micros??0;return q({cents:_,locale:f.value,numberOptions:{minimumFractionDigits:0,maximumFractionDigits:0}})});return(_,g)=>x.value?(p(),$("div",ue,[n("div",me,[c(a(O),{shape:"circle",width:"1.5rem",height:"1.5rem"})]),g[0]||(g[0]=n("div",{class:"flex-1"},null,-1)),c(a(O),{width:"8rem",height:"2rem"})])):(p(),$("div",pe,[_.showCreditsOnly?I("",!0):(p(),T(a(W),{key:0,severity:"secondary",rounded:"",class:"p-1 text-amber-400"},{icon:u(()=>g[1]||(g[1]=[n("i",{class:"icon-[lucide--component]"},null,-1)])),_:1})),n("div",{class:G(_.textClass)},i(_.showCreditsOnly?d.value:v.value),3)]))}});var F=(h=>(h.CREDIT_ADDED="credit_added",h.ACCOUNT_CREATED="account_created",h.API_USAGE_STARTED="api_usage_started",h.API_USAGE_COMPLETED="api_usage_completed",h))(F||{});const M=K.create({baseURL:H(),headers:{"Content-Type":"application/json"}}),ve=l(()=>{const h=A(!1),m=A(null),{d:x}=V();J(()=>H(),e=>{M.defaults.baseURL=e});const w=l((e,s,b)=>{if(te(e))return;let y;if(!K.isAxiosError(e))y=`${s} failed: ${e instanceof Error?e.message:String(e)}`;else{const o=e,r=o.response?.status;r&&b?.[r]?y=b[r]:y=o.response?.data?.message??`${s} failed with status ${r}`}m.value=y},"handleRequestError"),f=l(async(e,s)=>{const{errorContext:b,routeSpecificErrors:y}=s;h.value=!0,m.value=null;try{return(await e()).data}catch(o){return w(o,b,y),null}finally{h.value=!1}},"executeRequest");function v(e){switch(e){case"credit_added":return"Credits Added";case"account_created":return"Account Created";case"api_usage_completed":return"API Usage";default:return e}}l(v,"formatEventType");function d(e){const s=new Date(e);return x(s,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}l(d,"formatDate");function _(e){return e.split("_").map(s=>s.charAt(0).toUpperCase()+s.slice(1)).join(" ")}l(_,"formatJsonKey");function g(e){return typeof e=="number"?e.toLocaleString():typeof e=="string"&&e.match(/^\d{4}-\d{2}-\d{2}/)?new Date(e).toLocaleString():e}l(g,"formatJsonValue");function P(e){switch(e){case"credit_added":return"success";case"account_created":return"info";case"api_usage_completed":return"warning";default:return"info"}}l(P,"getEventSeverity");function E(e){const{amount:s,api_name:b,model:y,...o}=e.params||{};return Object.keys(o).length>0}l(E,"hasAdditionalInfo");function B(e){const{...s}=e.params||{};return Object.entries(s).map(([b,y])=>{const o=_(b),r=g(y);return`<strong>${o}:</strong> ${r}`}).join("<br>")}l(B,"getTooltipContent");function t(e){return e?(e/100).toFixed(2):"0.00"}l(t,"formatAmount");async function C({page:e=1,limit:s=10}={}){const b="Fetching customer events",y={400:"Invalid input, object invalid",404:"Not found"},o=await R().getAuthHeader();return o?await f(()=>M.get("/customers/events",{params:{page:e,limit:s},headers:o}),{errorContext:b,routeSpecificErrors:y}):(m.value="Authentication header is missing",null)}return l(C,"getMyEvents"),{isLoading:h,error:m,getMyEvents:C,formatEventType:v,getEventSeverity:P,formatAmount:t,hasAdditionalInfo:E,formatDate:d,formatJsonKey:_,formatJsonValue:g,getTooltipContent:B}},"useCustomerEventsService"),ge={key:0,class:"flex items-center justify-center p-8"},he={key:1,class:"p-4"},_e={class:"event-details"},ye={key:0,class:"font-semibold text-green-500"},Ce={key:1},be={key:2,class:"flex flex-col gap-1"},$e={class:"font-semibold"},xe={class:"text-sm text-smoke-400"},we=j({__name:"UsageLogsTable",setup(h,{expose:m}){const x=A([]),w=A(!0),f=A(null),v=ve(),d=A({page:1,limit:7,total:0,totalPages:0}),_=k(()=>(d.value.page-1)*d.value.limit),g=k(()=>{const t=new Map;return x.value.forEach(C=>{v.hasAdditionalInfo(C)&&C.event_id&&t.set(C.event_id,v.getTooltipContent(C))}),t}),P=l(async()=>{w.value=!0,f.value=null;try{const t=await v.getMyEvents({page:d.value.page,limit:d.value.limit});t?(t.events&&(x.value=t.events),t.page&&(d.value.page=t.page),t.limit&&(d.value.limit=t.limit),t.total&&(d.value.total=t.total),t.totalPages&&(d.value.totalPages=t.totalPages),se()?.checkForCompletedTopup(t.events)):f.value=v.error.value||"Failed to load events"}catch(t){f.value=t instanceof Error?t.message:"Unknown error",console.error("Error loading events:",t)}finally{w.value=!1}},"loadEvents"),E=l(t=>{d.value.page=t.page+1,P().catch(C=>{console.error("Error loading events:",C)})},"onPageChange");return m({refresh:l(async()=>{d.value.page=1,await P()},"refresh")}),(t,C)=>{const e=ce("tooltip");return p(),$("div",null,[w.value?(p(),$("div",ge,[c(a(X))])):f.value?(p(),$("div",he,[c(a(Y),{severity:"error",closable:!1},{default:u(()=>[D(i(f.value),1)]),_:1})])):(p(),T(a(z),{key:2,value:x.value,paginator:!0,rows:d.value.limit,"total-records":d.value.total,first:_.value,lazy:!0,class:"p-datatable-sm custom-datatable",onPage:E},{default:u(()=>[c(a(L),{field:"event_type",header:t.$t("credits.eventType")},{body:u(({data:s})=>[c(a(Z),{value:a(v).formatEventType(s.event_type),severity:a(v).getEventSeverity(s.event_type)},null,8,["value","severity"])]),_:1},8,["header"]),c(a(L),{field:"details",header:t.$t("credits.details")},{body:u(({data:s})=>[n("div",_e,[s.event_type===a(F).CREDIT_ADDED?(p(),$("div",ye,i(t.$t("credits.added"))+" $"+i(a(v).formatAmount(s.params?.amount)),1)):s.event_type===a(F).ACCOUNT_CREATED?(p(),$("div",Ce,i(t.$t("credits.accountInitialized")),1)):s.event_type===a(F).API_USAGE_COMPLETED?(p(),$("div",be,[n("div",$e,i(s.params?.api_name||"API"),1),n("div",xe,i(t.$t("credits.model"))+": "+i(s.params?.model||"-"),1)])):I("",!0)])]),_:1},8,["header"]),c(a(L),{field:"createdAt",header:t.$t("credits.time")},{body:u(({data:s})=>[D(i(a(v).formatDate(s.createdAt)),1)]),_:1},8,["header"]),c(a(L),{field:"params",header:t.$t("credits.additionalInfo")},{body:u(({data:s})=>[a(v).hasAdditionalInfo(s)?de((p(),T(S,{key:0,variant:"textonly",size:"icon-sm","aria-label":t.$t("credits.additionalInfo")},{default:u(()=>C[0]||(C[0]=[n("i",{class:"pi pi-info-circle"},null,-1)])),_:2},1032,["aria-label"])),[[e,{escape:!1,value:g.value.get(s.event_id)||"",pt:{text:{style:{width:"max-content !important"}}}},void 0,{top:!0}]]):I("",!0)]),_:1},8,["header"])]),_:1},8,["value","rows","total-records","first"]))])}}}),Ae={class:"flex h-full flex-col"},ke={class:"mb-2 text-2xl font-bold"},Ee={class:"flex flex-col gap-2"},Se={class:"text-sm font-medium text-muted"},De={class:"flex items-center justify-between"},Te={class:"flex flex-row items-center"},Pe={key:1,class:"text-xs text-muted"},Ue={class:"flex items-center justify-between"},Le={key:0,class:"grow"},Ie={class:"text-sm font-medium"},Be={class:"text-xs text-muted"},Fe={class:"flex flex-row gap-2"},He=j({__name:"LegacyCreditsPanel",setup(h){const{buildDocsUrl:m,docsPaths:x}=ae(),w=oe(),f=R(),v=ne(),d=ie(),{isActiveSubscription:_}=re(),g=k(()=>f.loading),P=k(()=>f.isFetchingBalance),E=A(null),B=k(()=>f.lastBalanceUpdateTime?f.lastBalanceUpdateTime.toLocaleString():"");J(()=>f.lastBalanceUpdateTime,(o,r)=>{o&&o!==r&&E.value&&E.value.refresh()});const t=l(()=>{w.showTopUpCreditsDialog()},"handlePurchaseCreditsClick"),C=l(async()=>{await v.accessBillingPortal()},"handleCreditsHistoryClick"),e=l(async()=>{await d.execute("Comfy.ContactSupport")},"handleMessageSupport"),s=l(()=>{window.open(m("/tutorials/api-nodes/faq",{includeLocale:!0}),"_blank")},"handleFaqClick"),b=l(()=>{window.open(m(x.partnerNodesPricing,{includeLocale:!0}),"_blank")},"handleOpenPartnerNodesInfo"),y=A([]);return(o,r)=>(p(),T(a(ee),{value:"Credits",class:"credits-container h-full"},{default:u(()=>[n("div",Ae,[n("h2",ke,i(o.$t("credits.credits")),1),c(a(N)),n("div",Ee,[n("h3",Se,i(o.$t("credits.yourCreditBalance")),1),n("div",De,[c(fe,{"text-class":"text-3xl font-bold"}),g.value?(p(),T(a(O),{key:0,width:"2rem",height:"2rem"})):a(_)?(p(),T(S,{key:1,loading:g.value,onClick:t},{default:u(()=>[D(i(o.$t("credits.purchaseCredits")),1)]),_:1},8,["loading"])):I("",!0)]),n("div",Te,[P.value?(p(),T(a(O),{key:0,width:"12rem",height:"1rem",class:"text-xs"})):B.value?(p(),$("div",Pe,i(o.$t("credits.lastUpdated"))+": "+i(B.value),1)):I("",!0),c(S,{variant:"muted-textonly",size:"icon-sm","aria-label":o.$t("g.refresh"),onClick:r[0]||(r[0]=()=>a(v).fetchBalance())},{default:u(()=>r[1]||(r[1]=[n("i",{class:"pi pi-refresh"},null,-1)])),_:1},8,["aria-label"])])]),n("div",Ue,[n("h3",null,i(o.$t("credits.activity")),1),c(S,{variant:"muted-textonly",loading:g.value,onClick:C},{default:u(()=>[r[2]||(r[2]=n("i",{class:"pi pi-arrow-up-right"},null,-1)),D(" "+i(o.$t("credits.invoiceHistory")),1)]),_:1},8,["loading"])]),y.value.length>0?(p(),$("div",Le,[c(a(z),{value:y.value,"show-headers":!1},{default:u(()=>[c(a(L),{field:"title",header:o.$t("g.name")},{body:u(({data:U})=>[n("div",Ie,i(U.title),1),n("div",Be,i(U.timestamp),1)]),_:1},8,["header"]),c(a(L),{field:"amount",header:o.$t("g.amount")},{body:u(({data:U})=>[n("div",{class:G(["text-center text-base font-medium",U.isPositive?"text-sky-500":"text-red-400"])},i(U.isPositive?"+":"-")+"$"+i(a(le)(U.amount,"usd")),3)]),_:1},8,["header"])]),_:1},8,["value"])])):I("",!0),c(a(N)),c(we,{ref_key:"usageLogsTableRef",ref:E},null,512),n("div",Fe,[c(S,{variant:"muted-textonly",onClick:s},{default:u(()=>[r[3]||(r[3]=n("i",{class:"pi pi-question-circle"},null,-1)),D(" "+i(o.$t("credits.faqs")),1)]),_:1}),c(S,{variant:"muted-textonly",onClick:b},{default:u(()=>[r[4]||(r[4]=n("i",{class:"pi pi-question-circle"},null,-1)),D(" "+i(o.$t("subscription.partnerNodesCredits")),1)]),_:1}),c(S,{variant:"muted-textonly",onClick:e},{default:u(()=>[r[5]||(r[5]=n("i",{class:"pi pi-comments"},null,-1)),D(" "+i(o.$t("credits.messageSupport")),1)]),_:1})])])]),_:1}))}});export{He as default};
2
+ //# sourceMappingURL=LegacyCreditsPanel-K_GDtrt_.js.map